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

# SDK structure

> The handful of patterns every part of the SDK follows: the ladder, namespaces, containers, hook state, and flows that return instead of throwing.

<Tip>
  **In a hurry?** Skip straight to [Set up OAuth authentication](/sdk/oauth-authentication) and come back when something surprises you. Nothing here is a prerequisite - it is the map, not the road.
</Tip>

The SDK is small in ideas and large in surface. Learn the five patterns on this page and the rest of the documentation stops being a list of APIs to memorise: you will be able to guess a method you have not read, and you will know which rung to reach for before you look anything up.

## The same feature at three levels

Every feature is available at several levels. Here is one feature - listing offers - at all three:

```tsx theme={null}
// L3  Components. UI and behavior, working.
<OffersGridContainer onOfferClick={(offer) => router.push(`/offers/${offer.id}`)} />

// L2  Hooks. The behavior; you write the UI.
const { offersState } = useOffers();

// L1  Client. The logic; you write the state and the UI.
const offers = await coinlist.offers.list();

// Below L1 there is no SDK - call the HTTP API directly.
```

All three do the same work. They differ only in how much of it you take over. **Start at L3** and drop a rung when the one above cannot give you what you need.

Two things follow from this that are easy to miss:

* **You pick a rung per feature, not per application.** Rendering `<CheckoutContainer>` while driving `coinlist.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](/sdk/invest-flow).

<Note>
  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](/sdk/quickstart#environment-where-code-runs).
</Note>

## 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, so `coinlist.swap` could only ever have meant one of them - which is exactly why it was renamed in v0.11.0.

Methods share one vocabulary, with the namespace's own noun dropped, so `offers.list()` rather than `offers.listOffers()`:

| Method                            | Meaning                                                       |
| --------------------------------- | ------------------------------------------------------------- |
| `list()`                          | Every item, paginating for you                                |
| `listPage(params)`                | One page, when you paginate yourself                          |
| `get(id)`                         | One item by id                                                |
| `create*` / `submit*` / `remove*` | Writes                                                        |
| A real domain verb                | Where one exists: `connectExternal`, `allowWallet`, `execute` |

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

<Card title="Package overview" icon="box" href="/sdks#namespaces" horizontal>
  The full table of namespaces and their methods, for looking one up.
</Card>

## 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 on `offer.type` so a new provider is a version bump rather than your problem. Reaching past it for `<OndoBuyCheckoutContainer>` means you have taken on that routing.
* **Optional `onX` callbacks are overrides, not wiring.** They default to something sensible - `RequirementsChecklistContainer`'s action button already calls `coinlist.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-readable `reason` codes:

```ts theme={null}
type LoadOffersState =
  | { type: "LOADING" }
  | { type: "ERROR"; reason: "not-authenticated" | "generic-error" }
  | { type: "CONTENT"; offers: Offer[] };
```

Switch on it exhaustively and assign the `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:

* **`data` is how server rendering works.** Pass data your backend already fetched with `CoinListServer` and the hook uses it as-is, skipping the initial client fetch. Where a `refetch()` exists it still goes to the network: the seed answers the first render only. [Worked example](/sdk/fetch-offers#server-side-pre-fetch).
* **`enabled` is how you turn a hook off.** React forbids calling a hook conditionally, so a hook you cannot skip must instead go quiet: `enabled: false` means no requests, no timers, and a benign state rather than a permanent `LOADING`. This is what lets `CheckoutContainer` call 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 take `data` only.

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

```ts theme={null}
const result = await coinlist.tokenSale.execute({ /* ... */ });

if (result.type === "success") {
  // result carries what the flow produced
} else {
  // result.error.step names exactly what went wrong,
  // and carries a typed WalletError cause where a wallet was involved
}
```

That is the difference to internalise: **wrap namespace calls in try/catch, and branch on a flow's result.** Every step is documented per provider, so you can write real copy for a rejected signature instead of one generic message. Flows also report progress through `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](/sdk/wallets).

## Next step

<Card title="Set up OAuth authentication" icon="shield-check" href="/sdk/oauth-authentication" horizontal>
  Add `CoinListProvider`, a sign-in surface, callback handling, and server routes.
</Card>
