Skip to main content

Buying and Selling

Every trade takes the same four calls. The options you add to the quote change three things: which kinds of legs come back, what each leg asks you to sign, and who pays the gas. This page maps every combination of priority, execution and payout_chain to what you get, so you can pick the right one and handle any quote it returns. The chain-by-chain rules behind each option are in Routing and Priority Mode.

The four calls​

  1. Quote. POST /quote/buy with amount_usdc, or POST /quote/sell with amount_shares, plus the ticker, the wallets, an ownership proof and any of the options below. You get a quote_id, an expires_at, warnings[] and quotes[]: one leg per chain, each carrying the signable_payloads to sign.
  2. Sign. Sign every payload of the legs you will submit, each as its type says (below).
  3. Submit. POST /trade/submit with the quote_id and, for each leg, its quote_index and signed_payloads. Treasures broadcasts them.
  4. Track. Poll GET /quote/{quote_id}/status at the poll_after_ms it returns until every leg is terminal.

Which legs you submit is the one rule that differs by side.

  • Buy: the legs are alternatives, best price first. Submit exactly one quote_index, usually 0. Two or more is 400 quote_index_mismatch.
  • Sell: the legs are parts of one sale, each selling a slice of your holdings. Submit every leg. A subset is 400 incomplete_submit; to sell from fewer chains, re-quote with chain.

Submit before expires_at. To price a buy with no wallet attached, POST /quote/preview takes the same options (it needs an integrator key) and returns the legs with nothing to sign.

The options​

OptionValuesWhat it changes
chain, protocolOne value (a pin) or an array (a subset)Which venues are considered. Default: every one the ticker lists
preferred_chainA chain inside your chain scopeOrder only: that chain's leg comes first on a buy, and is filled first on a sell
priority"speed"One-block swaps on Robinhood Chain and Base instead of gasless orders, and cross-chain buys
execution"transaction" (default) or "user_operation"With priority: "speed" only: EVM legs come back as one call bundle your own paymaster sponsors
payout_chain"sol", "eth" or "base"Sells only: the sale's USDC lands on that chain, and positions elsewhere can sell cross-chain
integrator_fee_bps0 to your ceiling, in net bpsWith your integrator key only: your own fee on this quote, instead of your configured default. See Integrator Fees and Payouts

These combinations are refused up front with 400 invalid_request:

  • execution: "user_operation" without priority: "speed".
  • payout_chain on /quote/buy or /quote/preview.
  • priority: "speed" with chain: "arbitrum", which has no speed route.
  • priority: "speed" with chain: "eth" on a sell, unless payout_chain names another chain.
  • A preferred_chain outside the chain you sent.

The kinds of leg​

Whatever you ask for, each leg that comes back is one of these. The rest of the page names them.

LegWhat you signWho pays gas
Swap (Solana)One solana_versioned_txUsually the wallet, in SOL; gasless on the leg says which
Gasless orderOne evm_eip712_typed_dataNobody on your side: a settlement network broadcasts it
Speed swapOne or two EVM transactions: an approve (first time only), then the swapThe wallet, in native ETH on that chain
Sponsored swapOne evm_calls bundle, packed into an ERC-4337 UserOperationYour paymaster
Cross-chain buy (origin_chain set)The origin chain's shape: a Solana transaction, an approve and deposit pair, or one evm_callsThe wallet on the origin chain, or your paymaster when sponsored
Cross-chain sell (payout_chain set)On Ethereum and Base, an approve and deposit pair, or one evm_calls when sponsored; on Solana, one transactionThe wallet on the chain the stock is sold on, or your paymaster when sponsored

Every leg carries gasless: true when nobody on your side pays (a gasless order, or your paymaster), false when the wallet does. It can be absent on a Solana leg, and absent is not false.

Buying: what each combination returns​

You addSolanaEthereumRobinhood ChainBase
NothingSwapGasless orderGasless orderGasless order
priority: "speed"Swap, or cross-chain buyCross-chain buy onlySpeed swapSpeed swap, or cross-chain buy
priority: "speed" and execution: "user_operation"Swap, or cross-chain buyCross-chain buy onlySponsored swapSponsored swap, or cross-chain buy
  • A cross-chain buy is offered only on a chain the wallet is not funded on, and only into sol/xstocks, eth/ondo and, on an unpinned or multi-chain request, base/coinbase. The funds leave from another chain where the wallet does hold enough, so send both sol_wallet and eth_wallet.
  • Arbitrum, once its venue opens, returns a gasless order by default and is never quoted under speed.
  • Choose the default for the best price with no native gas on the EVM chains. Choose speed when settlement inside one block matters, or when the user's USDC sits on a different chain from the listing. Add user_operation when you run an ERC-4337 stack and would rather your paymaster paid than hold native gas.

