rfq-fill route trades Backpack Securities tokens against USDC. It works differently from the other routes:
- On the other routes, the trade completes in one transaction. A
CONFIRMEDstatus fromPOST /v1/executemeans that the trade is done. - On
rfq-fill, your transaction opens an order on Solana. A solver then fills the order. ACONFIRMEDstatus means that the order is open, not that it filled.
POST /v1/execute request until the response says done. To look up orders later, use GET /v1/orders.
This recipe builds on the swap recipe. It uses the same headers, wallet, account, and signing code.
Before you start
- Eligibility. Buying and selling tokenized stocks is only available to eligible users in supported jurisdictions. Eligibility restrictions apply under Backpack and Trek documentation and applicable law. Your use of the Sunrise API is subject to the Sunrise Terms of Service, including its Backpack/Trek Tokenized Equities Disclosures.
- Market hours. When the stock’s market is closed,
rfq-fillgives no quote.unavailableRouteslistsrfq-fillwith the codemarket_closed. Another route can still quote the stock.
Step 1: Find a stock token
CallGET /v1/tokens?issuer=backpack_securities to list the Backpack Securities tokens. Use a token’s address as its mint and its decimals to convert amounts.
tokensResult.data.pagination.nextCursor isn’t null, call the endpoint again with cursor set to it, and keep issuer unchanged.
For a stock’s ticker, exchange, and fundamentals, see Stock data.
Step 2: Get a quote
rfq-fill quotes stock tokens against USDC only. To buy, pay with USDC. To sell, receive USDC. Choose the pair and the amount. Amounts are strings in base units.
- Buy
- Sell
Pay with USDC and receive the stock token. USDC has 6 decimals, so
"10000000" means 10 USDC.POST /v1/quotes, as in the swap recipe:
rfq-fill or from another route. Check quote.routeName:
rfq-fill: continue with this recipe.- Another route: the quote is a regular swap, and
CONFIRMEDmeans that the trade is done. Execute it as in the swap recipe, or skip it.
quote.fromAmount can be less than the USDC that you asked for. Show the quote’s amounts to your user, not the requested amount.
quote.estimatedDurationMs estimates how long the order takes to complete after you execute it.
To add your own fee to the trade, see Integrator fees and attribution.
Step 3: Sign the quote
Execute the quote beforequote.expiresAt, about 10 seconds after you get it. If the quote expires, get a new quote. If you show your user a live price, get a new quote before expiresAt passes.
Sign the quote’s transaction as in step 4 of the swap recipe.
Step 4: Execute and track the order status
CallPOST /v1/execute with the signed transaction, quoteId, routeName, and providerRequestId. Send the same request again until data.done is true. The order can take a few seconds to fill, so wait between requests.
status and done:
SUBMITTED: the transaction is still landing.doneisfalse.CONFIRMED: the transaction landed and the order is open.data.orderslists the order. It can be missing for a moment after the transaction lands, whiledoneis stillfalse.doneturnstrueonce every order is final.
CONFIRMED depends on the side:
- Buy. The order holds your USDC. Your wallet doesn’t hold the stock token yet. A solver fills the order shortly after, so
donestaysfalseuntil then. - Sell. The solver pays you USDC in the same transaction that opens the order. Thus, the order is usually
FILLEDanddoneistrueon the firstCONFIRMEDresponse. It can still showOPENonce, so keep the same loop.
quote.estimatedDurationMs. Show that estimate to your user. This example sends the request every 2 seconds, up to 300 times, in case the order takes longer. If the order still has no final status, it stays open. Send the same request again later.
Sending the same request again is safe: a signed transaction is executed at most once.
The solver can cancel an open order at any time. After the on-chain TTL, any wallet can cancel it. The refund always goes to your wallet. As of October 26, 2026, the TTL is 9,000 slots from when the order opens, about 30 minutes. The Sunrise API has no cancel endpoint.
Step 5: Handle the result
Each order indata.orders has orderId, type, status, and details. orderId is the quote’s providerRequestId. On rfq-fill, type is rfq. status decides which transaction fields details holds. “The input” is the token that you paid with: USDC for a buy, the stock token for a sell.
The fill statuses can give two amounts as strings of raw base units:
deliveredAmountRaw: the output token amount that the fill delivered to your wallet.returnedAmountRaw: the input amount that the fill returned to your wallet.
POST /v1/execute can show a partial fill as FILLED. To get the final status and amounts, use step 6. When an amount is not in details, examine fillTxHash in a Solana explorer.
CANCELLED, REFUNDED, and FAILED all mean the same thing: no trade happened, and the input is in their wallet. They differ only in which transaction to link to.
Step 6: Get order status later
Two endpoints read orders after execute, in the same{ orderId, type, status, details } shape:
GET /v1/orders?walletAddress=lists a wallet’s orders, newest placed first.GET /v1/orders/{txHash}returns the orders that one transaction opened, filled, or cancelled.
rfq-fill. A key without it gets 403 forbidden.
Orders show up on these endpoints about a minute after their transaction lands. Thus, they never show
PENDING or FAILED orders. To follow an order that you just placed, use the execute loop in step 4, which reads orders live.Orders opened before these endpoints launched aren’t listed.List a wallet’s orders
Pass the wallet aswalletAddress. To list only one status, add status: OPEN, FILLED, PARTIALLY_FILLED, CANCELLED, or REFUNDED. A page holds up to 100 orders. When pagination.nextCursor isn’t null, call the endpoint again with cursor set to it, and keep walletAddress and status unchanged.
Find the orders of a transaction
Pass the hash of any of the order’s transactions: itsopenTxHash, fillTxHash, or cancelTxHash. The response lists every order that the transaction touched.
404 with not_found means that no order is listed for the transaction. Within the first minute after it lands, that can just mean that the order isn’t listed yet.
An order fills at most once. A partial fill is final: your wallet gets less than the quote’s
toAmount, and the rest of the input comes back in the same transaction. These endpoints show a partial fill as PARTIALLY_FILLED, with the returned input in returnedAmountRaw.Errors
POST /v1/quotes, POST /v1/execute, and the orders endpoints return the errors in Errors. A 503 with execute_status_unknown means the API couldn’t find out whether the transaction was sent. Send the same request again. A 502 with bad_gateway that repeats won’t clear by itself: stop retrying and report it with the response’s requestId.
For the full request and response schemas, see the API specification.
