Skip to main content
Release notes for the Derive v3 API, newest first. Subscribe to the RSS feed to be notified of new entries.
Migrating from v2? See Migrating from V2.

Public subaccount lookup

  • public/get_subaccounts returns the subaccount IDs that a wallet owns. It takes the same wallet param and returns the same shape as private/get_subaccounts, but needs no authentication. A wallet with no account returns an empty list.
All changes are additive.

Maker RFQ history

  • private/get_maker_rfq_history lists filled, cancelled and expired RFQs that you were eligible to quote as a maker, including RFQs you did not quote. It takes the same filters as private/poll_rfqs, but from_timestamp and to_timestamp filter on RFQ creation time. Without from_timestamp, it returns the 30 days before to_timestamp (or before now). Results have the private/poll_rfqs RFQ shape, with fill_rate and recent_fill_rate set to null.
  • private/poll_rfqs returns only open RFQs, including partially filled ones. In v2 it also returned filled, cancelled and expired RFQs. Use private/get_maker_rfq_history for those.
All changes are additive.

Forwarded withdrawals to home chains

Withdrawals of bridged tokens can now go back to their home chain in one step. See Forwarded withdrawals.
  • private/register_withdraw_address returns a withdraw address for a (recipient, chain_id). Withdraw to it with private/withdraw, and Derive bridges the payout to the recipient. Derive pays the bridge fee.
  • private/get_withdrawal_history entries to a withdraw address carry a new forward object with the forward’s status, bridge message id and destination tx.
  • private/get_withdraw_addresses lists your withdraw addresses, where each forwards to, and whether it is on your whitelist.
  • New errors from private/register_withdraw_address: withdraw_destination_unsupported (11046) and invalid_withdraw_recipient (11047).
All changes are additive.
Breaking

public/get_all_instruments with expired: true lists only expired options

For instrument_type: "option", expired: true now returns only expired options. Previously it returned live and expired options together. expired: false is unchanged and returns live options.
  • risk_universe_id now filters expired options. It was ignored for them before.
  • pagination.count and num_pages count only expired options.
  • An empty listing returns instrument_not_found (12001), as every other empty listing does.
  • Perps and spot ignore expired, as before.
An option enters the expired: true listing up to a minute before it expires, so it does not drop out of both listings at expiry. For a few minutes after settlement, its option_details.settlement_price in this listing can be null. public/get_instrument returns the settlement price at once.To list live and expired options in a way that works before and after this change, call expired: false, then expired: true. Merge the results by instrument_name, and treat instrument_not_found (12001) as an empty result.

History endpoints report asset as the currency name

private/get_deposit_history, private/get_withdrawal_history and private/get_erc20_transfer_history now return asset as the currency name (e.g. "USDC"), matching v2 and the asset_name these operations are submitted with. Previously the field carried the protocol spot-asset contract address. private/get_withdrawal_history still reports the on-chain token under erc20_address.
Breaking

public/get_assets is removed

The route reported every asset of one asset_type for a currency, but only ever from the fallback risk universe, so its perp and lending figures did not describe any tradeable market. Nothing consumed it.Read assets from the endpoints that are keyed by risk universe instead:
  • public/get_currency and public/get_all_currencies — the option, perp and spot assets registered to a currency, with per-universe OI, discounts and lending.
  • public/get_all_instruments and public/get_instrument — tradeable instruments and their type-specific details.

Perp margin rates and max leverage are public again

srm_perp_margin_requirements returns, carrying the v2 field set — im_perp_req, mm_perp_req and max_leverage — as decimal strings. max_leverage is 1 / im_perp_req rounded to 2 decimals.It is reported in two places, both keyed by risk universe:
  • public/get_currency and public/get_all_currencies — on each entry of perp.universes[].
  • public/get_instrument and public/get_all_instruments — inside perp_details, for the instrument’s own risk universe.
The object is absent (or null) where no Standard Risk Manager in that universe prices the currency, and on option assets. A currency whose perp market has been scaled out reports im_perp_req of "0" and max_leverage of "0" — v2’s behaviour, retained.The v2 path is not restored: the object no longer sits at the top level of the currency response. See Migrating from V2.