Selling: what each combination returns​

Without payout_chain, each position sells on its own chain:

You addSolanaEthereumRobinhood ChainBase
NothingSwapGasless orderGasless orderGasless order
priority: "speed"SwapNot soldSpeed swapSpeed swap
priority: "speed" and execution: "user_operation"SwapNot soldSponsored swapSponsored swap

With payout_chain, all of the USDC lands on that chain, in the wallet you sent for it (sol_wallet on sol, eth_wallet on eth and base). Each position is planned by where it sits, whatever the priority:

PositionOn the payout chainOn another chain
sol/xstocks, base/coinbaseSells as in the table aboveCross-chain sell
eth/ondoSells as in the table above (so not under speed)Cross-chain sell
sol/ondo, eth/xstocksSells as in the table aboveLeft out, with a warning
Robinhood Chain, ArbitrumNever a payout chainLeft out, with a warning

A position that cannot pay out on payout_chain is never sold on its own chain instead, because that would pay out somewhere you did not ask for. It is named by a cross_chain_route_unavailable warning. Adding priority: "speed" and execution: "user_operation" turns the Ethereum and Base cross-chain sells into sponsored evm_calls bundles.

Handling a quote​

A single quote can mix leg kinds. An unpinned speed buy, for example, can return a Solana swap, a Base speed swap and an Ethereum cross-chain buy side by side. So handle each leg on its own: read what it is, then sign each payload by its type.

Recognise the leg​

On the legIt isRead it as
payout_chain presentA cross-chain sellchain is where the stock is sold; the USDC lands on payout_chain
origin_chain presentA cross-chain buychain is the destination; the funds leave origin_chain, and base_asset names that chain's currency
NeitherA same-chain legchain is where everything happens

A cross-chain leg also carries cross_chain_cost_bps and cross_chain_cost_usdc, the hop's own cost, and its cost_breakdown_bps.dex_swap_fee_bps is null.

Sign by payload type​

Sign every payload in signable_payloads, in the order issued:

Payload typeHow to sign itReturn
solana_versioned_txDeserialize tx_base64 and sign with the wallet's transaction signer, not signMessagesolana_versioned_tx with signed_tx_base64
evm_eip712_typed_dataeth_signTypedData_v4 over typed_data, exactly as givenevm_eip712_signature with signature
evm_eip1559_tx, evm_legacy_txsignTransaction over tx_hex as issued; never change the nonce, gas or feesevm_signed_tx with signed_tx_hex, one per payload
evm_callsPack calls into one ERC-4337 v0.7 UserOperation for sender on chain_id (execute for one call, executeBatch in order for two), sponsor it with your paymaster, sign itevm_user_operation with user_operation

Rules that hold for every leg:

  • Branch on type, never on chain or on the options you sent.
  • Return every payload, in order. Two EVM transactions on one leg (an approve, then the swap or deposit) are normal. Returning only one is incomplete_submit, and reordering them on a retry is read as a new submit.
  • Never broadcast a trade payload yourself. You sign; Treasures sends it. The only transaction you broadcast is a bridge.
  • Know what an approve grants. A speed swap's approve gives the router an unlimited allowance on the token spent, once per wallet, token and chain, so surface that consent to your user. A cross-chain leg's approve is for the exact amount, and on a sell it comes every time.
  • The sponsored lane needs a delegated wallet. The wallet must be EIP-7702-delegated to Modular Account v2 on that chain, and the operation must carry a paymaster. The lane serves Base and Robinhood Chain; a chain it cannot serve at the moment is dropped with speed_route_unavailable and reason: "disabled".

A signing loop that handles every combination above, whatever options were sent:

import { VersionedTransaction } from '@solana/web3.js';
import { parseTransaction } from 'viem';

