Skip to main content
This page covers changes in the v3 API in detail. We recommend importing this directly into your LLM / coding agent as a SKILL.MD.
If at any step there is not enough detail, refer to the Guides section or the relevant public SDK repos: Lastly, for granular API details, refer either to the API Reference tab or the openapi.json spec (simply add /openapi.json to the base URL).
1

Collapse to a single wallet

There is no longer a distinction between your “wallet” and a separate “Derive Wallet” or “Smart Contract Wallet”. Every wallet / owner field is your own EOA or multisig. A subaccount carries a single wallet address, and an action is authorised only when the subaccount’s wallet equals the action owner (the signer is either that owner or one of its session keys).Note that during the v2 -> v3 state migration all “Derive Wallets” will be automatically transferred to the user’s EOA or multisig.
2

Update action signing flow

Account creation is fully permissionless. Deposits move onchain. Some minor changes to certain actions.
3

Repoint hosts and constants

Switch base URLs to the v3 hosts (api.derive.xyz/v3 / testnet.api.derive.xyz/v3), recompute the EIP-712 domain separator for the target chain, and use the shared module addresses. See Constants.
4

API endpoint changes

Apply the mapping table. Note that several v3 targets (get_all_instruments, get_open_orders, get_order_history, get_ticker/get_tickers, order_quote) already existed in v2 — the corresponding v2 name was removed, not aliased.

Signed-action changes

Deposits are on-chain

There is no deposit action or private/deposit RPC or action in v3. Deposit directly on-chain via one of the three supported flows. Refer to the Depositing guide or SDKs for more information.

Withdrawals

The withdrawal action gains max_fee_usd, force_batch, and a recipient (in the signed action data), and the amount stays in the asset’s underlying decimals: private/withdraw response: { operation_id, op_uuid } — no status.

Transfers

  • private/transfer_spot — one spot asset at a time, between subaccounts of the same owner (existing, or a newly-created sender-owned subaccount via a non-zero new_subaccount_manager). Request carries max_fee_usd (the exchange may charge for creating a subaccount). It cannot send to a different owner.
  • private/transfer_positions — replaces transfer_position; handles one or many legs (position transfers still use the RFQ-style signed-quote envelope).
All transfer responses return { operation_id, op_uuid } (no status). Refer to Transfers & Withdrawals or SDKs for more information.

Session keys

v3 splits session-key management into a signed protocol action and an off-chain edit:
  • private/create_session_key — signs a CreateSessionKey action containing { session_key, expiry_sec, scopes, subaccounts }. Only protocol scopes are part of the signed action; off-chain scopes travel as unsigned metadata. Signed against subaccount_id = 0. This is the replacement for register_scoped_session_key and the old admin-key register/deregister/build-tx flow.
  • private/edit_session_key — existed in v2, but now edits only off-chain fields (label, ip_whitelist, offchain_scopes). Its v2 disable flag was removed.
  • To retire a key, re-register the same key via private/create_session_key with a past or zero expiry. v2’s deregister_session_key and the disable flag on edit_session_key have no direct replacement in v3.
Two scope kinds: protocol scopes are validated in the protocol and are part of the signed action; offchain scopes are validated off-chain and can be changed later via edit_session_key. See Access scopes.

RFQ quote signatures

Maker and taker quote (send_quote) signature_expiry_sec must satisfy roughly:
The lower bound is ~11 minutes after RFQ creation (a 600s validity window plus a 60s buffer); the upper bound is the current server time plus 1 day. Exact bounds may vary by environment.

Nonces: nanoseconds, and increasing

In v2, nonces were treated as milliseconds. In v3 a nonce is a UTC timestamp in nanoseconds (~19 digits for 2025, e.g. 1751558400000000000). A millisecond- or microsecond-scale nonce will be rejected. Beyond the unit change, nonces on most signed actions must be strictly increasing per subaccount, and must fall inside a validity window. The window differs by action type: Orders and RFQs are exempt from the increasing requirement. A coarser ± 5-minute check is also applied when the request is first received. Exact bounds may vary by environment.

Constants

The hosts and signing constants that changed between v2 and v3.

Endpoints

See Endpoints for the canonical host/path list and Connecting for the WebSocket handshake.

Module addresses are now shared across deployments

The per-action module contracts — the module field selected inside a signed Action — are identical on testnet and mainnet in v3. They are fixed protocol constants; hardcoding them is safe.

Domain separator is recomputed per chain

Unlike the module addresses, the EIP-712 domain separator is not shared: v3 recomputes it per chain. Note, since v3 is now on Ethereum Sepolia & Mainnet, the domain separators must be recomputer. The verifyingContract is constant across every network (the v2 mainnet Matching, 0xeB8d770ec18DB98Db922E9D83260A585b9F0DeAD); only chainId differs, so testnet and mainnet resolve to different separators.
Resultant values:

Auth headers: X-Lyra*X-Derive*

See Action signing or SDKs for more information.

Transaction ids → operation uuids

v2 returned a transaction_id (and a status) on state-changing calls. v3 removes status and returns an operation identifier instead:
  • op_uuid (string) — the stable UUID of the operation.
  • operation_id (integer) — a numeric operation id, returned alongside op_uuid on action responses (withdraw, transfers, whitelist).
tx_hash still exists but now points to the Ethereum L1 transaction in which the zk batch settled, not a Derive-chain transaction. The full lineage is op_uuid → operation → batch → L1 tx, and tx_hash (plus l1_block) is populated from the batch once it settles.

Settlement status: TxStatusBatchStatus

v2 tracked settlement with a single per-transaction status. Because v3 settles operations in zk-batches, the settlement lifecycle is a BatchStatus on the batch — the single source of truth for every operation in it (individual operations carry no status of their own). Each stage has a healthy value and a matching …Error value if that stage fails: Values serialize as the PascalCase variant name and are null until the operation is picked up into a batch. Deposit / withdrawal / erc20-transfer history expose this as batch_status; trade history keeps the field name tx_status but returns these same BatchStatus values (see History routes).
Don’t confuse this with the matching-engine tx_status on private/order and the quote channels (requested / pending / settled / reverted / ignored / timed_out). That is a separate field, lowercase, and unrelated to the batch settlement lifecycle above.

Renamed / consolidated methods

First, download the full openapi.json spec by adding /openapi.json to the base URL. Use that as the reference as you migrate the routes.

Response reshapes

public/get_currency & public/get_all_currencies

Both keep their names and params and return the same per-currency object shape (get_all_currencies returns an array of it). The object was completely restructured: v2 was flat, keyed risk by manager contract address, and assumed USDC collateral. v3 nests everything around two grouping keys — risk_universe_id and per-asset (option / perp / spot, where spot is now a list). New fields with no v2 equivalent: risk_universe_id on every managers[] / *.universes[] entry; option/perp become {name, address, universes[]}; spot[] gains name, min_deposit_usd, and erc20.decimals.
Type notes: sm / pm / all risk_universe_id are numbers (u32); erc20.decimals is a number; all money/discount/OI fields are decimal strings. In v2 the manager identifier was a hex address string — it is now a numeric id. managers[].pm is the PMRM2 manager (v2 margin_type: "PM2"), not the legacy v2 "PM".
Optional fields are omitted from JSON when absent: spot_price_24h, top-level option, top-level perp, managers[].sm, managers[].pm, spot[].universes[].lending, spot[].erc20.underlying_erc20. spot and every universes array are always present (may be empty).
To resolve a spot collateral’s discount/lending, iterate spot[] → universes[] and match on risk_universe_id (from private/get_subaccount’s new risk_universe_id) instead of reading a single top-level value. spot being an array is the most likely thing to break clients that assumed one spot asset per currency. Perp margin requirements are no longer in this response.

private/get_subaccount

New fields: manager_id (u32) and risk_universe_id (u32, the key into the per-universe currency data above). Also:
  • currency changed from a single string to a string[] — a breaking shape change.
  • margin_type widened from a fixed enum (PM | SM | PM2) to an open string.
  • New failed_to_fetch (bool) and vault_deposit_holds (array) fields.
  • projected_margin_change is currently always "0" (not yet implemented in v3, unlike v2 which computed a real value).

History routes

  • public/get_trade_history no longer accepts a tx_hash query param.
  • Status field: deposit / withdrawal / erc20-transfer history now expose batch_status; trade history (public and private) keeps tx_status. Both use the same set of status values (PascalCase strings) — success stages Batching, Executing, Proving, Settling, Settled, and their errored counterparts BatchingError, ExecutingError, ProvingError, SettlingError, SettledError. The value is null until the operation is picked up for settlement.
  • Deposit / withdrawal / erc20-transfer history: each row now carries operation_id (string UUID), batch_uuid, and batch_status. tx_hash is still present (nullable) — it is not removed, but it now resolves to the settling L1 transaction.
The status field name differs by endpoint: trade history uses tx_status; the deposit/withdrawal/erc20-transfer history endpoints use batch_status. The operation-uuid field is op_uuid on the private trade-history row and operation_id on the deposit/withdrawal/erc20 rows. Public trade history exposes no operation-uuid field.
##$ Channel changes The subscription surface went from 16 channels in v2 to 15 in v3. The only removed channel is the full ticker feed; every other channel carried over.

Removed / not currently exposed