Deposits that arrive over a bridge carry their origin

public/get_pending_deposits and private/get_deposit_history now report where a deposit came from when it reached your escrow over a bridge. No method was added, and nothing about crediting is different: a bridge delivers an ordinary ERC-20 transfer into the escrow, which is swept and credited exactly as a transfer from your own wallet would be.
  • public/get_pending_deposits lists every bridge into your escrows in pending_deposits[], one entry per deposit throughout. While the funds are in transit, before any L1 transaction exists, the entry has status: "bridging"; once the L1 deposit is recorded it is that deposit’s entry, with its own status. Key a bridging entry on bridge_to_l1.bridge_id: it gains tx_hash and log_index when the deposit lands.
  • Now optional on pending_deposits[] entries, absent while bridging: action_id, tx_hash, log_index and block_number. amount is also absent until the bridge delivers, then what arrived. They remain present on every L1 entry.
  • Entries carry an optional bridge_to_l1 object: rail, bridge_id, source_chain_id, source_tx_hash, source_address (your wallet on the source chain), bridge_status, substatus, deposit_address, token, expected_amount (source-chain units, an upper bound before fees), receiving_tx_hash, receiving_token, receiving_amount, delivered_at_ms and expires_at_ms. bridge_status moves submitted → delivered → matched, or ends failed (refunded on the source chain), expired (unresolved past expires_at_ms) or unmatched (delivered but not credited within 24 hours; contact support).
  • private/get_deposit_history entries gain an optional bridge_origin object with rail, source_chain_id, source_tx_hash and source_address, so provenance survives after the deposit is applied.
  • private/get_deposit_history entries also gain optional l1_tx_hash and l1_sender: the L1 transaction that moved the funds into your deposit address, and who sent it (your wallet, an exchange, or a bridge’s relayer). tx_hash remains the settlement batch’s commit, null until the batch settles. Both are absent when no tracked L1 transfer backs the deposit, or when one action credited several transfers at once.
rail is lifi today; the field exists so a second bridge protocol can be added without a breaking change. A bridge sent outside Derive’s own quotes is not tracked and appears as a plain deposit, exactly as before.See Programmatic Onboarding.
Breaking

Testnet settlement contracts redeployed

The Derive v3 testnet (Ethereum Sepolia, chain ID 11155111) settlement contracts were redeployed. The sequencer only watches the new deployment: a deposit sent to a previous OnchainActionManager is not credited, and earlier withdrawal outboxes receive no new payouts.The mock ERC-20 tokens are unchanged, but the protocol spot-asset addresses (the asset argument to deposit and depositToNewSubaccount) are new. Read them from public/get_risk_universes or public/get_all_currencies rather than hardcoding them.If you hardcoded a testnet ACTION_MANAGER, update it — see Contracts and Programmatic Onboarding. Signing constants (module addresses, ACTION_TYPEHASH, the EIP-712 domain separator) are unaffected.
Breaking

Proven operations share one per-wallet rate-limit budget

Every method that submits a proven operation outside order matching now carries the proven rate-limit class and decrements a single per-wallet budget of 1 TPS by default (5 points per 5s window on mainnet). Previously most of these methods drew from the far larger non_matching budget, and a few had their own per-method budget.Affected methods: private/transfer_spot, private/transfer_spot_external, private/transfer_positions, private/withdraw, private/liquidate, private/set_session_key, private/delete_subaccount, private/update_whitelisted_recipients, private/create_vault, private/mint_vault_shares, private/burn_vault_shares, private/request_vault_deposit, private/request_vault_withdraw, private/cancel_all_vault_requests, private/force_burn.
  • public/getRateLimits reports the budget under remaining_per_endpoint.proven.
  • Rejections use the usual -32000 code with message Rate limit exceeded: {wallet}-proven.
  • Order, quote, and RFQ execution methods are unaffected.
See Rate Limits.
Breaking

private/create_session_key renamed to private/set_session_key

