> ## Documentation Index
> Fetch the complete documentation index at: https://v2.docs.conduit.financial/llms.txt
> Use this file to discover all available pages before exploring further.

# Crypto Wallets

> On-chain non-custodial wallets for receiving and sending crypto

## Overview

A Crypto Wallet is the customer-facing resource for holding crypto on a single blockchain. Each wallet has one on-chain `address` on one `chain`, and its `balances[]` reflect the deposits and pending movements Conduit has observed for that address.

Wallets are scoped to a customer. No wallets are created during onboarding — they are provisioned when the customer claims non-custodial control (see [Creating Wallets](#creating-wallets)), which by default provisions a wallet on every supported chain in one step. A customer can hold multiple wallets across different chains.

The crypto-wallets feature must be enabled for your client. Calls to these endpoints while the feature is disabled return a feature-not-enabled error.

## Custody Model

Every wallet on this surface is **non-custodial** by default: the customer holds the keys (passkey or roster of signers) and must sign every outbound transfer. Conduit does not hold customer signing keys.

| Who controls the key                | How funds are moved                                                                              |
| ----------------------------------- | ------------------------------------------------------------------------------------------------ |
| Customer (passkey or signer roster) | Conduit and the customer's signers co-sign; Conduit broadcasts once the signing threshold is met |

On the non-custodial path, a customer must claim non-custodial control before any wallet address can be issued. `POST /v2/customers/:id/wallets` returns `422 WALLET_NO_PROVIDER_ACCOUNT` until that's done. Where the custodial path is enabled for your account, that same call instead mints a custodial wallet directly. Receiving crypto works once a wallet exists; the custody model determines what happens on **outbound** — see [Non-Custodial Wallets](/concepts/non-custodial-wallets) for the full co-signing flow.

Custodial wallets remain available where Conduit has the necessary licences in your jurisdiction; contact your Conduit representative to enable the custodial path.

### Signing modes

Signers don't have to be humans. Every customer has a **signing mode** — passkey required, programmatic, or programmatic unattended — that governs whether roster seats are held by human passkey holders, server-held API keys, or a mix. Conduit configures it out of band as an organization-wide default with per-customer overrides (the customer-level setting wins); there is no API to set it, so arrange it with your Conduit representative. See [Non-Custodial Wallets](/concepts/non-custodial-wallets#signing-modes) for the mode-by-mode breakdown and [Programmatic payout signing](/guides/machine-signer-stamping) for the machine flow.

## The Wallet Resource

| Field                | Type           | Description                                                                                                                                                                            |
| -------------------- | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                 | string         | Wallet ID, prefixed `wlt_`.                                                                                                                                                            |
| `chain`              | enum           | The blockchain this wallet operates on.                                                                                                                                                |
| `address`            | string \| null | The on-chain address. `null` while the wallet is provisioning.                                                                                                                         |
| `status`             | enum           | Lifecycle status — see below.                                                                                                                                                          |
| `custodyModel`       | enum \| null   | `non_custodial` for wallets on the non-custodial path; `custodial` for wallets minted on the custodial path where available. Omitted until the customer's custody model is determined. |
| `rotatedAt`          | string \| null | When this wallet was rotated, if it has been.                                                                                                                                          |
| `replacedByWalletId` | string \| null | The wallet that replaced this one after rotation.                                                                                                                                      |
| `clientReferenceId`  | string \| null | Your external reference, if you supplied one at creation.                                                                                                                              |
| `balances[]`         | array          | Per-asset balances, each with `available`, `pending`, and `frozen` amounts.                                                                                                            |
| `createdAt`          | string         | When the wallet was created.                                                                                                                                                           |
| `updatedAt`          | string         | When the wallet was last updated.                                                                                                                                                      |

### Balances

Each entry in `balances[]` splits a single asset into three buckets:

* **`available`** — settled funds you can move out.
* **`pending`** — funds observed on-chain but not yet fully settled.
* **`frozen`** — funds held and temporarily unavailable.

Balances reflect deposits as they arrive. See [Receiving crypto](#receiving-crypto) below.

### Status lifecycle

| Status     | Meaning                                                                                                                           |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `pending`  | The wallet has been requested and Conduit is provisioning its on-chain address. `address` is `null` until provisioning completes. |
| `active`   | The wallet has an address and can receive and send funds.                                                                         |
| `disabled` | The wallet is no longer in use — for example, it has been rotated and replaced.                                                   |

<Note>
  A wallet enters `pending` with a `null` `address` while it is being
  provisioned. Rather than polling, subscribe to
  [`crypto_wallet.completed`](/webhooks#crypto_wallet.completed) to learn when
  provisioning has finished and the `address` is populated.
</Note>

## Supported Chains

Wallets can be created on the following chains. The `chain` value is always lowercase.

| `chain`    | Network  |
| ---------- | -------- |
| `ethereum` | Ethereum |
| `base`     | Base     |
| `polygon`  | Polygon  |
| `solana`   | Solana   |
| `tron`     | Tron     |

The three EVM chains — `ethereum`, `base`, and `polygon` — share a single on-chain `address`. A customer's wallets on those chains all resolve to the same address, so funding one and reading another are the same account on each network. `solana` and `tron` each have their own distinct address.

<Note>
  `solana` wallets are address-only for now: you can create the wallet and
  receive on it, but outbound payouts and orders on Solana are not yet
  available. Payouts and orders are supported on the EVM chains and `tron`
  (`tron` carries USDT only). An order naming an unsupported asset/chain
  combination is refused with `UNSUPPORTED_ASSET`; a payout is refused as a
  validation error on `assetAmount.chain`.
</Note>

## Creating Wallets

Wallets are provisioned by claiming non-custodial control once per customer, which by default provisions a wallet on every supported chain. Adding a wallet on a chain you excluded from the claim is an optional second step.

<Steps>
  <Step title="Claim non-custodial control (once per customer)">
    Opt the customer into the non-custodial multi-signer model:

    ```http theme={null}
    POST /v2/customers/{customerId}/wallets/claim-non-custodial

    {
      "roster": [ ... ],
      "signingThreshold": 2
    }
    ```

    The request body may include an optional `chains` array naming which chains to provision. **Omit it** — the recommended default — and every supported chain is provisioned in one claim, so a customer that later needs a new chain does not have to re-authenticate its signers. Provisioning one wallet up front on each chain covers future use with no further signer ceremony.

    Because the three EVM chains share a single address and key, provisioning them costs one admin approval, not three. `solana` and `tron` are provisioned as their own addresses in the same claim.

    Returns `202`. The customer's signers complete their enrollment via per-signer verification URLs delivered through `wallet_signer.invited` webhooks. Once the final signer has enrolled, `crypto_wallet.completed` fires and the claim's wallets activate. Polling `GET /v2/customers/{customerId}/wallets/claims/{claimId}` returns a `chains` field listing every chain provisioned for the claim.
  </Step>

  <Step title="Add a wallet on another chain (optional)">
    When you omit `chains` at claim time, wallets on every supported chain already exist and this step is unnecessary. To add a wallet on a chain you explicitly excluded, call:

    ```http theme={null}
    POST /v2/customers/{customerId}/wallets
    idempotency-key: <unique-key>

    { "chain": "base", "clientReferenceId": "ext-wallet-001" }
    ```

    `clientReferenceId` is optional — 1-255 characters from A-Za-z, 0-9, underscore, hyphen, colon, and period; no spaces. The `idempotency-key` header is required — replaying the same key returns the same result rather than creating a duplicate.

    The response has **two shapes**, depending on whether the new wallet needs fresh signing material:

    * **`201 Created` with the wallet** — when the chain reuses key material the customer already controls. Adding an EVM chain (`ethereum`, `base`, or `polygon`) while the customer already has an EVM wallet reuses the existing shared address and key, so no admin approval is needed and the wallet is returned immediately, already `active` on the shared address.
    * **`202 Accepted` with `{ "ceremonyId": "wcm_…", "verificationUrl": "…" }`** — when the wallet needs new signing material (a chain that does not share the existing key, or a second wallet on a chain). This parks an admin-cosign ceremony rather than creating the wallet inline: route `verificationUrl` to one of the customer's admins to approve on the verify page. A [`wallet_ceremony.awaiting_admin_approval`](/webhooks#wallet_ceremony.awaiting_admin_approval) webhook carries the same admin link. On approval, [`wallet.created`](/webhooks#wallet.created) and `crypto_wallet.completed` fire and the wallet is `active`; if the admin declines, `wallet_ceremony.failed` fires and no wallet is created.
      Retrying while a ceremony for the same chain is already pending is idempotent: the call re-mints that pending ceremony's approval and returns `202` again (or `409 CEREMONY_IN_FLIGHT` if a concurrent request for the same chain has not finished recording its ceremony). Wait for the pending ceremony to resolve rather than opening a second one.

    Before the customer has claimed non-custodial control, the same request returns `422 WALLET_NO_PROVIDER_ACCOUNT`. See [Non-Custodial Wallets](/concepts/non-custodial-wallets) for the multi-signer model and the [`WALLET_NO_PROVIDER_ACCOUNT`](/errors#wallet-no-provider-account) error page for the resolution path.
  </Step>
</Steps>

## Reading Wallets

| Endpoint                                            | Returns                                                                                            |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `GET /v2/customers/{customerId}/wallets`            | A cursor-paginated list of the customer's wallets. Accepts an optional `clientReferenceId` filter. |
| `GET /v2/customers/{customerId}/wallets/{walletId}` | A single wallet.                                                                                   |

## Key Rotation

Rotating a wallet replaces its on-chain key. It produces a **new wallet** with a **new `address`**; the old wallet moves to `disabled`. The two are linked: the old wallet's `replacedByWalletId` points to the new wallet, and the new wallet carries the same `replacedByWalletId` back-reference so the chain of replacements is traceable.

```http theme={null}
POST /v2/customers/{customerId}/wallets/{walletId}/rotate
idempotency-key: <unique-key>
```

Returns `200` with the replacement wallet. The `idempotency-key` header is required.

<Warning>
  Move the funds out before rotating — the new key controls a different address,
  so rotation is blocked while the wallet still holds a balance worth moving. A
  leftover at or below the asset's dust floor does not block it.
</Warning>

A [`wallet.rotated`](/webhooks#wallet.rotated) webhook fires when rotation completes, carrying both the old `walletId` and the `replacedByWalletId`.

## Receiving Crypto

Share the wallet's `address` and incoming deposits surface in the wallet's `balances[]`. The end-to-end flow — including how deposits are detected and credited — is covered in the [Receive Crypto](/guides/receive-crypto-lifecycle) guide.

## Related

<CardGroup cols={2}>
  <Card title="Non-Custodial Wallets" href="/concepts/non-custodial-wallets">
    The two-signature model, the customer verify page, and outbound co-signing.
  </Card>

  <Card title="Receive Crypto" href="/guides/receive-crypto-lifecycle">
    How deposits arrive and credit a wallet's balances.
  </Card>

  <Card title="Add a Crypto Wallet" href="/guides/add-crypto-wallet">
    Step-by-step: enable the feature, claim non-custodial control, enroll the
    roster, and activate.
  </Card>

  <Card title="Programmatic payout signing" href="/guides/machine-signer-stamping">
    Approve payouts server-to-server with an API-key signer instead of a passkey.
  </Card>
</CardGroup>
