type: "superstate::swap", and it settles directly on-chain: the user pays a stablecoin and receives the fund share, an ERC-20, in the same flow.
Recommended: render
CheckoutContainer. It detects superstate::swap and renders this entire flow - wallet step, amount, review, confirmation - for you. This page covers what is specific to Superstate, and the rungs below the component.The namespace was
coinlist.swap before v0.11.0, and executeSwap is now execute. It is named after the provider now, because Ondo ships its own swap and coinlist.swap could only ever mean one of them. See the changelog for the migration table.What Superstate needs from you
Nothing required inCheckoutConfig. The swap contract is looked up from the chain, and the funding assets and issuer are the provider’s own constants. The key still has to be present, so that the day Superstate does need something the omission is a compile error rather than a runtime surprise:
- The wallet must be allowlisted. Superstate only settles to addresses the issuer has approved. The user signs an ownership challenge and the SDK allowlists the address on the swap contract, once per wallet per offer. The same allowlists are what make permissioned DeFi pools and RWA lending markets reachable.
- The fee is charged on top. The wallet is debited
inputTokenAmount + fee, so always show that sum as the total cost. (Ondo takes its fee off the deposit instead.) - Quotes are read quotes, polled every 15 seconds. Nothing is committed until the swap itself, and slippage protection guards the on-chain minimum output. There is no expiring committed quote to race, as there is with Ondo.
Prerequisites
- Completed OAuth with a working
CoinListProvider - A swap-enabled offer. CoinList provides the offer ID during onboarding - keep it in a constant (
SWAP_OFFER_ID).Offers expose atypefield, so you can detect Superstate swap offers from fetch offers data viaoffer.type === "superstate::swap"instead of hard-coding IDs. Ondo swap offers are'ondo::swap'and take a different flow. - A wallet integration. The partner demo uses wagmi + Reown AppKit; any stack that can sign messages and send transactions works. See Wallets.
Build your own UI with hooks (L2)
Build your own UI with hooks (L2)
useSuperstateSwapCheckoutViewModel wraps the whole flow and returns { state, onEvent }. Call it behind your own markup when you want the SDK’s logic, state and error handling but not its UI.state is a discriminated union - switch on it exhaustively and assign the default to const exhaustive: never = state, so a new step is a compile error rather than a blank screen.The smaller hooks it is composed from are exported too, so you can assemble your own viewmodel instead:All but
useSuperstateWalletViewModel and useSuperstateReviewViewModel take enabled - those two gate on their step input. Like all React hooks they must be called unconditionally. See SDK structure for the conventions they share.Drive it yourself with the client (L1)
Drive it yourself with the client (L1)
coinlist.superstate is a plain namespace with no React. The walkthrough below builds the flow from scratch and mirrors partner-demo’s useSwapViewModel.tsx, trimmed to the minimum that still compiles and runs.Flow at a glance
- connect_wallet - your wallet UI; the SDK only needs the connected account.
- authorizing -
coinlist.superstate.authorizeWalletproves the user owns the wallet (a message signature) and allow-lists it for the offer, broadcasting an allow-list transaction if the contract requires one. One-time per wallet per offer. - quoting -
useSwapOutputToken+useSwapQuoteshow a live quote (input, fee, output), refreshed every 15s. - swapping -
coinlist.superstate.executeruns the ERC-20 approval and then the swap, reporting progress phases. - success / error - show the confirmed output amount and an explorer link, or a readable error.
The flow involves two identities: the CoinList session (from OAuth) identifies who is buying, and the external wallet is where the shares are delivered.
authorizeWallet is what links them - which is why the user signs a message before ever swapping.Step 1: Pin the swap parameters
Keep the offer, chain, contract, and input token in one place.TOKEN_REGISTRY knows the ERC-20 metadata and per-chain address for USDC and USDT.constants.ts
This page runs on Sepolia throughout. The SDK ships
SUPERSTATE_SWAP_CONTRACT_ADDRESS_SEPOLIA; CoinList provides the production mainnet address when swaps go live. To move to mainnet, set SWAP_CHAIN to "ethereum_mainnet" and swap the contract constant - TOKEN_REGISTRY already knows the mainnet USDC/USDT addresses.Step 2: Bring your wallet
The swap flows never import wagmi. They drive any wallet through theEvmWallet interface, which you implement once for every flow in the SDK.Wallets: the EvmWallet seam
The five methods, the wagmi + AppKit adapter, and how wallet errors are classified.
Step 3: Allowlist the wallet
Superstate only settles to wallets the issuer has allowlisted.coinlist.superstate.authorizeWallet does both halves in one call: it proves the user controls the wallet by having them sign a challenge, then registers the address against the offer, broadcasting an allowlist transaction if the contract requires one.success immediately without prompting, so it is safe to call at the start of every session. Flow-level failures come back as { type: "error" } rather than throwing.Parameters
Progress phases
Emitted in order. Two of them open a wallet popup:checking-authorization → requesting-challenge → signing-message (wallet popup) → submitting-signature → broadcasting-transaction (wallet popup, only when the contract needs an allowlist transaction) → awaiting-confirmation → verifying-authorizationError steps
WalletAuthorizationError is tagged by the step that failed, so you can tell a user rejection apart from an infrastructure problem.authorizeWallet wraps three Frontline endpoints, listed here for non-SDK integrations. The middle one is also coinlist.wallets.createOwnershipChallenge, which the requirements checklist uses to bind a wallet to an offer option.Step 4: Show a live quote
Read the output token to learn its decimals, parse the user’s input, then poll a quote.useSwapQuote skips quoting until it has both the output-token decimals and a valid amount.SwapQuote has inputTokenAmount, fee, and outputTokenAmount, all BlockchainAmounts. Render them with formatAmount(amount, LOCALE), and derive the total and the slippage-protected minimum:The fee is charged on top of the amount that buys shares. The wallet is debited
inputTokenAmount + fee, so always show that sum as the total cost.SWAP_POLL_INTERVAL_MS). isRefreshing flags a background refresh; a failed poll keeps the last quote and retries on the next tick.Step 5: Execute the swap
coinlist.superstate.execute runs the whole on-chain sequence and returns a discriminated result.checking-status → checking-allowance → (resetting-allowance → confirming-allowance-reset, only for stale non-zero allowances) → approving (wallet popup) → confirming-approval → swapping (wallet popup) → confirming-swapThe user signs up to two transactions - an ERC-20
approve for input + fee, then the swap itself. USDT-style tokens with a stale non-zero allowance need one extra reset transaction first. All read-only checks run before the first popup, so the user never signs a transaction that would revert.slippageBps sets the on-chain minimum-output guard. DEFAULT_SLIPPAGE_BPS is 0.5% and is fine for a minimal integration; see Going further for a picker.Putting it together
A complete, minimal swap component. It compiles against@coinlist-co/react@0.11.0 and reuses constants.ts (Step 1) and buildEvmWallet (Step 2).MinimalSwap.tsx
useSwapViewModel.tsx in the partner demo.Going further
- Balances and a Max button -
useErc20TokenBalances({ address, chain, assets: [USDC_SYMBOL], enabled })returns aMapof per-asset balances so you can show the user’s USDC and prefill the max amount. It wasuseSwapTokenBalancesbefore v0.11.0; the options and result are unchanged. - A slippage picker -
SLIPPAGE_OPTIONS_BPS(0.25%-5%) withformatBpsAsPercent(bps, LOCALE)for labels; pass the selectedBpstocoinlist.superstate.execute. The SDK also shipsSlippagePickerif you want the packaged control. - More formatting helpers in
@coinlist-co/react/shared:shortenAddress,formattedPricePerShare.
Common questions
Why does the user sign a message before swapping?
Why does the user sign a message before swapping?
The flow has two identities: the OAuth CoinList session is who is buying, and the external wallet is where shares are delivered.
authorizeWallet links them - the user signs an ownership challenge, and the SDK allow-lists that address on the swap contract. It’s one-time per wallet per offer, and returns success immediately on later calls if the wallet is already authorized.Why does my wallet ask for two transactions?
Why does my wallet ask for two transactions?
A swap needs an ERC-20
approve (for input + fee) before the swap itself, so the contract can pull the stablecoin. USDT-style tokens that already have a stale non-zero allowance need one extra reset-to-zero transaction first. Every read-only check (status, allowance) runs before the first popup, so the user never signs a transaction that would revert.How fresh is the quote, and what if the price moves?
How fresh is the quote, and what if the price moves?
useSwapQuote polls every 15s. On execution, coinlist.superstate.execute passes a slippage-derived minimum output to the contract, so if the price moves beyond your slippageBps the swap reverts rather than over-charging. The result’s outputAmount is decoded from the on-chain Swapped event when the receipt yields one, and falls back to the quote’s estimate when it does not - outputAmountConfirmed tells you which one you got.Do I need wagmi / Reown AppKit specifically?
Do I need wagmi / Reown AppKit specifically?
No. Anything implementing the five-method
EvmWallet interface works - viem directly, Privy, ethers with a thin adapter. The SDK only calls signMessage, writeContract, broadcastRawTx, and awaitTx; it never sees your wallet library.How do I go to mainnet?
How do I go to mainnet?
This page uses Sepolia everywhere. CoinList provides the production swap contract address and swap offer ID during onboarding. Set
SWAP_CHAIN to "ethereum_mainnet", point SWAP_CONTRACT_ADDRESS at the mainnet contract, and the wallet adapter’s CHAINS map handles the network switch.Next step
See the full swap implementation
The partner demo’s swap feature adds balances, slippage selection, a review screen, and typed error handling on top of this flow.