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

# Programmatic Onboarding

> Integrate your app within minutes.

User onboarding on Derive is **fully programmatic** - no humans in the loop.
You can integrate the Derive Exchange for your users within minutes.

<video autoPlay muted loop playsInline className="w-full aspect-video rounded-xl" src="https://mintcdn.com/derive-a0490cef/BhszcT-bQHpTuFnc/migrating/programmatic-creation.mp4?fit=max&auto=format&n=BhszcT-bQHpTuFnc&q=85&s=e0c53e087357a8701fc59171cb6f02d7" data-path="migrating/programmatic-creation.mp4" />

Your account and first subaccount come into existence the moment a deposit is
credited to your wallet on-chain.

## The account model

Three terms show up throughout the API. They nest:

* **Wallet** — your Ethereum EOA (the address that owns everything). One wallet ↔ one account.
* **Subaccount** — the unit you actually trade from. It holds collateral and positions and is identified by a numeric `subaccount_id`. A wallet can own many.
* **Manager** — the margin/risk model a subaccount runs under: **standard (cross) margin** or **portfolio margin**. You pick a manager when the subaccount is created; it also fixes the subaccount's risk universe.

<Note>
  A brand-new wallet's first deposit creates **two** subaccounts: the funded one you asked for, plus a **fallback
  subaccount** (under manager id `0`) that catches any deposit that can't be applied to its intended target. Expect two
  ids to appear the first time — the fallback is normal.
</Note>

<Note>
  v3 has no `private/create_subaccount` method — subaccounts are created **on-chain by depositing**. See the
  [changelog](/changelog) for the v2 → v3 method changes.
</Note>

## Step 1 — Choose deposit params

A deposit is routed by three choices: **what asset** to credit, **what manager** the
subaccount runs under, and **what risk universe** it lives in. All three come from
`public/get_all_currencies`, which needs no authentication — for each currency it
lists the registered spot assets and the managers that risk-price it.

<CodeGroup>
  ```typescript TypeScript (SDK) theme={null}
  import { DeriveClient } from '@derivexyz/derive-ts';

  // Public read — no login required.
  const client = new DeriveClient({
    network: 'mainnet',
    wallet: process.env.PRIVATE_KEY!,
  });
  const currencies = await client.marketData.getAllCurrencies();
  const usdc = currencies.find((c) => c.currency === 'USDC');
  console.log(usdc?.spot, usdc?.managers);
  ```

  ```python Python (SDK) theme={null}
  # Coming soon.
  ```

  ```rust Rust (SDK) theme={null}
  // Coming soon.
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.derive.xyz/v3/public/get_all_currencies \
    -H "Content-Type: application/json" \
    -d '{}'
  ```

  ```json Request theme={null}
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "public/get_all_currencies",
    "params": {}
  }
  ```

  ```json Response (trimmed to USDC) theme={null}
  {
    "jsonrpc": "2.0",
    "id": 1,
    "result": [
      {
        "currency": "USDC",
        "spot_price": "1",
        "managers": [{ "risk_universe_id": 1, "sm": 2, "pm": 3 }],
        "spot": [
          {
            "name": "USDC",
            "address": "0xProtocolSpotAssetAddress",
            "erc20": {
              "decimals": 6,
              "underlying_erc20": "0xUnderlyingUsdcErc20"
            },
            "min_deposit_usd": "1"
          }
        ]
      }
    ]
  }
  ```
</CodeGroup>

### What asset

A deposit credits a **protocol spot asset** — the on-book balance — *not* the ERC-20
token you send. Read these from the currency's `spot[]` entry:

| Value                          | Where it comes from      | Used as                                                 |
| ------------------------------ | ------------------------ | ------------------------------------------------------- |
| Protocol spot asset address    | `spot[].address`         | the `asset` in the on-chain deposit call                |
| Underlying ERC-20 + `decimals` | `spot[].erc20`           | the token you approve/send, and how to scale the amount |
| Minimum deposit                | `spot[].min_deposit_usd` | reject dust below this before you pay gas               |

