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

# Requirements checklist

> Render RequirementsChecklistContainer and the SDK handles identity verification, tax documents, and wallet connection for you.

Before a user can participate in an offer, they have to satisfy its **requirements**: identity verification, a signed tax document, a connected wallet, a jurisdiction check. Which ones apply is decided per offer option, by CoinList.

`RequirementsChecklistContainer` renders the whole checklist and resolves every requirement type in-app. It is the highest rung (**L3, components**) and the one to start from.

## Prerequisites

* A completed [OAuth](/sdk/oauth-authentication) session with a working `CoinListProvider`
* An offer id and an offer option id from [Display offer details](/sdk/sale-details)

## Render it

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

import { RequirementsChecklistContainer } from "@coinlist-co/react";

export function Requirements({ offer, option }) {
  return (
    <RequirementsChecklistContainer
      offerId={offer.id}
      optionId={option.id}
      title="Before you invest"
      description="Complete these steps to participate in this offer."
      onContinue={() => router.push(`/offers/${offer.id}/checkout`)}
    />
  );
}
```

That is the whole integration. The container is **self-scoped**: it renders fully styled with no style provider in the tree. When the user is not authenticated it renders a sign-in card instead of the checklist, without fetching anything.

## What it does for you

Clicking a requirement's action button does the right thing per type, with no wiring from you:

| Requirement type                        | Default behavior                                                                                                               |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `kyc_approved`, `accreditation`         | Opens an inline Sumsub verification flow. The backend prescribes both the level and whether the applicant must be reset first. |
| `document`                              | Opens the built-in tax document modal.                                                                                         |
| `external_wallet`, `whitelisted_wallet` | Opens the connect-wallet modal, if you passed a `wallet`. Otherwise opens CoinList in a new tab.                               |
| `jurisdiction`                          | No action - there is no CoinList page to send the user to.                                                                     |

The fallback for everything is `coinlist.requirements.handle`, which opens the corresponding CoinList page in a new tab where the type has one.

<Tip>
  **Omit `onRequirementActionOverride` and rely on the defaults.** Pass your own handler only if you need custom navigation, or `null` to disable the action button entirely. The same applies to `onContactSupportOverride`, which defaults to `coinlist.support.contact`.
</Tip>

## Resolving wallet requirements in-app

Pass a `wallet` and the checklist resolves `external_wallet` and `whitelisted_wallet` requirements without leaving your app. It only needs signing capability, so an [`EvmSigner`](/sdk/wallets#two-shapes-one-seam) is enough - no gas, no transaction.

```tsx theme={null}
<RequirementsChecklistContainer
  offerId={offer.id}
  optionId={option.id}
  title="Before you invest"
  description="Complete these steps to participate in this offer."
  wallet={connectedWallet}          // ConnectWallet, or null while none is connected
  onRequestConnect={openConnectModal}
