> ## Documentation Index
> Fetch the complete documentation index at: https://docs.passage.coinlist.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Ondo Swap Sell

> Sell tokenized stocks back to Ondo. One config field switches direction, what a sale approves instead of a purchase, and how to skip the wallet step.

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](/sdk/ondo-swap-buy), through the same contract, with the same two-call order.

<Note>
  **Recommended:** render [`CheckoutContainer`](/sdk/checkout). 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.
</Note>

## 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:

```tsx theme={null}
"use client";

import {
  CheckoutContainer,
  defaultCheckoutConfig,
  type CheckoutWalletSelection,
} from "@coinlist-co/react";
import {
  AssetSymbol,
  type OfferDetail,
  type OrderBookSide,
} from "@coinlist-co/react/universal";

export function OndoCheckout({
  offer,
  wallets,
  side,
}: {
  offer: OfferDetail;
  wallets: CheckoutWalletSelection;
  side: OrderBookSide;
}) {
  const config = defaultCheckoutConfig({
    "ondo::swap": {
      symbol: (offer) => AssetSymbol(offer.asset.code),
      side: () => side,
    },
    "coinlist::token_sale": { render: () => null },
  });

  return (
    <CheckoutContainer
      // The side belongs in the key: the flow holds an approval granted for
      // whichever coin that side spends.
      key={`${offer.id}:ethereum_mainnet:${side}`}
      offer={offer}
      chain="ethereum_mainnet"
      wallets={wallets}
      config={config}
    />
  );
}
```

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.

