> ## 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 2 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)

<Note>
  On testnet, every token is mintable: open [testnet.app.derive.xyz](https://testnet.app.derive.xyz/), connect your
  wallet, and click **Deposit** in the top right, then **Mint** a token of your choice.
</Note>

<Frame>
  <img className="bg-black" src="https://mintcdn.com/derive-a0490cef/D5ZHF-QmjkhU2iF-/getting-started/deposit-methods.png?fit=max&auto=format&n=D5ZHF-QmjkhU2iF-&q=85&s=6564c9606a64778041f045b05bfd675a" alt="Diagram of the Derive deposit methods. Direct: call Deposit() or DepositNewSubaccount() on OnchainActionManager.sol; takes 1-2 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 1-2 minutes but needs no contract calls." width="1352" 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")?)
              .recipient(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 and testnet have different `ACTION_MANAGER` addresses. Take the one for your network from
      [Contracts](/getting-started/contracts).
    </Info>
  </Tab>

  <Tab title="Standard">
    `public/register_deposit_address` returns a **deterministic deposit address** for
    `(wallet, subaccount, manager)`. 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. It must belong to `wallet`. 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>

    <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).
      const client = new DeriveClient({
        network: 'mainnet',
        wallet: process.env.PRIVATE_KEY!,
      });
      const registration = await client.deposits.depositAddress.register({
        wallet: '0xYourWallet',
        managerId: 2,
      });
      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 RegisterDepositAddressParams


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

          registration = await client.public_api.rpc.register_deposit_address(
              RegisterDepositAddressParams(
                  wallet="0xYourWallet",
                  manager_id=2,
              )
          )
          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).
          let client = WsClient::new_public(Environment::Mainnet).await?;

          let params = RegisterDepositAddressParams::builder()
              .wallet("0xYourWallet")
              .manager_id(2)
              .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
        }'
      ```
    </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)` you registered — routing is fixed at
      registration time.
    </Warning>
  </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). It is a public call, so it works before your account exists.

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

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

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

### Depositing from another chain

Derive v3 settles as a ZK rollup, but its deposit contracts live on **Ethereum L1**. Whatever chain
your funds start on, they must arrive at L1 before the exchange can credit them.

The Derive front-end uses [Li.Fi](https://li.fi) for bridging deposits. However, you may choose any bridge you prefer.
Bridge to the deposit address from Step 2 and the deposit proceeds exactly as described above, with no extra integration.

<Note>
  Some collaterals, such as HYPE and SOL, have no native Ethereum token. They bridge through Derive's own
  LayerZero bridges — see [Custom Bridges](/getting-started/custom-bridges) for the bridge addresses.
</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

- [Gasless Onboarding](/getting-started/gasless-onboarding.md)
- [DevEx improvements](/migrating/v3-improvements.md)
- [Smart Contracts & Multi-sigs](/authentication/contract-owned-accounts.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.