Skip to main content
There is one Solana program, and Panofx did not write it. order-engine is Jupiter’s RFQ fill program (MIT), audited by Offside Labs in November 2024. It is vendored unchanged except for its program id, and CI rebuilds it from the pinned upstream commit plus the one patch and fails on any other difference.

What the program does

One instruction, fill(input_amount, output_amount, expire_at). The taker and the maker must both sign. It checks expire_at against the cluster clock, then moves both legs by CPI: the taker’s input to the maker and the maker’s output to the taker, through SPL Token or Token-2022. It refuses a Token-2022 mint that charges a transfer fee. It holds no state and charges nothing. Panofx’s protocol fee and the keeper’s gas repayment are two more transfer_checked instructions in the same transaction. The maker and the taker each sign the whole message, so neither can be removed or changed without breaking their signatures.

The canonical transaction

The settlement transaction is canonical: panofx-node/internal/solana/doc.go specifies it and testdata/vectors.json pins it. The relay and the web app share one TypeScript implementation, panofx-api/src/domain/solana/settlement.ts, copied byte for byte into the web app and checked by CI. Every party rebuilds the transaction from the terms and compares bytes before trusting a signature on it. Three signers: the maker (the node), the taker, and the fee payer (the relay’s keeper). The taker needs no SOL. The taker repays the keeper in the pair’s fee mint through the gas-fee instruction, which also covers the rent for the taker’s receiving token account when it does not exist yet.

Order of events

  1. The node signs as maker when it answers the RFQ. The RFQ carries the rest of the terms: feePayer, feeMint, feeBps, treasury, gasFee, computeUnitLimit, computeUnitPrice.
  2. The relay checks the transaction byte for byte against the terms it asked for, simulates it, and quotes it.
  3. The taker’s client rebuilds the same transaction from /v1/config and the quote, checks it, and signs as taker.
  4. The keeper signs last as fee payer and submits.

Node identity on Solana

A node’s identity with the relay is its EVM key. A node that quotes a Solana chain adds solanaAddress and solanaSignature to its hello: an ed25519 signature over panofx-node-hello|<nonce>|<solanaAddress>|<name>. A valid proof binds that key to the node; the relay stores it and rejects any Solana offer whose maker is not the bound key. Without a valid proof the relay keeps the node’s EVM corridors and drops its Solana ones. A key already bound to another connected node is refused.

Offer lifetime

The relay rejects a Solana offer whose deadline is under now + MIN_OFFER_TTL_MS (10 s) or over now + 120 s. A transaction’s blockhash expires after about a minute anyway, so nodes use 60.

Indexer

The program emits no events, so the indexer reads transactions.
  • What it reads. The program’s transactions from getSignaturesForAddress, oldest first, at finalized commitment. Finalized transactions do not roll back, so rows are written confirmed. Failed transactions are skipped.
  • Fills. Every top-level fill instruction becomes a Fill row with id <signature>:<instruction index>. Its resolver is the node whose bound Solana key made it, or the key itself when no node has bound it.
  • Settlements. A fill whose transaction is exactly the canonical settlement also becomes a Settlement row. The indexer rebuilds that settlement from the fill’s accounts, the compute budget, the gas-fee transfer and the chain’s configuration, and it must match the landed message byte for byte.
  • Matching a quote. First the quote whose execution sent that signature, then the quote storing the message hash as its offers hash, on the same chain only. A match settles the quote and releases the maker’s reservation.
  • Cursor. The last signature indexed and the slot indexed through. It moves up to the finalized slot when the program is idle, so an idle program does not show as lag. A listing never goes further than 150 slots before the cursor’s slot.
  • Start. An empty cursor starts at the current finalized slot. To backfill, set indexerStartSlot on the chain’s CHAINS entry.
  • Health. /health reports head and cursor as slots; the chain is unhealthy 300 slots (about two minutes) behind finalized. Before the first tick, the indexer checks that the RPC serves the cluster the chain id names (by genesis hash) and that the program is deployed there.
  • Not indexed. Fills that reach the program through another program (CPI), and versioned transactions.

Devnet

Deployed 13 September 2026 in slot 497,711,080. The deployed bytes equal a local build of the vendored directory. The end-to-end test passed on devnet the same day: the node signed both directions with the taker holding no SOL throughout, devnet answered SignatureFailure with the fee instruction removed, and the program refused an expired fill. The integrator-facing shapes are under Solana.