> ## Documentation Index
> Fetch the complete documentation index at: https://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

When depositing to a new subaccount you must chose a **risk universe / manager**.
Use `public/get_risk_universes` to list every
universe with its managers and their accepted collaterals / instruments.

See [Managers & risk universes](/trading/managers-and-risk-universes) for how to choose.

<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 universes = await client.marketData.getRiskUniverses();
  // Pick the manager that trades what you want (ETH options)
  // and accepts the collateral you'll post (USDC).
  const manager = universes
    .flatMap((u) => u.managers)
    .find((m) => m.instruments.includes('ETH-OPTION') && m.collaterals.some((c) => c.name === 'USDC'))!;
  const usdc = manager.collaterals.find((c) => c.name === 'USDC')!;
  console.log(manager.manager_id, usdc.address, usdc.erc20);
  ```

  ```python Python (SDK) theme={null}
  import asyncio

  from derive_py import WebSocketClient


  async def main():
      client = WebSocketClient.from_env()
      await client.connect()

      universes = await client.markets.get_risk_universes()
      # Pick the manager that trades what you want (ETH options)
      # and accepts the collateral you'll post (USDC).
      manager = next(
          m
          for u in universes
          for m in u.managers
          if "ETH-OPTION" in m.instruments and any(c.name == "USDC" for c in m.collaterals)
      )
      usdc = next(c for c in manager.collaterals if c.name == "USDC")
      print(manager.manager_id, usdc.address, usdc.erc20)

      await client.disconnect()


  asyncio.run(main())
  ```

  ```rust Rust (SDK) theme={null}
  use derive_rs::{Environment, WsClient};

  #[tokio::main]
  async fn main() -> Result<(), Box<dyn std::error::Error>> {
      // Public read — no login required.
      let client = WsClient::new_public(Environment::Mainnet).await?;
      let universes = client.rpc().market_data().get_risk_universes().await?;

      // Pick the manager that trades what you want (ETH options)
      // and accepts the collateral you'll post (USDC).
      let manager = universes
          .iter()
          .flat_map(|u| &u.managers)
          .find(|m| {
              m.instruments.contains(&"ETH-OPTION".to_string())
                  && m.collaterals.iter().any(|c| c.name == "USDC")
          })
          .expect("no manager trades ETH options against USDC");
      let usdc = manager
          .collaterals
          .iter()
          .find(|c| c.name == "USDC")
          .expect("USDC collateral missing");

      println!("{} {} {:?}", manager.manager_id, usdc.address, usdc.erc20);
      Ok(())
  }
  ```
</CodeGroup>

### What each asset field means

| Value                          | Where it comes from             | Used as                                                                         |
| ------------------------------ | ------------------------------- | ------------------------------------------------------------------------------- |
| Protocol spot asset address    | `collaterals[].address`         | the `asset` in the on-chain deposit call                                        |
| Underlying ERC-20 + `decimals` | `collaterals[].erc20`           | the token you approve/send, and how to scale the amount                         |
| Minimum deposit                | `collaterals[].min_deposit_usd` | deposits below this amount will be donated to the security module               |
| Margin credit                  | `collaterals[].im_discount`     | how much margin the asset earns under this manager (`"0"` = held but no credit) |

## 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** (`collaterals[].address` from Step 1), **not** the ERC-20 token. `amount` is in the token's **native ERC-20 decimals**
    </Warning>

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

      // The SDK holds no provider — supply an ethers signer connected to the
      // settlement-chain RPC; it signs the approve + deposit for you.
      const provider = new ethers.JsonRpcProvider(SETTLEMENT_CHAIN_RPC_URL);
      const signer = new ethers.Wallet(OWNER_PRIVATE_KEY, provider);

      // No login needed — a Direct deposit is a pure on-chain call; the network
      // config supplies the ActionManager and USDC addresses.
      const client = new DeriveClient({ network: 'mainnet' });

      // ASSET_ADDRESS = collaterals[].address (protocol asset, NOT the ERC-20) and
      // MANAGER_ID = manager_id, both from Step 1. Approves the ActionManager to
      // pull the ERC-20 (defaults to the network's USDC), then deposits into a NEW
      // subaccount under MANAGER_ID, owned by the signer.
      const { txHash } = await client.deposits.contractCall.depositToNewSubaccount({
        signer,
        asset: ASSET_ADDRESS,
        amount: '100', // human units — scaled by the ERC-20's on-chain decimals()
        managerId: MANAGER_ID,
      });
      // Mined on-chain; crediting still happens asynchronously (Step 3).
      ```

      ```python Python (SDK) theme={null}
      import asyncio

      from derive_py import WebSocketClient
      from derive_py.data_types import D, MarginType, RiskUniverseID


      async def main():
          client = WebSocketClient.from_env()
          await client.connect()

          # No login needed for the on-chain part — the client holds the wallet key
          # and the network config supplies the ActionManager and USDC addresses.
          # amount is in HUMAN units; the SDK scales by the ERC-20's decimals().
          steps = client.plan_deposit_to_new_subaccount(
              risk_universe_id=RiskUniverseID.PRIME,
              margin_type=MarginType.SM,
              asset_name="USDC",
              amount=D("100"),
          )

          # One step per tx: the ERC-20 approve (skipped if already approved),
          # then the deposit into a NEW subaccount owned by the signer.
          async for step in steps:
              print(f"[{step.kind}] {step.description}")
              tx_hash = await step.submit()
              receipt = await step.wait_for_finality()
              print(f"  {tx_hash} in block {receipt.blockNumber}")
          # Mined on-chain; crediting still happens asynchronously (Step 3).

          await client.disconnect()


      asyncio.run(main())
      ```

      ```rust Rust (SDK) theme={null}
      use std::str::FromStr;

      use alloy::primitives::Address;
      use bigdecimal::BigDecimal;
      use derive_rs::{
          Environment, WsClient,
          actions::{DepositArgs, DepositTypes, DirectDepositType, SupportDepositAssets},
      };

      #[tokio::main]
      async fn main() -> Result<(), Box<dyn std::error::Error>> {
          let wallet: Address = std::env::var("DERIVE_WALLET")?.parse()?;
          let client = WsClient::new(
              Environment::Mainnet,
              Some(std::env::var("DERIVE_PRIVATE_KEY")?),
              Some(wallet.to_string()),
              None, // no subaccount yet — this deposit creates one
          )
          .await?;

          // MANAGER_ID comes from Step 1. amount is in HUMAN units; the SDK scales it
          // by the ERC-20's decimals() and approves the ActionManager first if needed.
          let args = DepositArgs::builder()
              .asset(SupportDepositAssets::USDC)
              .amount(BigDecimal::from_str("100")?)
              .recepient_address(wallet) // owner of the new subaccount
              .manager_id(2)
              .deposit_type(DepositTypes::Direct(
                  DirectDepositType::DepositToNewSubaccount,
              ))
              .build();

          // One hash per tx sent: the ERC-20 approve (if required), then the deposit.
          for hash in client.fund_movements().deposit(args).await? {
              println!("{hash:?}");
          }
          // Mined on-chain; crediting still happens asynchronously (Step 3).
          Ok(())
      }
      ```

      ```solidity Solidity theme={null}
      // Raw calls to OnchainActionManager.sol — from your own contract, a Foundry
      // script, or `cast send`.
      //   ASSET_ADDRESS  = collaterals[].address              — protocol asset, NOT the ERC-20
      //   ERC20_ADDRESS  = collaterals[].erc20.underlying_erc20
      //   MANAGER_ID     = manager_id of the SM-margin manager
      //   ACTION_MANAGER = settlement contract — see /getting-started/contracts
      interface IERC20 {
          function approve(address spender, uint256 amount) external returns (bool);
      }

      interface IOnchainActionManager {
          function depositToNewSubaccount(
              address asset,
              uint256 amount,
              uint32 managerId,
              address owner
          ) external returns (uint256 actionId);
      }

      function depositToDerive() external {
          uint256 amount = 100e6; // native ERC-20 units (USDC = 6 decimals)

          // 1. Approve the ActionManager to pull the underlying ERC-20.
          IERC20(ERC20_ADDRESS).approve(ACTION_MANAGER, amount);

          // 2. Create a new subaccount under MANAGER_ID, owned by this wallet;
          //    crediting still happens asynchronously (Step 3).
          IOnchainActionManager(ACTION_MANAGER).depositToNewSubaccount(
              ASSET_ADDRESS,
              amount,
              MANAGER_ID,
              msg.sender
          );
      }
      ```
    </CodeGroup>

    <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>
      `"standard"` for Standard, `"instant"` 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: 'standard',
      });
      console.log(registration.deposit_address);
      ```

      ```python Python (SDK) theme={null}
      import asyncio

      from derive_py import WebSocketClient
      from derive_py.data_types.generated_models import DepositType, RegisterDepositAddressParams


      async def main():
          # Public call — no login required. The address is deterministic per
          # (wallet, subaccount, manager, deposit_type).
          client = WebSocketClient.from_env()
          await client.connect()

          registration = await client.public_api.rpc.register_deposit_address(
              RegisterDepositAddressParams(
                  wallet="0xYourWallet",
                  manager_id=2,
                  deposit_type=DepositType.standard,
              )
          )
          print(registration.deposit_address)

          await client.disconnect()


      asyncio.run(main())
      ```

      ```rust Rust (SDK) theme={null}
      use derive_rs::{Environment, WsClient, models::RegisterDepositAddressParams};

      #[tokio::main]
      async fn main() -> Result<(), Box<dyn std::error::Error>> {
          // Public call — no login required. The address is deterministic per
          // (wallet, subaccount, manager, depositType).
          let client = WsClient::new_public(Environment::Mainnet).await?;

          let params = RegisterDepositAddressParams::builder()
              .wallet("0xYourWallet")
              .manager_id(2)
              .deposit_type("standard")
              .try_into()?;

          let registration = client
              .rpc()
              .onchain_actions()
              .register_deposit_address(params)
              .await?;
          println!("{}", registration.deposit_address);
          Ok(())
      }
      ```

      ```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": "standard"
        }'
      ```
    </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: "instant"`, 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: 'instant',
      });
      console.log(registration.deposit_address);
      ```

      ```python Python (SDK) theme={null}
      import asyncio

      from derive_py import WebSocketClient
      from derive_py.data_types.generated_models import DepositType, RegisterDepositAddressParams


      async def main():
          # Public call — no login required. The address is deterministic per
          # (wallet, subaccount, manager, deposit_type).
          client = WebSocketClient.from_env()
          await client.connect()

          registration = await client.public_api.rpc.register_deposit_address(
              RegisterDepositAddressParams(
                  wallet="0xYourWallet",
                  manager_id=2,
                  deposit_type=DepositType.instant,
              )
          )
          print(registration.deposit_address)

          await client.disconnect()


      asyncio.run(main())
      ```

      ```rust Rust (SDK) theme={null}
      use derive_rs::{Environment, WsClient, models::RegisterDepositAddressParams};

      #[tokio::main]
      async fn main() -> Result<(), Box<dyn std::error::Error>> {
          let client = WsClient::new_public(Environment::Mainnet).await?;

          let params = RegisterDepositAddressParams::builder()
              .wallet("0xYourWallet")
              .manager_id(2)
              .deposit_type("instant")
              .try_into()?;

          let registration = client
              .rpc()
              .onchain_actions()
              .register_deposit_address(params)
              .await?;
          println!("{}", registration.deposit_address);
          Ok(())
      }
      ```

      ```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": "instant"
        }'
      ```
    </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": "instant",
              "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": "instant",
              "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}
  import asyncio

  from derive_py import WebSocketClient


  async def main():
      client = WebSocketClient.from_env()
      await client.connect()

      subaccounts = await client.account.get_subaccounts()
      print(subaccounts.subaccount_ids)

      await client.disconnect()


  asyncio.run(main())
  ```

  ```rust Rust (SDK) theme={null}
  use derive_rs::{Environment, WsClient, models::GetSubaccountsRequest};

  #[tokio::main]
  async fn main() -> Result<(), Box<dyn std::error::Error>> {
      let wallet = std::env::var("DERIVE_WALLET")?;
      let client = WsClient::new(
          Environment::Mainnet,
          Some(std::env::var("DERIVE_PRIVATE_KEY")?),
          Some(wallet.clone()),
          Some(1),
      )
      .await?;
      client.login().await?;

      let params = GetSubaccountsRequest::builder().wallet(wallet).try_into()?;
      let subaccounts = client.rpc().subaccounts().get_subaccounts(params).await?;
      println!("{:?}", subaccounts.subaccount_ids);
      Ok(())
  }
  ```

  ```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}
  import asyncio

  from derive_py import WebSocketClient
  from derive_py.data_types.generated_models import GetOnchainActionHistoryParams


  async def main():
      # Public read — no login required.
      client = WebSocketClient.from_env()
      await client.connect()

      history = await client.public_api.rpc.get_onchain_action_history(
          GetOnchainActionHistoryParams(wallet="0xYourWallet")
      )
      for action in history.actions:
          print(action.action_type_label, action.status, action.tx_hash, action.op_uuid)

      await client.disconnect()


  asyncio.run(main())
  ```

  ```rust Rust (SDK) theme={null}
  use derive_rs::{Environment, WsClient, models::GetOnchainActionHistoryParams};

  #[tokio::main]
  async fn main() -> Result<(), Box<dyn std::error::Error>> {
      // Public read — no login required.
      let client = WsClient::new_public(Environment::Mainnet).await?;

      let params = GetOnchainActionHistoryParams::builder()
          .wallet("0xYourWallet".to_string())
          .try_into()?;

      let history = client
          .rpc()
          .onchain_actions()
          .get_onchain_action_history(params)
          .await?;
      for action in history.actions {
          println!(
              "{:?} {:?} {:?} {:?}",
              action.action_type_label, action.status, action.tx_hash, action.op_uuid
          );
      }
      Ok(())
  }
  ```

  ```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}
  import asyncio

  from derive_py import WebSocketClient


  async def main():
      client = WebSocketClient.from_env()
      await client.connect()

      # Whole wallet. client.history is scoped to the ACTIVE subaccount;
      # client.account.history is wallet-wide. Amounts and fee are decimals.
      history = await client.account.history.deposits()
      for deposit in history.deposits:
          print(deposit.subaccount_id, deposit.asset, deposit.amount, deposit.fee)

      await client.disconnect()


  asyncio.run(main())
  ```

  ```rust Rust (SDK) theme={null}
  use derive_rs::{Environment, WsClient, models::GetDepositHistoryRequest};

  #[tokio::main]
  async fn main() -> Result<(), Box<dyn std::error::Error>> {
      let wallet = std::env::var("DERIVE_WALLET")?;
      let client = WsClient::new(
          Environment::Mainnet,
          Some(std::env::var("DERIVE_PRIVATE_KEY")?),
          Some(wallet.clone()),
          Some(1),
      )
      .await?;
      client.login().await?;

      // Whole wallet (omit subaccount_id). Amounts and fee are decimal strings.
      let params = GetDepositHistoryRequest::builder()
          .wallet(wallet)
          .try_into()?;

      let history = client.rpc().history().get_deposit_history(params).await?;
      for deposit in history.deposits {
          println!(
              "{} {} {} {}",
              deposit.subaccount_id, deposit.asset, deposit.amount, deposit.fee
          );
      }
      Ok(())
  }
  ```

  ```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)
- [Smart Contracts & Multi-sigs](/authentication/contract-owned-accounts.md)
- [Error Codes](/error-codes.md)
