The same feature at three levels
Every feature is available at several levels. Here is one feature - listing offers - at all three:- You pick a rung per feature, not per application. Rendering
<CheckoutContainer>while drivingcoinlist.offers.list()yourself is normal, not a compromise. The rungs are built from each other, so mixing them cannot desynchronise anything. - A feature may stop at a rung, but never skips one. CoinList token sales have a namespace and a hook and no component yet. That absence is deliberate and readable: it means the flow is not drop-in yet, so its UI is yours to render.
L2 and L3 are browser-only, because a backend renders nothing. L1 is the only level present everywhere, which is why
coinlist.offers.list() reads identically on your server and in your React tree. See the grid in the quickstart.Namespaces
The entire L1 surface is namespaces.coinlist.init() is the only top-level member on the browser client, because it is lifecycle rather than domain; CoinListServer has none at all. So every operation you will ever want is coinlist.{something}.{verb}(), and finding it means picking the right noun.
Two naming rules tell you which noun:
- CoinList’s own capabilities are named after what they do:
offers,requirements,wallets,auth,erc20,tokens,support. - A third party’s namespace is named after the provider:
superstate,ondo. Not after what it does. A second provider ships its own swap, socoinlist.swapcould only ever have meant one of them - which is exactly why it was renamed in v0.11.0.
offers.list() rather than offers.listOffers():
Any method taking more than one argument takes a single params object. Single-argument methods take the value directly. You never have to remember an argument order.
API-backed methods throw
NotAuthenticatedError when there is no session. The one exception is coinlist.tokens, which is public: it reads a CDN, needs no login, and works with a read-only server session store.
Package overview
The full table of namespaces and their methods, for looking one up.
Components
Every component you render is a container:CheckoutContainer, OffersGridContainer, RequirementsChecklistContainer. You hand it input and config, and it owns everything else.
Three habits will keep you out of trouble:
- Prefer the container that decides for you.
<CheckoutContainer>routes onoffer.typeso a new provider is a version bump rather than your problem. Reaching past it for<OndoBuyCheckoutContainer>means you have taken on that routing. - Optional
onXcallbacks are overrides, not wiring. They default to something sensible -RequirementsChecklistContainer’s action button already callscoinlist.requirements.handle. Omit them until a default is wrong for you. - You never need a style provider. Every exported component wraps its own output in a style scope, so it renders fully styled wherever you put it.
Hooks
State is a machine, not a pair of booleans. Loading, error and content are one discriminated union with machine-readablereason codes:
default branch to const exhaustive: never = state. A state added in a future release then becomes a compile error rather than a blank screen your users find first. The field is named after what it holds - offersState, requirementsState - except on viewmodels, which return { state, onEvent }.
Two options recur, and both exist because of how React works rather than as conveniences:
datais how server rendering works. Pass data your backend already fetched withCoinListServerand the hook uses it as-is, skipping the initial client fetch. Where arefetch()exists it still goes to the network: the seed answers the first render only. Worked example.enabledis how you turn a hook off. React forbids calling a hook conditionally, so a hook you cannot skip must instead go quiet:enabled: falsemeans no requests, no timers, and a benign state rather than a permanentLOADING. This is what letsCheckoutContainercall every provider’s viewmodel on every render and enable only the matching one. Hooks that poll or drive a flow take it; the plain catalogue reads takedataonly.
Flows return, they don’t throw
Anything that drives a wallet -coinlist.superstate.execute, coinlist.ondo.prepareSwap, coinlist.tokenSale.execute - is a flow, and a flow is total. It does not throw. It returns a result tagged with the step that failed:
onProgress, a callback of named phases, so you can tell a user which wallet popup they are looking at.
Let your wallet library’s own errors propagate out of your EvmWallet implementation. The SDK catches and classifies them into a typed WalletError, so you never parse a wallet error string. See Wallets.
Next step
Set up OAuth authentication
Add
CoinListProvider, a sign-in surface, callback handling, and server routes.