Gorilla Markets

Gorilla Settlement · for developers

Settle your own markets against real sports results.

One call, no oracle to run. Pass a PredicateQuery fixture_id, stat_key, period, predicate — and get a proof-verified bool back, or a revert. The fixture/stat/period binding is enforced insidethe engine, so you can't get it wrong.

Honest scope:the engine adds no cryptographic trust over CPIing TxLINE's oracle directly — the oracle is already the trust root. Its value is the forced binding every consumer is routed through, plus one audited deployment instead of each product re-implementing settlement.

One engine, two products.

Two structurally different consumers settle through the sameengine on devnet. Verify it on chain — each settle tx's logs show Program 9S6SwSp5…invoke.

Market

forge-markets

A pari-mutuel prop market. Reads its (fixture, stat, period, predicate), CPIs the engine, maps the bool to a Side. Escrows SOL.

settle txGprY…Xoiv

Insurance policy

forge-insurance

A structurally different product — fixed indemnity, asymmetric insurer/insured roles, USDC. Same engine, re-implements zero settlement logic.

settle tx2jwF…o17c

Both CPI the same engine 9S6S…jEjT. One deployment to audit; two products that share zero settlement code.

The integration, in ~10 lines

Consume it from your program.

This is the actual call — the shape lifted verbatim from forge-markets's settle.rs. It re-implements no CPI or binding logic: build the query from your market's tuple, thread the three accounts, read the result fail-closed.

use settlement_core::{client, PredicateQuery}; // 1. Declare what THIS market resolves. The engine binds the caller's oracle//    args to exactly this tuple before it proves anything — you can't get it wrong.let query = PredicateQuery {    fixture_id: market.fixture_id, // engine asserts fixture_summary.fixture_id == this    stat_key:   market.stat_key,   // engine asserts stat_a.stat_to_prove.key    == this    period:     market.period,     // engine asserts stat_a.stat_to_prove.period == this    predicate:  market.predicate,  // the YES condition, forwarded to txoracle}; // 2. One CPI. F1-binding + txoracle proof + fail-closed decode all live inside.let predicate_held: bool = client::resolve(    &ctx.accounts.settlement_engine.to_account_info(),        // engine, pinned by addr    &ctx.accounts.daily_scores_merkle_roots.to_account_info(), // txoracle daily roots    &ctx.accounts.txoracle_program.to_account_info(),         // txoracle, pinned by addr    &query, ts, &fixture_summary, &fixture_proof, &main_tree_proof,    &stat_a, &stat_b, &op,)?; // Err = tampered/unbound proof -> your instruction reverts. Fail-closed. // 3. Apply the outcome. This is the only market-specific line you write.market.winner = if predicate_held { Side::Yes } else { Side::No };

How it fails: a tampered proof makes the inner CPI Err, so resolve reverts and your instruction with it. A proof for the wrong fixture never even reaches the oracle — the engine rejects it with FixtureMismatch.

Reference, not a released package — there is no npm/crate to install. Copy the call; the engine is deployed.

Interface reference

client::resolve(query, proof) -> Result<bool>

PredicateQuery

What you declare you're resolving — the engine binds the proof to it.

  • fixture_id: i64engine asserts fixture_summary.fixture_id == this
  • stat_key: u32engine asserts stat_a.stat_to_prove.key == this
  • period: i32engine asserts stat_a.stat_to_prove.period == this
  • predicate: TraderPredicatethe YES condition, forwarded to txoracle

Accounts (in order)

  • settlement_enginethe engine program — makes the resolve CPI callable
  • daily_scores_merkle_rootstxoracle-owned daily roots; threaded straight through
  • txoracle_programtxoracle, pinned by address at both hops

Returns

  • Ok(true)predicate held
  • Ok(false)did not hold
  • Errtampered / undecodable / unbound → the caller reverts

the one gotcha

Read the return data immediately after the engine CPI — no intervening CPI. The engine CPIs txoracle, then re-sets the return data to its own bool; client::resolvereads it right away and decodes fail-closed. Slip another CPI in between and you'd read the wrong program's bytes. This is the whole two-hop return-data contract.