Skip to main content
Selling liquidates a holding of an Ondo tokenized stock - AAPLon, TSLAon - and settles the proceeds in USDC. It runs on the same ondo::swap offer type as buying, through the same contract, with the same two-call order.
Recommended: render CheckoutContainer. It detects ondo::swap, reads the direction from your config, and renders the whole sell flow. This page covers what is specific to selling, and the rungs below the component.

One field switches direction

There is no separate sell container. The same CheckoutContainer, the same offer, the same config object - side is what picks the flow:
That is the whole integration. A host with a buy page and a sell page passes the same object with a different side, or closes the thunk over whatever selects the direction - a route, a tab, a toggle.
side is read once, at mount. The flow holds a committed quote and an approval granted for one specific token, so a resolver that starts answering differently mid-flow is ignored rather than obeyed. Remount with a key covering the offer, the chain and the side, as above.
Everything else on the config - the symbol resolver and its totality requirement, onOrderConfirmed - works exactly as it does on the buy page.

What a sale approves

A purchase approves the funding coin, USDC, and receives the asset. A sale is the mirror: it approves the asset and receives USDC. That matters because the SDK ships no registry entry for a provider’s asset. TOKEN_REGISTRY knows the stablecoins swaps are funded with and nothing else, so on a sale:
  • the contract address the balance is read on and the approval is granted for, and
  • the decimals the typed amount is parsed at
both come off the sell quote (OndoQuote.assetAddress, OndoQuote.asset.decimals) and nowhere else. useOndoSellCheckoutViewModel does that resolution for you; a host composing its own viewmodel has to read the quote before it can read a balance. The settlement coin is USDC, and it is display-only: the scale of every amount on a built sale comes off the response’s receive_output_decimals, which is the only authority on how the proceeds are counted.

Skipping the wallet step

A sell page is usually reached from a position, and a position already names the wallet that holds it. Set CheckoutWalletSelection.preselected and the checkout opens on the amount, renumbering the two cards that remain:
The wallet does not have to appear in embedded or external - you supply a ready EvmWallet either way. Like side, it is read once at mount.
The Ondo sell checkout is the only flow that honours preselected today. The field sits on the wallet seam rather than in Ondo’s config because nothing about it is one provider’s, but a provider that has not adopted it ignores it rather than half-implementing it.

Placing an order still takes two calls

The split is the same as a purchase, and for the same reason: an approval takes most of a built transaction’s ~60-second life, so bundling both behind one button would hand the seller an expired quote.
  1. prepareSell approves the swap contract to pull the asset, then builds the sale that delivers it. The result is a committed quote: firm calldata with an expiresAt.
  2. The seller reviews firm numbers - what goes, the fee, what arrives, and the floor below which the fill reverts.
  3. executeSwap broadcasts that calldata verbatim and waits for it to mine. It is one method for both directions, because broadcasting reads calldata and a deadline and knows about neither.
buildSellTransaction spends an attestation on every call. The two reads - getTradingStatus and getQuote - are free to poll while the seller sizes an order. Budget one build per order placed, plus one per refresh the seller asks for.
Sell caps are independent of buy caps. getTradingStatus takes a required side and reports tradable with size limits for that side only - a working buy does not imply a working sell, and the market can be open one way and closed the other.
useOndoSellCheckoutViewModel wraps the whole flow - trading status, sell quotes, the wallet step, the approval, the review and the broadcast - and returns { state, onEvent }. It takes the same options as its buy counterpart.
OndoSellCheckoutUiState is a discriminated union with a flow-level error arm: an offer that names no CoinList swap contract on the chain you passed has no checkout to render, and the viewmodel discovers that before any step runs, so nothing is fetched and no timer starts. switch exhaustively and assign the default to const exhaustive: never = state.Inside the content arm, wallet is null exactly when the host preselected a wallet. That null is the whole skip - it is what you render nothing for, and the single fact both remaining step numbers derive from.The smaller hooks it composes are exported too, so you can assemble your own viewmodel instead:useErc20Balance is the address-keyed counterpart to useErc20TokenBalances: a token whose contract address arrives at runtime has no registry symbol to look up, which is exactly the sell case. A tokenAddress of null fetches nothing, which is the normal state before the first quote lands.Every hook that fetches or polls takes enabled, and all of them must be called unconditionally. See SDK structure for the conventions they share.
coinlist.ondo is a plain namespace with no React. The reads and the two builders exist on both the browser client and CoinListServer; the two wallet-driven halves are client-only.
Both flows are total: every failure comes back step-tagged rather than thrown, so you map each one to your own copy. prepareSell returns the same OndoSwapPreparationError a purchase does - every arm names a remedy, and an unsupported chain or a refused approval is fixed the same way whichever token was being approved.prepareSell phases, in order. An allowance that already covers the sale skips the approval phases:checking-allowance(resetting-allowanceconfirming-allowance-reset, for stale non-zero allowances)approving (wallet popup)confirming-approvalbuilding-transactionexecuteSwap phases: broadcasting-swap (wallet popup)confirming-swapexecuteSwap broadcasts the calldata verbatim. Never re-encode it: the contract verifies a signature over the exact arguments inside.
expected is what the sale should return; minimum is the floor the calldata enforces on chain. A fill below minimum reverts, so that - not expected - is what a seller is actually guaranteed. Show both.

Which contract the seller approves

The ERC-20 spender comes off the offer, the same way it does for a purchase: swapSpender(offer.swapContracts, chain) answers the CoinList contract that chain settles through, or null where this offer cannot be swapped there. See Which contract the buyer approves.

Next steps

Ondo Swap Buy

The other direction: the funding coin, the committed quote, the two-call order.

Building the Checkout flow

One container for every provider, and the config both Ondo products share.

Wallets

The EvmWallet seam, and preselected for skipping the wallet step.

Errors and edge cases

Every error shape the flows return, and how to handle it.