/>
```

When `wallet` is omitted entirely - or is `null` with no `onRequestConnect` to act on - wallet requirements fall back to opening CoinList in a new tab.

### Props

| Prop                          | Type                                 | Description                                                                  |
| ----------------------------- | ------------------------------------ | ---------------------------------------------------------------------------- |
| `offerId`                     | `OfferId`                            | Required.                                                                    |
| `optionId`                    | `OfferOptionId`                      | Required. Requirements are per option.                                       |
| `title`, `description`        | `string`                             | Required. Heading copy.                                                      |
| `onContinue`                  | `() => void`                         | Called when every requirement is satisfied.                                  |
| `wallet`                      | `ConnectWallet \| null`              | A connected signer, for in-app wallet requirements.                          |
| `onRequestConnect`            | `() => void`                         | Called when the user asks to connect and none is connected.                  |
| `onRequirementActionOverride` | `((r: Requirement) => void) \| null` | Override the action button for all types, or `null` to disable it.           |
| `onContactSupportOverride`    | `((r: Requirement) => void) \| null` | Override the contact-support button, or `null` to disable it.                |
| `getLabel`, `getDescription`  | `(r: Requirement) => string`         | Override the default copy per requirement type.                              |
| `identityVerificationOptions` | `{ levelName?, locale? }`            | Override the backend-prescribed Sumsub level, or set the Sumsub UI language. |
| `data`                        | `RequirementsData`                   | Pre-fetched server-side. Skips the initial client fetch.                     |
| `loading`, `error`            | `ReactNode`                          | Slots.                                                                       |
| `unauthenticatedState`        | `ReactNode`                          | Defaults to the sign-in card. Pass `null` to render nothing.                 |
| `className`                   | `string`                             |                                                                              |

<Note>
  `data` skips the requirements fetch, but connected-wallet addresses are not part of `RequirementsData` and are always fetched client-side.
</Note>

## The individual containers

The checklist mounts these for you. Render one directly only if you are building your own checklist:

* **`IdentityVerificationContainer`** - the inline Sumsub flow. Takes `levelName`, `reset`, `locale`, `onSubmitted`.
* **`TaxDocumentModalContainer`** - the tax document form. Takes `isOpen`, `onClose`, `onSubmitted`.
* **`ConnectWalletModalContainer`** - wallet ownership proof and binding. Takes `isOpen`, `onClose`, `offerId`, `optionId`, `wallet`, `onRequestConnect`, `onConnected`.

<AccordionGroup>
  <Accordion title="Build your own UI with hooks (L2)">
    `useRequirementsChecklistViewModel` backs the container and returns `{ state, onEvent }`. Below it are the data hooks it composes, each usable on its own:

    ```tsx theme={null}
    import { useRequirements } from "@coinlist-co/react";

    const { requirementsState, refetch } = useRequirements(offerId);

    switch (requirementsState.type) {
      case "LOADING":  return <Spinner />;
      // reason: "not-authenticated" | "generic-error"
      case "ERROR":    return <Error reason={requirementsState.reason} onRetry={refetch} />;
      case "CONTENT":  return <MyChecklist requirements={requirementsState.requirements} statuses={requirementsState.statuses} />;
    }
    ```

    | Hook                                | What it does                                      | Options           |
    | ----------------------------------- | ------------------------------------------------- | ----------------- |
    | `useRequirements(offerId, options)` | Fetches requirements and their statuses together. | `data`            |
    | `useOptionAddresses`                | Lists the wallets bound to an offer option.       | `data`, `enabled` |
    | `useKycToken`                       | Mints a Sumsub token for the prescribed level.    |                   |
    | `useTaxDocument`                    | Loads and submits a tax document.                 |                   |
    | `useConnectWallet`                  | Runs the ownership-proof and binding flow.        |                   |

    See [SDK structure](/sdk/structure#hooks) for the conventions they share.
  </Accordion>

  <Accordion title="Drive it yourself with the client (L1)">
    `coinlist.requirements` is a plain namespace with no React. It is available on both the browser client and `CoinListServer`, except `handle`, which needs a browser.

    | Method                    | Returns                                                                                        |
    | ------------------------- | ---------------------------------------------------------------------------------------------- |
    | `forOffer(offerId)`       | `Record<OfferOptionId, Requirement[]>` - which requirements apply to each option.              |
    | `statuses(offerId)`       | `RequirementStatusInfo[]` - the user's progress, with the Sumsub level that resolves each one. |
    | `createKycToken(params?)` | A Sumsub token. Takes `{ levelName, reset }`.                                                  |
    | `getPii()`                | The user's personal information on file.                                                       |
    | `submitDocument(params)`  | Submits a document. Takes `{ documentType, fields }`.                                          |
    | `handle(requirement)`     | Client only. Opens the corresponding CoinList page in a new tab.                               |

    ```ts theme={null}
    const [requirements, statuses] = await Promise.all([
      coinlist.requirements.forOffer(offerId),
      coinlist.requirements.statuses(offerId),
    ]);
    ```

    A requirement `type` is one of `kyc_approved`, `external_wallet`, `whitelisted_wallet`, `jurisdiction`, `accreditation`, `document` - the six the [API reference](/api-reference) declares. A status is `not_started`, `in_progress`, `action_needed`, `completed`, or `rejected`.

    Every method requires a logged-in user and throws `NotAuthenticatedError` otherwise.

    Wallet ownership proof lives on `coinlist.wallets` rather than here, because it is provider-agnostic. See [Wallets](/sdk/wallets#proving-wallet-ownership).
  </Accordion>
</AccordionGroup>

## Next step

<Card title="Building the Checkout flow" icon="cart-shopping" href="/sdk/checkout" horizontal>
  Once requirements are satisfied, render `CheckoutContainer` and let it pick the provider.
</Card>