// `signers` is yours: a Solana transaction signer, an EVM account that signs typed data and
// transactions without sending them, and (sponsored lane only) your ERC-4337 account SDK.
async function signLeg(leg: QuoteLeg, signers: Signers) {
const signedPayloads = [];
for (const payload of leg.signable_payloads) {
switch (payload.type) {
case 'solana_versioned_tx': {
const unsigned = VersionedTransaction.deserialize(Buffer.from(payload.tx_base64, 'base64'));
const signed = await signers.solana.signTransaction(unsigned);
signedPayloads.push({
type: 'solana_versioned_tx',
signed_tx_base64: Buffer.from(signed.serialize()).toString('base64'),
});
break;
}
case 'evm_eip712_typed_data': {
const { domain, types, primaryType, message } = payload.typed_data;
const { EIP712Domain: _domainType, ...orderTypes } = types; // viem adds the domain type itself
const signature = await signers.evm.signTypedData({ domain, types: orderTypes, primaryType, message });
signedPayloads.push({ type: 'evm_eip712_signature', signature });
break;
}
case 'evm_eip1559_tx':
case 'evm_legacy_tx': {
const signedTxHex = await signers.evm.signTransaction(parseTransaction(payload.tx_hex));
signedPayloads.push({ type: 'evm_signed_tx', signed_tx_hex: signedTxHex });
break;
}
case 'evm_calls': {
// Build one UserOperation from payload.calls for payload.sender on payload.chain_id,
// run your paymaster's sponsorship, sign it, and return the unpacked v0.7 fields.
const userOperation = await signers.userOperation.buildSponsorAndSign(payload);
signedPayloads.push({ type: 'evm_user_operation', user_operation: userOperation });
break;
}
default:
throw new Error(`Unrecognised payload type ${payload.type}; update your integration`);
}
}
return { quote_index: leg.quote_index, signed_payloads: signedPayloads };
}

Submit one signed leg for a buy (signed: [await signLeg(quote.quotes[0], signers)]), and every leg for a sell.

Recipes​

GoalAdd to the quote requestWhat to expect
Cheapest buy, no native gas on EVMNothingquotes[0] is the best price
Buy on one chain only"chain": "base"One leg, or 422 no_routes
Prefer a chain, keep the fallbacks"preferred_chain": "sol"That chain's leg is quotes[0]
Settle in one block"priority": "speed"A speed swap, with an approve the first time
Settle in one block, no native gas"priority": "speed", "execution": "user_operation"One evm_calls per EVM leg
Buy where the user holds no USDC"priority": "speed", both walletsA leg with origin_chain set
Sell, all USDC to Solana"payout_chain": "sol"Cross-chain legs carry payout_chain; submit every leg
Sell to USDC on Base, no native gas"payout_chain": "base", "priority": "speed", "execution": "user_operation"Ethereum and Base legs as evm_calls

The request for the last recipe, with the proof built as in Ownership Proof:

{
"ticker": "NVDA",
"amount_shares": "2.5",
"sol_wallet": "7xKXtg2C…",
"eth_wallet": "0xab5801a7…",
"payout_chain": "base",
"priority": "speed",
"execution": "user_operation",
"max_slippage_bps": 100,
"ownership_proof": {
"issued_at": 1750000000,
"sol_signature": "<base64 Ed25519 signature>",
"eth_signature": "0x<EIP-191 signature>"
}
}

Tracking the trade​

Poll GET /quote/{quote_id}/status at the poll_after_ms it returns: 1250 while a speed swap is in flight, 3250 otherwise. Which handle identifies a leg depends on its kind:

LegWhat to watch
Swap (Solana)tx_hash, usually final at submit. On broadcast_unknown, do not resubmit: poll
Gasless orderorder_hash until the order fills, then tx_hash
Speed swaptx_hash, set from submit; on an approve and swap pair it lands once the approve mines
Sponsored swapuser_op_hash; tx_hash is filled in once the operation is included
Cross-chain selltx_hash is the sale, payout_tx_hash the payout, set on completed. The completed leg's payout_chain is where the USDC landed: it equals chain if the payout was returned there
Cross-chain buyThe leg's status. cross_chain_unsettled after 6 hours means wait, because it may still fill or refund; do not re-quote

Resubmitting the same signed payloads for the same quote_index is safe and returns the original result. Only one speed trade per wallet per chain can be in flight at a time; wait for it to finish before quoting the next.

When a quote comes back short​

You seeWhat to do
speed_route_unavailable warningA Robinhood Chain or Base leg was dropped under speed; its reason says why. Take another leg, fix the reason, or re-quote without priority
cross_chain_route_unavailable warningA listing could not be reached cross-chain, or (with payout_chain) a position was left out. Choose another leg or payout chain, or re-quote later
422 no_routes, reason: "insufficient_balance"The wallet's own balance refused a speed request. Fund the token spent on that chain
422 no_routes, reason: "cross_chain_wallet_missing"The buy could only be funded from a chain whose wallet you did not send. Send both wallets
422 no_routes, reason: "payout_route_unavailable"You hold enough, but not enough of it can pay out on payout_chain. Pay out elsewhere, or drop payout_chain
400 wallet_not_delegatedThe sponsored lane on a pinned chain, with a wallet not delegated there. Delegate it, or drop execution
insufficient_native_gas at submitThe wallet pays gas on this leg and has too little. Fund it and re-quote
nonce_conflict at submitAnother speed trade from this wallet on that chain is in flight. Wait, then re-quote
sponsorship_rejected at submitYour paymaster declined the operation. Fix the sponsorship and re-sign

Every error code and field is in the API Reference.