The route now matches what the action does (register, refresh, or retire a key by writing a nearer expiry) and its L1 counterpart, the SetSessionKey onchain action. Everything about the rename is mechanical:
  • Route: private/create_session_key → private/set_session_key (and the debug variant private/create_session_key_debug → private/set_session_key_debug). The old route names are gone.
  • Protocol scope: create_session_key → set_session_key in protocol_scopes on registration requests and in private/session_keys responses.
  • TypeScript SDK: client.sessionKeys.create(...) → client.sessionKeys.set(...); ProtocolScopeCode.CreateSessionKey → ProtocolScopeCode.SetSessionKey.

Signed action payloads now use canonical Solidity ABI encoding

Every signed action’s data bytes are now exactly what Solidity abi.encode produces — if you sign with a standard ABI coder (ethers AbiCoder, viem encodeAbiParameters, abi.encode in a contract), nothing changes for you. Hand-rolled encoders must drop the previous non-standard layouts, which are no longer accepted:private/set_session_key and the L1 SetSessionKey action (type 51)The payload is canonical abi.encode(address sessionKey, uint256 expirySec, uint256[] scopes, uint256[] subaccountIds) — dynamic arrays sit behind standard offset words, each prefixed by its length word. The old hand-packed layout ([key][expiry][scopeCount][subaccountCount][scopes…] [subaccounts…], no offsets) is rejected. In Solidity, build the L1 payload with a plain abi.encode call instead of packing words manually.private/update_whitelisted_recipientsThe payload is canonical abi.encode(address[] add, address[] remove). The old hand-packed layout ([addCount][removeCount][adds…][removes…], no offsets) is rejected.Orders (private/order) and liquidations (private/liquidate)Negative int256 values (limit_price, amount, price_limit) are now full 32-byte two’s-complement words, sign-extended through the high 16 bytes — standard ABI coder output. Previously the server produced and expected a non-standard form with the value packed into the low 16 bytes only, which broke standard-signing clients on negative liquidation price limits. Non-negative values encode identically, so most existing signatures are unaffected.Decoding is also strict across these payloads: offset words must point at their canonical positions, lengths must match the byte count, and value words must fit their field’s range — dirty high bytes are rejected instead of silently truncated, matching Solidity abi.decode.See Action signing for the per-module payload layouts.
Breaking

Settlement status unified under batch_status

The tx_status field and the TxStatus type are removed. Every route and channel that reported settlement status now uses batch_status, typed as the shared BatchStatus enum — Batching, Executing, Proving, Settling, Settled and their …Error counterparts — and null until the operation is picked up into a batch.This replaces the old granular transaction states (applied, in_batch, proving, submitted) and the lowercase matching-engine values (requested, pending, settled, reverted, ignored, timed_out), which are all gone across the entire API. There is now one status field, one set of values.REST methods
  • public/get_trade_history — optional batch_status request filter (unset returns trades in every batch state) and on each trade
  • private/get_trade_history — batch_status on each trade
  • private/order, private/replace — batch_status on each trades[] entry (always null at placement)
  • private/send_quote, private/cancel_quote, private/replace_quote, private/get_quotes, private/transfer_positions — batch_status on each quote (present only for executed quotes)
  • private/get_deposit_history, private/get_withdrawal_history, private/get_erc20_transfer_history, private/get_funding_history — batch_status on each row
  • public/get_transaction — status uses the same BatchStatus values
WebSocket channels
  • trades.{instrument_type}.{currency}.{tx_status} and {subaccount_id}.trades.{tx_status} — removed; settlement is no longer streamed. Poll public/get_trade_history for batch_status / tx_hash instead
  • trades.{instrument_name}, trades.{instrument_type}.{currency} — each event now carries the full trade detail (wallet, subaccount_id, liquidity_role, fees, realized PnL)
  • {subaccount_id}.trades, {subaccount_id}.quotes — batch_status on each payload item

instrument_type is now a typed enum

On the trade-history schemas, instrument_type is typed as the AssetType enum (erc20 / option / perp) instead of a free-form string.See Breaking changes → Settlement status for the full route table and value reference.