Solana RFQ API
In an RFQ (Request For Quote) trade there is no pool. The taker asks market makers for a price off chain, a maker signs a firm quote, and the only thing that reaches the chain is a settlement instruction that moves both sides of the trade at once.
From 15:06 UTC that day, DEXTrades, DEXTradeByTokens and the Trading cube include
Jupiter Z fills and swaps on Tessera V, HumidiFi, QuantumAMM and Scorch. Trades on these
venues from before that time are not in the trade tables.
Jupiter Limit Order v2, Mayan Swift, ZeroFi and 1inch Fusion still produce no trade rows.
For volume and prices, start with RFQ trades in DEX trade data. The rest of the page works at instruction level, for the detail the trade tables leave out: quote expiry, who paid gas, the raw accounts, and the venues that are still not in trade data. Those sections use the Solana Instructions cube, the Transfers cube and Instruction Balance Updates.
Quickstart
Endpoint, auth header, and a request you can paste into a terminal right now.
curl -X POST https://streaming.bitquery.io/graphql \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $BITQUERY_TOKEN" \
-d '{"query":"{ Solana { Instructions(limit: {count: 3} orderBy: {descending: Block_Time} where: {Transaction: {Result: {Success: true}} Instruction: {Program: {Address: {is: \"61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH\"} Method: {is: \"fill\"}}}}) { Block { Time } Transaction { Signature } Instruction { Program { Arguments { Name Value { ... on Solana_ABI_BigInt_Value_Arg { bigInteger } } } } } } } }"}'
For streams, the same document works over WebSocket at
wss://streaming.bitquery.io/graphql?token=YOUR_TOKEN with the graphql-ws subprotocol.
See generating a token and
WebSocket authorization.
Historical depth depends on your plan, so a
query with an old since date can come back empty even when the filter is correct. Test
without a date filter first.
Which protocols exist
Two families. Both price off chain, but only the first settles through a named RFQ instruction.
Family 1: true RFQ and intent settlement
| Protocol | Program ID | IDL / instruction | What it is | In DEX trade data |
|---|---|---|---|---|
| Jupiter Z (JupiterZ) | 61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH | order_engine → fill | Same-chain RFQ. The maker signs and pays gas, the taker pays nothing. | Yes |
| Jupiter Z (2nd deployment) | 2En5Y11SEAGLNmEezTuRUCwTyzyNReHaMbSnS5gjGsL1 | order_engine → fill | Same IDL and same maker set, but the taker or a relayer pays gas. | Yes |
| Jupiter RFQ v2 | fd3nMFYTQjX1yr5ER8u7tPdHJB7qt8RpDpNtLQX2Br5 | rfq_v2 → fill_exact_in | Newer Jupiter RFQ program. Very little traffic so far. | No |
| Jupiter Limit Order v2 | j1o2qRpjcyUwEvwtcfhEQefh773ZgjxcVRry7LDqg5X | limit_order_2 → fill_order | Resting maker orders filled by a keeper. | No |
| Mayan Swift | mayan34VedncxdK2XobtvWFDXQASUTBXhUVzt2kKgny | swift → init_order / fulfill / settle | Cross-chain intent auction with competing drivers (solvers). | No |
| 1inch Fusion | HNarfxC3kYMMhFkxUFeYb8wHVdPzY5t9pupqW5fL2meM | fusion_swap → fill | Dutch-auction intents. Deployed, but barely used on Solana. | No |
Family 2: proprietary market-maker AMMs
These have RFQ economics (one professional maker quotes, aggregators route to it) with AMM
plumbing (a swap instruction against program-owned inventory). Several publish quote
updates directly on chain, covered in the quote tape.
| Venue | Program ID | Indexed as DEX trades? |
|---|---|---|
| SolFi v2 | SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF | Yes |
| GoonFi v2 | goonuddtQRrWqqn5nFyczVKaie28f3kDkHWkHtURSLE | Yes |
| BisonFi | BiSoNHVpsVZW2F7rx2eQ59yQwKxzU5NvBcmKshCSUypi | Yes |
| AlphaQ | ALPHAQmeA7bjrVuccPsYPiCvsi428SNwte66Srvs4pHA | Yes |
| Aquifer | AQU1FRd7papthgdrwPTTq5JacJh8YtwEXaBfKU3bTz45 | Yes |
| Tessera V | TessVdML9pBGgG9yGks7o4HewRaXVAMuoVj4x83GLQH | Yes |
| HumidiFi | 9H6tua7jkLhdm3w8BvgpTn5LZNU7g4ZynDmCiNN3q6Rp | Yes |
| QuantumAMM | QuaNtZsgYRe5Z9Bk4LZ4cTD9tbkVoyCNf1R2BN9bBDv | Yes |
| Scorch | SCoRcH8c2dpjvcJD6FiPbCSQyQgu3PcUAWj2Xxx3mqn | Yes |
| ZeroFi | ZERor4xhbUycZ6gb9ntrhqscUcZmAbQDjEAtCf4hbZY | No |
Scorch posts its prices through a separate program, ojh19ojaKduoJZuaJADhcVGp4xt1TcdAvZmpVsCorch,
which Scorch swaps pass in as an account. That program moves no tokens itself, so it has no
trade rows of its own.
RFQ trades in DEX trade data
These venues appear in DEXTrades, DEXTradeByTokens and the Trading cube under the
following names.
| Venue | Dex.ProtocolName | Dex.ProtocolFamily | Program |
|---|---|---|---|
| Jupiter Z | order_engine | Jupiter | 61DFfeTK… and 2En5Y11S… |
| Tessera V | tessera_v | Tessera | TessVdML… |
| HumidiFi | humidifi | HumidiFi | 9H6tua7j… |
| QuantumAMM | quantum_amm | QuantumAMM | QuaNtZsg… |
| Scorch | scorch | Scorch | SCoRcH8c… |
Latest Jupiter Z trades
query LatestJupiterZTrades {
Solana {
DEXTrades(
limit: { count: 20 }
orderBy: { descending: Block_Time }
where: {
Trade: { Dex: { ProtocolName: { is: "order_engine" } } }
Transaction: { Result: { Success: true } }
}
) {
Block { Time }
Transaction { Signature }
Trade {
Dex { ProgramAddress ProtocolName ProtocolFamily }
Market { MarketAddress }
Buy { Amount AmountInUSD Currency { Symbol MintAddress } Account { Owner } }
Sell { Amount AmountInUSD Currency { Symbol MintAddress } Account { Owner } }
}
}
}
}
How to read the result:
Trade.Market.MarketAddressis the maker's wallet. RFQ has no pool, so each maker is its own market.Trade.Buy.Account.OwnerandTrade.Sell.Account.Ownerare the taker.- Do not use
Transaction.Signeras the trader. On the main program the maker signs and pays gas, so the signer field holds the maker's wallet. - A native SOL side shows up as
11111111111111111111111111111111, while thefillinstruction names the wrapped SOL mint (So111…112).
Trades and volume by venue
query RfqAndPropAmmVolume {
Solana {
DEXTrades(
where: {
Trade: {
Dex: {
ProtocolName: { in: ["order_engine", "tessera_v", "humidifi", "quantum_amm", "scorch"] }
}
}
Transaction: { Result: { Success: true } }
Block: { Time: { since_relative: { hours_ago: 1 } } }
}
) {
Trade { Dex { ProtocolName ProtocolFamily } }
trades: count
volumeUSD: sum(of: Trade_Buy_AmountInUSD)
}
}
}
DEXTrades returns trades from failed transactions unless you filter them out, and bots fire
a lot of failing swaps at the prop AMMs. Between 15:10 and 15:30 UTC on 29 September 2026,
failed transactions made up 92% of HumidiFi rows, 82% of Scorch rows, 66% of Tessera V rows
and 56% of QuantumAMM rows, against under 1% for Jupiter Z. Keep
Transaction: { Result: { Success: true } } in every query.
Anatomy of a Jupiter Z fill
One instruction, three arguments, eleven accounts.
program: 61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH (IDL name: order_engine)
method: fill
arguments
input_amount u64 exact amount the taker sends (raw, token decimals)
output_amount u64 exact amount the taker receives (raw, token decimals)
expire_at i64 unix timestamp the quote stops being valid
accounts (positional)
0 taker
1 maker
2 taker_input_mint_token_account
3 maker_input_mint_token_account
4 taker_output_mint_token_account
5 maker_output_mint_token_account
6 input_mint
7 input_token_program
8 output_mint
9 output_token_program
10 system_program
The execution price is exact, so you never touch pool math:
price = (output_amount / 10^outDecimals) / (input_amount / 10^inDecimals)
There is no fee tier or curve to model. The number on chain is the fill.
When the taker pays with native SOL, position 2 holds the program ID itself as a stand-in for an unused optional account. Read mints from positions 6 and 8 rather than assuming every token account slot is populated.
Latest RFQ fills
query LatestRFQFills {
Solana {
Instructions(
limit: { count: 20 }
orderBy: { descending: Block_Time }
where: {
Transaction: { Result: { Success: true } }
Instruction: {
Program: {
Address: { is: "61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH" }
Method: { is: "fill" }
}
}
}
) {
Block { Time Slot }
Transaction { Signature Signer Fee }
Instruction {
Accounts { Address Token { Mint Owner } }
Program {
Method
AccountNames
Arguments {
Name
Type
Value {
... on Solana_ABI_BigInt_Value_Arg { bigInteger }
... on Solana_ABI_Integer_Value_Arg { integer }
}
}
}
}
}
}
}
A trimmed real response:
{
"Block": { "Slot": "436710297", "Time": "2026-08-02T05:57:11Z" },
"Transaction": {
"Signature": "5y22FzbLk7iTPQTs6kMuLEjd3P7YGmdNvWM21G4LkFU3pydps7ou9HTkAdw2qRZWTbXhebdEyLGrhgFMJTM7D1Fc",
"Signer": "CreQJ2t94QK5dsxUZGXfPJ8Nx7wA9LHr5chxjSMkbNft"
},
"Instruction": {
"Accounts": [
{ "Address": "F8FEvP6ekyGhDQLsKopD2qgD1j3qcYeYWJ1cWotJnGhn" },
{ "Address": "CreQJ2t94QK5dsxUZGXfPJ8Nx7wA9LHr5chxjSMkbNft" },
{ "Address": "12To3szF9J3gJGUwYkPJD6Y8efHt3TxD6dsz3fyCddYe" },
{ "Address": "6jz3UuC5tKeYGt5FiX18LRDEeceDmC55jmn5cDUL8wh7" },
{ "Address": "BWZYEPYehnLddXBa31LUo5yDv4ns1tC4RskLrnsqHYfR" },
{ "Address": "2rXQvUwk9P2gQhzoPvVwNFhJAKnSsjoidrXFko2qrqZt" },
{ "Address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" },
{ "Address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" },
{ "Address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB" },
{ "Address": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA" },
{ "Address": "11111111111111111111111111111111" }
],
"Program": {
"Method": "fill",
"Arguments": [
{ "Name": "input_amount", "Type": "u64", "Value": { "bigInteger": "3298000943" } },
{ "Name": "output_amount", "Type": "u64", "Value": { "bigInteger": "3300746136" } },
{ "Name": "expire_at", "Type": "i64", "Value": { "bigInteger": "1785650286" } }
]
}
}
}
Transaction.Signer is the fee payer. On the main deployment that is always the maker, which
is how you detect gasless RFQ. See who pays gas.
The mints are in there (positions 6 and 8, EPjFWdd5… and Es9vMFrz…) but you get base58,
not symbols or decimals. Two ways to fix that: decode positionally against your own token
list, or use Instruction Balance Updates, which returns
Currency metadata and USD values directly.
Decoding a fill
Positions 6 and 8 are the mints, positions 0 and 1 the counterparties. Applied to the response above:
const KNOWN = {
So11111111111111111111111111111111111111112: { symbol: "SOL", decimals: 9 },
EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v: { symbol: "USDC", decimals: 6 },
Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB: { symbol: "USDT", decimals: 6 },
};
function decodeFill(node) {
const accounts = node.Instruction.Accounts.map((a) => a.Address);
const args = Object.fromEntries(
node.Instruction.Program.Arguments.map((a) => [a.Name, BigInt(a.Value.bigInteger)])
);
const [taker, maker] = accounts;
const tokenIn = KNOWN[accounts[6]] ?? { symbol: accounts[6].slice(0, 4), decimals: 9 };
const tokenOut = KNOWN[accounts[8]] ?? { symbol: accounts[8].slice(0, 4), decimals: 9 };
const amountIn = Number(args.input_amount) / 10 ** tokenIn.decimals;
const amountOut = Number(args.output_amount) / 10 ** tokenOut.decimals;
return {
time: node.Block.Time,
taker,
maker,
feePayer: node.Transaction.Signer,
pair: `${tokenIn.symbol}/${tokenOut.symbol}`,
amountIn,
amountOut,
price: amountOut / amountIn,
quoteValidForSec:
Number(args.expire_at) - Math.floor(Date.parse(node.Block.Time) / 1000),
};
}
Output:
{
time: '2026-08-02T05:57:11Z',
taker: 'F8FEvP6ekyGhDQLsKopD2qgD1j3qcYeYWJ1cWotJnGhn',
maker: 'CreQJ2t94QK5dsxUZGXfPJ8Nx7wA9LHr5chxjSMkbNft',
feePayer: 'CreQJ2t94QK5dsxUZGXfPJ8Nx7wA9LHr5chxjSMkbNft',
pair: 'USDC/USDT',
amountIn: 3298.000943,
amountOut: 3300.746136,
price: 1.0008323809020816,
quoteValidForSec: 55
}
A 3,298 USDC to USDT swap at 1.00083, on a quote with 55 seconds left to live.
Which currencies actually moved
If you would rather not decode positions yourself, query the balance updates attached to the
same instruction. This returns Currency with symbol and decimals, a signed decimal Amount,
and AmountInUSD.
query RFQFillsWithCurrency {
Solana {
InstructionBalanceUpdates(
limit: { count: 40 }
orderBy: { descending: Block_Time }
where: {
Transaction: { Result: { Success: true } }
Instruction: {
Program: {
Address: { is: "61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH" }
Method: { is: "fill" }
}
}
BalanceUpdate: { Currency: { Native: false } }
}
) {
Block { Time }
Transaction { Signature }
BalanceUpdate {
Amount
AmountInUSD
Currency { Symbol Name MintAddress Decimals }
Account { Address Token { Owner } }
}
}
}
}
One fill comes back as a readable four-row set. Negative is the sender, positive the receiver:
| Symbol | Amount | AmountInUSD | Token.Owner |
|---|---|---|---|
| USD1 | -56.995411 | -56.937057 | 7mXZXgRT6LR8iA… (taker) |
| USDC | -56.943005 | -56.934574 | CreQJ2t94QK5ds… (maker) |
| USD1 | 56.995411 | 56.937057 | CreQJ2t94QK5ds… (maker) |
| USDC | 56.943005 | 56.934574 | 7mXZXgRT6LR8iA… (taker) |
Currency: { Native: false } keeps the SPL token sides and drops lamport noise. When one side
of the trade is native SOL, that side disappears from the results and you will see only the
maker's WSOL row. Remove the filter to catch it, but expect a native SOL side to emit both a
lamport movement and a WSOL token update for the same value. Do not sum them.
Pair this with the fill instruction when you need expire_at, which balance updates do not
carry.
Stream fills in real time
Change query to subscription and drop the ordering.
subscription RFQFillStream {
Solana {
Instructions(
where: {
Transaction: { Result: { Success: true } }
Instruction: {
Program: {
Address: { is: "61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH" }
Method: { is: "fill" }
}
}
}
) {
Block { Time }
Transaction { Signature Signer }
Instruction {
Accounts { Address Token { Mint Owner } }
Program {
Arguments {
Name
Value {
... on Solana_ABI_BigInt_Value_Arg { bigInteger }
}
}
}
}
}
}
}
Both Jupiter Z deployments in one stream:
subscription AllJupiterZFills {
Solana {
Instructions(
where: {
Transaction: { Result: { Success: true } }
Instruction: {
Program: {
Address: {
in: [
"61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH"
"2En5Y11SEAGLNmEezTuRUCwTyzyNReHaMbSnS5gjGsL1"
]
}
Method: { is: "fill" }
}
}
}
) {
Block { Time }
Transaction { Signature Signer }
Instruction {
Accounts { Address }
Program {
Address
Arguments {
Name
Value { ... on Solana_ABI_BigInt_Value_Arg { bigInteger } }
}
}
}
}
}
}
The same decodeFill function above works on each pushed message.
The money flows
The Transfers cube gives you two USD-denominated rows per fill. It cannot filter on the RFQ program directly, so filter on the makers instead, which works because the maker is the fee payer.
The addresses below are placeholders. The active maker set is small and it rotates, so pull it from the maker leaderboard query and substitute the current values rather than copying these.
query RFQTransferLegs {
Solana {
Transfers(
limit: { count: 20 }
where: {
Transaction: {
# replace with the current maker set, see the maker leaderboard query
Signer: { in: [
"CreQJ2t94QK5dsxUZGXfPJ8Nx7wA9LHr5chxjSMkbNft"
"MfDuWeqSHEqTFVYZ7LoexgAK9dxk7cy4DFJWjWMGVWa"
"FkaLnX17cXZGyeu3kZGdHCNdFMJJzBrPPYVvd18B3MZp"
] }
Result: { Success: true }
}
}
orderBy: { descending: Block_Time }
) {
Block { Time }
Transaction { Signature }
Transfer {
Amount
AmountInUSD
Currency { Symbol MintAddress Decimals }
Sender { Address }
Receiver { Address }
}
}
}
}
This also catches the maker's own hedging transactions, since those share the same fee payer.
Join back to the fill instruction by signature if you need fills only.
Filter fills by asset
Account-level filtering uses Accounts: { includes: { Address: ... } }. Pass a mint and you
get every fill where that token was on either side.
query RFQFillsForAsset {
Solana {
Instructions(
limit: { count: 25 }
orderBy: { descending: Block_Time }
where: {
Transaction: { Result: { Success: true } }
Instruction: {
Program: { Address: { is: "61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH" } }
Accounts: { includes: { Address: { is: "XsoCS1TfEyfFhfvj8EtZ528L3CaKBDBRqRapnBbDF2W" } } }
}
}
) {
Block { Time }
Transaction { Signature }
Instruction {
Accounts { Address }
Program {
Arguments {
Name
Value { ... on Solana_ABI_BigInt_Value_Arg { bigInteger } }
}
}
}
}
}
}
That mint is SPYx (SP500 xStock). Swap in TSLAx
XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB or anything from the
mint reference.
For a stable-quoted pair, price is one line. With input_mint USDC (6 decimals) and
output_mint TSLAx (9 decimals):
price_usd_per_share = (input_amount / 1e6) / (output_amount / 1e9)
Invert when the direction is reversed. Both amounts are exact and the quote was firm, so this is a genuine executed print rather than a mid or an estimate.
Quote expiry: how long a maker commits
expire_at minus block time gives the validity remaining at settlement. It is one of the few
places where a market maker's risk appetite is legible on chain.
query QuoteExpiry {
Solana {
Instructions(
limit: { count: 50 }
orderBy: { descending: Block_Time }
where: {
Transaction: { Result: { Success: true } }
Instruction: {
Program: {
Address: { is: "61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH" }
Method: { is: "fill" }
}
}
}
) {
Block { Time }
Instruction {
Program {
Arguments {
Name
Value { ... on Solana_ABI_BigInt_Value_Arg { bigInteger } }
}
}
}
}
}
}
Jupiter Z quotes are minted with roughly a minute of validity and typically land within a few
seconds of issuance, so the remaining validity clusters just under the ceiling. Mayan Swift
intents carry a deadline an order of magnitude longer, because a cross-chain fill has to
survive settlement latency on the far side.
Watch the distribution rather than any single fill. Compression in remaining validity is a volatility signal from professional makers, and it shows up before anything you can read off an AMM.
Maker leaderboard and market share
query RFQMakerActivity {
Solana {
Instructions(
limit: { count: 30 }
orderBy: { descendingByField: "fills" }
where: {
Transaction: { Result: { Success: true } }
Instruction: {
Program: {
Address: { is: "61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH" }
Method: { is: "fill" }
}
}
}
) {
Transaction { Signer }
Block {
firstSeen: Time(minimum: Block_Time)
lastSeen: Time(maximum: Block_Time)
}
fills: count
gasUsd: sum(of: Transaction_FeeInUSD)
}
}
}
Since the maker pays the fee on the main deployment, grouping by Transaction.Signer gives
you the maker leaderboard for free. No extra join, no address list to maintain.
The active maker set is small (low tens) and turns over, so treat any specific roster as a result you generate rather than a constant. Two patterns hold up across runs and are worth building around:
- Fill count and notional rank differently. The maker with the most fills is usually not the maker moving the most money. Rank by both, or you will mistake a dust book for a dominant one.
- Makers specialise by size. Some quote block flow at thousands of dollars a fill, others run dust books averaging a few dollars or less. Dividing notional by fills separates them immediately, and the spread they charge tracks that split (see execution quality).
Notional is not returned directly, so pair the query above with
Instruction Balance Updates and sum AmountInUSD per
maker.
Who pays for the transaction
query RFQFeePayer {
Solana {
Instructions(
limit: { count: 10 }
orderBy: { descendingByField: "txs" }
where: {
Instruction: {
Program: {
Address: { is: "61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH" }
Method: { is: "fill" }
}
}
}
) {
Transaction { Signer Result { Success } }
txs: count(distinct: Transaction_Signature)
totalFeeUsd: sum(of: Transaction_FeeInUSD)
}
}
}
Compare the returned Signer set against the takers (account 0) and makers (account 1). The
two deployments differ by design:
- On
61DFfeTK…the fee payer is the maker, on essentially every fill. The taker pays nothing, which is what makes the flow gasless. - On
2En5Y11S…the maker never pays. The cost falls on the taker or on a relayer submitting for them.
Either way the fee sits close to the base rate: two signatures worth of lamports plus a negligible priority fee. RFQ fills barely bid in the priority-fee auction, because a firm off-chain price leaves nothing to front-run. Compare that against any AMM route on the same chain and the difference is a couple of orders of magnitude.
Execution quality in basis points
You need two sources: the RFQ fill, and an AMM reference price for the same minute.
Pull the RFQ prints with Latest RFQ fills filtered to one pair, then
compute price = output/input with decimals applied. Then pull the AMM reference:
query AmmReferencePrice {
Solana {
DEXTradeByTokens(
limit: { count: 100 }
orderBy: { descending: Block_Time }
where: {
Trade: {
Currency: { MintAddress: { is: "So11111111111111111111111111111111111111112" } }
Side: { Currency: { MintAddress: { is: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" } } }
}
Transaction: { Result: { Success: true } }
}
) {
Block { Time }
Trade { Price PriceInUSD Amount AmountInUSD }
}
}
}
For a taker buying the base asset, bps = (ref - rfq_price) / ref * 10000. Invert the sign
when selling. Positive means the taker beat the AMM mid.
What to expect from the result
Run this on a liquid pair such as SOL/USDC and the shape of the answer is consistent, even though the exact basis points move with volatility and with which makers are active.
Expect the median RFQ fill to price a few basis points worse than the AMM mid, with only a small minority of fills beating it. That gap is the maker's spread, and on liquid pairs it runs several times the AMM's own effective spread at retail size. If you are benchmarking venues on price alone, RFQ loses. Verify with your own window rather than trusting a number published here.
The advantage sits at the two ends of the size distribution:
- Small trades. Gasless settlement dominates. When the fee to send an AMM swap is a meaningful fraction of a small trade, and a third of those swaps have to be retried, a few bps of spread is cheap by comparison.
- Large trades. AMM price impact grows with size while the maker's quoted spread stays roughly flat. Past a certain notional the pool costs more than the spread, and that crossover is the number worth measuring for your own sizes.
- In between. Roughly a wash on price, and RFQ wins on certainty instead.
Fill sizes reflect that: the distribution is a barbell, with a large count of very small fills
and most of the notional carried by a handful of large ones. Compute the AMM side by bucketing
AmountInUSD and taking the absolute deviation from the minute mid, so you get the crossover
for the pair and period you actually trade.
Landing rate
Bitquery indexes failed transactions, so you can measure how many attempts actually settle.
Drop the Success filter and group by result.
query LandingRateComparison {
Solana {
rfq: Instructions(
limit: { count: 4 }
where: {
Instruction: {
Program: { Address: { is: "61DFfeTKM7trxYcPQCM78bJ794ddZprZpAwAnLiwTpYH" } }
}
}
) {
Transaction { Result { Success } }
txs: count(distinct: Transaction_Signature)
}
jupiterRoute: Instructions(
limit: { count: 4 }
where: {
Instruction: {
Program: {
Address: { is: "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4" }
Method: { in: ["route", "route_v2", "shared_accounts_route", "shared_accounts_route_v2"] }
}
}
}
) {
Transaction { Result { Success } }
txs: count(distinct: Transaction_Signature)
}
solfi: Instructions(
limit: { count: 4 }
where: {
Instruction: {
Program: {
Address: { is: "SV2EYYJyRz2YhfXwXnhNAevDEui5Q6yrfyo13WtupPF" }
Method: { is: "swap" }
}
}
}
) {
Transaction { Result { Success } }
txs: count(distinct: Transaction_Signature)
}
}
}
The ordering this produces is stable even as the absolute counts change hour to hour. Off-chain-quoted settlement lands almost every time it is submitted, because the price was agreed before the transaction was built and nothing on chain can move underneath it. AMM routing does not: a large share of submitted swaps fail on slippage or on a stale pool, and oracle-priced proprietary AMMs fare worst of all because bots race to hit quotes that have already moved.
Arbitrage-bot spam inflates the AMM failure counts, and a retail-only figure would look better. The fees on those failures are still real and still paid. If you are choosing what to route through, run this for your own venues and weigh the landing rate against the spread, because the gap between the two families is far larger than the few basis points separating their prices.
Tokenized equities on RFQ
Two issuers of tokenized equities are active on Solana: xStocks from Backed (symbols ending
x) and Ondo Global Markets (ending on). Both are quoted by RFQ makers, but they trade very
differently.
- xStocks trade in pools as well. On 28 September 2026 the 15 xStocks tickers listed below saw about 390,000 trades on Raydium CLMM and Orca alone.
- Ondo tickers barely do. The same day, all the Ondo tickers below together had 20 pool trades. For them, RFQ fills are close to the only price print on chain.
With the RFQ fills in DEX trade data, you can pull them with
DEXTradeByTokens and keep them apart from pool trades by filtering on order_engine:
query TokenizedEquityRFQTrades {
Solana {
DEXTradeByTokens(
limit: { count: 50 }
orderBy: { descending: Block_Time }
where: {
Trade: {
Dex: { ProtocolName: { is: "order_engine" } }
Currency: {
MintAddress: {
in: [
"Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh"
"XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB"
"gEGtLTPNQ7jcg25zTetkbmF7teoDLcrfTnQfmn2ondo"
"KeGv7bsfR4MheC1CkmnAVceoApjrkvBhHYjWb67ondo"
]
}
}
}
Transaction: { Result: { Success: true } }
}
) {
Block { Time }
Trade {
Currency { Symbol MintAddress }
Amount
PriceInUSD
Side { Amount Currency { Symbol } }
Market { MarketAddress }
}
}
}
}
The four mints are NVDAx, TSLAx, NVDAon and TSLAon. Drop the ProtocolName filter to see RFQ
and pool trades side by side. Older fills are not in the trade tables.
The cross-issuer spread
Both issuers list several of the same underlyings (S&P 500, Nasdaq, Tesla, Alphabet, NVIDIA, Meta, Microsoft, Intel, Circle, SpaceX). Decode a fill from each and you get two independent prints of the same asset, quoted by two independent market makers.
In practice they track each other closely, usually well inside a percent. That is a useful
sanity check on your decoding: if GOOGLx and GOOGLon disagree by orders of magnitude, you
have a decimals bug rather than an arbitrage. It is also a genuine cross-issuer spread you can
stream, and the moments it widens are the interesting ones.
Liquidity is concentrated
Expect a handful of makers per issuer. In the first half hour of trade data, three makers
quoted every xStocks RFQ fill and two quoted every Ondo fill. Resolve the current set from
Trade.Market.MarketAddress (or account index 1 of the fill instruction) rather than
hard-coding addresses, because that is exactly the kind of thing that rotates. For the Ondo
tickers, which barely trade in pools, a maker that stops quoting means the ticker stops
trading, so a per-issuer maker heartbeat is a worthwhile alert.
A large part of this flow also lands outside US market hours, when the underlying is closed. The maker is pricing a shut market and carrying the overnight gap risk, which is one reason the spread here is wider than on a crypto pair.
Mint reference
| Symbol | Name | Mint |
|---|---|---|
| SPYx | SP500 xStock | XsoCS1TfEyfFhfvj8EtZ528L3CaKBDBRqRapnBbDF2W |
| QQQx | Nasdaq xStock | Xs8S1uUs1zvS2p7iwtsG3b6fkhpvmwz4GYU3gWAmWHZ |
| TSLAx | Tesla xStock | XsDoVfqeBukxuZHWhdvWHBhgEHjGNst4MLodqsJHzoB |
| NVDAx | NVIDIA xStock | Xsc9qvGR1efVDFGLrVsmkzv3qi45LTBjeUKSPmx9qEh |
| GOOGLx | Alphabet xStock | XsCPL9dNWBMvFtTmwcCA5v3xWPSMEBCszbQdiLLq6aN |
| METAx | Meta xStock | Xsa62P5mvPszXL1krVUnU5ar38bBSVcWAB6fmPCo5Zu |
| MSFTx | Microsoft xStock | XspzcW1PRtgf6Wj92HCiZdjzKCyFekVD8P5Ueh3dRMX |
| AMZNx | Amazon.com xStock | Xs3eBt7uRfJX8QUs4suhyU8p2M6DoUDrJyWBa8LLZsg |
| AAPLx | Apple xStock | XsbEhLAtcf6HdfpFZ5xEMdqW8nfAvcsP5bdudRLJzJp |
| COINx | Coinbase xStock | Xs7ZdzSHLU9ftNJsii5fCeJhoRWSC32SQGzGQtePxNu |
| CRCLx | Circle xStock | XsueG8BtpquVJX9LVLLEGuViXUungE6WmK5YZ3p3bd1 |
| MSTRx | MicroStrategy xStock | XsP7xzNPvEHS1m6qfanPUGjNmdnmsLKEoNAnHjdxxyZ |
| HOODx | Robinhood xStock | XsvNBAYkrDRNhA7wPHQfX3ZUXZyZLdnCQDfHZ56bzpg |
| INTCx | Intel xStock | XshPgPdXFRWB8tP1j82rebb2Q9rPgGX37RuqzohmArM |
| SPCXx | SpaceX xStock | Xs3oZwbHvqis4NYcf4YKWmEia2eC84wSiVrcYcTqpH8 |
| SPYon | SPDR S&P 500 ETF (Ondo) | k18WJUULWheRkSpSquYGdNNmtuE2Vbw1hpuUi92ondo |
| QQQon | Invesco QQQ (Ondo) | HrYNm6jTQ71LoFphjVKBTdAE4uja7WsmLG8VxB8ondo |
| TSLAon | Tesla (Ondo) | KeGv7bsfR4MheC1CkmnAVceoApjrkvBhHYjWb67ondo |
| NVDAon | NVIDIA (Ondo) | gEGtLTPNQ7jcg25zTetkbmF7teoDLcrfTnQfmn2ondo |
| GOOGLon | Alphabet Class A (Ondo) | bbahNA5vT9WJeYft8tALrH1LXWffjwqVoUbqYa1ondo |
| METAon | Meta Platforms (Ondo) | fDxs5y12E7x7jBwCKBXGqt71uJmCWsAQ3Srkte6ondo |
| MSFTon | Microsoft (Ondo) | FRmH6iRkMr33DLG6zVLR7EM4LojBFAuq6NtFzG6ondo |
| AAPLon | Apple (Ondo) | 123mYEnRLM2LLYsJW3K6oyYh8uP1fngj732iG638ondo |
| AMDon | AMD (Ondo) | 14diAn5z8kjrKwSC8WLqvBqqe5YmihJhjxRxd8Z6ondo |
| AVGOon | Broadcom (Ondo) | 1FWZtdWN7y38BSXGzbs8D6Shk88oL9atDNgbVz9ondo |
| ARMon | Arm Holdings plc (Ondo) | 15SsCZqCsM9fZGhTmP4rdJTPT9WGZKazDSsgeQ8ondo |
| CRCLon | Circle Internet Group (Ondo) | 6xHEyem9hmkGtVq6XGCiQUGpPsHBaoYuYdFNZa5ondo |
| INTCon | Intel (Ondo) | cJpUMp5R7rZ6fGeLHbHhrRuJzK9mkyKDjZqNpT3ondo |
| MUon | Micron Technology (Ondo) | Fz9edBpaURPPzpKVRR1A8PENYDEgHqwx5D5th28ondo |
| MRVLon | Marvell Technology (Ondo) | FovBwhoV5KQjZCdhoM6jgXYwXLX3F8vgAfvmLH7ondo |
| SKHYon | SK Hynix (Ondo) | Huyb2fyDDjSuDKCRWsN9ci2rmcgPo6NFiLbx9ZDondo |
| SNDKon | SanDisk (Ondo) | EJmUVvDqAdfH5zEohkdS4234bi3c6iunqEMobjmondo |
| SPCXon | SpaceX (Ondo) | wzAyQTorWyoVXuJKj2x8EqKEGJpS13z6EWE9z5Aondo |
| TSMon | Taiwan Semiconductor (Ondo) | keybg184d4vyXeQdFqs4o99YsMg7xBthxTJ6Ky3ondo |
| GLWon | Corning (Ondo) | YQzNQh2YSFQ6nh91E8Ja71U6JuZDLap5jJCsELGondo |
| USDon | Ondo US Dollar Token | ZPFtoCe7WWqG4N3ZFRccS8T9SMBeHsd1Vmgv2i7ondo |
All Ondo mints use 9 decimals. The xStocks API page covers the pool-traded side of these assets.
What else trades on RFQ
Aggregate AmountInUSD from
Instruction Balance Updates grouped by currency pair and
the mix is consistently unlike the Solana DEX tape. SOL against the majors leads, but a large
block of volume is stablecoin to stablecoin (USDC/USDT and the newer dollar tokens), with
wrapped BTC and ETH, a handful of large-cap tokens, and the tokenized equities making up the
rest. Long-tail memecoin churn is largely absent.
That mix is the signature of professional treasury and inventory flow rather than retail speculation, and it is the main reason RFQ notional looks small next to DEX notional while being far more concentrated per fill.
Jupiter Limit Order v2
Resting orders rather than quotes, but the fill path has the same shape (taker, maker,
order).
query JupiterLimitOrders {
Solana {
Instructions(
limit: { count: 20 }
orderBy: { descendingByField: "cnt" }
where: {
Transaction: { Result: { Success: true } }
Instruction: {
Program: {
Address: { is: "j1o2qRpjcyUwEvwtcfhEQefh773ZgjxcVRry7LDqg5X" }
Method: { in: ["initialize_order", "fill_order", "cancel_order"] }
}
}
}
) {
Instruction { Program { Method } }
Transaction { Signer }
cnt: count
signers: count(distinct: Transaction_Signer)
}
}
}
Two things fall out of that grouping and both are structural. Orders are cancelled far more
often than they are filled, so treat initialize_order as intent rather than volume. And
while many distinct makers create orders, the fill_order signer set collapses to a single
Jupiter-operated keeper, which makes the fill path a single point of failure worth monitoring
separately from the orders themselves.
Mayan Swift: cross-chain intents
Solana is both a source and a destination here. init_order starts an outbound intent, while
fulfill and settle complete an inbound one.
query MayanSwiftIntents {
Solana {
Instructions(
limit: { count: 20 }
orderBy: { descending: Block_Time }
where: {
Transaction: { Result: { Success: true } }
Instruction: {
Program: {
Address: { is: "mayan34VedncxdK2XobtvWFDXQASUTBXhUVzt2kKgny" }
Method: { is: "init_order" }
}
}
}
) {
Block { Time }
Transaction { Signature Signer }
Instruction {
Accounts { Address }
Program {
Method
AccountNames
Arguments {
Name
Type
Value {
... on Solana_ABI_Json_Value_Arg { json }
... on Solana_ABI_BigInt_Value_Arg { bigInteger }
}
}
}
}
}
}
}
The InitOrderParams JSON carries the complete intent:
amount_in_min, amount_out_min user's limit price
chain_dest, token_out destination chain (Wormhole chain id) and token
deadline intent expiry (minutes, not seconds)
gas_drop destination gas top-up
fee_cancel, fee_refund failure-path economics
addr_ref, fee_rate_ref referrer address and referral fee rate
fee_rate_mayan protocol fee rate
auction_mode auction type
addr_ref and fee_rate_ref together give you per-integrator revenue attribution: which
frontend sourced the order, and what it earned.
Outbound and inbound flows run at broadly similar rates, so Solana is a genuine two-way hub
rather than mostly an exit. Decode chain_dest against Wormhole chain ids to get the current
destination mix; the major EVM chains dominate it.
The trader side of Mayan is wide, with a distinct signer on almost every order. The solver
side is not: fulfils come from a very small set of driver addresses, typically with one taking
the large majority. Track drivers with account index 1 on fulfill and alert on that set
shrinking, because it is the part of the system with the least redundancy.
To stream cross-chain flow leaving Solana:
subscription MayanOutboundIntents {
Solana {
Instructions(
where: {
Transaction: { Result: { Success: true } }
Instruction: {
Program: {
Address: { is: "mayan34VedncxdK2XobtvWFDXQASUTBXhUVzt2kKgny" }
Method: { is: "init_order" }
}
}
}
) {
Block { Time }
Transaction { Signature }
Instruction {
Accounts { Address }
Program {
Arguments {
Name
Value { ... on Solana_ABI_Json_Value_Arg { json } }
}
}
}
}
}
}
The on-chain quote tape
Several proprietary market makers publish quote updates as top-level instructions that move zero tokens. They are not trades and appear in no trade table, yet they are the highest-frequency structured data on Solana.
Use Instruction.Depth to tell them apart. Depth 0 is a standalone quote update. Depth 1
or deeper is a CPI from an aggregator router, meaning a real swap.
query QuoteTapeVsFills {
Solana {
Instructions(
limit: { count: 10 }
orderBy: { descendingByField: "cnt" }
where: {
Transaction: { Result: { Success: true } }
Instruction: {
Program: { Address: { is: "9H6tua7jkLhdm3w8BvgpTn5LZNU7g4ZynDmCiNN3q6Rp" } }
}
}
) {
Instruction { Depth }
cnt: count
signers: count(distinct: Transaction_Signer)
}
}
}
Swap the program address for any venue in Family 2 and the depth-0 to depth-1 ratio sorts them into two architectures:
- Quote-posting. Depth-0 instructions vastly outnumber routed fills, often by an order of magnitude. HumidiFi, Tessera V, QuantumAMM, Aquifer and BisonFi work this way (Scorch does the same through its separate price program), and the busiest of them emit updates at a rate measured in millions per day. This is a live market-maker quote feed sitting on chain, and it exists in no trade table.
- Oracle-at-swap. Little or no depth-0 traffic, because the venue prices from an oracle at execution time. SolFi, GoonFi, ZeroFi and AlphaQ sit here. There is nothing to watch until a trade lands.
The signers count is the second tell. A venue running its whole quote feed from a single
signer is a different operational risk from one spreading it across dozens.
Bitquery decodes the swaps on HumidiFi, Tessera V, QuantumAMM and Scorch (methods such
as swap, swap2, swapV3 and swap2022). Their quote updates still arrive undecoded, as
raw Instruction.Data with Program.Parsed: false, and ZeroFi has no IDL at all. You can
still measure how often each maker updates, and from which signer.
Notes on method
This page deliberately states patterns and gives you the queries, rather than publishing point in time statistics that go stale. A few things to get right when you run them yourself:
- Value the stablecoin side. For notional, take the stablecoin side at $1 where one exists and
fall back to the SOL side otherwise.
AmountInUSDon Instruction Balance Updates already does this for you. - Prefer medians for reference prices.
average(of: Trade_PriceInUSD)is badly skewed on thin pairs and will hand you a SOL price that is tens of percent wrong. Use a median over a liquid pair instead. - A minute-median AMM price approximates the mid, not a same-block quote. It is good enough for spread work on liquid pairs and misleading on illiquid ones.
- Size buckets need volume. The interesting crossover sits above $100k, where fills are rare. Widen the window before drawing conclusions about large size.
- AMM landing rates include bot traffic. A retail-only figure is higher. Compare like with like if you are using it to justify a routing decision.
One open item: the second order_engine deployment is identified here from a shared maker set
and matching IDL, not from protocol documentation.
Related
- Solana Instructions API, the cube most queries here use
- Instruction Balance Updates, for currency and USD on each side of a fill
- Solana Transfers API
- Solana Jupiter API, the aggregator and routing side
- xStocks API, pool-traded tokenized equities
- SolFi API and GoonFi API
- Solana DEX Orders API, limit-order book data
Ready to run this in production?
Get an API key and run these queries in minutes, or talk to us about plans and enterprise delivery.