Building on SettleKit
Add proof-settled payouts to your own program — the goalshield walkthrough.
SettleKit is a general truth → money primitive. If your contract can name a predicate over TxODDS's signed stat feed, SettleKit can prove it, and your program can pay on it — without trusting us, the keeper, or any admin.
The whole integration, honestly
goalshield-mini is a complete parametric-insurance product in ~260 lines. This is its
entire oracle integration:
// claim_payout: release escrow iff the proof exists
let v = &ctx.accounts.verdict; // Account<'info, fw_settle::state::Verdict>
require!(v.fixture_id == p.fixture_id, ShieldError::WrongVerdict);
require!(v.question_hash == p.question_hash, ShieldError::WrongVerdict);
require!(v.outcome, ShieldError::VerdictNotTrue);Anchor enforces the account owner (fw-settle) and discriminator; the three lines pin
which truth. A Verdict is an init-once PDA keyed by sha256(borsh(question)) — if
it exists with outcome == true, the oracle proved exactly that predicate against an
anchored Merkle root before the account could be created.
Depend on the engine (Rust)
Your program reads SettleKit accounts as first-class Anchor types. One dependency line:
# Cargo.toml — exactly how goalshield-mini and fw-markets do it
[dependencies]
fw-settle = { git = "https://gitlab.com/Bprime/finalwhistle", features = ["cpi", "no-entrypoint"] }use fw_settle::state::{Verdict, MatchFacts};
#[derive(Accounts)]
pub struct PayOnTruth<'info> {
// owner == fw-settle and the discriminator are checked by Anchor right here
pub verdict: Account<'info, Verdict>,
// …your accounts
}No CPI into fw-settle is ever needed — truth is an account you read, not a call you make.
Deployed addresses (devnet)
| Program | Address |
|---|---|
fw-settle (the engine) | HJR3owk8ARzqayhwELA72S38yome3X4HWvb7HMP9AA8K |
fw-markets (LMSR consumer) | 3as8xHoWivvFTEcg1N6MCb81RANkahr9dt484vr1Camc |
goalshield-mini (reference consumer) | BaRqEqDuhqEJPsApRKgHtz3ozFtqAhEgPKLxPsipeDae |
txoracle (TxODDS, devnet) | 6pW64gN1s2uqjHkn1unFeEjAwJkPGHoppGvS715wyP2J |
PDA seeds you will read: Verdict = ["verdict", fixture_id_le_i64, question_hash],
MatchFacts = ["facts", fixture_id_le_i64], BatchAnchor = ["batch_anchor", fixture_id_le_i64, batch_ts_le_i64] — derivation helpers for all of them ship in the SDK
(verdictPda, factsPda, anchorPda).
Installing the SDK
@finalwhistle/settle is a workspace package today: clone the repo and pnpm install,
then import it as the monorepo does (packages/web and packages/keeper are living
examples). An npm publish is planned post-hackathon.
Naming a question (TypeScript SDK)
import {
cornersSumQuestion, questionHash, postVerdictIx, verdictPda,
} from "@finalwhistle/settle";
// "total corners (home + away) > 10 at full time" — the classic parametric prop
const q = cornersSumQuestion(fixtureId, 10, 5 /* phase: full time */);
const hash = questionHash(q);
// escrow your product against that hash; anyone can later prove it:
const { ix, verdict } = postVerdictIx(keeper.address, q, payload, rootsPda(batchTs));payload is the Merkle proof bundle from TxLINE's
GET /api/scores/stat-validation?fixtureId=…&seq=…&statKeys=7,8, mapped by the SDK's
buildPayload. Binary predicates (sums/differences of two stats) are first-class:
the oracle evaluates stat_a op stat_b against your threshold inside the CPI.
The lifecycle
- Underwrite — escrow a payout against
question_hash(your program's account). - Sell / activate — your business logic; SettleKit does not care.
- Prove — anyone posts the verdict after the match. Validity comes from the proof, not the poster.
- Pay — your program reads the Verdict and releases funds. No claims process.
What SettleKit guarantees you
- The verdict corresponds to exactly your predicate (G-HASH binds hash ↔ semantics).
- The proven stats carry the phase you demanded (G-PHASE — no mid-match spoofs).
- The oracle's answer could not be forged (G-ROOT + G-RETURN pins).
- If the truth never arrives, nothing false ever gets written — pair your product with a
time-based refund path, as goalshield's
expiredoes.
Test your integration in ten minutes
The repo ships a LiteSVM harness that loads the dumped production oracle binary and a real anchored roots account, so your consumer runs against the same verifier that runs on devnet — no network, no faucet:
anchor build && ./scripts/sync-idl.sh
./scripts/dump-oracle.sh # fetches txoracle + a real roots account (once)
pnpm test:svm # the existing suites are your templatesStart from tests/litesvm/inplay-shield.test.ts — it posts a real golden-proof verdict
and drives goalshield end-to-end. Load your program next to the others in
tests/litesvm/util.ts (makeSvm), point a test at a Verdict you created, and you
have a proof-settled integration test in one file.