Skip to main content
A Receipt is the output of Gecko’s simulate engine. It closes a built transaction into one legible, honest verdict, before any money moves. The loop: intent → derive the full account set → external build → unsigned prelude assembly → simulateTransaction against a node → Receipt → a binding over the exact bytes. Only a passing Receipt with an exact binding hands back a transaction. The signer is never Gecko.

What a Receipt asserts

  • Would this transaction LAND against a snapshot of on-chain state (status: pass / fail), via simulateTransaction (sigVerify:false, commitment:"processed"). The money paths (prepare_purchase, prepare_instruction, plan_swap) simulate the exact bytes with replaceRecentBlockhash:false, so the binding covers the blockhash the caller is about to sign over.
  • Why it wouldn’t, as a categorical revert_class, a stable string vocabulary: slippage, custom_program_error:<code>, insufficient_funds, account_error, other. Never a fabricated dollar number.
  • Compute units consumed (units_consumed).
  • Best-effort deltas: sol_delta (post − pre lamports for the first tracked account) and tokens_received (only when a token account is tracked and decodable, otherwise None, never guessed).
  • A network_label honesty caveat, present on every Receipt.

What a Receipt does NOT do

Read this section before you build a policy on top of a Receipt. These limits are deliberate, not gaps we intend to close by loosening the claim.
  • It does not predict price or slippage, only whether the tx lands against a snapshot.
  • A fork/RPC result is NOT mainnet. The network_label says so; a surfpool-fork Receipt is never presented as mainnet truth.
  • It is state-specific and it expires. An exact binding dies with its blockhash, about 60 seconds; the prepare result carries the budget as expires.last_valid_block_height and expires.blocks_remaining. A receipt taken before the buyer decided is a receipt for bytes nobody will sign. Re-running is free.
  • It does not quote a priority fee. SetComputeUnitPrice defaults to 0 in the simulated bundle; landing under mainnet load needs a fee the operator (or builder) supplies. The Receipt’s units_consumed is the honest input for the CU limit, not the price.
  • It stores nothing. The Receipt is returned to the caller and persisted nowhere (the control-plane invariant). No payload, pubkey, or log line is written.
  • It never signs. No keypair exists on any Gecko path. The one send path, submit_transaction, relays signed bytes only after they verify at exact strength against this receipt’s binding, and refuses without one.

Two paths to a Receipt

Path A: Gecko runs it

Hand a built plan (with fee_recipient supplied) to the simulate MCP tool on the program surface; it builds, simulates, and returns the Receipt. One tool call, no glue code. On the hosted orquestra surface, prepare_purchase and prepare_instruction do this against a public mainnet RPC by default.

Path B: self-serve

plan_buy returns a simulate recipe block: fill fee_recipient, POST build_url to get the tx, then run simulateTransaction yourself. You own the loop; Gecko supplies the correct account set and the recipe.
Either path, fee_recipient stays an honest gap: Gecko will not guess it; the caller supplies it.

The proof

Two live side-by-sides, both on a surfpool mainnet fork (a mainnet-backed state snapshot, not mainnet), simulation only, $0, nothing signed, nothing broadcast, nothing stored. The Pump.fun run reverts on the buyer’s uninitialized ATA. The Gecko bundle passes because it carries a curve-quoted max_sol_cost, the recovered bonding_curve_v2, the buyback fee-recipient remaining accounts, an idempotent-ATA prelude, and a compute budget. The Meteora run exercises the full native-SOL bundle: both ATAs idempotent, wSOL wrap, the swap with the three bitmap-selected bin_array remaining accounts the IDL never names, and a CloseAccount unwrap, one unsigned simulated transaction.
Compute-unit numbers are measured per run against a fork snapshot and can vary slightly with on-chain state. The stable claim is the side-by-side verdict: the naive path’s revert class vs Gecko’s pass. The base_factor derivation row is a separate result and carries no CU number.
Both runs are re-runnable from the engine repo (they need surfpool on PATH and a mainnet RPC), and their verbatim output is recorded in docs/proofs.md. On mainnet, the same receipt has preceded 50 landed transactions as of 2026-09-01. See A real transaction.

Recording an outcome (opt-in)

A run can append its categorical outcome (status, revert family plus public code, compute units, slot, network category, and a values-free recipe hash; never a pubkey, amount, or log) to a segregated series. The default is record nothing; it takes an explicit record_to opt-in.
That series is the episodic memory behind Stay correct.

The binding

A passing receipt on a money path comes back with two more fields:
Between handing over bytes and getting a signature back, nothing of Gecko’s runs. A signer will sign a substituted transaction just as cleanly, and a custody backend protects the key, never the action. So the prepare result’s next_step orders the work: sign, then verify_signed_transaction (signed bytes against the binding), then submit. submit_transaction runs that check itself, at exact strength, before anything reaches the wire, and refuses without a binding. Verifying after broadcast is a post-mortem; verifying before it is a decision. What the binding does not say: that this is the purchase the user wanted. It says the signed bytes are the bytes a passing simulation attested, against the state observed at that slot, for as long as that blockhash lives.