Get OAuth credentials
You need values from Passage before you write code:client_id- public identifier for your appredirect_uri- exact callback URL registered with Passage (must match what you pass in the SDK)client_secret- server-only; never expose this to the browser or commit it to frontend bundles
COINLIST_CLIENT_SECRET), and use NEXT_PUBLIC_ only for values that are safe in the client (typically client_id and redirect_uri).
Install the SDK
react and react-dom >= 18, and viem ^2 (added in v0.9.0 for the on-chain flows).
See Package overview for all import paths and the full namespace map.
How the SDK is organised
The SDK splits three ways, in this order: environment, then level, then feature. Learn those three and you can find anything in it.Level: how much you want to build
Every feature is available at several levels. Each rung down hands you more control and asks for more code from you. Start at the top and descend only when you need to.-
L3 - React components (highest). You get UI and behavior together, working. You provide the input and a config object, and you are done.
<CheckoutContainer offer chain wallets config />is an entire purchase flow: wallet selection, amount entry, approval, review, confirmation. Others:<OffersGridContainer>,<RequirementsChecklistContainer>,<CoinListSignInCardContainer>. -
L2 - React hooks (mid). You get the behavior: data fetching, state, polling, error handling, and the on-chain sequencing. You provide the UI.
useOffers()hands you aLOADING / ERROR / CONTENTstate machine and you render whatever you like from it;useOndoBuyCheckoutViewModel()runs the whole Ondo flow behind your own markup. -
L1 - TypeScript clients (low). You get the logic: typed API calls, domain mapping, and total on-chain flows that return a step-tagged result instead of throwing. You build the state management and the UI.
CoinListClientin the browser,CoinListServeron your backend, no React anywhere. Use it for a non-React app, a custom state layer, or full control.
Environment: where code runs
Environment comes first in the split because it is a security boundary, not a convenience:CoinListServer holds your client_secret, and it must never reach a browser bundle. That boundary is exactly the three import paths you type.
The gaps in that grid are the point. L2 and L3 exist only in the browser, because a backend renders nothing. L1 is the only level present everywhere, which is why
coinlist.offers.list() reads the same on your server as in your React tree.
Feature
Inside each level, the code is grouped by feature, and the same four words appear at every rung: auth, offers, requirements, checkout. Socoinlist.requirements (L1), useRequirements() (L2) and <RequirementsChecklistContainer> (L3) are three rungs of one ladder, not three unrelated APIs.
Checkout is grouped one level deeper, by provider and product - Superstate swap, Ondo buy, CoinList token sale - because every provider’s flow differs in roughly a fifth of its substance. <CheckoutContainer> is what saves you from caring: it routes on offer.type so you integrate once.
Next step
SDK structure
The patterns every part of the SDK follows: namespaces, containers, hook state, and flows that return instead of throwing. Skippable if you would rather start building.
OffersGridContainer, then Display offer details for a single offer. Then Requirements for what a user must satisfy before participating, Wallets to plug in your wallet stack, and Building the Checkout flow to render the purchase itself. Use the API reference when you need raw request and response details.