Skip to main content
Solana chains are entries in GET /v1/config with "kind": "solana". Chain ids are 101 (mainnet-beta), 102 (testnet), 103 (devnet), and 104 and above for local validators. Keys and signatures are base58; transactions are standard base64; amounts are base-unit decimal strings.

Who signs what

A settlement transaction has three signers: the maker (the resolver node), the taker, and the fee payer (the relay’s keeper). The taker needs no SOL.
  1. The node signs as maker when it answers the RFQ.
  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.
The taker repays the keeper in the pair’s fee mint through the gas-fee instruction. That fee includes the rent for the taker’s receiving token account when it does not exist yet (gasFee.rent is true). The canonical transaction pays only the taker, so recipient must equal taker.

Fee mints and pairs

fee.tokens in the chain’s config lists the mints fees may be charged in, in priority order. A pair’s fee mint is the first of fee.tokens that is its sell mint or its buy mint: cNGN/USDC is charged in USDC and cNGN/USDT in USDT. pairs lists the corridors the relay quotes on the chain, as mint pairs. A pair outside that list is refused 400 unsupported_pair.

Requesting a quote

Same as EVM, with base58 values and a base58 taker proof:
The response has "kind": "solana" and a solana block instead of permit, auth, offers and typedData:
Amounts. Let fee be floor(leg × bps / 10000), where the leg is sellAmount when the fee mint is the sell mint and received otherwise. Let gas be gasFee.amount.

Checks before the wallet signs

1

Build the terms

Take the fee mint as the first of config.fee.tokens that is sellToken or buyToken. Check that solana.feeMint, fee.token and gasFee.token are that mint. Then:
2

Verify the transaction

verifySettlement(terms, base64Decode(solana.transaction), [solana.maker]) must pass. The reference implementation is panofx-api/src/domain/solana/settlement.ts, copied byte for byte into the web app.
3

Check the amounts

What the wallet will show must match takerPays, proceeds, fee and gasFee computed from those terms.
4

After signing

Check that the message bytes are unchanged, the taker’s signature verifies, and the maker’s signature is still present.

Submitting

POST /v1/executions with { "quoteId": "<uuid>", "transaction": "<base64>" }: the quote’s transaction with the taker’s signature added. The fee payer’s slot may be empty. The relay requires the message bytes to equal the quoted message, verifies both signatures, and builds the transaction it sends from its own stored copy plus the taker’s signature and the keeper’s.

Other routes

  • GET /v1/executions/:id: txHash is the base58 transaction signature; gasUsed is compute units consumed.
  • GET /v1/quotes/:id: txHash values are base58 and settlement.block is the slot. final is true once the transaction is finalized.
  • GET /v1/limits?chainId=103&…: the same shape as EVM, with allowance equal to balance.
  • GET /v1/corridors: a Solana chain lists only its pairs, each with its own fee mint.
  • GET /v1/health: the chain’s entry has slot, keeperLamports and keeperLow.
  • Limit-order routes answer 400 limit_orders_unsupported.
Out of scope on Solana: limit orders, a recipient other than the taker, and Permit2 or allowance steps.