<Tip>
  A currency can register more than one spot asset (e.g. lending `USDC` and non-lending `USDC-NL`) — match the `name`.
</Tip>

### What manager

The **manager** sets the subaccount's margin model: `managers[].sm` is the Standard
(cross-margin) id and `managers[].pm` the Portfolio-margin id. Pass your chosen id as
`manager_id` when creating the subaccount. See
[Managers & risk universes](/trading/managers-and-risk-universes) for how to choose.

### What risk universe

Each manager belongs to one **risk universe** (`managers[].risk_universe_id`), so
choosing the `manager_id` also fixes the universe — the risk boundary that bounds what
the subaccount can trade and hold. See
[Managers & risk universes](/trading/managers-and-risk-universes).

## Step 2 — Deposit

You can deposit 3 different ways - each ideal for different types of users:

1. Direct: call the `Deposit()` or `DepositNewSubaccount()` calls on the `OnchainActionManager.sol`. (\~2 min)
2. Standard: less integration work as simply requires user to send funds to a custom ETH address from where a keeper will auto deposit funds into the exchange. (\~2 min)
3. Instant: fastest deposit times at \~15 seconds, with same integration simplicity to `standard`. Currently only supported for USDC (reach out if you'd like other collaterals) and for amounts \<\$1k. There also may be a small
   fee depending on network conditions.

<Frame>
  <img className="bg-black" src="https://mintcdn.com/derive-a0490cef/BhszcT-bQHpTuFnc/getting-started/deposit-methods.png?fit=max&auto=format&n=BhszcT-bQHpTuFnc&q=85&s=67f13baf5df4f318d17be91564e67214" alt="Diagram of the three Derive deposit methods. Direct: call Deposit() or DepositNewSubaccount() on OnchainActionManager.sol; takes 5-15 minutes while the L1 reaches finality. Standard: send funds to a custom ETH address from which a keeper auto-deposits them into the exchange; also 5-15 minutes but needs no contract calls. Instant: send funds to a custom ETH address and the keeper fronts them directly in the exchange within 1-3 minutes; fastest, but currently USDC-only for amounts under $1,000 and charges a small fee." width="1912" height="736" data-path="getting-started/deposit-methods.png" />
</Frame>

<Tabs>
  <Tab title="Direct">
    Your own wallet calls the settlement contract (`ACTION_MANAGER`, address per [Contracts](/getting-started/contracts)). First
    `approve` the contract to pull your ERC-20, then call one of:

    * **`depositToNewSubaccount(asset, amount, managerId, owner)`** — create a new subaccount under `managerId`, owned by `owner`.
    * **`deposit(asset, amount, subaccountId, fallbackRecipient)`** — fund an existing `subaccountId`; `fallbackRecipient` receives the funds into its fallback subaccount if the deposit cannot be applied.

    <Warning>
      `asset` is the **protocol spot asset address** (`spot[].address` from Step 1), **not** the ERC-20 token. `amount` is in the token's **native ERC-20 decimals**
    </Warning>

    ```javascript theme={null}
    import { ethers } from 'ethers';

    const provider = new ethers.JsonRpcProvider(SETTLEMENT_CHAIN_RPC_URL);
    const wallet = new ethers.Wallet(OWNER_PRIVATE_KEY, provider);

    // From public/get_all_currencies (Step 1) and /getting-started/contracts:
    //   ASSET_ADDRESS  = spot[].address                 — protocol asset, NOT the ERC-20
    //   ERC20_ADDRESS  = spot[].erc20.underlying_erc20
    //   DECIMALS       = spot[].erc20.decimals          — 6 for USDC
    //   MANAGER_ID     = managers[].sm                  — standard cross-margin
    //   ACTION_MANAGER = settlement contract address    — see /getting-started/contracts
    const amount = ethers.parseUnits('100', DECIMALS); // native ERC-20 units

    // 1. Approve the settlement contract to pull your token.
    const erc20 = new ethers.Contract(
      ERC20_ADDRESS,
      ['function approve(address,uint256)'],
      wallet
    );
    await (await erc20.approve(ACTION_MANAGER, amount)).wait();

    // 2. Deposit into a new subaccount under MANAGER_ID, owned by your wallet.
    const actionManager = new ethers.Contract(
      ACTION_MANAGER,
      [
        'function depositToNewSubaccount(address asset, uint256 amount, uint32 managerId, address owner)',
      ],
      wallet
    );
    const tx = await actionManager.depositToNewSubaccount(
      ASSET_ADDRESS,
      amount,
      MANAGER_ID,
      wallet.address
    );
    await tx.wait(); // mined on-chain; crediting still happens asynchronously (Step 3)
    ```

    <Info>
      Mainnet settlement-contract addresses are deployment-specific — do not hardcode them from the docs. Confirm the
      `ACTION_MANAGER` address for your target deployment on [Contracts](/getting-started/contracts) before mainnet use.
    </Info>
  </Tab>

  <Tab title="Standard">
    `public/register_deposit_address` returns a **deterministic deposit address** for
    `(wallet, subaccount, manager, deposit_type)`. Send the token there from anywhere; an off-chain sweeper forwards it
    into the protocol and credits your subaccount. It is a plain public call — **no signature** — and idempotent: calling
    it again returns the same address.

    <ParamField path="wallet" type="string" required>
      Wallet to credit. EIP-55 checksummed.
    </ParamField>

    <ParamField path="subaccount_id" type="integer" default="0">
      Existing subaccount to route the deposit into. Omit (or `0`) to create a new subaccount instead.
    </ParamField>

    <ParamField path="manager_id" type="integer">
      Manager the new subaccount is created under. **Required and non-zero** when `subaccount_id` is omitted or `0`; ignored
      when routing into an existing subaccount.
    </ParamField>

    <ParamField path="deposit_type" type="string" required>
      `"slow"` for Standard, `"fast"` for the **Instant** tab. Each type gets its **own distinct address**
      for the same `(wallet, subaccount, manager)`.
    </ParamField>

    <CodeGroup>
      ```typescript TypeScript (SDK) theme={null}
      import { DeriveClient } from '@derivexyz/derive-ts';

      // Public call — no login required. The address is deterministic per
      // (wallet, subaccount, manager, depositType).
      const client = new DeriveClient({
        network: 'mainnet',
        wallet: process.env.PRIVATE_KEY!,
      });
      const registration = await client.deposits.depositAddress.register({
        wallet: '0xYourWallet',
        managerId: 2,
        depositType: 'slow',
      });
      console.log(registration.deposit_address);
      ```

      ```python Python (SDK) theme={null}
      # Coming soon.
      ```

      ```rust Rust (SDK) theme={null}
      // Coming soon.
      ```

      ```bash cURL theme={null}
      curl -X POST https://api.derive.xyz/v3/public/register_deposit_address \
        -H "Content-Type: application/json" \
        -d '{
          "wallet": "0xYourWallet",
          "manager_id": 2,
          "deposit_type": "slow"
        }'
      ```

      ```json Request theme={null}
      {
        "jsonrpc": "2.0",
        "id": 1,
        "method": "public/register_deposit_address",
        "params": {
          "wallet": "0xYourWallet",
          "manager_id": 2,
          "deposit_type": "slow"
        }
      }
      ```

      ```json Response theme={null}
      {
        "jsonrpc": "2.0",
        "id": 1,
        "result": {
          "wallet": "0xYourWallet",
          "subaccount_id": 0,
          "manager_id": 2,
          "deposit_type": "slow",
          "deposit_address": "0xDeterministicDepositAddress"
        }
      }
      ```
    </CodeGroup>

    <Warning>
      Only send the **registered token** to a deposit address, on the **correct chain** (see [Contracts](/getting-started/contracts)).
      The address is bound to the `(wallet, subaccount, manager, deposit_type)` you registered — routing is fixed at
      registration time.
    </Warning>
  </Tab>

  <Tab title="Instant">
    The same mechanism as Standard — register with `public/register_deposit_address`, this time with
    `deposit_type: "fast"`, and send the token to the returned address — but instead of waiting for L1 finality, the
    keeper **fronts** the credit into the exchange within **1-3 minutes**. This is the fastest path to a funded
    subaccount.

    <Info>
      Instant deposits are currently **USDC only**, for amounts **under \$1,000**, and carry a small fee. An amount above
      the instant cap is still credited through this path, in cap-sized chunks: the first chunk lands near-instantly and
      the remainder follows chunk by chunk, each with more confirmation depth.
    </Info>

    <Note>
      The Instant address is **different** from the Standard address for the same `(wallet, subaccount, manager)` — the
      deposit type salts the escrow. Register with the type you intend to use.
    </Note>

    <CodeGroup>
      ```typescript TypeScript (SDK) theme={null}
      import { DeriveClient } from '@derivexyz/derive-ts';

      const client = new DeriveClient({
        network: 'mainnet',
        wallet: process.env.PRIVATE_KEY!,
      });
      const registration = await client.deposits.depositAddress.register({
        wallet: '0xYourWallet',
        managerId: 2,
        depositType: 'fast',
      });
      console.log(registration.deposit_address);
      ```

      ```python Python (SDK) theme={null}
      # Coming soon.
      ```

      ```rust Rust (SDK) theme={null}
      // Coming soon.
      ```

      ```bash cURL theme={null}
      curl -X POST https://api.derive.xyz/v3/public/register_deposit_address \
        -H "Content-Type: application/json" \
        -d '{
          "wallet": "0xYourWallet",
          "manager_id": 2,
          "deposit_type": "fast"
        }'
      ```

      ```json Response theme={null}
      {
        "jsonrpc": "2.0",
        "id": 1,
        "result": {
          "wallet": "0xYourWallet",
          "subaccount_id": 0,
          "manager_id": 2,
          "deposit_type": "fast",
          "deposit_address": "0xDistinctFastDepositAddress"
        }
      }
      ```
    </CodeGroup>

    #### Tracking an Instant deposit

    Instant deposits are credited **off-chain as transfers**, so they never appear in `public/get_onchain_action_history`
    or `private/get_deposit_history`. Their lifecycle lives in `public/get_pending_deposits` — a public call, so it works
    before your account exists. Each entry moves through:

    * `pending` — the deposit was observed on-chain and is awaiting payout.
    * `crediting` — a credit transfer is in flight.
    * `credited` — paid out.

    A deposit above the instant cap shows **one entry per credit chunk** (`credit_nonce` disambiguates them), summing to
    the on-chain amount. The deposit is fully paid once **every** entry reads `credited`. `reverted` marks a deposit
    reorged out before its block became safe.

    <CodeGroup>
      ```typescript TypeScript (SDK) theme={null}
      // Snapshot the lifecycle...
      const { pending_deposits } = await client.deposits.getPending();

      // ...or block until the deposit funded by your transfer is fully credited.
      const credited = await client.deposits.awaitFastDeposit({ txHash: '0xYourTransferTx' });
      ```

      ```bash cURL theme={null}
      curl -X POST https://api.derive.xyz/v3/public/get_pending_deposits \
        -H "Content-Type: application/json" \
        -d '{ "wallet": "0xYourWallet" }'
      ```

      ```json Response theme={null}
      {
        "jsonrpc": "2.0",
        "id": 1,
        "result": {
          "wallet": "0xYourWallet",
          "pending_deposits": [
            {
              "action_id": 0,
              "action_type": "FastDeposit",
              "asset": "USDC",
              "amount": "400000000",
              "subaccount_id": 10,
              "manager_id": 0,
              "tx_hash": "0xYourTransferTx",
              "log_index": 3,
              "block_number": 123456,
              "status": "credited",
              "deposit_type": "fast",
              "credit_nonce": "1730000000001",
              "timestamp": 1731000000000,
              "updated_at_ms": 1731000012000
            },
            {
              "action_id": 0,
              "action_type": "FastDeposit",
              "asset": "USDC",
              "amount": "200000000",
              "subaccount_id": 10,
              "manager_id": 0,
              "tx_hash": "0xYourTransferTx",
              "log_index": 3,
              "block_number": 123456,
              "status": "pending",
              "deposit_type": "fast",
              "timestamp": 1731000000000,
              "updated_at_ms": 1731000012000
            }
          ]
        }
      }
      ```
    </CodeGroup>
  </Tab>
</Tabs>

## Step 3 — Confirm the deposit was credited

A mined deposit transaction is **not** the end of the story: the exchange credits the funds asynchronously, and the new
`subaccount_id` is **not** carried in the transaction receipt. Two endpoints cover the gap.

### First feedback — `public/get_pending_deposits`

Whichever method you used, an entry appears here the moment the exchange picks the deposit up: **Direct** and
**Standard** deposits as soon as the action lands in the `OnchainActionManager` (for Standard, when the keeper sweeps
the deposit address); **Instant** deposits as soon as the keeper indexes the transfer. It is a public call, so it works
before your account exists. From there the paths diverge:

* **Instant** deposits are processed straight from this feed — keep polling it until every entry reads `credited`
  (the [lifecycle above](#tracking-an-instant-deposit)); the SDK's `client.deposits.awaitFastDeposit` does exactly
  that.
* **Direct / Standard** deposits sit here (`pending` → `confirmed`) while the exchange waits roughly **two minutes**
  of confirmations before including them in state — once your entry shows up, switch to polling
  `private/get_subaccounts` below.

### Crediting — `private/get_subaccounts`

Discover a **new** subaccount by snapshotting your subaccount ids **before** depositing and polling
`private/get_subaccounts` afterward for the id that appears — the SDK's `client.deposits.awaitNewSubaccount` does this.
For a deposit into an **existing** subaccount, poll `private/get_subaccount` and watch its collateral balance increase.

<CodeGroup>
  ```typescript TypeScript (SDK) theme={null}
  import { DeriveClient } from '@derivexyz/derive-ts';

  const client = new DeriveClient({
    network: 'mainnet',
    wallet: process.env.PRIVATE_KEY!,
  });
  await client.connect();
  await client.login();

  const subaccountIds = await client.subaccounts.list();
  console.log(subaccountIds);
  ```

  ```python Python (SDK) theme={null}
  # Coming soon.
  ```

  ```rust Rust (SDK) theme={null}
  // Coming soon.
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.derive.xyz/v3/private/get_subaccounts \
    -H "Content-Type: application/json" \
    -H "X-DeriveWallet: 0xYourWallet" \
    -H "X-DeriveTimestamp: 1695836058725" \
    -H "X-DeriveSignature: 0x…" \
    -d '{ "wallet": "0xYourWallet" }'
  ```

  ```json Request theme={null}
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "private/get_subaccounts",
    "params": { "wallet": "0xYourWallet" }
  }
  ```

  ```json Response theme={null}
  {
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
      "wallet": "0xYourWallet",
      "subaccount_ids": [9, 10]
    }
  }
  ```
</CodeGroup>

For **Direct** and **Standard** deposits (both observed on-chain), you can track whether the exchange has picked up your
on-chain action with `public/get_onchain_action_history`. It lists each `OnchainActionManager` action the sequencer
scraped and its `status` — applied (with an `op_uuid`), consumed as a fallback no-op, or still retrying. (Instant
deposits are keeper-fronted and do not appear here.)

<CodeGroup>
  ```typescript TypeScript (SDK) theme={null}
  import { DeriveClient } from '@derivexyz/derive-ts';

  // Public read — no login required.
  const client = new DeriveClient({
    network: 'mainnet',
    wallet: process.env.PRIVATE_KEY!,
  });
  const { actions } = await client.marketData.getOnchainActionHistory({
    wallet: '0xYourWallet',
  });
  for (const a of actions)
    console.log(a.action_type_label, a.status, a.tx_hash, a.op_uuid);
  ```

  ```python Python (SDK) theme={null}
  # Coming soon.
  ```

  ```rust Rust (SDK) theme={null}
  // Coming soon.
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.derive.xyz/v3/public/get_onchain_action_history \
    -H "Content-Type: application/json" \
    -d '{ "wallet": "0xYourWallet" }'
  ```
</CodeGroup>

For a full record of credited **Direct** and **Standard** deposits use `private/get_deposit_history`, scoped to the
whole `wallet` or a single `subaccount_id`. Amounts and `fee` are decimal strings; the net credited amount is
`amount - fee`. **Instant** deposits are credited as transfers, so they do not appear here — they show up as incoming
rows in `private/get_erc20_transfer_history` (see [Transfers & Withdrawals](/trading/transfers-withdrawals)), with their
crediting lifecycle in `public/get_pending_deposits`.

<CodeGroup>
  ```typescript TypeScript (SDK) theme={null}
  import { DeriveClient } from '@derivexyz/derive-ts';

  const client = new DeriveClient({
    network: 'mainnet',
    wallet: process.env.PRIVATE_KEY!,
  });
  await client.connect();
  await client.login();

  // Whole wallet (omit subaccountId). Amounts and fee are decimal strings.
  const { deposits } = await client.deposits.getHistory();
  for (const d of deposits)
    console.log(d.subaccount_id, d.asset, d.amount, d.fee);
  ```

  ```python Python (SDK) theme={null}
  # Coming soon.
  ```

  ```rust Rust (SDK) theme={null}
  // Coming soon.
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.derive.xyz/v3/private/get_deposit_history \
    -H "Content-Type: application/json" \
    -H "X-DeriveWallet: 0xYourWallet" \
    -H "X-DeriveTimestamp: 1695836058725" \
    -H "X-DeriveSignature: 0x…" \
    -d '{ "wallet": "0xYourWallet" }'
  ```

  ```json Request theme={null}
  {
    "jsonrpc": "2.0",
    "id": 1,
    "method": "private/get_deposit_history",
    "params": { "wallet": "0xYourWallet" }
  }
  ```

  ```json Response theme={null}
  {
    "jsonrpc": "2.0",
    "id": 1,
    "result": {
      "deposits": [
        {
          "operation_id": "a1b2...",
          "new_subaccount": true,
          "subaccount_id": 10,
          "wallet": "0xYourWallet",
          "asset": "USDC",
          "amount": "100",
          "fee": "0",
          "timestamp": 1731000000000,
          "batch_uuid": "c3d4...",
          "batch_status": "Settled",
          "tx_hash": "0x..."
        }
      ]
    }
  }
  ```
</CodeGroup>

<Note>
  `private/get_subaccounts` and `private/get_deposit_history` are private methods — authenticate the connection with
  [session login](/authentication/session-login) first. Once a subaccount is funded, you can log in and start trading.
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Quickstart" icon="rocket" href="/getting-started/quickstart">
    Now that your subaccount is funded, log in, sign an order, and stream your fills.
  </Card>

  <Card title="Authentication" icon="key" href="/authentication/session-login">
    Session login and the per-action signing model.
  </Card>

  <Card title="Transfers & Withdrawals" icon="arrow-right-arrow-left" href="/trading/transfers-withdrawals">
    Move collateral between subaccounts and back on-chain.
  </Card>
</CardGroup>


## Related topics

- [DevEx improvements](/migrating/v3-improvements.md)
- [Error Codes](/error-codes.md)