<Warning>
  **`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.
</Warning>

Everything else on the config - the `symbol` resolver and its totality requirement, `onOrderConfirmed` - works exactly as it does on the [buy page](/sdk/ondo-swap-buy#what-ondo-needs-from-you).

## 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`](/sdk/wallets#skipping-the-wallet-step) and the checkout opens on the amount, renumbering the two cards that remain:

```ts theme={null}
const wallets: CheckoutWalletSelection = {
  embedded,
  external,
  preselected: walletHoldingThePosition, // an EvmWallet, or null to let the user pick
  connectExternal,
  disconnectExternal,
};
```

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.

<Note>
  **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.
</Note>

## 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.

<Warning>
  `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.
</Warning>

<Note>
  **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.
</Note>

<AccordionGroup>
  <Accordion title="Build your own UI with hooks (L2)">
    `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.

    ```tsx theme={null}
    "use client";

    import { useOndoSellCheckoutViewModel } from "@coinlist-co/react";
    import { AssetSymbol } from "@coinlist-co/react/universal";

    export function OndoSellCheckout({ offer, wallets }) {
      const { state, onEvent } = useOndoSellCheckoutViewModel({
        offer,
        ondoSymbol: AssetSymbol(offer.asset.code),
        chain: "ethereum_mainnet",
        wallets,
      });

      switch (state.type) {
        case "error":
          // state.reason: "unsupported-chain"
          return <MyUnsupportedChainNotice />;
        case "content":
          // state.wallet is null when `preselected` skipped the wallet step.
          // state.amount, state.review, state.sidebar, state.orderConfirmed
          return <MySellFlow state={state} onEvent={onEvent} />;
        default: {
          const exhaustive: never = state;
          return exhaustive;
        }
      }
    }
    ```

    `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:

    | Hook                           | Returns                                                                | `enabled`  |
    | ------------------------------ | ---------------------------------------------------------------------- | ---------- |
    | `useOndoTradingStatus`         | Whether the sell market is open, and this side's caps. Takes `side`.   | yes        |
    | `useOndoPrice`                 | A pollable read quote. Takes `side` and a size.                        | yes        |
    | `useOndoSellTransaction`       | The committed sale from `prepareSell`, with its expiry. Never polls.   | yes        |
    | `useErc20Balance`              | The seller's balance of the asset, keyed by the address off the quote. | yes        |
    | `useOndoSellAmountViewModel`   | The amount step.                                                       | not needed |
    | `useOndoSellReviewViewModel`   | The review and confirm step.                                           | not needed |
    | `useOndoWalletSelectViewModel` | The wallet step. Shared with buy.                                      | not needed |
    | `useOndoSidebarViewModel`      | The asset panel. Shared with buy.                                      | not needed |

    `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](/sdk/structure#hooks) for the conventions they share.
  </Accordion>

  <Accordion title="Drive it yourself with the client (L1)">
    `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.

    | Method                         | Kind        | Notes                                                                           |
    | ------------------------------ | ----------- | ------------------------------------------------------------------------------- |
    | `getTradingStatus(params)`     | read        | Free to poll. Requires `side`.                                                  |
    | `getQuote(params)`             | read        | Free to poll. Requires `side`. Carries `assetAddress` and the asset's decimals. |
    | `buildSellTransaction(params)` | write       | Spends an attestation. `POST /v1/ondo/swap/sell`.                               |
    | `prepareSell(params)`          | client only | Approves the asset, then builds. Step-tagged result.                            |
    | `executeSwap(params)`          | client only | Broadcasts and confirms, either direction. Step-tagged result.                  |

    ```ts theme={null}
    // The quote is the only source of the asset's address and decimals.
    const quote = await coinlist.ondo.getQuote({
      symbol: AssetSymbol("AAPLon"),
      side: "sell",
      tokenAmount: amount,
    });

    const prepared = await coinlist.ondo.prepareSell({
      wallet,                                   // your EvmWallet
      symbol: AssetSymbol("AAPLon"),            // Ondo's API symbol
      chain: "ethereum_mainnet",
      swapContracts: offer.swapContracts,       // the spender, resolved per chain
      tokenAddress: quote.assetAddress,         // the ASSET, not a stablecoin
      amount,                                   // BlockchainAmount, asset base units
      onProgress: (phase) => setBusy(phase),
    });

    if (prepared.type === "error") {
      // prepared.error.step: "unsupported-chain" | "allowance-check"
      //   | "approval" | "approval-reverted" | "insufficient-allowance"
      //   | "build-transaction"
      return;
    }

    // prepared.transaction.expected  — what the sale should return
    // prepared.transaction.minimum   — the floor the calldata enforces

    const filled = await coinlist.ondo.executeSwap({
      wallet,
      transaction: prepared.transaction,
      chain: "ethereum_mainnet",
      onProgress: (phase) => setBusy(phase),
    });

    if (filled.type === "success") {
      // filled.txHash, filled.transaction
    } else {
      // filled.error.step: "quote-expired" | "swap" | "swap-reverted"
    }
    ```

    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-allowance` → `confirming-allowance-reset`, for stale non-zero allowances)* → **`approving`** *(wallet popup)* → `confirming-approval` → `building-transaction`

    **`executeSwap` phases**: **`broadcasting-swap`** *(wallet popup)* → `confirming-swap`

    `executeSwap` broadcasts the calldata **verbatim**. Never re-encode it: the contract verifies a signature over the exact arguments inside.

    <Note>
      `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.
    </Note>
  </Accordion>
</AccordionGroup>

## 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](/sdk/ondo-swap-buy#which-contract-the-buyer-approves).

## Next steps

<CardGroup cols={2}>
  <Card title="Ondo Swap Buy" icon="chart-line" href="/sdk/ondo-swap-buy">
    The other direction: the funding coin, the committed quote, the two-call order.
  </Card>

  <Card title="Building the Checkout flow" icon="cart-shopping" href="/sdk/checkout">
    One container for every provider, and the config both Ondo products share.
  </Card>

  <Card title="Wallets" icon="wallet" href="/sdk/wallets">
    The `EvmWallet` seam, and `preselected` for skipping the wallet step.
  </Card>

  <Card title="Errors and edge cases" icon="triangle-exclamation" href="/sdk/errors">
    Every error shape the flows return, and how to handle it.
  </Card>
</CardGroup>
