# dYdX Documentation import { HomePage } from 'vocs/components' dYdX Documentation This website contains all the required documentation for dYdX protocol to start trading. Get started GitHub ## Open Source Repositories Please find the open source repositories on our [GitHub](https://github.com/dydxprotocol): * [Monorepo](https://github.com/dydxprotocol/v4-chain) * [Protocol](https://github.com/dydxprotocol/v4-chain/tree/main/protocol) * [Indexer](https://github.com/dydxprotocol/v4-chain/tree/main/indexer) * [Clients](https://github.com/dydxprotocol/v4-clients) * [Frontend](https://github.com/dydxprotocol/v4-web) * [iOS](https://github.com/dydxprotocol/v4-native-ios) * [Android](https://github.com/dydxprotocol/v4-native-android) * [Terraform](https://github.com/dydxprotocol/v4-infrastructure) * [dYdX Technical Docs](https://github.com/dydxprotocol/v4-documentation) * [Pocket protector TG bot docs](https://docs.pocketprotector.xyz/) When contributing, please ensure your commits are verified. You can follow these steps to do so: * [Generate a new signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key) for work use and [turn on Vigilant Mode](https://docs.github.com/en/authentication/managing-commit-signature-verification/displaying-verification-statuses-for-all-of-your-commits) * [Tell Git about your GPG key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) and install `pinentry` if necessary #### Third-Party Integrations * [LEAN / dYdX Exchange Plugin](https://qnt.co/dydx-lean-repo) ## Third-Party Integrations ### QuantConnect QuantConnect is a leading algorithmic trading platform that empowers developers and traders to research, backtest, and deploy quantitative strategies across various asset classes. The [integration with dYdX](https://qnt.co/dydx-docs) enables members to live trade Crypto Futures on the dYdX decentralized exchange from within the QuantConnect platform. QC's battle-tested infrastructure provides a stable environment that handles all the communication with the dYdX API, so you can focus on researching new strategies and monitoring your live algorithms. The LEAN engine that powers QC has an extensive suite of features to equip you with all the AI models, indicators, and dataset you'll need to take your trading to the next level with just a few lines of code. ## TODO #### Fill Add testnet USDC to a subaccount. ##### Method Declaration :::code-group ```python [Python] async def fill( self, address: str, subaccount_number: int, amount: float, headers: Optional[Dict] = None, ) -> httpx.Response ``` ```rust [Rust] pub async fn fill(&self, subaccount: &Subaccount, amount: &Usdc) -> Result<(), Error> ``` ```typescript [TypeScript] public async fill( address: string, subaccountNumber: number, amount: number, headers?: {}, ): Promise ``` ```url [API] /faucet/tokens ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------------- | -------- | ------------------ | -------- | ------------------------------------------------------------ | | `address` | body | [Address] | true | The wallet address that owns the account. | | `subaccount_number` | body | [SubaccountNumber] | true | A number that identifies a certain subaccount of the address | | `amount` | body | [BigDecimal] | true | Amount to fill | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------ | ------------------------------------ | | `200` | [OK] | | The response | | `400` | [Bad Request] | | The request was malformed or invalid | | `404` | [Not Found] | | The subaccount was not found. | [Python] | [TypeScript] [Python]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-py-v2/examples/faucet_endpoint.py [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-js/examples/faucet_endpoint.ts [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [BigDecimal]: /types/big_decimal [OK]: /types/ok [Bad Request]: /types/bad-request [Not Found]: /types/not-found #### Fill Native Add native dYdX testnet token to an address. ##### Method Declaration :::code-group ```python [Python] async def fill_native( self, address: str, headers: Optional[Dict] = None, ) -> httpx.Response ``` ```rust [Rust] pub async fn fill_native(&self, address: &Address) -> Result<(), Error> ``` ```typescript [TypeScript] public async fillNative(address: string, headers?: {}): Promise ``` ```url [API] /faucet/native-token ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | --------- | -------- | ----------------------------------------- | | `address` | body | [Address] | true | The wallet address that owns the account. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------ | ------------------------------------ | | `200` | [OK] | | The response | | `400` | [Bad Request] | | The request was malformed or invalid | Examples: [Python] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/fund_account_example.py#L25 [Address]: /types/address [OK]: /types/ok [Bad Request]: /types/bad-request import FaucetClient from './intro.mdx' import Fill from './fill.mdx' import FillNative from './fill_native.mdx' ## Faucet API ### Methods ## Indexer API The Indexer is a high-availability system designed to provide structured data. It serves both over its [HTTP/REST API](/indexer-client/http) for spontaneous requests and over its [WebSockets API](/indexer-client/websockets) for continuous data streaming. See the [guide](/interaction/endpoints#indexer-client) on how to use the available Indexer client to learn how to connect to it. ## Connecting to dYdX dYdX provides two networks for trading: a **mainnet**, and a **testnet**: * **mainnet**: The core network where real financial transactions occur; * **testnet**: A separate, risk-free, network. Served mainly for the purposes of testing and experimenting before transitioning to the **mainnet**. For the purposes of this guide, we'll assume that the **mainnet** is being used. Nevertheless, the API is exactly the same for both the **mainnet** and the **testnet**, so any code working in the **mainnet** should work in the **testnet**. Choosing between the **mainnet** and the **testnet** is simply a matter of changing the used endpoints. :::note It is advisable that for the purposes of learning and trying out the dYdX ecosystem that the **testnet** is used and preferred over the **mainnet**. ::: ### Available clients Interacting with the dYdX network API is made through several sets of methods grouped with structures referred to as clients. Each of these clients essentially connects to a different server with its own functionality and purpose. #### Node client The Node client (also known as the Validator client) is the main client for interacting with the dYdX network. It provides the [Node API](/node-client/index) allowing the user to do operations that require authentication (e.g., issue trading orders) through the [Private API](/node-client/private/index). You'll need an endpoint to setup the Node client. Grab an RPC/gRPC endpoint from [here](#node). Additionally for the Python client, you'all also need a HTTP and WebSockets endpoints. :::tip[OEGS] With the release of the Order Entry Gateway Service (OEGS), users can now connect to dYdX via OEGS endpoints. OEGS provides both gRPC and RPC endpoints. for more info on OEGS, check [here](/concepts/architecture/oegs.mdx) ::: :::code-group ```python [Python] from dydx_v4_client.network import make_mainnet from dydx_v4_client.node.client import NodeClient config = make_mainnet( # [!code focus] node_url="oegs.dydx.trade:443" # [!code focus] rest_indexer="https://indexer.dydx.trade", # [!code focus] websocket_indexer="wss://indexer.dydx.trade/v4/ws", # [!code focus] ).node # [!code focus] # Call make_testnet() to use the testnet instead. # [!code focus] # Connect to the network. # [!code focus] node = await NodeClient.connect(config) # [!code focus] ``` ```typescript [TypeScript] import { ValidatorClient, Network } from '@dydxprotocol/v4-client-js'; // Using a pre-configured endpoint. // [!code focus] const config = Network.mainnet().validatorConfig; // [!code focus] // Or use `Network.testnet()` for the testnet. [!code focus] // You can modify the endpoint doing `config.restEndpoint = "...";` // Connect to the network. // [!code focus] const node = await ValidatorClient.connect(config); // [!code focus] ``` ```rust [Rust] use dydx::{config::ClientConfig, node::NodeClient}; // The configuration file should have the endpoint. Use a gRPC endpoint. // [!code focus] let config = ClientConfig::from_file("config.toml").await?; // [!code focus] // Connect to the network. // [!code focus] let node = NodeClient::connect(config.node).await?; // [!code focus] ``` ::: While the Node client can also query data through the [Public API](/node-client/public/index), the Indexer client should be preferred. #### Indexer client The Indexer is a high-availability system designed to provide structured data and offload computational burden from the core full nodes. The Indexer client provides methods from the [Indexer API](/indexer-client/index). It serves both as a spontaneuous source of data retrieval through its REST endpoint, or a continuous feed of trading data through its WebSockets endpoint. Given that the Indexer client can use these two different protocols, you'll need two endpoints to setup it up. Grab these from [here](#indexer). :::code-group ```python [Python] from dydx_v4_client.network import make_mainnet from dydx_v4_client.indexer.rest.indexer_client import IndexerClient from dydx_v4_client.indexer.socket.websocket import IndexerSocket config = make_mainnet( # [!code focus] node_url="your-custom-grpc-node.com", # [!code focus] rest_indexer="https://your-custom-rest-indexer.com", # [!code focus] websocket_indexer="wss://your-custom-websocket-indexer.com" # [!code focus] ).node # [!code focus] # Instantiate the HTTP sub-client. # [!code focus] indexer = IndexerClient(config.rest_indexer) # [!code focus] # Instatiate the WebSockets sub-client, connecting to the network. # [!code focus] socket = await IndexerSocket(network.websocket_indexer).connect() # [!code focus] ``` ```typescript [TypeScript] import { IndexerClient, Network, SocketClient } from '@dydxprotocol/v4-client-js'; const apiTimeout = 1000; // Using a pre-configured endpoint. // [!code focus] const config = Network.mainnet().indexerConfig; // [!code focus] // You can modify the HTTP endpoint doing `config.restEndpoint = "...";` [!code focus] // You can modify the WebSockets endpoint doing `config.websocketEndpoint = "...";` [!code focus] // Instantiate the HTTP client. // [!code focus] const indexer = new IndexerClient(config, apiTimeout); // [!code focus] // Instantiate the WebSockets client, connecting to the network. // [!code focus] const socket = new SocketClient( // [!code focus] config.indexerConfig, // [!code focus] () => {}, // onOpenCallback () => {}, // onCloseCallback () => {}, // onMessageCallback () => {} // onErrorCallback ); // [!code focus] socket.connect(); // [!code focus] ``` ```rust [Rust] use dydx::{config::ClientConfig, indexer::IndexerClient}; // The configuration file should have the endpoint. // [!code focus] let config = ClientConfig::from_file("config.toml").await?; // [!code focus] // Instantiate the client. // [!code focus] // Both HTTP and WebSockets methods are provided with the `indexer`. // [!code focus] let indexer = IndexerClient::new(config.indexer); // [!code focus] ``` ::: #### Composite client (TypeScript only) The Composite client groups commonly used methods into a single structure. It is essentially composed by both the Node and Indexer clients. ```typescript [TypeScript] import { CompositeClient, Network } from '@dydxprotocol/v4-client-js'; const network = Network.mainnet(); const client = await CompositeClient.connect(network); // [!code focus] ``` :::info The Python and Rust APIs do not have a Composite client. The explicit Node and Indexer clients should be used instead. ::: #### Faucet client To test your trading strategy, test funds can be requested from the Faucet client. This client only works in the **testnet**. The acquired test funds can only be used in the **testnet**. :::code-group ```python [Python] from dydx_v4_client.network import TESTNET_FAUCET from dydx_v4_client.faucet_client import FaucetClient faucet = FaucetClient(TESTNET_FAUCET) # [!code focus] ``` ```typescript [TypeScript] import { FaucetApiHost, FaucetClient } from '@dydxprotocol/v4-client-js'; const client = new FaucetClient(FaucetApiHost.TESTNET); // [!code focus] ``` ```rust [Rust] // The feature `faucet` must be enabled. use anyhow::anyhow as err; use dydx::{config::ClientConfig, faucet::FaucetClient}; let config = ClientConfig::from_file("config.toml").await?; let faucet = FaucetClient::new( // [!code focus] config // [!code focus] .faucet // [!code focus] .ok_or_else(|| err!("The config file must contain a [faucet] config!"))?, // [!code focus] ); // [!code focus] ``` ::: #### Noble client To move assets in and out of the dYdX network, the Noble network is commonly employed. :::tip[Alternatives] Moving assets is not restricted to using the Noble client directly. Please see the [Deposits and Withdrawals](/interaction/deposits-withdrawals/overview) page. ::: :::code-group ```python [Python] from dydx_v4_client.indexer.rest.noble_client import NobleClient client = NobleClient("https://rpc.testnet.noble.strange.love") # [!code focus] await client.connect(MNEMONIC) # [!code focus] ``` ```typescript [TypeScript] import { NobleClient } from '@dydxprotocol/v4-client-js'; const client = new NobleClient('https://rpc.testnet.noble.strange.love', 'Noble example'); // [!code focus] ``` ```rust [Rust] // The feature `noble` must be enabled. use anyhow::anyhow as err; use dydx::{config::ClientConfig, noble::NobleClient}; let config = ClientConfig::from_file("config.toml").await?; let noble = NobleClient::connect( // [!code focus] config // [!code focus] .noble // [!code focus] .ok_or_else(|| err!("The config file must contain a [noble] config!"))?, // [!code focus] ).await?; // [!code focus] ``` ::: ### Endpoints Some known endpoints are provided below. Use these to connect to the dYdX networks. Feel free to compare the most suitable gRPC endpoint from this [status endpoint](https://grpc-status.dydx.trade/) #### Node Connections to the trading client to the full nodes are established using the (g)RPC protocol. ##### mainnet ##### gRPC | Team | URI | Rate limit | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------- | | OEGS | `grpc://oegs.dydx.trade:443` | | | Polkachu | `https://dydx-dao-grpc-1.polkachu.com:443`
`https://dydx-dao-grpc-2.polkachu.com:443`
`https://dydx-dao-grpc-3.polkachu.com:443` | 300 req/m | | KingNodes | `https://dydx-ops-grpc.kingnodes.com:443` | 250 req/m | | Enigma | `https://dydx-dao-grpc.enigma-validator.com:443` | | ##### Archive gRPC | Team | URI | Rate limit | | --------- | --------------------------------------------------------- | ---------- | | Polkachu | `https://dydx-dao-archive-grpc-1.polkachu.com:443` | 300 req/m | | KingNodes | `https://dydx-ops-archive-grpc.kingnodes.com:443` | 250 req/m | | Enigma | `https://dydx-dao-grpc-archive.enigma-validator.com:1492` | | ##### RPC | Team | URI | Rate limit | | --------- | ----------------------------------------------- | ---------- | | OEGS | `https://oegs.dydx.trade:443` | | | Polkachu | `https://dydx-dao-rpc.polkachu.com:443` | 300 req/m | | KingNodes | `https://dydx-ops-rpc.kingnodes.com:443` | 250 req/m | | Enigma | `https://dydx-dao-rpc.enigma-validator.com:443` | | ##### Archive RPC | Team | URI | Rate limit | | --------- | -------------------------------------------------------- | ---------- | | Polkachu | `https://dydx-dao-archive-rpc.polkachu.com:443` | 300 req/m | | KingNodes | `https://dydx-ops-archive-rpc.kingnodes.com:443 ` | 250 req/m | | Enigma | `https://dydx-dao-rpc-archive.enigma-validator.com:443 ` | | ##### REST | Team | URI | Rate limit | | --------- | ----------------------------------------------- | ---------- | | Polkachu | `https://dydx-dao-api.polkachu.com:443` | 300 req/m | | KingNodes | `https://dydx-ops-rest.kingnodes.com:443` | 250 req/m | | Enigma | `https://dydx-dao-lcd.enigma-validator.com:443` | | ##### Archive REST | Team | URI | Rate limit | | --------- | ------------------------------------------------------- | ---------- | | Polkachu | `https://dydx-dao-archive-api.polkachu.com:443` | 300 req/m | | KingNodes | `https://dydx-ops-archive-rest.kingnodes.com:443` | 250 req/m | | Enigma | `https://dydx-dao-lcd-archive.enigma-validator.com:443` | | ##### testnet ##### gRPC | Team | URI | | --------- | -------------------------------------------------- | | OEGS | `oegs-testnet.dydx.exchange:443` | | KingNodes | `test-dydx-grpc.kingnodes.com:443 (TLS)` | | Polkachu | `dydx-testnet-grpc.polkachu.com:23890 (plaintext)` | ##### RPC | Team | URI | | --------- | ----------------------------------------------- | | OEGS | `https://oegs-testnet.dydx.exchange:443` | | Enigma | `https://dydx-rpc-testnet.enigma-validator.com` | | KingNodes | `https://test-dydx-rpc.kingnodes.com` | | Polkachu | `https://dydx-testnet-rpc.polkachu.com` | ##### REST | Team | URI | | --------- | ----------------------------------------------- | | Enigma | `https://dydx-lcd-testnet.enigma-validator.com` | | KingNodes | `https://test-dydx-rest.kingnodes.com` | | Polkachu | `https://dydx-testnet-api.polkachu.com` | #### Indexer Connections with the Indexer are established either using HTTP (for spontaneuous data retrieval) or WebSockets (for data streaming). ##### mainnet | Type | URI | | ---- | -------------------------------- | | HTTP | `https://indexer.dydx.trade/v4` | | WS | `wss://indexer.dydx.trade/v4/ws` | ##### testnet | Type | URI | | ---- | --------------------------------------------- | | HTTP | `https://indexer.v4testnet.dydx.exchange` | | WS | `wss://indexer.v4testnet.dydx.exchange/v4/ws` | #### Faucet Used to retrieve test funds. ##### testnet `https://faucet.v4testnet.dydx.exchange` #### Noble Connections with the Noble blockchain. Similarly to the dYdX networks, Noble also has a **mainnet** and a **testnet**. ##### mainnet | Team | URI | | -------- | -------------------------------------------------- | | Polkachu | `http://noble-grpc.polkachu.com:21590 (plaintext)` | ##### testnet | Team | URI | | -------- | --------------------------------------------------- | | Polkachu | `noble-testnet-grpc.polkachu.com:21590 (plaintext)` | :::info In the Cosmos blockchains (dYdX, Noble, etc.) inter-blockchain communications require IBC relayers to be present that facilitate bridging between networks. These IBC relayers may not be active, specially for the **testnet** networks. ::: ## Guide import Details from '../../components/Details'; ## Wallet Setup To manage your accounts, issue orders, and perform other operations that are required to be signed, a Wallet is required. To instantiate a Wallet, you must first have your associated **mnemonic**.
* The Python client requires the use of an address to setup the Wallet. However, the address can only be fetched using a Wallet. The address is derived from the mnemonic (address \< public key \< private key \< mnemonic). * Wallet, accounts, subaccounts are all handled differently among the clients. Probably the Rust client handles this best, giving the user more control: 1. There is a `Wallet`; 2. The `Wallet` is used to derive an `Account` by index (each `Account` is associated with a keypair); 3. An `Account` is used to derive a `Subaccount` by index. A `Subaccount` is employed to create orders.
::::steps ### Getting the mnemonic A Wallet is setup using your secret **mnemonic** phrase. A **mnemonic** is a set of 24 words to back up and access your account. You can fetch your **mnemonic** from the [dYdX Frontend](https://dydx.trade). After logging in, follow the instructions in "Export secret phrase", accessed by clicking your address in the upper right corner. For the purpose of this guide, lets copy and store the **mnemonic** in a `mnemonic.txt` file. :::warning Handle your **mnemonic** in a secure manner. **Do not share** it with other parties. Do not commit your **mnemonic** to a public VCS like GitHub. Access to your **mnemonic** provides access to your account and funds. ::: ### Read the mnemonic Lets start coding. Load the mnemonic into a string variable. This assumes the mnemonic is stored in a text file. :::code-group ```python [Python] mnemonic = open('mnemonic.txt').read().strip() ``` ```typescript [TypeScript] const mnemonic = require('fs').readFileSync('mnemonic.txt', 'utf8').trim(); ``` ```rust [Rust] let mnemonic = std::fs::read_to_string("mnemonic.txt").unwrap().trim().to_string(); ``` ::: ### Create the Wallet Use the **mnemonic** to create a Wallet instance capable of signing transactions. :::code-group ```python [Python] from dydx_v4_client.key_pair import KeyPair from dydx_v4_client.wallet import Wallet # Define your address. address = Wallet(KeyPair.from_mnemonic(mnemonic), 0, 0).address() # Create a Wallet with updated parameters required for trading wallet = await Wallet.from_mnemonic(node, mnemonic, address) ``` ```typescript [TypeScript] import { BECH32_PREFIX, LocalWallet } from '@dydxprotocol/v4-client-js'; const wallet = await LocalWallet.fromMnemonic(mnemonic, BECH32_PREFIX); ``` ```rust [Rust] use dydx::node::Wallet; let wallet = Wallet::from_mnemonic(&mnemonic)?; ``` ::: :::note Please check the list of [available endpoints here](/interaction/endpoints#endpoints). ::: ### Instantiate a Subaccount :::note This step is not required in the Python client. ::: When issuing orders, the relevant Subaccount must be chosen to place the order under. A Subaccount is associated with an Account, and is meant to provide trade isolation against your other Subaccounts and enhance funds management. See more about [Accounts and Subaccounts](/concepts/trading/accounts). :::code-group ```python [Python] # Not required. The `wallet` instance created above already contains the necessary information. # The Subaccount to be used is defined using an integer when creating an order. ``` ```typescript [TypeScript] import { SubaccountInfo } from '@dydxprotocol/v4-client-js'; const subaccount = new SubaccountInfo(wallet, 0); ``` ```rust [Rust] // Create an `Account` instance for the account index 0. This `Account` has updated parameters required for trading. let account = wallet.account(0, &mut node).await?; // Create a `Subaccount` instance for the subaccount index 0. let subaccount = account.subaccount(0)?; ``` ::: :::info By default, both Python and TypeScript client Wallets will derive and use the Account indexed at 0. ::: :::: #### Connect Connect the Noble client to the Noble network. ##### Method Declaration :::code-group ```python [Python] async def connect(self, mnemonic: str) ``` ```rust [Rust] pub async fn connect(config: NobleConfig) -> Result ``` ```typescript [Typescript] async connect(wallet: LocalWallet): Promise ``` ```url [API] ``` ::: ##### Parameters ##### Response Examples: [TypeScript] | [Rust] [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/wallet.rs#L89 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/noble_example.ts#L19 #### Get account Query for [an account](https://github.com/cosmos/cosmos-sdk/tree/main/x/auth#account-1) by it's address. ##### Method Declaration :::code-group ```python [Python] async def get_account(self, address: str) -> BaseAccount ``` ```rust [Rust] pub async fn get_account(&mut self, address: &Address) -> Result ``` ```typescript [TypeScript] ``` ```url [API] ``` ::: ##### Parameters ##### Response Examples: [Python] | [Rust] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/basic_adder.py#L159 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/wallet.rs#L89 #### Get Account Balance Query token balance of an account/address. ##### Method Declaration :::code-group ```python [Python] async def get_account_balance( self, address: str, denom: str ) -> bank_query.QueryBalanceResponse ``` ```rust [Rust] pub async fn get_account_balance( &mut self, address: Address, denom: &Denom, ) -> Result ``` ```typescript getAccountBalance(denom: string): Promise ``` ```url [API] ``` ::: ##### Parameters ##### Response Examples: [TypeScript] [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/noble_example.ts#L93 #### Get Account Balances Query all balances of an account/address. ##### Method Declaration :::code-group ```python [Python] async def get_account_balances( self, address: str ) -> bank_query.QueryAllBalancesResponse: ``` ```rust [Rust] pub async fn get_account_balances(&mut self, address: Address) -> Result, Error> ``` ```typescript [TypeScript] getAccountBalances(): Promise ``` ::: ##### Parameters ##### Response Examples: [TypeScript] | [Rust] [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/noble_transfer.rs#L62 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/noble_example.ts#L58 import NobleClient from './intro.mdx' import Connect from './connect.mdx' import GetAccountBalance from './get_account_balance.mdx' import GetAccountBalances from './get_account_balances.mdx' import GetAccount from './get_account.mdx' import QueryAddress from './query_address.mdx' import SendTokenIbc from './send_token_ibc.mdx' import Simulate from './simulate.mdx' ## Noble API ### Methods #### Query Address Fetch account's number and sequence number from the network. ##### Method Declaration :::code-group ```python [Python] async def query_address(self, address: str) -> (int, int) ``` ```rust [Rust] pub async fn query_address(&mut self, address: &Address) -> Result<(u64, u64), Error> ``` ```typescript [TypeScript] ``` ::: ##### Parameters ##### Response import Details from '../../components/Details'; #### Send Token Ibc Transfer a token asset between Cosmos blockchain networks. ##### Method Declaration :::code-group ```python [Python] ``` ```rust [Rust] pub async fn send_token_ibc( &mut self, account: &mut Account, sender: Address, recipient: Address, token: impl Tokenized, source_channel: String, ) -> Result ``` ```typescript [TypeScript] async IBCTransfer(message: MsgTransferEncodeObject): Promise async send( messages: EncodeObject[], gasPrice: GasPrice = GasPrice.fromString('0.1uusdc'), memo?: string, ): Promise ``` ```url [API] /ibc.applications.transfer.v1.Msg/Transfer ``` :::
* Generalize to all IBC/blockchains. * Missing in Python. * Missing in TS: user required to produce the low-level message and then `post.send()` it.
##### Parameters | Parameter | Location | Type | Mandatory | Description | | ---------------- | -------- | --------- | --------- | ---------------------------------- | | `account` | query | [Account] | true | The account. | | `sender` | query | [Address] | true | Address of the sender. | | `recipient` | query | [Address] | true | Address of the recipient. | | `token` | query | int | true | Token type and amount to transfer. | | `source_channel` | query | String | true | Source IBC relay channel. | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | | | `400` | [Bad Request] | | The request was malformed or invalid. | [OK]: /types/ok [Account]: /types/account [Address]: /types/address [TxHash]: /types/tx_hash [Bad Request]: /types/bad-request #### Simulate ##### Method Declaration :::code-group ```python [Python] async def simulate_transaction( self, messages: List[dict], gas_price: str = "0.025uusdc", memo: Optional[str] = None, ) -> Fee: ``` ```rust [Rust] async fn simulate(&mut self, tx_raw: &tx::Raw) -> Result ``` ```typescript [TypeScript] async simulateTransaction( messages: readonly EncodeObject[], gasPrice: GasPrice = GasPrice.fromString('0.1uusdc'), memo?: string, ): Promise ``` ::: ##### Parameters ##### Response Examples: [TypeScript] [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/noble_example.ts#L78 ## Node API Nodes are the servers that manage and maintain the dYdX network. Trading transactions are broadcast to these, which then are evaluated and eventually comitted into state by the underlying consensus mechanim. It serves both a [Private API](/node-client/private), which receives transactions signed by the user, and a [Public API](/node-client/public), available for different data queries. The [Permissioned Keys API](/node-client/authenticators) is also available. See the [guide](/interaction/endpoints#node-client) on how to use the available Node client to learn how to connect to it. :::tip Consider using the [Indexer API](/indexer-client) over the Node Public API for data queries. ::: ## Network Constants ### Chain ID **mainnet**: `dydx-mainnet-1` **testnet**: `dydx-testnet-4` ### Native Token Denom **mainnet**: `adydx` **testnet**: `adv4tnt` ### Chain Registry **mainnet**: `dydx` **testnet**: `dydxtestnet` ## Resources ### `networks` Repositories mainnet: [https://github.com/dydxopsdao/networks/tree/main/dydx-mainnet-1](https://github.com/dydxopsdao/networks/tree/main/dydx-mainnet-1) testnet: [https://github.com/dydxprotocol/v4-testnets/tree/main/dydx-testnet-4](https://github.com/dydxprotocol/v4-testnets/tree/main/dydx-testnet-4) ### Upgrades History :::details[mainnet] | Block Height | Proposal | Compatible Versions | Comments | | ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 1 \~ 1,805,000 | N/A | [v2.0.1](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv2.0.1)
[v1.0.1](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv1.0.1)
[v1.0.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv1.0.0) | `v1.0.1` was a rolling upgrade;
`v2.0.1` was backported to enable easier syncing from block 1 | | 1,805,001 \~ 7,147,831 | N/A | [v2.0.1](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv2.0.1)
[v2.0.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv2.0.0) | `v2.0.0` was an emergency fix | | 7,147,832 \~ 12,791,711 | 7 | [v3.0.2](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv3.0.2)
[v3.0.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv3.0.0) | `v3.0.2` allows easier syncing from block 1 | | 12,791,712 \~ 14,404,199 | 46 | [v4.0.5](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv4.0.5) | | | 14,404,200 \~ 17,559,999 | 53 | [v4.1.2](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv4.1.2)
[v4.1.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv4.1.0) | `v4.1.2` adds performance improvements | | 17,560,000 \~ 21,141,999 | 59 | [v5.0.6](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv5.0.6)
[v5.0.4](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv5.0.4)
[v5.0.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv5.0.0) | `v5.0.4` adds performance improvements
`v5.0.6` fixes a chain liveness issue | | 21,142,000 \~ 22,169,999 | 125 | [v5.1.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv5.1.0) | | | 22,170,000 \~ 26,784,999 | 130 | [v5.2.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv5.2.0) | | | 26,785,000 \~ 29,949,999 | 160 | [v6.0.4](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv6.0.4)
[v6.0.9](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv6.0.9)
| `v6.0.9` a Comet Security patch
`v6.0.4` Integrates and adds Marketmap functionality, expands transaction sequence number validation to accept timestamp nonces, and introduces individual vault parameters. | | 29,950,000 \~ 35,601,999 | 173 | [v7.0.1](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv7.0.1) | `v7.0.1`
  • MegaVault
  • Permissionless / Instant Market Listing
  • Affiliates Program
  • Optimistic Execution
  • Comet Security patch
| | 35,602,000 \~ 48,979,999 | 200 | [v8.0.5](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv8.0.5)
[v8.0.1](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv8.0.1)
| `v8.0.5` Addresses ASA-2025-001 and ASA-2025-002
`v8.0.1` changes in Permissioned Keys, Marketmap Removals | | 48,980,000 \~ 50,240,950 | 257 | [v8.1.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv8.1.0) | `v8.1.0`
  • Builder Codes
  • Clob Transaction Batching
  • Cross Instant Market Listings
  • IML\_5x liquidity tier
| | 50,240,951 \~ 54,449,999 | 261 | [v8.2.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv8.2.0) | `v8.2.0` Addresses ISA-2025-005 | | 54,450,000 \~ 56,529,999 | 271 | [v9.0.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.0.0) | `v9.0.0`
  • TWAP Orders
  • Order Router Revenue Sharing
  • Proposer Set Reduction
| | 56,530,000 \~ 58,492,678 | 283 | [v9.1.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.1.0) | `v9.1.0` Fixes validator set hash computation to match standard CometBFT | | 58,492,679 \~ 59,834,999 | N/A | [v9.2.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.2.0)
[9.2.1](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.2.1)
| `v9.2.0` The chain expereinced a halt at height 58,492,678 and needed emergency patching
`v9.2.1` applies a Cosmos-SDK security patch | | 59,835,000 \~ 62,249,999 | 303 | [v9.3.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.3.0) | `v9.3.0` Permanent fix for the chain halt at height 58,492,678 | | 62,250,000 \~ 66,629,999 | 303 | [v9.4.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.4.0) | `v9.4.0` Leverage enforcement, staking-based fee tiers, and refined affiliate rewards | | 66,630,000 \~ 73,859,999 | 323 | [v9.5.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.5.0) | `v9.5.0` Governance-controlled transfer mechanism, epoch querying commands, and short block window parameter adjustments | | 73,860,000 \~ | 342 | [v9.6.1](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.6.1)
[v9.6.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.6.0) | `v9.6.0` Governance controls for perpetual market step and tick sizes
`v9.6.1` Dependency bumps for security fixes | ::: :::details[testnet] | Block Height | Proposal | Compatible Versions | Comments | | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------- | | 1 \~ 4,999,999 | N/A | [v2.0.1](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv2.0.1)
[v2.0.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv2.0.0)
[v1.0.1](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv1.0.1)
[v1.0.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv1.0.0) | The chain was never upgraded to `v2.0.0` | | 5,000,000 \~ 6,879,999 | 18 | [v3.0.2](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv3.0.2)
[v3.0.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv3.0.0)
| | | 6,880,000 \~ 10,449,999 | 45 | [v4.0.5](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv4.0.5) | | | 10,450,000 \~ 12,071,999 | 73 | [v4.1.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv4.1.0) | | | 12,072,000 \~ 16,291,699 | 82 | [v5.0.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv5.0.0) | | | 16,291,700 \~ 17,706,999 | 110 | [v5.1.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv5.1.0) | | | 17,707,000 \~ 19,487,299 | 113 | [v5.2.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv5.2.0) | | | 19,487,300 \~ 20,579,999 | 185 | [v6.0.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv6.0.0) | | | 20,580,000 \~ 21,669,999 | 208 | [v6.0.3-rc0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv6.0.3-rc0) | | | 21,670,000 \~ 23,527,799 | 222 | [v6.0.4-rc2](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv6.0.4-rc2) | | | 23,527,800 \~ 28,234,999 | 227 | [v7.0.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv7.0.0) | | | 28,235,000 \~ 42,857,847 | 266 | [v8.0.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv8.0.0) | | | 42,857,848 \~ 43,913,064 | 307 | [v8.1.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv8.1.0) | | | 43,913,065 \~ 46,589,845 | 310 | [v8.2.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv8.2.0) | | | 46,589,846 \~ 49,045,932 | 313 | [v9.0.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.0.0) | | | 49,045,933 \~ 51,610,520 | 337 | [v9.1.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.1.0) | | | 51,610,521 \~ 52,967,285 | 342 | [v9.3.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.3.0) | | | 52,967,286 \~ 56,861,499 | 345 | [v9.4.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.4.0) | | | 56,861,500 \~ 60,703,999 | 360 | [v9.5.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.5.0) | | | 60,704,000 \~ | 368 | [v9.6.0](https://github.com/dydxprotocol/v4-chain/releases/tag/protocol%2Fv9.6.0) | | ::: ### Seed Nodes :::details[mainnet] | Team | URI | | ---------- | ------------------------------------------------------------------------------- | | Polkachu | `ade4d8bc8cbe014af6ebdf3cb7b1e9ad36f412c0@seeds.polkachu.com:23856` | | KingNodes | `df1f145848d253800d4e4216e8793158688912f1@seeds.kingnodes.com:23856` | | Enigma | `6a720a1e5e8be9acf2752b22dc868ea2f95aaaf7@dydx-seeds.enigma-validator.com:1490` | | CryptoCrew | `c2c2fcb5e6e4755e06b83b499aff93e97282f8e8@tenderseed.ccvalidators.com:26401` | ::: :::details[testnet] | Team | URI | | ----------- | --------------------------------------------------------------------------------------- | | AllThatNode | `19d38bb5cea1378db3e16615e63594dc26119a1a@dydx-testnet4-seednode.allthatnode.com:26656` | | Crosnest: | `87ee8de5f0f82af6ee6740a30f8844bbe6434413@seed.dydx-testnet.cros-nest.com:26656` | | CryptoCrew: | `38e5a5ec34c578dc323cbdd9b98330abb448d586@tenderseed.ccvalidators.com:29104` | ::: ### Indexer Endpoints See [Endpoints](/interaction/endpoints#indexer). ### State Sync Nodes :::details[mainnet] | Team | State Sync Peers | Region | | --------- | ----------------------------------------------------------------------- | ------- | | Polkachu | `580ec248de1f41d4e50abe132b7838348db55b80@176.9.144.40:23856`
| Germany | | Polkachu | `90b0ee8e73d8237b06356b244ff9854d1991a1f8@65.109.115.228:23856`
| Finland | | Polkachu | `874b5ab53d8f5edae6674ad394f20e2b297cf73f@199.254.199.182:23856`
| Japan | | KingNodes | `92266f1badca0ca332d2f0a178f040050b873267@5.61.208.101:238566`
| APAC | | KingNodes | `4ebd98a10a76ccbec97714ac4435cb6315fc4dbb@57.129.53.67:23856`
| EU | | Enigma | `6a720a1e5e8be9acf2752b22dc868ea2f95aaaf7@135.181.183.118:1490`
| Finland | | Enigma | `4e7ad7c7a8e8054d2005ea669b9329934882b58c@136.243.35.160:1490`
| Germany | | Enigma | `477d6e72440e02fc99aaa9b67a0d2903a53da350@15.235.227.122:1490`
| Japan | ::: :::details[testnet] | Team | State Sync Peers | | -------- | -------------------------------------------------------------- | | Polkachu | `0d17772cbba3b488ad895b17b9a48948e480b1fa@65.109.23.114:23856` | ::: ### Snapshot Service :::details[mainnet] | Team | URI | Pruning | Index | | --------- | ------------------------------------------------------------------ | ------- | ----- | | Polkachu | `https://polkachu.com/tendermint_snapshots/dydx` | Yes | null | | KingNodes | `https://snapshots.kingnodes.com/network/dydx` | No | kv | | Enigma | `https://enigma-validator.com/networks?asset=dydx#networkservices` | Yes | | ::: :::details[testnet] | Team | URI | Pruning | Index | | -------- | -------------------------------------------------- | ------- | ----- | | Polkachu | `https://www.polkachu.com/testnets/dydx/snapshots` | Yes | null | ::: ### Live Peer Node Providers :::details[mainnet] | Team | URI | | -------- | -------------------------------------- | | Polkachu | `https://polkachu.com/live_peers/dydx` | ::: ### Address Book Providers :::details[mainnet] | Team | URI | | -------- | ------------------------------------- | | Polkachu | `https://polkachu.com/addrbooks/dydx` | ::: ### Full Node Endpoints See [Endpoints](/interaction/endpoints#node). ### Archival Node Endpoints :::details[mainnet] **RPC** | Team | URI | Rate limit | | --------- | ------------------------------------------------------- | ---------- | | Polkachu | `https://dydx-dao-archive-rpc.polkachu.com:443` | 300 req/m | | KingNodes | `https://dydx-ops-archive-rpc.kingnodes.com:443` | 50 req/m | | Enigma | `https://dydx-dao-rpc-archive.enigma-validator.com:443` | | **REST** | Team | URI | Rate limit | | --------- | ------------------------------------------------------- | ---------- | | Polkachu | `https://dydx-dao-archive-api.polkachu.com:443` | 300 req/m | | KingNodes | `https://dydx-ops-archive-rest.kingnodes.com:443` | 50 req/m | | Enigma | `https://dydx-dao-lcd-archive.enigma-validator.com:443` | | **gRPC** | Team | URI | Rate limit | | --------- | ------------------------------------------------------------------------------------------------------------ | ---------- | | Polkachu | `https://dydx-dao-archive-grpc-1.polkachu.com:443`
`https://dydx-dao-archive-grpc-2.polkachu.com:443` | 300 req/m | | KingNodes | `https://dydx-ops-archive-grpc.kingnodes.com:443` | 50 req/m | | Enigma | `https://dydx-dao-grpc-archive.enigma-validator.com:1492` | | ::: :::details[testnet] No Archival Nodes. ::: ### Other Links :::details[mainnet] | Name | URI | | --------------------------- | ------------------------------------------------------------------------------------------------------ | | dYdX Chain Web Frontend | `https://dydx.trade/` | | Status Page | `https://status.dydx.trade` | | Mintscan | `https://www.mintscan.io/dydx` | | Keplr | `https://wallet.keplr.app/chains/dydx` | | Validator Metrics | `https://p.ap1.datadoghq.com/sb/610e1836-51dd-11ee-a995-da7ad0900009-78607847ff8632d8a96737ed3437f40c` | | #validators Discord Channel | `https://discord.com/channels/724804754382782534/1029585380170805379` | | FE Bug Report Form | `https://www.dydxopsdao.com/feedback` | ::: :::details[testnet] | Name | URI | | -------------------------- | ----------------------------------------------------------------------------------------------------- | | Public Testnet Front End | `https://v4.testnet.dydx.exchange` | | Status Page | `https://status.v4testnet.dydx.exchange` | | Mintscan | `https://www.mintscan.io/dydx-testnet` | | Keplr | `https://testnet.keplr.app/chains/dydx-testnet` | | Validator Metrics | `https://p.datadoghq.com/sb/dc160ddf0-05a98d2dbe2a01d8caa5783eb616f826` | | Discord Channel (Feedback) | `https://discord.com/channels/724804754382782534/1117897181886677012` | | Google Form (Feedback) | `https://docs.google.com/forms/d/e/1FAIpQLSezLsWCKvAYDEb7L-2O4wOON1T56xxro9A2Azvl6IxXHP_15Q/viewform` | ::: ## Security ### Independent Audits The protocol has been audited by the [Informal Systems](https://informal.systems/) team. Additional audits are planned as more protocol code is developed. You can find all finalized audit reports in the [v4\_chain audits folder](https://github.com/dydxprotocol/v4-chain/tree/main/audits). ### Bug Bounty With all core dYdX Chain (v4) software GitHub repos now made public, we are inviting the community to help us identify any vulnerabilities to improve the security of dYdX Chain. To find more details, please see our blog post [here](https://dydx.exchange/blog/dydx-bug-bounty-program)! ## Terms-of-Use & Privacy Policy By using, recording, referencing, or downloading (i.e., any “action”) any information contained on this page or in any dYdX Trading Inc. ("dYdX") database or documentation, you hereby and thereby agree to the [v4 Terms of Use](https://dydx.exchange/v4-terms) and [Privacy Policy](https://dydx.exchange/privacy) governing such information, and you agree that such action establishes a binding agreement between you and dYdX. This documentation provides information on how to use dYdX v4 software (”dYdX Chain”). dYdX does not deploy or run v4 software for public use, or operate or control any dYdX Chain infrastructure. dYdX is not responsible for any actions taken by other third parties who use v4 software. dYdX services and products are not available to persons or entities who reside in, are located in, are incorporated in, or have registered offices in the United States or Canada, or Restricted Persons (as defined in the dYdX [Terms of Use](https://dydx.exchange/terms)). The content provided herein does not constitute, and should not be construed, or relied upon as, financial advice, legal advice, tax advice, investment advice or advice of any other nature, and you agree that you are responsible to conduct independent research, perform due diligence and engage a professional advisor prior to taking any financial, tax, legal or investment action related to the foregoing content. The information contained herein, and any use of v4 software, are subject to the [v4 Terms of Use](https://dydx.exchange/v4-terms). #### Method Name // TODO: Add description ##### Method Declaration :::code-group ```rust [Rust] ``` ```python [Python] ``` ```typescript [TypeScript] ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | ---- | -------- | ----------- | | | | | | | ##### Response | Status | Meaning | Description | Schema | | ------ | ------- | ----------- | ------ | | | | | | ## Indexer Deep Dive A good way to think about the Indexer is as similar to Infura or Alchemy’s role in the Ethereum ecosystem. However, unlike Infura/Alchemy, and like everything else in dYdX Chain, the Indexer is completely open source and can be run by anyone! #### What is the Indexer? As part of tooling for the dYdX ecosystem, we want to ensure that clients have access to performant data queries when using exchanges running on dYdX Chain software. Cosmos SDK Full Nodes offer a number of APIs that can be used to request onchain data. However, these Full Nodes are optimized for committing and executing blocks, not for serving high frequency, low-latency requests from web/mobile clients. This is why we wrote software for an indexing service. The Indexer is a read-only service that serves off chain data to clients over REST APIs and Websockets. Its purpose is to store and serve data that exists on dYdX Chain in an easier to use way. In other words, the purpose of an indexer is to index and serve data to clients in a more performant, efficient and web2-friendly way. For example the indexer will serve websockets that provide updates on the state of the orderbook and fills. These clients will include front-end applications (mobile and web), market makers, institutions, and any other parties looking to query dYdX Chain data via a traditional web2 API. #### Onchain vs. Offchain data The Indexer will run two separate ingestion/storage processes with data from a v4 Full Node: one for onchain data and one for offchain data. Currently, throughput of onchain data state changes is expected to be from 10-50 events/second. On the other hand, the expected throughput of offchain data state changes is between 500-1,000 events/second. This represents a 10-100x difference in throughput requirements. By handling these data types separately, v4 is built to allow for different services to better scale according to throughput requirements. #### Onchain Data Onchain data is all data that can be reproduced by reading committed transactions on a dYdX Chain deployment. All onchain data has been validated through consensus. This data includes: 1. Account balances (USDC) 2. Account positions (open interest) 3. Order Fills 1. Trades 2. Liquidations 3. Deleveraging 4. Partially and completely filled orders 4. Funding rate payments 5. Trade fees 6. Historical oracle prices (spot prices used to compute funding and process liquidations) 7. Long-term order placement and cancellation 8. Conditional order placement and cancellation #### Offchain Data Offchain data is data that is kept in-memory on each v4 node. It is not written to the blockchain or stored in the application state. This data cannot be queried via the gRPC API on v4 nodes, nor can it be derived from data stored in blocks. It is effectively ephemeral data on the v4 node that gets lost on restarts/purging of data from in-memory data stores. This includes: 1. Short-term order placement and cancellations 2. Order book of each perpetual exchange pair 3. Indexed order updates before they hit the chain ### Indexer Architecture ![image](/indexer-architecture.png) The Indexer is made up of a series of services that ingest information from v4 Full Nodes and serve that information to various clients. Kafka topics are used to pass events/data around to the services within the Indexer. The key services that make up Indexer are outlined below. #### Ender (Onchain ingestion) Ender is the Indexer’s onchain data ingestion service. It consumes data from the “to-ender” Kafka topic (which queues all onchain events by block) and each payload will include all event data for an entire block. Ender takes all state changes from that block and applies them to a Postgres database for the Indexer storing all onchain data. Ender will also create and send websocket events via a “to-websocket-?” Kafka topic for any websocket events that need to be emitted. #### Vulcan (Offchain ingestion) Vulcan is the Indexer’s offchain data ingestion service. It will consume data from the “to-vulcan” Kafka topic (queues all offchain events), which will carry payloads that include active order book updates, place order updates, cancel order updates, and optimistic fills. This data will be stored in a Redis cache. Vulcan will update Redis with any new open orders, set the status of canceled orders to cancel pending, and update orderbooks based on the payload received. Vulcan will also update Postgres whenever a partially filled order is canceled to update the state of the order in Postgres. Vulcan will also create and send websocket events via a “to-websocket-?” Kafka topic for any websocket events that need to be emitted. #### Comlink (API Server) Comlink is an API server that will expose REST API endpoints to read both onchain and offchain data. For example, a user could request their USDC balance or the size of a particular position through Comlink, and would receive a formatted JSON response. As an explicit goal set out by the dYdX team, we’re designing v4 APIs to closely match the [v3 APIs](https://dydx.exchange/blog/v4-deep-dive-indexer#:~\:text=closely%20match%20the-,v3%20exchange%20APIs,-.%20We%20have%20had). We have had time to gather feedback and iterate on these APIs over time with v3, and have confidence that they are reasonable at the product-level. #### Roundtable Roundtable is a periodic job service that provides required exchange aggregation computations. Examples of these computations include: 24h volume per market, open interest, PnL by account, candles, etc. #### Socks (Websocket service) Socks is the Indexer’s websockets service that allows for real-time communication between clients and the Indexer. It will consume data from ender, vulcan, and roundtable, and send websocket messages to connected clients. ### Hosting & Deploying the Indexer In service of creating an end-to-end decentralized product, the Indexer will be open source. This will include comprehensive documentation about all services and systems, as well as infrastructure-as-code for running the Indexer on popular cloud providers. The specific responsibilities of a third party operator looking to host the Indexer generally include initial deployment and ongoing maintenance. Initial deployment will involve: * Setting up AWS infrastructure to utilize the open-source repo. * Deploying Indexer code to ingest data from a full-node and expost that information through APIs and websockets * Datadog (provides useful metrics and monitoring for Indexer services), and Bugsnag (real-time alerting on bugs or issues requiring human intervention). Maintenance of the Indexer will involve: * Migrating and/or upgrading the Indexer for new open-source releases * Monitoring Bugsnag and Datadog for any issues and alerting internal team to address * Debugging and fixing any issues with a run book provided by dYdX dYdX believes that, at minimum, a DevOps engineer will be required to perform the necessary duties for deployment and maintenance of the Indexer. An operator will need to utilize the services below: * AWS * ECS - Fargate * RDS - Postgres Database * EC2 * Lambda * ElastiCache Redis * EC2 ELB - Loadbalancer * Cloudwatch - Logs * Secret Manager * Terraform Cloud - for deploying to the cloud * Bugsnag - bug awareness * Datadog - metrics and monitoring * Pagerduty - alerting Operators should be able to host the open-sourced Indexer for public access in a highly available (i.e., high uptime) manner. Requirements include owning accounts to the services above and hiring the appropriate personnel to perform deployment and maintenance responsibilities. ## OEGS ### What is the Order Entry Gateway Service (OEGS) The Order Entry Gateway represents the next step in dYdX’s multi-stage performance evolution: 1. Designated proposers — A governance-selected subset of validators responsible for proposing blocks. This creates a predictable topology for faster routing (available in v9 software upgrade). 2. Order Entry Gateway Service (OEGS) — specialized nodes for direct, one-hop delivery to proposers (available after v9 upgrade). The Order Entry Gateway Service (OEGS) is open-sourced infrastructure that provides a direct, optimized path from traders to the proposer set, reducing latency, increasing throughput, and lowering barriers for professional and retail traders alike. OEGS is now live on testnet, reach out to us for more information. ### 1. Previous State In dYdX previous architecture (original blog post [here](https://www.dydx.xyz/blog/v4-technical-architecture-overview)), orders from traders — whether via the web app, mobile, API, or third-party integration — are submitted to full nodes, which then gossip them across the network until they reach the current block proposer. * **Pros**: Fully decentralized, no single point of routing. * **Cons**: Multi-hop gossip introduces latency and unpredictability. To achieve competitive speeds, professional trading firms have had to typically run their own private full nodes with streaming enabled, directly injecting orders into the gossip layer. **Previous State** — orders flow through full nodes, then across an unpredictable set #### Validator and Full Node Roles Validators drive consensus, maintain an off-chain in-memory order book, gossip transactions across the network, and propose blocks following a weighted round-robin proof of stake model. Full Nodes run the same protocol software but hold no staking power—they don’t vote or propose. They gossip transactions, process committed blocks, and stream blockchain state to the Indexer—a read-optimized service that powers trading UIs with order book and trade data. #### Why Professional Traders run Full Nodes Full-node access has become a performance necessity for high-speed trading — but it also represents a high barrier to entry—technical, financial, and operational. Running a full node means orders don’t need to traverse geo-distributed public RPCs — you eliminate middle-hop latency by injecting them directly into the gossip network. Full nodes also enable real-time streaming of L3 order book updates, fills, taker orders, and subaccount changes—via gRPC or WebSocket—supporting highly responsive UI or algorithmic trading logic. Historically, traders relying solely on the public Indexer have faced reliability and uptime challenges, making self-hosted nodes the only way to guarantee consistent, low-latency market data. Since April 2025, we've made huge [improvements](https://x.com/AntonioMJuliano/status/1924593158165344628) (98%!) to API performance and reliability. ### 2. Designated Proposers We recently introduced the concept of designated proposers (blog post [here](https://www.dydx.xyz/blog/governance-controlled-path-reliability-and-performance)) — a governance-selected subset of validators responsible for proposing blocks. This change to the open-source software creates a predictable topology, making it possible to route transactions directly to the next proposer instead of broadcasting widely. This is a fully deterministic enhancement to CometBFT that brings increased resilience, network performance, and operational clarity — while preserving the full validator set, stake-based voting power, and decentralized governance of the network. ### 3. The Order Entry Gateway Service (OEGS) The OEGS builds on the designated proposer model by creating a specialized set of gateway nodes that: * Peer directly with all designated proposers. * Accept orders via public, high-performance endpoints (gRPC). * Bypass standard gossip, broadcasting orders in a single hop to the proposer set. This infrastructure built to: * Simplify access – traders can send orders to a public, high-performance gRPC endpoint instead of deploying their own nodes. * Ensure fairness – the Gateway peers directly with validator nodes, improving routing latency and propagation uniformity. * Scale gracefully – governance can update, expand, or delegate the Gateway set without disrupting overall network topology. Gateway nodes streamline communication between traders and proposers, replacing the need for multiple gossip hops with direct and parallel message delivery. ![OEGS](/OEGS.png) ### How It Works: Infrastructure Flow 1. Trader submits an order via UI, API, or third-party partner integration to the OEGS. 2. OEGS full nodes processes validation checks (similar to regular full node). 3. Gateway is persistently peered with the proposer set—gossiping the order directly, bypassing standard gossip hops due to direct peering. 4. Designated proposers include it in the next proposed block by consensus. 5. Order fills are committed on-chain; full nodes and Indexers update their state accordingly. This streamlined flow ensures minimal latency while preserving decentralization and consensus integrity. #### Deployment Options dYdX Labs plans to fully open-source the OEGS code and infrastructure requirements. Any community deployed infrastructure (e.g., front-end, mobile app, API) could consider sending orders to the OEGS but this remains fully opt-in. Traders may still send orders directly to a full node which maintains decentralization and censorship-resistence. Governance may consider additional incentives for an OEGS operator, given their elevated role and service expectations. #### For Market Makers & Traders You’ll get the benefit of ultra-low-latency order routing and immediate streaming data—without the burden of node deployment or uptime management. It levels the playing field between solo operators and well-resourced trading firms. ### Getting Started Head over to the OEGS endpoints [here](/interaction/endpoints) or take a look at the Node client Python example [here](/interaction/endpoints#node-client). OEGS complements full-node streaming, validators’ performance, and indexer infrastructure. We’re planning further enhancements to scale alongside trader needs. :::info DISCLAIMER The OEGS Gateway service (the “Service”) is provided and operated solely by Imperator Alliance – FZCO (“Imperator”), an independent third-party service provider. Imperator is entirely independent from, and is not owned or otherwise affiliated with dYdX Operations Services Limited ("DOS”). DOS does not (i) provide, operate, or make available the Service, (ii) exercise any control over the Service, or (iii) assume or accept any responsibility or liability whatsoever in connection with the Service, including, without limitation, with respect to its availability, functionality, security, accuracy, or reliability. Reference to the Service on any DOS-related website, documentation, or resource does not constitute, and shall not be construed as, an endorsement, recommendation, or guarantee by DOS. Without limiting the generality of the foregoing, under no circumstances shall DOS be held liable or responsible to any person or entity for any claim, demand, cause of action, damages, losses, liabilities, costs, or expenses of any kind, whether direct, indirect, incidental, punitive, special, consequential, exemplary, or otherwise, arising out of or in connection with: (a) access to, reliance upon, use of, or inability to use the Service; (b) any information or content made available through the Service; or (c) any activities, transactions, or conduct undertaken via the Service. Access to and use of the Service is undertaken solely at the risk of the user and is subject exclusively to the terms, conditions, policies, and practices established and maintained by Imperator, all of which are independent of, and not reviewed, approved, or enforced by, DOS. ::: ## Intro to dYdX Chain Architecture #### System Architecture dYdX Chain (sometimes referred to as "v4") has been designed to be completely decentralized end-to-end. The main components broadly include the protocol, the Indexer, and the front end. Each of these components are available as open source software. None of the components are run by dYdX Trading Inc. ![image](/system-architecture.png) #### Protocol (or "Application") The open-source protocol is an L1 blockchain built on top of [CometBFT](https://dydx.exchange/blog/v4-technical-architecture-overview#:~\:text=on%20top%20of-,CometBFT,-and%20using%20CosmosSDK) and using [CosmosSDK](https://docs.cosmos.network/). The node software is written in Go, and compiles to a single binary. Like all CosmosSDK blockchains, dYdX Chain uses a proof-of-stake consensus mechanism. The protocol is supported by a network of nodes. There are two types of nodes: * **Validators**: Validators are responsible for storing orders in an in-memory orderbook (i.e. off chain and not committed to consensus), gossipping transactions to other validators, and producing new blocks for dYdX Chain through the consensus process. The consensus process will have validators take turns as the proposer of new blocks in a weighted-round-robin fashion (weighted by the number of tokens staked to their node). The proposer is responsible for proposing the contents of the next block. When an order gets matched, the proposer adds it to their proposed block and initiates a consensus round. If ⅔ or more of the validators (by stake weight) approve of a block, then the block is considered committed and added to the blockchain. Users will submit transactions directly to validators. * **Full Nodes**: A Full Node represents a process running the dYdX Chain open-source application that does not participate in consensus. It is a node with 0 stake weight and it does not submit proposals or vote on them. However, full nodes are connected to the network of validators, participate in the gossiping of transactions, and also process each new committed block. Full nodes have a complete view of a dYdX Chain and its history, and are intended to support the Indexer. Some parties may decide (either for performance or cost reasons) to run their own full node and/or Indexer. #### Indexer The Indexer is a read-only collection of services whose purpose is to index and serve blockchain data to end users in a more efficient and web2-friendly way. This is done by consuming real time data from a dYdX Chain full node, storing it in a database, and serving that data through a WebSocket and REST requests to end-users. While the dYdX Chain open-source protocol itself is capable of exposing endpoints to service queries about some basic onchain data, those queries tend to be slow as validators and full nodes are not optimized to efficiently handle them. Additionally, an excess of queries to a validator can impair its ability to participate in consensus. For this reason, many Cosmos validators tend to disable these APIs in production. This is why it is important to build and maintain Indexer and full-node software separate from validator software. Indexers use Postgres databases to store onchain data, Redis for offchain data, and Kafka for consuming and streaming on/offchain data to the various Indexer services. #### Front-ends In service of building an end-to-end decentralized experience, dYdX has built three open-source front ends: a web app, an iOS app, and an Android app. * **Web application**: The website was built using JavaScript and React. The website interacts with the Indexer through an API to get offchain orderbook information and will send trades directly to the chain. dYdX has open sourced the front-end codebase and associated deployment scripts. This allows anyone to easily deploy and access the dYdX front end to/from their own domain/hosting solution via IPFS/Cloudflare gateway. * **Mobile**: The iOS and Android apps are built in native Swift and Kotlin, respectively. The mobile apps interact with the Indexer in the same way the web application does, and will send trades directly to the chain. The mobile apps have been open sourced as well, allowing anyone to deploy the mobile app to the App Store or Play store. Specifically for the App store, the deployer needs to have a developer account as well as a Bitrise account to go through the app submission process. #### Order Entry Gateway Service (OEGS) ? The Order Entry Gateway represents the next step in dYdX’s multi-stage performance evolution and is made possible by the following 2 designs: 1. Designated proposers — A governance-selected subset of validators responsible for proposing blocks. This creates a predictable topology for faster routing (available in v9 software upgrade). 2. Order Entry Gateway Service (OEGS) — open-sourced infrastructure that provides a direct, optimized path from traders to the proposer set, reducing latency, increasing throughput, and lowering barriers for professional and retail traders alike (available now on testnet). #### Lifecycle of an Order Now that we have a better understanding of each of the components of dYdX Chain, let's take a look at how it all comes together when placing an order. When an order is placed on dYdX Chain, it follows the flow below: 1. User places a trade on a decentralized front end (e.g., website) or via API 2. The order is routed to a validator. That validator gossips that transaction to other validators and full nodes to update their orderbooks with the new order. 3. The consensus process picks one validator to be the proposer. The selected validator matches the order and adds it to its next proposed block. 4. The proposed block continues through the consensus process. 1. If ⅔ of validator nodes vote to confirm the block, then the block is committed and saved to the onchain databases of all validators and full nodes. 2. If the proposed block does not successfully hit the ⅔ threshold, then the block is rejected. 5. After the block is committed, the updated onchain (and offchain) data is streamed from full nodes to Indexers. The Indexer then makes this data available via API and WebSockets back to the front end and/or any other outside services querying for this data. ## Onboarding FAQs ### Background
1. How does the network work? * dYdX Chain (or "v4") is composed of full nodes and each maintains an in-memory order book. Anyone can use the open source software to run a full node. Traders can submit order placements and cancellations to full nodes, which gossip the transactions amongst themselves. * Full nodes with enough delegated layer 1 governance tokens participate in block building as validators. Validators on dYdX Chain take turns proposing blocks of trades every \~1 second. The validator whose turn it is to propose a block at a given height is called the proposer. The proposer uses its mempool orderbook to propose a block of matches, which validators either accept or reject according to CometBFT (Tendermint) consensus. * All full nodes have visibility into the consensus process and the transactions in the mempool. Another component of dYdX Chain is the indexer software, an application that reads data from full nodes and exposes it via REST / WebSocket APIs for convenience.
2. What is the difference between a full node and a validator? * A full node does not participate in consensus. It receives data from other full nodes and validators in the network via the gossip protocol. A validator participates in consensus by broadcasting votes signed by each validator’s private keys.
3. What are the benefits of running a full node as a market maker? * Running a full node will eliminate the latency between placing an order and when the actual order is gossiped throughout the network. Without your own node, your order will need to first be relayed to the nearest geographic node, which will then propagate it throughout the network for you. With your own node, your order will directly be gossiped. * Additionally, running a full node allows you to use [full node streaming](/nodes/full-node-streaming), a feature that aims to provide real-time, accurate orderbook updates and fills. * Instructions on setting up a full node can be found [here](/nodes/running-node/setup).
4. What is the current block time? * The current block time is \~1 second on average.
5. What is an indexer? * The indexer is a read-only service that consumes real-time data from dYdX Chain to a database for visibility to users. The indexer consumes data from dYdX Chain via a connection to a full node. The full node contains a copy of the blockchain and an in-memory order book. When the full node updates its copy of the blockchain and in-memory order book due to processing transactions, it will also stream these updates to the indexer. The indexer keeps the data in its database synced with the full-node using these updates. This data is made available to users querying through HTTPS REST APIs and streaming via websockets. More info can be found [here](/indexer-client).
### Trading
1. How can I understand how finality works on dYdX Chain? * When your order fills, a block proposer will propose a block containing the fill (visible to the whole network), and then the block will go through consensus. If the block is valid it will be finalized a couple seconds later (in Cosmos-speak this happens at the “commit” stage of consensus after all validators have voted). At that point, an indexer service will communicate the fill to you. * It is recommended to post orders with a “Good-Til-Block” of the current block height, and adjusting prices once per block. If the block is published without a match to your order, you know that it is no longer active and did not fill.
2. What are the different order types in dYdX Chain? * There are two order types: Short-Term orders and stateful orders. * Short-Term orders are meant for programmatic, low-latency traders that want to place orders with shorter expirations. * Stateful orders are meant for retail that wants to place orders with longer expirations. These orders exist on chain.
3. How does the orderbook work in dYdX Chain for short-term orders? * Each validator runs their own in-memory orderbook (also known as mempool), and the set of orders each validator knows about is what order placement transactions are in their mempool. * User places a trade on a decentralized front end (e.g., website) or via the typescript or python client that places orders directly to a full node or validator API. * The consensus process picks one validator to be the block proposer. The selected validator will propose their view of the matches in the next proposed block. * If the matches are valid (orders cross, subaccounts well-collateralized, etc.) and accepted by ⅔+ of validator stake weight (consensus), then the block is committed and those matches are written to state as valid matches. * After the block is committed, the updated onchain (and offchain) data is streamed from full nodes to Indexers. The Indexer then makes this data available via API and websockets back to the front end and/or any other outside services querying for this data. * Note: the block proposer’s matches are the canonical matches for the next block assuming their block is accepted by consensus. * Other validators maintain a list of matches and those matches might differ from the block proposer’s matches, but if they’re not the block proposer those matches will not be proposed in the next block. * Similarly, the indexer is not the block proposer so its list of matches might be different from the block proposer’s matches, until the network reaches finality.
4. Why should market makers only use short-term orders? * Short-Term orders are placed and can be immediately matched after they’re added to the mempool, while stateful orders can only be placed and matched after they’re added to a block. * Short-Term orders should always have superior time priority to stateful orders. * Stateful orders have worse time priority since they can only be matched after they are included on the block, short-term orders can be matched in the same block. * Short-Term orders have less restrictive rate limits than stateful order rate limits. See rate limits later on in this section. * Short-Term orders can be replaced, and stateful orders currently don’t support replacement. * Short-Term orders can be canceled immediately after they’re placed, while stateful orders can only be canceled after they’ve been included in a block. * Short-Term orders can be received by validators in any order, while stateful orders have an ordering requirement and will fail to be placed if they’re received out of order. * This is because stateful orders use a “sequence number”, which is equivalent to a nonce on Ethereum. Short-Term orders don’t use this.
5. How can I place a short-term order? * Please use the latest dYdX Chain [typescript client](https://www.npmjs.com/package/@dydxprotocol/v4-client-js) to place orders. * Please refer to the [order.proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/clob/order.proto) for parameter and field definitions. * For more advanced order placements, please refer to one of the [validator clients](/interaction/trading).
6. How can I tell if the block proposer has placed my short-term order? * The block proposer has proposed and filled the order in the block. * The block proposer has the order in their mempool.
7. How can I tell if my short-term order is canceled? * Short-term order placements and cancellations are best-effort, and therefore can't be considered final (actually placed or canceled and unfillable) until expiry. * A FOK or IOC order is also unfillable after expiry. * The indexer **does not** send a websocket notification when a short-term order expires, which happens when the chain height exceeds the goodTilBlock of the order. * The block height is the only reliable way to know if a short-term order is canceled with finality. * However, the indexer **does** send a websocket notification when a short-term order cancel is received by the indexer's full node. * In most cases, this "best effort" cancel means the order is cancelled. However, some small fraction of these cancels are not successful. * See [Limit Order Book and Matching](/concepts/trading/limit-orderbook) for more information.
8. How can I replace an order? * Replacing an order reuses the short-term order placement function with the [same order ID](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/clob/order.proto#L10) and an equal-to-or-greater good til block. * Note: when replacing partially-filled orders, the previous fill amount is counted towards your current order. * Example: Buy 1 BTC order @ $20k is filled for 0.5 BTC. After replacing that order with a Buy 2 BTC order @ $25k, that order can only be filled for a maximum of 1.5 BTC. This is because the previously replaced order was already filled for 0.5 BTC.
9. Are fills computed/updates streamed only when a block is finalized? How about order placements? * Fills are computed only when a block is finalized. * Short term order place / cancel (including IOC / FOK orders being canceled due to not filling / being on the book or POST-ONLY orders crossing) are streamed when the full node the Indexer deployment is listening to receives the order / cancel and not only when the block is finalized. * This is why the status “BEST\_EFFORT\_OPENED” or “BEST\_EFFORT\_CANCELED” since the Indexer only knows that a full-node received the order / cancel, and it’s not guaranteed to be true across the whole network. * For the orderbook updates, these are sent when the full-node the Indexer is listening to receives orders / cancels and not just when the block is finalized. * For example, when the full-node receives a short term order it will be approximate how much is filled and how much would go on the orderbook. This is what the Indexer uses to stream orderbook updates. However, there is no guarantee that the orderbook looks the same in other nodes in the network. * Note that you can now stream the orderbook directly through your full node for the orderbook. Read more about that [here](/nodes/full-node-streaming).
10. Do orders get matched and removed from the book in between blocks? * For removal of short term orders, yes they can be removed in between blocks, however this is on a node-by-node basis and not across the whole network. * E.g. a short-term order could be removed on one node, but still be present on another. * When a short-term order expires (current block height > goodtilBlock), then it is guaranteed to be removed from all nodes. * For removal of stateful orders, they can be removed from the book in-between blocks. This is on a node-by-node basis. * If the node removing the stateful orders is the block proposer, these stateful order removals will also be propagated to all other nodes, and be entirely removed from the network. * For all orders, regarding matching: * For matching, each node on the network will attempt to match the orders as they are received in-between blocks. * Per block, only 1 node (the block proposer) will propagate the matches it’s done during the block to all other nodes in the network. Validator nodes take turns being the block proposer based on their stake weight. * If a set of validators with ⅔+ of the stake weight of the network see the matches propagated as valid, then those matches are included in the block when finalized. * The only matches that occur on the network are the ones in the block.
11. Do certain order types have priority? Are cancels prioritized? * Short term orders when received by a node will be matched against its in-memory orderbook. * Cancels of short-term orders are also processed by a node when received. * Stateful orders (long-term / conditional) are matched at the end of the block when they are received. * E.g. Stateful orders have at least a 1 block-time delay (it’s possible the order does not get included in the block) between a node receiving the order, and it being matched. * Stateful order placement will be processed AFTER short-term order placements and cancellations for a block. * Stateful order cancellations are also done at the end of the block they are received. * The stateful order cancellations are also processed AFTER short-term placements and cancellations for a block. * As mentioned above, only the matches from the block proposer will be included in the block (if a set of validators with ⅔+ of the stake weight of the network see the matches as valid).
12. How does the order cancellation mechanism work? * Short-term: * When validators receive a cancellation, if they don't already see a match for the order, they will remove the order from their order book. * Only once every validator receives the cancellation is when the order will no longer be able to be matched. * The other way an order would no longer be matchable is if the block height is past the good til block. * Long-term: * Once a stateful order cancellation is included into a block, the order will be canceled and no longer matchable. This could take 1s+ for a cancelation to be included in a block.
13. Why is it slower to cancel orders than place orders? * An order placement only needs to be on a single validator to have a match happen, but the cancellation has to have arrived at the block proposer. Since the BP rotates, to be completely sure that the order won't be matched, it has to arrive at all the validators who will be block proposer before the order expires. This is why cancelations seem to be guaranteed slower than placing/matching orders.
14. How do order statuses transition for the Indexer, for short-term and long-term orders? * Short-term: * Once the order is placed and seen by the Indexer's full-node, the order has status BEST\_EFFORT\_OPENED. * If the order is matched and has a fill in a block, the order has status OPEN. * If the order is fully-filled, the order has status FILLED. * If the order is canceled by a cancel order message, the order will have status BEST\_EFFORT\_CANCELED. The order may still have fills if other validator nodes haven't received the cancel message yet. * If the order expires due to the block height exceeding the good til block of the order, the order status will be CANCELED. The order can no longer be filled. * Long-term: * Once the order is placed and included in a block, the order has status OPEN. * If the order is fully filled, the order has status FILLED. * If the cancelation of the order is included in a block, the order has status CANCELED. The order can no longer be filled.
15. How do subaccounts work on dYdX Chain? * Each address’s subaccounts all fall under a single address, but they are labeled subaccount0, subaccount1, etc. This is unlike v3, where each subaccount was a secondary address. * To begin trading, you need to make sure your funds are in your subaccount. You can do this two ways: * Frontend: Simply leave your frontend open and it will automatically sweep. * Backend: Simply transfer USDC to it like in [this example](https://github.com/dydxprotocol/v4-clients/blob/123cd819939fe47ff80dda04b1ac1144dffa4fda/v4-client-js/examples/transfer_example_subaccount_transfer.ts).
16. Do I need gas when I transfer funds to create a new subaccount? * Yes, you will need gas. Fortunately, both USDC and cosmos native dYdX can be used to pay for gas fees. This USDC must be in the main wallet and not another subaccount to pay for fees. * To ensure this, the frontend leaves a small amount of USDC in your wallet when sweeping into your subaccount, to ensure there's enough to pay for gas.
17. What impact do subaccounts have on rate limits? * Rate limits are per account, and not per subaccount.
18. How do we compete for liquidation orders? * If you run a full-node, there is a liquidations daemon that has metrics on what accounts are up for liquidation orders, and they could try and compete for liquidations that way. * However, this is not at all documented so you'll have to work it out by reading code.
### Full Nodes & Validators
1. How much throughput and latency can be expected from a self-hosted full node? Would having multiple full nodes in different regions improve speed? * Throughput of up to 1500 orders/second from our load-testing. Latency depends on which validator is the proposer. Having multiple full-nodes in different regions where there are validators (so maybe 1 in Europe + 1 in Tokyo) would lead to improved latency.
2. Do validators communicate through a public P2P network, or is there an internal network? * It's a public P2P network.
3. What is the expected order-to-trade latency under normal conditions? * Expected order → trade latency would be: * Time for order to get from the node it was submitted to, to the proposer, so location dependent. * Order match -> trade, probably at least 1 block so \~0.8s, could be more than 1 block.
4. Is it faster to submit transactions directly to a validator or to broadcast transactions to the network? * It is faster to submit a transaction directly to the block proposer. The speed difference between a full-node and a validator is negligible unless that validator is the proposer.
5. Do you have some validators that we can send orders to? * Validators don't expose the RPC endpoints for orders.
6. How do other teams improve their speed? * Some teams are trying to get data about the order book/order updates from a full-node they are running to improve the latency to receiving data, as there is additional latency to getting order updates due to the Indexer systems having additional latency. We currently do not have documentation around this, but are working on it.
### Indexer
1. How does the indexer reconstruct the orderbook when it started/initial snapshot of the book? * A full node is run alongside the Indexer and sends messages to the indexer when it receives orders either from the RPC or gossiped from other nodes, as well as any updates from: * node pruning the order when it expires. * another order that matches an order that the node received earlier. * node removing order due to receiving a cancel from RPC or gossip. * The indexer also updates the order book whenever it receives these order messages.
2. How does the indexer know what orders are in the book on start up? * On a cold-start, a full-node would still have all the stateful orders and would send them to the indexer. For short-term orders, the full-node would not know them, nor would the indexer. Since short-term orders only are valid for 20 blocks, within 20 blocks the indexer would have an accurate view of the order book, but for the first 20 blocks it would not.
### MEV
1. How will dYdX Chain handle MEV? * Unlike general-purpose smart contract environments, the Cosmos infrastructure enables unique MEV solutions to be built that align a validator’s incentives with a user’s incentives. dYdX Chain has a framework where MEV is measured via a dashboard. The first step would be to punish validators with slashing. Further proposed solutions are still being considered, and will be announced once finalized.
2. When do I have finality related to fills? * When your order fills, a block proposer proposes a block containing the fill (visible to the whole network), and then the block undergoes consensus. If the block is valid, it finalizes shortly thereafter (in Cosmos-speak this happens at the “commit” stage of consensus, after all validators have voted). In Cosmos, every block is final (no reorgs or forks). * If you’re connected via full node, you’ll see each step of this process. If you’re connected via the indexer service, you’ll see order updates over webSocket as soon as each block is confirmed.
3. Is deliberately taking canceled orders considered an attack against makers? * Nodes should respect cancels as soon as they receive them. If they don't, then we see that as MEV and the aforementioned dashboard/metrics tracking MEV will track that.
### Pricing
1. How is the oracle price computed? * The oracle price has five parts: * Slinky: sidecar that pulls price data from external sources and caches them for the validator to use [link](https://github.com/dydxprotocol/slinky). * Vote Extensions: Every block during the Precommit stage, all validators will submit vote extensions for what they believe the oracle price of all tracked assets should be. * Consensus: The block after VE are submitted, Slinky deterministically aggregates all VE from the previous block and proposes a new updated price which is voted into consensus. * `x/prices` Module: updates the state based on the new price, also has logic for validation and etc. [link](https://github.com/dydxprotocol/v4-chain/tree/main/protocol/x/prices). * Params: determines the external sources and sensitivity [link](https://github.com/dydxprotocol/v4-testnets/blob/aa1c7ac589d6699124942a66c2362acad2e6f50d/dydx-testnet-4/genesis.json#L6106), these are configured per network (testnet genesis example), but should query the network config for these `dydxprotocold query prices list-market-param`.
2. How often are prices updated on-chain? * Prices within Slinky are committed on a one-block delay, since validators use the vote extensions from block `n-1` to securely submit their price data for block `n`. * Most of the time, prices will update every single block. Price updates happen when over `2/3` of validators are correctly running the Slinky sidecar. * Prices will not update on any given block if: * The market is disabled within `x/marketmap` * Less than `2/3s` of validators (by stake weight) contributed to a price update. This can happen if not enough validators run Slinky, or there is a massive, widespread outage across providers.
3. Does Slinky store historical prices? * No. Prices are stored in `x/oracle` module, and only stores the most recently posted price. However, you can use blockchain indexers or inspect past blocks to see the prices committed on previous heights.
### Rewards
1. How will trading rewards work on dYdX Chain? * Trading rewards are not controlled by dYdX. dYdX recommends that trading rewards could be calculated primarily based on total taker fees paid, along with a few other variables. The full proposed formula can be found [here](/concepts/trading/rewards),
2. Do liquidity provider rewards exist on v4 (dYdX Chain)? * Liquidity provider rewards in v4 are not controlled by dYdX. dYdX recommends that liquidity provider rewards should cease to exist in v4. Makers could be rewarded with a maker rebate ranging from 0.5bps to 1.1bps, based on their nominal volume and volume share. The full proposed fee schedule can be found [here](/concepts/trading/rewards).
## Account `address`: [Address] `subaccount_number`: [SubaccountNumber] [Address]: /types/address [SubaccountNumber]: /types/subaccount_number ## AccountWithParentSubaccountNumber `address`: [Address] `parent_subaccount_number`: [ParentSubaccountNumber] [Address]: /types/address [ParentSubaccountNumber]: /types/parent_subaccount_number ## Address An address of an account, represented by a string. :::code-group ```rust [Rust] String ``` ```python [Python] str ``` ```typescript [TypeScript] string ``` ::: ## AddressResponse `subaccounts`: [SubaccountResponseObject] ⛁ `total_trading_rewards`: [BigDecimal] [SubaccountResponseObject]: /types/subaccount_response_object [BigDecimal]: /types/big_decimal ## ApiOrderStatus `ApiOrderStatus` is an enum consists of the following values * `OrderStatus` * `BestEffort` ## ApiTimeInForce `ApiTimeInForce` is an enum consists of the following values * `Gtt` * `Fok` * `Ioc` ## AssetId A string identifier representing a specific asset (e.g., "USDC"). Used to uniquely reference assets within the system. :::code-group ```rust [Rust] String ``` ```python [Python] str ``` ```typescript [TypeScript] string ``` ::: ## AssetId A `u32` integer identifier representing a specific asset (e.g., USDC, dYdX token). See more on [Perpetuals and Assets](/concepts/trading/assets). ## AssetPosition `asset_id`: [u32] `quantums`: [u8] `index`: [u64] [u32]: /types/u32 [u8]: /types/u8 [u64]: /types/u64 ## AssetPositionResponseObject `symbol`: [Symbol] `side`: [PositionSide] `size`: [Quantity] `subaccountNumber`: [SubaccountNumber] `assetId`: [AssetId] [Symbol]: /types/symbol [PositionSide]: /types/position_side [Quantity]: /types/quantity [SubaccountNumber]: /types/subaccount_number [AssetId]: /types/asset_id ## AssetPositionSubaccountMessage Update sub-message received on the `v4_subaccounts` channel. `address`: [`Address`] `subaccountNumber`: [`SubaccountNumber`] `positionId`: string `assetId`: [`AssetId`] `symbol`: [`Symbol`] `side`: [`PositionSide`] `size`: [`Quantity`] [`Address`]: /types/address [`SubaccountNumber`]: /types/subaccount_number [`PositionSide`]: /types/position_side [`Quantity`]: /types/quantity [`AssetId`]: /types/asset_id [`Symbol`]: /types/symbol ## AssetPositionsMap Key: [Ticker] Value: [AssetPositionResponseObject] [Ticker]: /types/ticker [AssetPositionResponseObject]: /types/asset_position_response_object ## Authenticator `Authenticator` is an enum represented by the following values * `SignatureVerification` * `MessageFilter` * `SubaccountFilter` * `ClobPairIdFilter` * `AnyOf` * `AllOf` ## Bad Request [https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1](https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.1) ## BaseAccount `address`: string `pub_key`: Any `account_number`: [u64] `sequence`: [u64] [u64]: /types/u64 ## BigDecimal A high-precision decimal number type used to represent values requiring exact precision, such as prices or quantities. Typically serialized as a string to avoid precision loss. :::code-group ```rust [Rust] big_decimal::BigDecimal ``` ```python [Python] str ``` ```typescript [TypeScript] string ``` ::: ## Block \`header: [Header] `data`: [Data] `evievidence`: [EvidenceList] `last_commit`: [Commit] [Header]: /types/header [Data]: /types/data [EvidenceList]: /types/evidence_list [Commit]: /types/commit ## BlockHeightInitialMessage Initial message received on the `v4_block_height` channel. It is a [`HeightResponse`] object. [`HeightResponse`]: /types/height_response ## BlockHeightUpdateMessage Update message received on the `v4_block_height` channel. `blockHeight`: [`Height`] `time`: [`DateTime`] [`DateTime`]: /types/date_time [`Height`]: /types/height ## BlockId `hash`: [u8] ⛁ `part_set_header`: [PartSetHeader] [u8]: /types/u8 [PartSetHeader]: /types/part_set_header ## BridgeEvent `id`: [u32] `coin`: [Coin] `address`: string `eth_block_height`: [u64] [u32]: /types/u32 [Coin]: /types/coin [u64]: /types/u64 ## BroadcastMode `BroadcastMode` is an enum consists of the following values: * BroadcastTxSync * BroadcastTxCommit ## BuilderCodeParameters Represents the metadata for the partner or builder of an order. This allows them to specify a fee for providing their service which will be paid out in the event of an order fill. `builder_address`: [Address] * The address of the builder to which the fee will be paid `fee_ppm`: [u32] * The fee enforced on the order in parts per million (ppm) [Address]: /types/address [u32]: /types/u32 ## CandleResolution `CandleResolution` is an enum represented by the following values * `M1` * `M5` * `M15` * `M30` * `H1` * `H4` * `D1` import Opt from '../../components/Opt'; ## CandleResponseObject `ticker`: [Ticker] `trades`: [u64] `startedAt`: [DateTime in UTC] `baseTokenVolume`: [Quantity] `open`: [Price] `low`: [Price] `high`: [Price] `close`: [Price] `resolution`: [CandleResolution] `usdVolume`: [Quantity] `startingOpenInterest`: [BigDecimal] `orderBookMidPriceOpen`: [BigDecimal] `orderBookMidPriceClose`: [BigDecimal] [Ticker]: /types/ticker [DateTime in UTC]: /types/date_time [Quantity]: /types/quantity [Price]: /types/price [CandleResolution]: /types/candle_resolution [BigDecimal]: /types/big_decimal [u64]: /types/u64 import Array from '../../components/Array'; ## CandlesInitialMessage Initial message received on the `v4_candles` channel. `candles`: [`CandleResponseObject`] [`CandleResponseObject`]: /types/candle_response_object import Array from '../../components/Array'; ## CandlesUpdateMessage Update message received on the `v4_candles` channel. It is a [`CandleResponseObject`]. [`CandleResponseObject`]: /types/candle_response_object ## ClientId `ClientId` is represented by [u32] [u32]: /types/u32 ## ClientMetadata A wrapper around a [u32] value used to attach optional, client-defined metadata to orders or fills. Useful for tracking or categorizing actions on the client side. [u32]: /types/u32 ## ClobPair `id`: [u32] `metadata`: [Metadata] `quantum_conversion_exponent`: [i32] `step_base_quantums`: [u64] `subticks_per_tick`: [u32] `status`: [ClobPairStatus] [u32]: /types/u32 [u64]: /types/u64 [i32]: /types/i32 [Metadata]: /types/metadata [ClobPairStatus]: /types/clob_pair_status ## ClobPairId A CLOB (Central Limit Order Book) Pair ID refers to the identifier for a specific order book (spot, perpetual, etc.). It uniquely identifies where liquidity rests, tick sizes, step sizes, and other trading configuration for that product. `ClobPairId` is represented by [u32] [u32]: /types/u32 ## ClobPairStatus `ClobPairStatus` is an enum consists of the following values: * `Unspecified` * `Active` * `Paused` * `CancelOnly` * `PostOnly` * `Initializing` * `FinalSettlement` ## Coin `denom`: string `amount`: string ## Commission `commission_rates`: [CommissionRates] `update_time`: [Timestamp] [CommissionRates]: /types/commission_rates [Timestamp]: /types/timestamp ## CommissionRates `rate`: string `max_rate`: string `max_change_rate`: string ## Commit `height`: [i64] `round`: [i32] `block_id`: [BlockId] `signatures`: [CommitSig] ⛁ [i64]: /types/i64 [i32]: /types/i32 [BlockId]: /types/block_id [CommitSig]: /types/commit_sig ## CommitSig `block_id_flag`: [i32] `validator_address`: [u8] ⛁ `timestamp`: [Timestamp] `signature`: [u8] ⛁ [i32]: /types/i32 [u8]: /types/u8 [Timestamp]: /types/timestamp ## ComplianceReason `ComplianceReason` is an enum consists of the following values * MANUAL * US\_GEO * CA\_GEO * GB\_GEO * SANCTIONED\_GEO * COMPLIANCE\_PROVIDER ## ComplianceResponse `restricted`: bool `reason`: string ## ComplianceReason `ComplianceReason` is an enum consists of the following values * COMPLIANT * FIRST\_STRIKE\_CLOSE\_ONLY * FIRST\_STRIKE * CLOSE\_ONLY * BLOCKED import Opt from '../../components/Opt'; ## ComplianceV2Response `status`: [ComplianceStatus] `reason`: [ComplianceReason] `updatedAt`: [DateTime] [ComplianceStatus]: /types/compliance_status [ComplianceReason]: /types/compliance_reason [DateTime]: /types/date_time ## Consensus `block`: [u64] `app`: [u64] [u64]: /types/u64 ## Cosmos // TODO This type is from cosmos SDK. The exact response will be added later. ## Data `txs`: [u8] ⛁⛁ [u8]: /types/u8 ## DateTime A a timestamp type representing a specific date and time in Coordinated Universal Time (UTC), with nanosecond precision. It must be represented as an ISO 8601 formatted string. ## DelayedCompleteBridgeMessages `message`: [MessageCompleteBridge] `block_height`: [u32] [MessageCompleteBridge]: /types/message_complete_bridge [u32]: /types/u32 ## Delegation `delegator_address`: string `validator_address`: string `shares`: string ## DelegationResponse `delegation`: [Delegation] `balance`: [Coin] [Delegation]: /types/delegation [Coin]: /types/coin ## Denom `Denom` is an enum consists of the following values * `Usdc` * `Dydx` * `DydxInt` * `NobleUsdc` * `Custom` ## Description `moniker`: string `identity`: string `website`: string `security_contact`: string `details`: string ## EquityTierLimit `usd_tnc_required`: [u8] ⛁ `limit`: [u32] [u8]: /types/u8 [u32]: /types/u32 ## EquityTierLimitConfiguration `short_term_order_equity_tiers`: [EquityTierLimit] ⛁ `stateful_order_equity_tiers`: [EquityTierLimit] ⛁ [EquityTierLimit]: /types/equity_tier_limit ## Evidence `sum`: [EvidenceSum] [EvidenceSum]: /types/evidence_sum ## EvidenceList `evidence`: [Evidence] ⛁ [Evidence]: /types/evidence ## EvidenceSum `EvidenceSum` is an enum consists of the following values: * `DuplicateVoteEvidence` * `LightClientAttackEvidence` \#f64 `f64` represents 64 bit floating point number ## FillId A wrapper around a String that uniquely identifies a specific fill event. Used to reference and track individual trade executions. import Opt from '../../components/Opt'; ## FillResponseObject Represents the details of a trade fill, including size, price, market information, and metadata related to the order and subaccount. `id`: [FillId] `side`: [OrderSide] `liquidity`: [Liquidity] `type`: [FillType] `market`: [Ticker] `market_type`: [MarketType] `price`: [Price] `size`: [BigDecimal] `fee`: [BigDecimal] `affiliate_rev_share`: [BigDecimal] `created_at`: [DateTime] `created_at_height`: [Height] `order_id`: [OrderId] `client_metadata`: [ClientMetadata] `subaccount_number`: [SubaccountNumber] `builder_fee`: [BigDecimal] > **Note:** Builder fee fields will be introduced in a future version of the API (v9.0). `builder_address`: [Address] [FillId]: /types/fill_id [OrderSide]: /types/order_side [BigDecimal]: /types/big_decimal [FillType]: /types/fill_type [Liquidity]: /types/liquidity [Ticker]: /types/ticker [MarketType]: /types/market_type [Price]: /types/price [SubaccountNumber]: /types/subaccount_number [Height]: /types/height [DateTime]: /types/date_time [ClientMetadata]: /types/client_metadata [OrderId]: /types/order_id [Address]: /types/address import Opt from '../../components/Opt'; import Array from '../../components/Array'; ## FillSubaccountMessage `id`: [`FillId`] `subaccountId`: [`SubaccountId`] `side`: [`OrderSide`] `liquidity`: [`Liquidity`] `type`: [`FillType`] `clobPairId`: [`ClobPairId`] `size`: [`Quantity`] `price`: [`Price`] `quoteAmount`: string `eventId`: string `transactionHash`: string `createdAt`: [`DateTime`] `createdAtHeight`: [`Height`] `ticker`: [`Ticker`] `orderId`: [`OrderId`] `clientMetadata`: [`ClientMetadata`] [`FillId`]: /types/fill_id [`SubaccountId`]: /types/subaccount_id [`OrderSide`]: /types/order_side [`Liquidity`]: /types/liquidity [`FillType`]: /types/fill_type [`ClobPairId`]: /types/clob_pair_id [`DateTime`]: /types/date_time [`Height`]: /types/height [`Quantity`]: /types/quantity [`Ticker`]: /types/ticker [`Price`]: /types/price [`OrderId`]: /types/order_id [`ClientMetadata`]: /types/client_metadata ## FillType An enum representing the origin or nature of a fill. Possible string values are: * `LIMIT` – Result of a regular limit order. * `LIQUIDATED` – Result of liquidating another trader's position. * `LIQUIDATION` – Result of one's own position being liquidated. * `DELEVERAGED` – Caused by automatic deleveraging in risk management. * `OFFSETTING` – Result of offsetting positions, often for risk reduction. ## FundingPaymentResponseObject Represents the details of a funding payment, including payment amount, funding rate, position information, and metadata related to the perpetual market and subaccount. `createdAt`: [DateTime] `createdAtHeight`: [Height] `perpetualId`: string `ticker`: [Ticker] `oraclePrice`: [Price] `size`: [BigDecimal] `side`: [PositionSide] `rate`: [BigDecimal] `payment`: [BigDecimal] `subaccountNumber`: [SubaccountNumber] `fundingIndex`: [BigDecimal] [DateTime]: /types/date_time [Height]: /types/height [Ticker]: /types/ticker [Price]: /types/price [BigDecimal]: /types/big_decimal [PositionSide]: /types/position_side [SubaccountNumber]: /types/subaccount_number ## FundingPaymentsResponseObject Represents a paginated response containing funding payment data for a subaccount or parent subaccount. `pageSize`: [u32] `totalResults`: [u32] `offset`: [u32] `fundingPayments`: [FundingPaymentResponseObject][] [u32]: /types/u32 [FundingPaymentResponseObject]: /types/funding_payment_response_object ## GasInfo `gas_wanted`: [u64] `gas_used`: [u64] [u64]: /types/u64 ## GetNodeInfoResponse `default_node_info`: [::tendermint\_proto::p2p::DefaultNodeInfo][::tendermint_proto::p2p::DefaultNodeInfo] `application_version`: [VersionInfo] [VersionInfo]: /types/version_info [::tendermint_proto::p2p::DefaultNodeInfo]: /types/cosmos ## GoodTilOneof `GoodTilOneof` is an enum consists of the following values * `GoodTillBlock` * `GoodTillBlockTime` ## Header `version`: [Consensus] `chain_id`: string `height`: [i64] `time`: [TimeStamp] [Consensus]: /types/consensus [i64]: /types/i64 [TimeStamp]: /types/timestamp ## Height A block number representing a specific point in the blockchain's history. Used to fetch historical data or to set an expiration block for an order. ## HeightResponse `height`: [Height] `time`: [DateTime in UTC] [Height]: /types/height [DateTime in UTC]: /types/date_time ## HistoricalBlockTradingReward `trading_reward`: [BigDecimal] `created_at_height`: [Height] `created_at`: [DateTime] [BigDecimal]: /types/big_decimal [Height]: /types/height [DateTime]: /types/date_time ## HistoricalFundingResponseObject `ticker`: [Ticker] `effective_at`: [DateTime in UTC] `effective_at_height`: [Height] `price`: [Price] `rate`: [BigDecimal] [Ticker]: /types/ticker [DateTime in UTC]: /types/date_time [Height]: /types/height [Price]: /types/price [BigDecimal]: /types/big_decimal ## HistoricalTradingRewardAggregation `trading_reward`: [BigDecimal] `started_at_height`: [Height] `started_at`: [DateTime] `ended_at_height`: [Height] `ended_at`: [TradingRewardAggregationPeriod] [BigDecimal]: /types/big_decimal [Height]: /types/height [DateTime]: /types/date_time [TradingRewardAggregationPeriod]: /types/trading_reward_aggregation_period ## i32 signed integer ## i64 signed 64 bit integer ## KeyPair `key`: string ## Liquidity An enum indicating the liquidity role of the fill. Possible string values are: * `TAKER` – The order removed liquidity from the order book (matched an existing order). * `MAKER` – The order added liquidity to the order book (rested before being matched). ## MarketMapperRevShareDetails `expiration_ts`: int ## MarketPrice `id`: [u32] `exponent`: [i32] `price`: [u64] [u32]: /types/u32 [i32]: /types/i32 [u64]: /types/u64 ## Market Type An enum indicating the type of market, with possible case-insensitive string values: * `PERPETUAL` * `SPOT` ## MarketsInitialMessage Initial message received on the `v4_markets` channel. `markets`: Map\[[`Ticker`], [`PerpetualMarket`]] [`Ticker`]: /types/ticker [`PerpetualMarket`]: /types/perpetual_market import Opt from '../../components/Opt'; import Array from '../../components/Array'; ## MarketsUpdateMessage Update message received on the `v4_markets` channel. `trading`: Map\[[`Ticker`], [`TradingPerpetualMarket`]] `oraclePrices`: Map\[[`Ticker`], [`OraclePriceMarket`]] [`Ticker`]: /types/ticker [`TradingPerpetualMarket`]: /types/trading_perpetual_market [`OraclePriceMarket`]: /types/oracle_price_market ## MegavaultOwnerSharesResponse `address`: string `shares`: [NumShares] `share_unlocks`: [ShareUnlock] ⛁ `equity`: [u8] ⛁ `withdrawable_equity`: [u8] ⛁ [NumShares]: /types/num_shares [ShareUnlock]: /types/share_unlock [u8]: /types/u8 ## MessageCompleteBridge `authority`: string `event`: [BridgeEvent] [BridgeEvent]: /types/bridge_event ## Metadata `Metadata` is an enum consists of the following values * [PerpetualClobMetadata] * [SpotClobMetadata] [PerpetualClobMetadata]: /types/perpetual_clob_metadata [SpotClobMetadata]: /types/spot_clob_metadata ## Module `path`: string `version`: string `sum`: string ## Not Found [https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4](https://datatracker.ietf.org/doc/html/rfc7231#section-6.5.4) ## NumShares `num_shares`: [u8] ⛁ [u8]: /types/u8 ## OK [https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-6.3.1) import Opt from '../../components/Opt'; import Array from '../../components/Array'; ## OraclePriceMarket `oraclePrice`: [`Price`] `effectiveAt`: [`DateTime`] `effectiveAtHeight`: [`Height`] `marketId`: [`u64`] [`Price`]: /types/price [`DateTime`]: /types/date_time [`Height`]: /types/height [`u64`]: /types/u64 import Opt from '../../components/Opt'; ## Order Order represents a single order belonging to a `Subaccount` for a particular `ClobPair`. `order_id`: [OrderId] * The unique ID of this order * Meant to be unique across all orders `side`: [OrderSide] * The direction of the order (buy or sell) `quantums`: [u64] * The size of this order in base quantums * Must be a multiple of `ClobPair.StepBaseQuantums` (where `ClobPair.Id = orderId.ClobPairId`) `subticks`: [u64] * The price level that this order will be placed at on the orderbook, in subticks * Must be a multiple of `ClobPair.SubticksPerTick` (where `ClobPair.Id = orderId.ClobPairId`) `time_in_force`: [i32] * The time in force of this order `reduce_only`: bool * Enforces that the order can only reduce the size of an existing position * If a ReduceOnly order would change the side of the existing position, its size is reduced to that of the remaining size of the position * If existing orders on the book with ReduceOnly would already close the position, the least aggressive (out-of-the-money) ReduceOnly orders are resized and canceled first `client_metadata`: [u32] * Set of bit flags set arbitrarily by clients and ignored by the protocol * Used by indexer to infer information about a placed order `condition_type`: [i32] * The type of condition for this order `conditional_order_trigger_subticks`: [u64] * The price at which this order will be triggered, in subticks * If condition\_type is CONDITION\_TYPE\_UNSPECIFIED, this value must be 0 * If this value is nonzero, condition\_type cannot be CONDITION\_TYPE\_UNSPECIFIED * Must be a multiple of ClobPair.SubticksPerTick (where `ClobPair.Id = orderId.ClobPairId`) `twap_parameters`: [TwapParameters] * Configuration for a TWAP order * Must be set for TWAP orders * When `twap_parameters` is supplied, `OrderFlags` must be set to 128 (TWAP) on `OrderId.OrderFlags` * Ignored for all other order types `builder_code_parameters`: [BuilderCodeParameters] * Metadata for the partner or builder of an order specifying the fees charged `good_til_oneof`: [GoodTilOneof] * Information about when the order expires `order_router_address`: string * Router address to share the revenue [OrderId]: /types/order_id [OrderSide]: /types/order_side [i32]: /types/i32 [u32]: /types/u32 [u64]: /types/u64 [GoodTilOneof]: /types/good_til_oneof [TwapParameters]: /types/twap_parameters [BuilderCodeParameters]: /types/builder_code_parameters ## OrderBatch `clob_pair_id`: [u32] `client_ids`: [u32] ⛁ [u32]: /types/u32 ## OrderBookResponseObject `bids`: List of [OrderbookResponsePriceLevel] `asks`: List of [OrderbookResponsePriceLevel] [OrderBookResponsePriceLevel]: /types/order_book_response_price_level ## OrderbookResponsePriceLevel `price`: [Price] `size`: [Quantity] [Price]: /types/price [Quantity]: /types/quantity ## OrderFlags `OrderFlags` is an enum represented by the following values * `ShortTerm` * `Conditional` * `LongTerm` * `TWAP` ## OrderId A string value that uniquely identifies a specific order. Used to track and reference orders within the system. ## OrderId `subaccount_id`: [SubaccountId] `client_id`: [u32] `order_flags`: [u32] `clob_pair_id`: [u32] [SubaccountId]: /types/subaccount_id [u32]: /types/u32 ## OrderMarketParams `atomic_resolution`: [i32] `clob_pair_id`: [ClobPairId] `oracle_price`: [Price] `quantum_conversion_exponent`: [i32] `step_base_quantums`: [u64] `subticks_per_ticks`: [u32] [i32]: /types/i32 [ClobPairId]: /types/clob_pair_id [Price]: /types/price [u64]: /types/u64 [u32]: /types/u32 import Opt from '../../components/Opt'; ## OrderResponseObject `client_id`: [ClientId] `client_metadata`: [ClientMetadata] `clob_pair_id`: [ClobPairId] `created_at_height`: [Height] `good_til_block`: [Height] `good_til_block_time`: [DateTime in UTC] `id`: [OrderId] `order_flags`: [OrderFlags] `post_only`: `bool` `price`: [Price] `reduce_only`: `bool` `side`: [OrderSide] `size`: [Quantity] `status`: [ApiOrderStatus] `subaccount_id`: [SubaccountId] `subaccount_number`: [SubaccountNumber] `ticker`: [Ticker] `time_in_force`: [ApiTimeInForce] `total_filled`: [BigDecimal] `type`: [OrderType] `updated_at`: [DateTime in UTC] `updated_at_height`: [Height] `trigger_price`: [Price] `builder_fee`: [BigDecimal] > **Note:** Builder fee fields will be introduced in a future version of the API (v9.0). `fee_ppm`: [BigDecimal] [ClientId]: /types/client_id [ClientMetadata]: /types/client_metadata [ClobPairId]: /types/clob_pair_id [Height]: /types/height [DateTime in UTC]: /types/date_time [OrderId]: /types/order_id [OrderFlags]: /types/order_flags [Price]: /types/price [OrderSide]: /types/order_side [Quantity]: /types/quantity [ApiOrderStatus]: /types/api_order_status [SubaccountId]: /types/subaccount_id [SubaccountNumber]: /types/subaccount_number [Ticker]: /types/ticker [ApiTimeInForce]: /types/api_time_in_force [BigDecimal]: /types/big_decimal [OrderType]: /types/order_type ## OrderRouterRevShare `address`: string `share_ppm`: int ## OrderSide An enum representing the direction of an order. Possible string values are: * `BUY` – Indicates a purchase order. * `SELL` – Indicates a sell order. ## OrderStatus `OrderStatus` is an enum consists of the following values * `Open` * `Filled` * `Canceled` * `BestEffortCanceled` * `Untriggered` * `BestEffortOpened` * `Pending` import Opt from '../../components/Opt'; import Array from '../../components/Array'; ## OrderSubaccountMessage Update sub-message received on the `v4_subaccounts` channel. `id`: string `subaccountId`: [`SubaccountId`] `clientId`: [`ClientId`] `clobPairId`: [`ClobPairId`] `side`: [`OrderSide`] `size`: [`Quantity`] `ticker`: [`Ticker`] `price`: [`Price`] `type`: [`OrderType`] `timeInForce`: [`ApiTimeInForce`] `postOnly`: bool `reduceOnly`: bool `status`: [`ApiOrderStatus`] `orderFlags`: [`OrderFlags`] `totalFilled`: [`BigDecimal`] `totalOptimisticFilled`: [`BigDecimal`] `goodTilBlock`: [`Height`] `goodTilBlockTime`: [`DateTime`] `triggerPrice`: [`Price`] `updatedAt`: [`DateTime`] `updatedAtHeight`: [`Height`] `removalReason`: string `createdAtHeight`: [`Height`] `clientMetadata`: [`ClientMetadata`] [`SubaccountId`]: /types/subaccount_id [`ClientId`]: /types/client_id [`OrderSide`]: /types/order_side [`ClobPairId`]: /types/clob_pair_id [`OrderType`]: /types/order_type [`Ticker`]: /types/ticker [`OrderFlags`]: /types/order_flags [`ApiTimeInForce`]: /types/api_time_in_force [`ApiOrderStatus`]: /types/api_order_status [`BigDecimal`]: /types/big_decimal [`Height`]: /types/height [`DateTime`]: /types/date_time [`Price`]: /types/price [`ClientMetadata`]: /types/client_metadata [`Quantity`]: /types/quantity ## OrderType An enum specifying the type of order to be placed or processed. Possible string values include: * `LIMIT` – Executes at a specified price or better. * `MARKET` – Executes immediately at the best available price. * `STOP_LIMIT` – Becomes a limit order once a stop price is reached. * `STOP_MARKET` – Becomes a market order once a stop price is reached. * `TRAILING_STOP` – A dynamic stop order that adjusts based on market movement. * `TAKE_PROFIT` – A limit order to secure profits once a target price is reached. * `TAKE_PROFIT_MARKET` – A market order to secure profits once a target price is reached. * `HARD_TRADE` – A special internal trade type, typically used in matching engines. * `FAILED_HARD_TRADE` – Represents a failed execution of a `HardTrade`. * `TRANSFER_PLACEHOLDER` – A placeholder type used internally for transfers. import Array from '../../components/Array'; ## OrdersInitialMessage Initial message received on the `v4_orderbook` channel. `bids`: [`OrderbookResponsePriceLevel`] `asks`: [`OrderbookResponsePriceLevel`] [`OrderbookResponsePriceLevel`]: /types/order_book_response_price_level import Array from '../../components/Array'; import Opt from '../../components/Opt'; ## OrdersInitialMessage Update message received on the `v4_orderbook` channel. `bids`: [`OrderbookResponsePriceLevel`] `asks`: [`OrderbookResponsePriceLevel`] [`OrderbookResponsePriceLevel`]: /types/order_book_response_price_level ## PageRequest | Parameter | Location | Type | Required | Description | | ------------- | -------- | ----- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `key` | query | [u8] | false | key is a value returned in PageResponse.next\_key to begin querying the next page most efficiently. Only one of offset or key should be set. | | `offset` | query | [u64] | false | offset is a numeric offset that can be used when key is unavailable. It is less efficient than using key. Only one of offset or key should be set. | | `limit` | query | [u64] | false | limit is the total number of results to be returned in the result page. If left empty it will default to a value to be set by each app. | | `count_total` | query | bool | false | count\_total is set to true to indicate that the result set should include a count of the total number of items available for pagination in UIs. count\_total is only respected when offset is used. It is ignored when key is set. | | `reverse` | query | bool | false | reverse is set to true if results are to be returned in the descending order. | [u8]: /types/u8 [u64]: /types/u64 ## ParentSubaccountNumber The value is represented by [u32]. [u32]: /types/u32 ## ParentSubaccountResponseObject `address`: [Address] `parent_subaccount`: [SubaccountNumber] `equity`: [BigDecimal] `free_collateral`: [BigDecimal] `margin_enabled`: bool `child_subaccounts`: [SubaccountResponseObject] ⛁ [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [BigDecimal]: /types/big_decimal [SubaccountResponseObject]: /types/subaccount_response_object import Array from '../../components/Array'; ## ParentSubaccountTransferResponse `transfers`: [ParentSubaccountTransferResponseObject] [ParentSubaccountTransferResponseObject]: /types/parent_subaccount_transfer_response_object ## ParentSubaccountTransferResponseObject `id`: [TransferId] `sender`: [AccountWithParentSubaccountNumber] `recipient`: [AccountWithParentSubaccountNumber] `size`: [BigDecimal] `created_at`: [DateTime] `created_at_height`: [Height] `symbol`: [Symbol] `type`: [TransferType] `transaction_hash`: [TxHash] [TransferId]: /types/transfer_id [AccountWithParentSubaccountNumber]: /types/account_with_parent_subaccount_number [BigDecimal]: /types/big_decimal [DateTime]: /types/date_time [Height]: /types/height [Symbol]: /types/symbol [TransferType]: /types/transfer_type [TxHash]: /types/tx_hash import Opt from '../../components/Opt'; import Array from '../../components/Array'; ## ParentSubaccountsInitialMessage Initial message received on the `v4_parent_subaccounts` channel. `subaccount`: [`ParentSubaccountResponseObject`] `orders`: [`OrderResponseObject`] `block_height`: [`Height`] [`ParentSubaccountResponseObject`]: /types/parent_subaccount_response_object [`OrderResponseObject`]: /types/order_response_object [`Height`]: /types/height import Opt from '../../components/Opt'; import Array from '../../components/Array'; ## ParentSubaccountsUpdateMessage Update message received on the `v4_parent_subaccounts` channel. `perpetualPositions`: [`PerpetualPositionSubaccountMessage`] `assetPositions`: [`AssetPositionSubaccountMessage`] `orders`: [`OrderSubaccountMessage`] `fills`: [`FillSubaccountMessage`] `transfers`: [`TransferSubaccountMessage`] `tradingReward`: [`TradingRewardSubaccountMessage`] `blockHeight`: [`Height`] [`PerpetualPositionSubaccountMessage`]: /types/perpetual_position_subaccount_message [`AssetPositionSubaccountMessage`]: /types/asset_position_subaccount_message [`OrderSubaccountMessage`]: /types/order_subaccount_message [`FillSubaccountMessage`]: /types/fill_subaccount_message [`TransferSubaccountMessage`]: /types/transfer_subaccount_message [`TradingRewardSubaccountMessage`]: /types/trading_reward_subaccount_message [`Height`]: /types/height ## PartSetHeader `total`: [u32] `hash`: [u8] ⛁ [u8]: /types/u8 [u32]: /types/u32 ## Perpetual `params`: [PerpetualParams] `funding_index`: [u8] ⛁ `open_interest`: [u8] ⛁ [PerpetualParams]: /types/perpetual_params [u8]: /types/u8 ## PerpetualClobMetadata `perpetual_id`: [u32] [u32]: /types/u32 ## PerpetualFeeTier `name`: string `absolute_volume_requirement`: [u64] `total_volume_share_requirement_ppm`: [u32] `maker_volume_share_requirement_ppm`: [u32] `maker_fee_ppm`: [i32] `taker_fee_ppm`: [i32] [u64]: /types/u64 [u32]: /types/u32 [i32]: /types/i32 import Opt from '../../components/Opt'; ## PerpetualMarket `atomicResolution`: [i32] `baseOpenInterest`: [BigDecimal] `clobPairId`: [ClobPairId] `defaultFundingRate1H`: [BigDecimal] `initialMarginFraction`: [BigDecimal] `maintenanceMarginFraction`: [BigDecimal] `marketType`: [PerpetualMarketType] `nextFundingRate`: [BigDecimal] `openInterest`: [BigDecimal] `openInterestLowerCap`: [BigDecimal] `openInterestUpperCap`: [BigDecimal] `oraclePrice`: [Price] `priceChange24H`: [BigDecimal] `quantumConversionExponent`: [i32] `status`: [PerpetualMarketStatus] `stepBaseQuantums`: [u64] `stepSize`: [BigDecimal] `subticksPerTick`: [u32] `tickSize`: [BigDecimal] `ticker`: [Ticker] `trades24H`: [u64] `volume24H`: [Quantity] `defaultFundingRate1H`: [BigDecimal] [i32]: /types/i32 [u64]: /types/u64 [u32]: /types/u32 [Ticker]: /types/ticker [BigDecimal]: /types/big_decimal [ClobPairId]: /types/clob_pair_id [PerpetualMarketType]: /types/perpetual_market_type [Price]: /types/price [PerpetualMarketStatus]: /types/perpetual_market_status [Quantity]: /types/quantity ## PerpetualMarketMap Key-value pair of [Ticker] and [PerpetualMarket] where [Ticker] is the `key`. [Ticker]: /types/ticker [PerpetualMarket]: /types/perpetual_market ## PerpetualMarketStatus `PerpetualMarketStatus` is an enum represented by following values * `Active` * `Paused` * `CancelOnly` * `PostOnly` * `Initializing` * `FinalSettlement` ## PerpetualMarketType `PerpetualMarketType` is an enum consists of the following values * `Cross` * `Isolated` ## PerpetualParams `id`: [u32] `ticker`: string `market_id`: [u32] `atomic_resolution`: [i32] `default_funding_ppm`: [i32] `liquidity_tier`: [u32] `market_type`: [i32] [u32]: /types/u32 [i32]: /types/i32 ## PerpetualPosition `perpetual_id`: [u32] `quantums`: [u8] ⛁ `funding_index`: [u8] ⛁ `quote_balance`: [u8] ⛁ [u32]: /types/u32 [u8]: /types/u8 ## PerpetualPositionResponseObject `market`: [Ticker] `status`: [PerpetualPositionStatus] `side`: [PositionSide] `size`: [Quantity] `maxSize`: [Quantity] `entryPrice`: [Price] `exitPrice`: [Price] `realizedPnl`: [BigDecimal] `createdAt`: [DateTime in UTC] `createdAtHeight`: [Height] `sumOpen`: [BigDecimal] `sumClose`: [BigDecimal] `netFunding`: [BigDecimal] `unrealizedPnl`: [BigDecimal] `closedAt`: [DateTime in UTC] `subaccount_number`: [SubaccountNumber] [Ticker]: /types/ticker [PerpetualPositionStatus]: /types/perpetual_position_status [PositionSide]: /types/position_side [Quantity]: /types/quantity [Price]: /types/price [DateTime in UTC]: /types/date_time [Height]: /types/height [SubaccountNumber]: /types/subaccount_number [BigDecimal]: /types/big_decimal ## PerpetualPositionStatus A `PerpetualPositionStatus` is an enum having one of the following string in case insensitive manner * `OPEN` * `CLOSED` * `LIQUIDATED` import Opt from '../../components/Opt'; import Array from '../../components/Array'; ## PerpetualPositionSubaccountMessage Update sub-message received on the `v4_subaccounts` channel. `address`: [`Address`] `subaccountNumber`: [`SubaccountNumber`] `positionId`: string `market`: [`Ticker`] `side`: [`PositionSide`] `status`: [`PerpetualPositionStatus`] `size`: [`Quantity`] `maxSize`: [`Quantity`] `netFunding`: [`BigDecimal`] `entryPrice`: [`Price`] `exitPrice`: [`Price`] `sumOpen`: [`BigDecimal`] `sumClose`: [`BigDecimal`] `realizedPnl`: [`BigDecimal`] `unrealizedPnl`: [`BigDecimal`] [`Address`]: /types/address [`SubaccountNumber`]: /types/subaccount_number [`Ticker`]: /types/ticker [`Quantity`]: /types/quantity [`BigDecimal`]: /types/big_decimal [`Price`]: /types/price [`PositionSide`]: /types/position_side [`PerpetualPositionStatus`]: /types/perpetual_position_status ## PerpetualPositionsMap Key: [Ticker] Value: [PerpetualPositionResponseObject] [Ticker]: /types/ticker [PerpetualPositionResponseObject]: /types/perpetual_position_response_object ## PnlTickId `PnlTickId` is represented by `string`. ## PnlTickInterval `PnlTickInterval` is an enum consists of the following values * `hour` * `day` ## PnlTicksResponseObject `blockHeight`: [Height] `blockTime`: [DateTime in UTC] `createdAt`: [DateTime in UTC] `equity`: [BigDecimal] `totalPnl`: [BigDecimal] `netTransfer`: [BigDecimal] [Height]: /types/height [BigDecimal]: /types/big_decimal [DateTime in UTC]: /types/date_time ## PositionSide An enum representing the direction of a position, with possible case-insensitive string values: * `LONG` * `SHORT` ## PositionStatus A PositionStatus is an enum having one of the following string in case insensitive manner * `OPEN` * `CLOSED` * `LIQUIDATED` ## Price A [BigDecimal] value that is representing the execution price of a trade. Uses high-precision decimal format to ensure accuracy in financial calculations. [BigDecimal]: /types/big_decimal ## Quantity A numeric value representing the amount of an asset. It must be a positive [BigDecimal] number, typically expressed as a string to preserve precision. [BigDecimal]: /types/big_decimal ## QueryMarketMapperRevShareDetailsResponse `details`: [MarketMapperRevShareDetails] [MarketMapperRevShareDetails]: /types/market_mapper_rev_share_details ## QueryMarketMapperRevenueShareParamsResponse `params`: [MarketMapperRevenueShareParams] [MarketMapperRevenueShareParams]: /types/market_mapper_revenue_share_params ## QueryMegavaultOwnerSharesResponse `address`: `string` `shares`: [NumShares] `shares_unlocks`: [ShareUnlock] ⛁ `equity`: [u8] ⛁ `withdrawable_equity`: [u8] ⛁ [NumShares]: /types/num_shares [ShareUnlock]: /types/share_unlock [u8]: /types/u8 ## QueryMegavaultWithdrawalInfoResponse `shares_to_withdraw`: [NumShares] `expected_quote_quantums`: [u8] ⛁ `megavault_equity`: [u8] ⛁ `total_shares`: [NumShares]: /types/num_shares [u8]: /types/u8 ## QueryOrderRouterRevShareResponse `order_router_rev_share`: [OrderRouterRevShare] [OrderRouterRevShare]: /types/order_router_rev_share ## QueryUnconditionalRevShareConfigResponse `config`: [UnconditionalRevShareConfig] ⛁ [UnconditionalRevShareConfig]: /types/unconditional_rev_share_config ## RecipientConfig `address`: string `share_ppm`: int ## RewardParams `treasury_account`: string `denom`: string `denom_exponent`: [i32] `market_id`: [u32] `fee_multiplier_ppm`: [u32] [i32]: /types/i32 [u32]: /types/u32 ## ShareUnlock `shares`: [NumShares] `unlock_block_height`: [u32] [NumShares]: /types/num_shares [u32]: /types/u32 ## SparklineResponseObject `SparklineResponseObject` is a key-value pair of [Ticker] and List of [BigDecimal] [Ticker]: /types/ticker [BigDecimal]: /types/big_decimal ## SparklineTimePeriod `SparklineTimePeriod` is an enum consists of the following values * `OneDay` * `SevenDays` ## SpotClobMetadata `base_asset_id`: [u32] `quote_asset_id`: [u32] [u32]: /types/u32 ## String String ## Subaccount `address`: [Address] `number`: [SubaccountNumber] [Address]: /types/address [SubaccountNumber]: /types/subaccount_number ## SubaccountId `SubaccountId` is represented by `string`. ## SubaccountInfo `id`: [SubaccountId] `asset_position`: [AssetPosition] ⛁ `perpetual_positions`: [PerpetualPosition] ⛁ `margin_enabled`: bool [SubaccountId]: /types/subaccount_id [AssetPosition]: /types/asset_position [PerpetualPosition]: /types/perpetual_position ## SubaccountNumber A [u32] integer used to identify a specific subaccount within a main account. Enables organizing and managing multiple positions or strategies under a single user account. [u32]: /types/u32 ## SubaccountResponseObject `assetPositions`: [AssetPositionsMap] `address`: [Address] `subaccountNumber`: [SubaccountNumber] `equity`: [BigDecimal] `freeCollateral`: [BigDecimal] `latestProcessedBlockHeight`: [Height] `marginEnabled`: bool `openPerpetualPositions`: [PerpetualPositionsMap] `updatedAtHeight`: [Height] [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [BigDecimal]: /types/big_decimal [AssetPositionsMap]: /types/asset_positions_map [PerpetualPositionsMap]: /types/perpetual_positions_map [Height]: /types/height import Array from '../../components/Array'; ## SubaccountsInitialMessage Initial message received on the `v4_subaccounts` channel. `subaccount`: [`SubaccountMessageObject`] `orders`: [`OrderMessageObject`] `blockHeight`: [`Height`] [`SubaccountMessageObject`]: /types/subaccount_response_object [`OrderMessageObject`]: /types/order_response_object [`Height`]: /types/height import Opt from '../../components/Opt'; import Array from '../../components/Array'; ## SubaccountsUpdateMessage Update message received on the `v4_subaccounts` channel. `perpetualPositions`: [`PerpetualPositionSubaccountMessage`] `assetPositions`: [`AssetPositionSubaccountMessage`] `orders`: [`OrderSubaccountMessage`] `fills`: [`FillSubaccountMessage`] `transfers`: [`TransferSubaccountMessage`] `tradingReward`: [`TradingRewardSubaccountMessage`] `blockHeight`: [`Height`] [`PerpetualPositionSubaccountMessage`]: /types/perpetual_position_subaccount_message [`AssetPositionSubaccountMessage`]: /types/asset_position_subaccount_message [`OrderSubaccountMessage`]: /types/order_subaccount_message [`FillSubaccountMessage`]: /types/fill_subaccount_message [`TransferSubaccountMessage`]: /types/transfer_subaccount_message [`TradingRewardSubaccountMessage`]: /types/trading_reward_subaccount_message [`Height`]: /types/height ## Symbol A string identifier representing a trading pair or asset (e.g., "BTC-USD"). It is used to specify markets or instruments and must be provided as a string. :::code-group ```rust [Rust] String ``` ```python [Python] str ``` ```typescript [TypeScript] string ``` ::: ## Ticker A Ticker is a pair of currency like "BTC-USD", represented by a string. :::code-group ```rust [Rust] String ``` ```python [Python] str ``` ```typescript [TypeScript] string ``` ::: ## Time in Force An enum which indicates how long an order will remain active before it is executed or expires. * `UNSPECIFIED`: represents the default behavior where an order will first match with existing orders on the book, and any remaining size will be added to the book as a maker order; * `IOC` enforces that an order only be matched with maker orders on the book. If the order has remaining size after matching with existing orders on the book, the remaining size is not placed on the book; * `POST_ONLY`: enforces that an order only be placed on the book as a maker order. Note this means that validators will cancel any newly-placed post only orders that would cross with other maker orders; * `FILL_OR_KILL`: enforces that an order will either be filled completely and immediately by maker orders on the book or canceled if the entire amount can‘t be matched. :::warning The `FILL_OR_KILL` option is deprecated and will be removed in a future version. ::: ## TimeResponse `iso`: [DateTime in UTC] `epoc`: f64 [DateTime in UTC]: /types/date_time ## Timestamp `seconds`: [i64] `nanos`: [i32] [i64]: /types/i64 [i32]: /types/i32 ## Tokenized `denom`: [Denom] `coin`: [Coin] [Denom]: /types/denom [Coin]: /types/coin ## TradeId `TradeId` is represented by `string` ## TradeResponseObject `id`: [TradeId] `created_at_height`: [Height] `created_at`: [DateTime in UTC] `side`: [OrderSide] `price`: [Price] `size`: [Quantity] `trade_type`: [TradeType] [TradeId]: /types/trade_id [Height]: /types/height [DateTime in UTC]: /types/date_time [OrderSide]: /types/order_type [Price]: /types/price [Quantity]: /types/quantity [TradeType]: /types/trade_type ## TradeType `TradeType` is an enum represented by the following values * `Limit` * `Liquidated` * `Deleveraged` ## TradeUpdate `id`: [`TradeId`] `createdAt`: [`DateTime`] `side`: [`OrderSide`] `price`: [`Price`] `size`: [`Quantity`] `type`: [`TradeType`] [`TradeId`]: /types/trade_id [`DateTime`]: /types/date_time [`OrderSide`]: /types/order_side [`TradeType`]: /types/trade_type [`Price`]: /types/price [`Quantity`]: /types/quantity import Array from '../../components/Array'; ## TradesInitialMessage `trades`: [`TradeResponseObject`] [`TradeResponseObject`]: /types/trade_response_object import Array from '../../components/Array'; ## TradesUpdateMessage Update message received on the `v4_trades` channel. `trades`: [`TradeUpdate`] [`TradeUpdate`]: /types/trade_update import Opt from '../../components/Opt'; import Array from '../../components/Array'; ## TradingPerpetualMarket `atomicResolution`: [`i32`] `baseAsset`: string `base_openInterest`: [`BigDecimal`] `basePositionSize`: [`Quantity`] `clobPairId`: [`ClobPairId`] `id`: string `marketId`: [`u64`] `incrementalPositionSize`: [`Quantity`] `initialMarginFraction`: [`BigDecimal`] `maintenanceMarginFraction`: [`BigDecimal`] `maxPositionSize`: [`Quantity`] `openInterest`: [`BigDecimal`] `quantum_conversion_exponent`: [`i32`] `quoteAsset`: string `status`: [`PerpetualMarketStatus`] `stepBaseQuantums`: [`i32`] `subticksPerTick`: [`i32`] `ticker`: [`Ticker`] `priceChange24H`: [`BigDecimal`] `trades24H`: [`u64`] `volume24H`: [`Quantity`] `nextFundingRate`: [`BigDecimal`] [`ClobPairId`]: /types/clob_pair_id [`Quantity`]: /types/quantity [`BigDecimal`]: /types/big_decimal [`i32`]: /types/i32 [`u64`]: /types/u64 [`Ticker`]: /types/ticker [`PerpetualMarketStatus`]: /types/perpetual_market_status ## TradingRewardAggregationPeriod A `TradingRewardAggregationPeriod` is an enum having one of the following string * `DAILY` * `WEEKLY` * `MONTHLY` import Opt from '../../components/Opt'; import Array from '../../components/Array'; ## TradingRewardSubaccountMessage Update sub-message received on the `v4_subaccounts` channel. `tradingReward`: [`BigDecimal`] `createdAt`: [`DateTime`] `createdAtHeight`: [`Height`] [`BigDecimal`]: /types/big_decimal [`DateTime`]: /types/date_time [`Height`]: /types/height ## TransferResponseObject `id`: string `created_at`: [DateTime in UTC] `created_at_height`: [u32] `sender`: [Account] `recipient`: [Account] `size`: [BigDecimal] `symbol`: [Symbol] `transaction_hash`: string `transfer_type`: [TransferType] [DateTime in UTC]: /types/date_time [u32]: /types/u32 [Account]: /types/account [BigDecimal]: /types/big_decimal [Symbol]: /types/symbol [TransferType]: /types/transfer_type import Opt from '../../components/Opt'; import Array from '../../components/Array'; ## TransferSubaccountMessageContents Update sub-message received on the `v4_subaccounts` channel. `sender`: [`Account`] `recipient`: [`Account`] `symbol`: [`Symbol`] `size`: [`Quantity`] `type`: [`TransferType`] `transaction_hash`: string `created_at`: [`DateTime`] `created_at_height`: [`Height`] [`Account`]: /types/account [`Symbol`]: /types/symbol [`Quantity`]: /types/quantity [`TransferType`]: /types/transfer_type [`DateTime`]: /types/date_time [`Height`]: /types/height ## TransferType TransferType is an enum having one of the following value * `TransferIn` * `TransferOut` * `Deposit` * `Withdrawal` ## TwapParameters When using TWAP parameters, the `OrderFlags` value must be set to 128 (TWAP) on `OrderId.OrderFlags` in the order placement message. `duration`: [`u32`] * Duration of the TWAP order execution in seconds * Must be between 300 (5 minutes) and 86,400 (24 hours) `interval`: [`u32`] * Interval in seconds for each suborder execution * Must be a whole number and a factor of the duration * Must be between 30 seconds and 3,600 (1 hour) `price_tolerance`: [`u32`] * Price tolerance in parts per million (ppm) for each suborder * Applied to the oracle price when each suborder is triggered * Must be between 0 and 1,000,000 [`u32`]: /types/u32 ## Tx `Tx` is represented by binary/byte data. ## TxHash `TxHash` is represented by a string. ## TxOptions `authenticators`: [i32] ⛁ `sequence`: [i32] `account_number`: [i32] [i32]: /types/i32 ## u32 An unsigned 32-bit integer type, representing whole numbers in the range from **0** to **4,294,967,295** (2³² − 1). It cannot store negative values. ## u64 unsigned 64 bit integer ## u8 An unsigned 8-bit integer type, representing whole numbers in the range from **0** to **255** (28 − 1). It cannot store negative values. ## UnbondingDelegation `delegator_address`: string `validator_address`: string `entries`: [UnbondingDelegationEntry] ⛁ [UnbondingDelegationEntry]: /types/unbounding_delegation_entry ## UnbondingDelegationEntry `created_height`: [i64] `completion_time`: [Timestamp] `initial_balance`: string `balance`: string `unbonding_id`: [u64] `unbonding_on_hold_ref_count`: [i64] [i64]: /types/i64 [u64]: /types/u64 [Timestamp]: /types/timestamp ## UnconditionalRevShareConfig `configs`: [RecipientConfig] [RecipientConfig]: /types/recipient_config ## UserStats `taker_notional`: [u64] `maker_notional`: [u64] [u64]: /types/u64 ## Validator `operator_address`: string `consensus_pubkey`: Any `jailed`: bool `status`: [i32] `token`: string `delegator_shares`: string `description`: [Description] `unbounding_height`: [i64] `unbounding_time`: [Timestamp] `commission`: [Commission] `min_self_delegation`: string `unbounding_on_hold_ref_count`: [i64] `unbounding_ids`: [u64] ⛁ [i32]: /types/i32 [Description]: /types/description [i64]: /types/i64 [Timestamp]: /types/timestamp [Commission]: /types/commission [u64]: /types/u64 ## VaultHistoricalPnl `ticker`: string `historical_pnl`: [PnlTicksResponseObject] [PnlTicksResponseObject]: /types/pnl_ticks_response_object ## VaultPosition `ticker`: string `asset_position`: [AssetPositionResponseObject] `perpetual_position`: [PerpetualPositionResponseObject] `equity`: [BigDecimal] [AssetPositionResponseObject]: /types/asset_position_response_object [PerpetualPositionResponseObject]: /types/perpetual_position_response_object [BigDecimal]: /types/big_decimal ## VersionInfo `name`: string `app_name`: string `version`: string `git_commit`: string `build_tags`: string `go_version`: string `build_deps`: [Module] ⛁ [Module]: /types/module ## Wallet `key`: [KeyPair] `account_number`: [i32] `sequence`: [i32] [KeyPair]: /types/key_pair [i32]: /types/i32 ## Accounts and Subaccounts Accounts and subaccounts serve as identifiers in the dYdX ecosystem. ### Main Account A (main) account is associated with a public-private keypair, and with a trader's on-chain identity. * Known publicly and associated with an address; * Holds tokens/assets that are sent to/from the chain, including tokens used for gas and collateral; * Gas for transactions is used from the main account; * Main accounts cannot trade; * Several main accounts can be derived from the same mnemonic phrase. Each user main account is completely independent and private/unlinkable from each other. ### Subaccounts Subaccounts provide a way to isolate funds and manage risk within an account. They are used to trade. * Each main account can have 128,001 subaccounts; * Each subaccount is uniquely identified using as subaccount ID of `(main account address, integer)`; * Once you deposit funds to a valid subaccount ID, the subaccount will automatically be created; * Only the main account can send transactions on behalf of a subaccount; * Subaccounts do not require gas (no gas is used for trading); * Subaccounts require collateral token (currently USDC) in order to trade. ## Perpetuals and Assets ### Perpetuals Perpetual contracts, or "perps," is a derivative and a type of futures contract commonly used in crypto trading. Unlike traditional futures, they do not have an expiration date, allowing traders to hold positions indefinitely. This makes them ideal for continuous speculation on asset prices. #### Benefits * Gain exposure to crypto prices without holding the actual tokens. * Go long or short depending on market expectations. * Hedge existing positions against volatility. * Potential for higher returns (with increased risk). * Earn from funding rate payments even when prices are stable. #### Risks * Falling below maintenance margin can lead to forced closure of positions. * High market swings can magnify losses, especially with leverage. * Requires understanding of derivatives mechanics (margin, funding, leverage). ### Assets and Collateral To trade within dYdX, a collateral is required. Financially, a collateral is an asset that is pledged to secure a loan like borrowing cryptocurrency. In dYdX a collateral is used to: * Open and maintain trading positions; * Manage margin requirements; * Fee payments; * Other rewards. Principal assets in dYdX are USDC and the dYdX token. ## Permissioned Keys ### Overview Permissioned Keys are a dYdX specific extension to the Cosmos authentication system that allows an account to add custom logic for verifying and confirming transactions placed on that account. For example, an account could enable other accounts to sign and place transactions on their behalf, limit those transactions to certain message types or clob pairs etc, all in a composable way. To enable this there are currently six types of "authenticator" that can used, four that enable specific authentication methods and two that allow for composability: **Sub-Authenticator Types** * **SignatureVerification** – Enables authentication via a specific key * **MessageFilter** – Restricts authentication to certain message types * **SubaccountFilter** – Restricts authentication to certain subaccount constraints * **ClobPairIdFilter** – Restricts transactions to specific CLOB pair IDs **Composable Authenticators** * **AnyOf** - Succeeds if *any* of its sub-authenticators succeeds * **AllOf** - Succeeds only if *all* sub-authenticators succeed ### Capabilities #### Available Features ✅ 1. **Account Access Control** * Limit withdrawals/transfers entirely * Multiple trading keys under same account * Trading key separation from withdrawal keys 2. **Asset-Specific Trading** * Whitelist specific trading pairs * E.g., Allow BTC/USD and ETH/USD, restrict others 3. **Subaccount Management** * Control trading permissions per subaccount * E.g., Enable trading on subaccount 0, restrict subaccount 1 #### Current Limitations ❌ 1. **Position Management** * Cannot set maximum position sizes * No order size restrictions * No custom leverage limits ## Contract Loss Mechanisms During periods of high volatility in the markets underlying the perpetual contracts, the value of some accounts may drop below zero before they can be liquidated. In these cases, the protocol’s deleveraging system kicks in. ### Liquidation and Deleveraging When an account is undercollateralized, the protocol automatically liquidates its positions until the account is sufficiently collateralized or the positions are closed. If an account’s value turns negative, deleveraging occurs immediately against randomly chosen offsetting positions, which may reduce the expected profits of offsetting accounts. #### Example 1. A deposits $100 and goes long 500 ABC contracts at $1. 2. B deposits $100 and goes short 500 ABC contracts at $1. 3. The price jumps from $1 to $2: a. A expects a profit of ($2 - $1) \* 500 = $500. b. B’s equity becomes negative: $100 - ($2 - $1) \* -500 = $-400. c. B’s bankruptcy price is $1.2, so A’s position is closed at $1.2, resulting in a $100 profit for A and a $100 loss for B, leaving B’s account value at $0. Deleveraging usually occurs after an account is eligible for liquidation, followed by further adverse price movement. If a large price shift occurs in one oracle update, an account may go directly from collateralized to negative and be immediately deleveraged. ### Insurance Fund The insurance fund covers insufficient collateral by adjusting liquidation order prices (up to a max spread from the oracle) to improve their chances of filling. See Liquidations on dYdX Chain for details. #### Example 1. A deposits $100 and goes long 500 ABC contracts at $1 with a maintenance margin fraction (MMF) of 3%. a. A’s maintenance margin requirement (MMR) = 3% \* $1 \* 500 = $15. 2. ABC’s price drops to $0.801: a. A’s pnl = (0.801 - 1.000) \* 500 = -$99.5. b. A’s equity is now $0.50, below the MMR. 3. The protocol liquidates A’s position at $0.77: a. Note: by default, the maximum spread at which the liquidation order may be placed from the oracle price is 1.5 \* MMF = 4.5%. b. A’s pnl at $0.77 would be (0.77 - 1.0) \* 500 = -$115, more than A’s $100 equity. c. The insurance fund contributes $15 to “aggress” the limit price, enabling a fill at $0.77. d. The liquidation order is matched against the order book just like any other order; if sufficient bid liquidity exists, the fill price may be better than the limit price. Note that once an account balance turns negative, deleveraging occurs immediately without using the insurance fund. ### FAQ > When does auto-deleveraging occurs? Deleveraging usually occurs after an account is eligible for liquidation, followed by further adverse price movement. If a large price shift occurs in one oracle update, an account may go directly from collateralized to negative and be immediately deleveraged. > Do isolated markets have their own insurance fund? Isolated markets are markets that have segregated pools of collateral and their own insurance fund. Each isolated market, then, has its own individual risk properties. ## Funding :::note Funding rates contemplated in the default v4 software will be subject to adjustments by the applicable Governance Community. ::: ### What are funding rates? Perpetual contracts have no expiry date and therefore no final settlement or delivery. Funding payments are therefore used to incentivize the price of the perpetual to trade at the price of the underlying. The purpose of the funding rate is to keep the price of each perpetual market trading close to its Oracle Price. When the price is too high, longs pay shorts, incentivizing more traders to sell / go short, and driving the price down. When the price is too low, shorts pay longs, incentivizing more traders to buy / go long, driving the price up. ### How are funding rates calculated on dYdX ? The main component of the funding rate is a premium that considers market activity for a perpetual. It is calculated for every market using the formula: ``` Premium = (Max(0, Impact Bid Price - Index Price) - Max(0, Index Price - Impact Ask Price)) / Index Price ``` Where the impact bid and impact ask prices are defined as: * `Impact Bid Price` = Average execution price for a market sell of the impact notional value * `Impact Ask Price` = Average execution price for a market buy of the impact notional value * `Impact Notional Amount = 500 USDC / Initial Margin Fraction` For example, at a 10% initial margin fraction, the impact notional value is 5,000 USDC. At the end of each hour, the one-hour premium is calculated as the simple average (i.e. TWAP) of the 60 premiums calculated over the last hour. How is the sample calculated? At a high level, the proposer determines the premium for each block based on their local view of the order book and then proposes a `FundingPremiumVote`. At the end of each `funding-sample` period (default to 1 minute), the median `FundingPremiumVote` is taken as the sample for that period. Therefore, at the end of each `funding-tick` period (default to 1 hour), the average of the past samples is used as the final funding rate. In addition to the premium component, each market has a fixed interest rate component that aims to account for the difference in interest rates of the base and quote currencies. The funding rate is then: ``` Funding Rate = (Premium Component / 8) + Interest Rate Component ``` As part of the default settings of the v4 open source software, the interest rate component for cross markets is 0% . The funding rate is simply the one-hour premium for markets with no interest rate component. As part of [governance vote 220](https://dydx.forum/t/drc-update-default-funding-rate-for-isolated-markets/3417) the default interest rate component for isolated markets is 0.125 bps per hour or 1 bps per 8 hours. ### What role do block proposers play in funding rates on dYdX? As part of the default settings of the v4 open source software, there are two distinct epochs established with the Epochs module. Every block proposer proposes a `FundingPremiumVote` during each block. At the end of a `funding-sample` epoch, the state machine deterministically computes a funding sample from all the `FundingPremiumVote`s in this epoch (1 minute). The second epoch is the "funding-tick epoch," which occurs every hour at the start of the hour and is responsible for adjusting funding rates based on the funding samples collected from the preceding epoch. ### Where is the funding rate for a particular market located on dYdX? Please see the [Get Historical Funding](/indexer-client/http#get-historical-funding) API method. ### What is a funding rate cap? The funding rate cap refers to a predetermined maximum limit on the funding rate applied to a particular contract. It aims to limit the potential costs incurred by traders, especially during volatile market conditions. As part of the default settings of the v4 open source software, there’s a cap on each funding sample (per minute) and the funding rate (per hour). ### How is the funding rate cap calculated? The 8-hour rate cap is calculated by `600% * (Initial Margin - Maintenance Margin)`. | Market | Initial margin | Maintenance margin | | --------- | -------------- | ------------------ | | Large-Cap | 5% | 3% | | Mid-Cap | 10% | 5% | | Long-Tail | 20% | 10% | For example, for BTC-USD, which falls under Large-Cap, the 8-hour rate is capped by `600% * (IMF - MMF) = 600% * (5% - 3%) = 12%`. ### FAQ > What Funding parameters can be controlled by Governance? Governance has the ability to adjust Funding Rate parameters: * Funding rate clamp factor, premium vote clamp factor, and min number of votes per premium sample. Proto * Epoch information, which defines the funding interval and premium sampling interval. Proto * Liquidity Tier, which defines the impact notional value. Proto > How does the funding rate impact P\&L? Realized P\&L increases or decreases while you hold a position due to funding fees, which are paid or received every hour depending on your position and the funding rate. > When are the funding rates charged? We charge funding rates every hour on our platform. The funding rate is calculated at the end of each hour and is based on the average of premiums collected over the last 60 minutes. This hourly funding rate helps keep the perpetual contract prices aligned with their underlying asset prices. > Where can I find the details of how much I paid for the funding rate and view my payment history? Funding rate payments are reflected in the "Funding" section of your account history on the dYdX frontend. You can also view your funding rate payment history through the [Get Funding History](/indexer-client/http#get-funding-payments) API method. ## Governance Functionalities Below is a current list of all module parameters that `x/gov` has the ability to update directly. Further documentation will be released which outlines overviews of each custom module, how modules interact with one another, and technical guides regarding how to properly submit governance proposals. ### Trading Stats & Fees #### Stats Module The Stats Module tracks user maker and taker volumes over a period of time (aka look-back window). This is currently set to 30 days. The maker and taker volume info is used to place users in corresponding fee-tiers. Governance has the ability to update the params of the Stats Module, which defines the look-back window (measured in seconds). [Proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/stats/params.proto#L10-L14) #### FeeTiers Module Governance has the ability to update fee tiers ([proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/feetiers/params.proto#L6-L10)). To read more about fee tiers head to [V4 Deep Dive: Rewards and Parameters](https://dydx.exchange/blog/v4-rewards-and-parameters). ### Trading Core #### Insurance Fund Governance has the ability to send funds from the Protocol’s Insurance Fund. Funds can be sent to individual accounts, or other modules. Note: any account has the ability to send assets to the Insurance Fund. #### Liquidations Config Governance has the ability to adjust how liquidations are processed. [Proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/clob/liquidations_config.proto#L8-L34) * Max Insurance Fund quantums for deleveraging: The maximum number of quote quantums (exclusive) that the insurance fund can have for deleverages to be enabled. * The maximum liquidation fee, in parts-per-million. 100% of this fee goes to the Insurance Fund * The maximum amount of how much a single position can be liquidated within one block. * The maximum amount of how much a single subaccount can be liquidated within a single block * Fillable price config: configuration regarding how the fillable-price spread from the oracle price increases based on the adjusted bankruptcy rating of the subaccount. #### Funding Rate Governance has the ability to adjust Funding Rate parameters: * Funding rate clamp factor, premium vote clamp factor, and min number of votes per premium sample. [Proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/perpetuals/params.proto#L6-L19) * Epoch information, which defines the funding interval and premium sampling interval. [Proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/epochs/epoch_info.proto#L6-L43) * Liquidity Tier, which defines the impact notional value. [Proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/perpetuals/perpetual.proto#L100-L139) ### Trading Rewards #### Vest Module The Vest Module is responsible for determining the rate of tokens that vest from Vester Accounts to other accounts such as a Community Treasury Account and a Rewards Treasury Account. The rate of token transfers is linear with respect to time. Thus, block timestamps are used to vest tokens. Governance has the ability to create, update, or delete a `VestEntry` ([proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/vest/vest_entry.proto#L9-L30)), which defines: * The start and end time of vesting * The token that is vested * The account to vest tokens to * The account to vest tokens from #### Rewards Module The Rewards Module distributes trading rewards to traders (previously written about [V4 Deep Dive: Rewards and Parameters](https://dydx.exchange/blog/v4-rewards-and-parameters)). Governance has the ability to adjust the following ([proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/rewards/params.proto#L6-L26)): * Which account Trading Rewards are funded from * The token Trading Rewards are funded in * The market which tracks the oracle price of the token that Trading Rewards are funded in * `C` which is a protocol constant further explained in the post linked above ### Markets #### Oracles Governance has the ability to adjust the list of oracles used for each market. [Proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/prices/market_param.proto#L31-L33) Note that this functionality does not include creating / removing an exchange-source supported by the protocol as a whole, which will require a binary upgrade. #### Liquidity Tiers Liquidity Tiers group markets of similar risk into standardized risk parameters. Liquidity tiers specify the margin requirements needed for each market and should be determined based on the depth of the relative market’s spot book as well as the token’s market capitalization. [Current Liquidity](https://dydx-ops-rest.kingnodes.com/dydxprotocol/perpetuals/liquidity_tiers) Tiers include: | ID | Name | initial margin fraction | maintenance fraction (what fraction MMF is of IMF) | impact notional | maintenance margin fraction (as is) | impact notional (as is) | Lower Cap (USDC Millions) | Upper Cap (USDC Millions) | | -- | --------- | ----------------------- | -------------------------------------------------- | --------------- | ----------------------------------- | ----------------------- | ------------------------- | ------------------------- | | 0 | Large-Cap | 0.02 | 0.6 | 500 USDC / IM | 0.012 | 25\_000 USDC | None | None | | 1 | Small-Cap | 0.1 | 0.5 | 500 USDC / IM | 0.05 | 5\_000 USDC | 20 | 50 | | 2 | Long-Tail | 0.2 | 0.5 | 500 USDC / IM | 0.1 | 2\_500 USDC | 5 | 10 | | 3 | Safety | 1 | 0.2 | 2500 USDC / IM | 0.2 | 2\_500 USDC | 2 | 5 | | 4 | Isolated | 0.05 | 0.6 | 125 USDC / IM | 0.03 | 2\_500 USDC | 0.5 | 1 | | 5 | Mid-Cap | 0.05 | 0.6 | 250 USDC / IM | 0.03 | 5\_000 USDC | 40 | 100 | | 6 | FX | 0.01 | 0.5 | 25 USDC / IM | 0.0005 | 2\_500 USDC | 0.5 | 1 | | 7 | IML 5x | 0.2 | 0.5 | 25 USDC / IM | 0.1 | 125 USDC | 0.5 | 1 | * Each market has a `Lower Cap` and `Upper Cap` denominated in USDC. * Each market already has a `Base IMF`. * At any point in time, for each market: * Define * `Open Notional = Open Interest * Oracle Price` * `Scaling Factor = (Open Notional - Lower Cap) / (Upper Cap - Lower Cap)` * `IMF Increase = Scaling Factor * (1 - Base IMF)` * Then a market’s `Effective IMF = Min(Base IMF + Max(IMF Increase, 0), 1.0)` * The effective IMF is the base IMF while the Open Notional \< Lower Cap, and increases linearly until Open Notional = Upper Cap, at which point the IMF stays at 1.0 (requiring 1:1 collateral for trading) Governance has the ability to create and modify Liquidity Tiers as well as update existing markets’ Liquidity Tier placements. ([proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/perpetuals/perpetual.proto#L100-L139)) #### Updating a Live Market This functionality allows the community to update parameters of a live market, which can be composed of 4 parts * Updating a liquidity tier * Perpetual (`x/perpetuals`), governance-updatable through `MsgUpdatePerpetualFeeParams` ([proto definition](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/feetiers/tx.proto#L19)) * Market (`x/prices`), governance-updatable through `MsgUpdateMarketParam` ([proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/prices/market_param.proto#L6-L34)) * Clob pair (`x/clob`), governance-updatable through `MsgUpdateClobPair` ([proto](https://github.com/dydxprotocol/v4-chain/blob/b2c6062b4e588b98a51454f50da9e8e712cfc2d9/proto/dydxprotocol/clob/tx.proto#L102)) #### Adding New Markets The action of a governance proposal is defined by the [list of messages that are executed](https://github.com/dydxprotocol/cosmos-sdk/blob/4fadfe5a4606b6dc76644d377ed34420f3b80801/x/gov/abci.go#L72-L90) when it’s accepted. A proposal to add a new market should include the following messages (in this particular order): ``` MsgCreateOracle (create objects in x/prices) MsgCreatePerpetual (create object in x/perpetual) MsgCreatePerpetualClobPair (create object in x/clob) MsgDelayMessage (schedule a MsgSetClobPairStatus to enable trading in x/clob) ``` ### Safety #### Spam Mitigation To prevent spam on the orderbook and prevent the blockchain state from getting too large, governance has the ability to adjust: * How many open orders a subaccount can have based on its equity tier. [Proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/clob/equity_tier_limit_config.proto#L8-L19) * Order placement rate limits. [Proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/clob/block_rate_limit_config.proto#L8-L35) ### Bridge #### Bridge Module The Bridge Module is responsible for receiving bridged tokens from the Ethereum blockchain. Governance has the ability to update: * Event Parameters: Specifies which events to recognize and which tokens to mint. [Proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/bridge/params.proto#L9-L20) * Proposal Parameters: Determines how long a validator should wait until it proposes a bridge event to other validators, and how many or often to propose new bridge events. [Proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/bridge/params.proto#L22-L45) * Safety Parameters: Determines if bridging is enabled/disabled and how many blocks mints are delayed after being accepted by consensus. [Proto](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/proto/dydxprotocol/bridge/params.proto#L47-L55) ### Community Assets #### Community Pool & Treasury There are two addresses intended for managing funds owned by the community: 1. a Community Pool and 2. a Community Treasury. The Community Pool is the recipient of any Community Tax that is implemented via the Distribution Module. The Community Pool is controllable by governance. The Community Treasury is an account controlled by governance and can be funded via any account or module sending tokens to it. ### CosmosSDK Default Modules For more information on default modules, head to the [Cosmos SDK official documentation](https://docs.cosmos.network/sdk/v0.47/build/modules). dYdX Chain inherits the same governance properties of any standard CosmosSDK modules that are present on dYdX Chain. ## Isolated Markets In v5.0.0 the Isolated Markets feature was added to the V4 chain software. The below is an overview of how trading will work on Isolated Markets on the V4 chain software. > Note: This document covers how the feature works from the protocol point of view and not the front end or the indexer. For more details on what isolated markets are see the [blog post](https://dydx.exchange/blog/introducing-isolated-markets-and-isolated-margin). ## Trading Isolated Markets Positions in isolated markets can only be opened on a subaccount with no open perpetual positions or a subaccount with an existing perpetual position in the same isolated market. Once a perpetual position for an isolated market is opened on a subaccount, no positions in any other markets can be opened until the perpetual position is closed. The above restriction only applies to positions, orders can still be placed for different markets on a subaccount while it holds an open position for an isolated market. The orders will fail and be canceled when they match if the subaccount still holds an open position for a different isolated market. A new [error code](https://github.com/dydxprotocol/v4-chain/blob/protocol/v5.0.0/protocol/x/clob/types/errors.go#L364-L368) `2005` has been added to indicate such a failure. Other than the above caveat, isolated markets can be traded in the same way as before v5.0.0. > Note: The maximum number of subaccounts per address was increased from 127 to 128000 in v5.0.0 to address the need for a separate subaccount per isolated market. ## Querying for Isolated Markets There is a new `market_type` parameter in the `PerpetualParams` proto struct that indicates the type of market. There are 2 possible values for this parameter: * `PERPETUAL_MARKET_TYPE_CROSS` - markets where subaccounts can have positions cross-margined with other `PERPETUAL_MARKET_TYPE_CROSS` markets, all markets created before the v5.0.0 upgrade are `PERPETUAL_MARKET_TYPE_CROSS` markets * `PERPETUAL_MARKET_TYPE_ISOLATED` - markets that can only be margined in isolated, no cross-margining with other markets is possible An example of how each type of market looks when queried using the `/dydxprotocol/perpetuals/perpetual/:id` REST endpoint. * `PERPETUAL_MARKET_TYPE_CROSS` ```json { "perpetual": { "params": { "id": 1, "ticker": "ETH-USD", "market_id": 1, "atomic_resolution": -9, "default_funding_ppm": 0, "liquidity_tier": 0, "market_type": "PERPETUAL_MARKET_TYPE_CROSS" }, "funding_index": "0", "open_interest": "0" } } ``` * `PERPETUAL_MARKET_TYPE_ISOLATED` ```json { "perpetual": { "params": { "id": 1, "ticker": "FLOKI-USD", "market_id": 37, "atomic_resolution": -13, "default_funding_ppm": 0, "liquidity_tier": 2, "market_type": "PERPETUAL_MARKET_TYPE_ISOLATED" }, "funding_index": "0", "open_interest": "0" } } ``` ## Isolated Positions **Isolated positions** on the **dYdX frontend** are perpetual positions held in subaccounts with a subaccount number greater than 127, up to the limit of 128,000. Each isolated position is held in a separate subaccount. :::tip **Isolated positions** are a feature provided and managed by the **dYdX frontend** (web) interface. This page provides information on how to integrate this feature into your API-based implementation. ::: ### Mapping of isolated positions to subaccounts The dYdX frontend implementation separates subaccounts (0 - 128,000) into 2 separate types. #### Parent subaccounts Subaccounts 0 to 127 are parent subaccounts. Parent subaccounts can have multiple positions opened and all positions are cross-margined. #### Child subaccounts Subaccounts 128 to 128,000 are child subaccounts. Child subaccounts will only ever have up to 1 position open. Each open isolated position on the frontend is held by a separate child subaccount. Once an isolated position is closed in the frontend, the subaccount associated with isolated position can be re-used for the next isolated position. Child subaccounts are mapped to parent subaccounts using the formula: e.g. parent subaccount 0 has child subaccounts 128, 256,... parent subaccount 1 has child subaccounts 129, 257,... ``` parent_subaccount_number = child_subaccount_number % 128 ``` :::note Note that currently only parent subaccount 0 is exposed via the frontend and so isolated positions will be held in subaccounts number 128, 256, ... ::: :::note Note that the above "types" of subaccounts are not enforced at a protocol level, and only on the frontend. Any subaccount can hold any number of positions in cross-marginable markets which all will cross-margined at the protocol level. ::: :::note When you are using the dYdX frontend, any margin transferred to an empty child subaccount that isn’t used for placing a trade will get sent back to the cross subaccount after some time. ::: ### Getting data for parent subaccount API endpoints exist to get data for a parent subaccount and all it's child subaccounts on the Indexer. > Currently all data for an account viewable on the frontend can be fetched by using the parent subaccount APIs to fetch data for parent subaccount number 0. See the [Indexer API](/indexer-client/http#get-parent-subaccount) page for more details of the parent subaccount APIs. ## Limit Order Book and Matching This document outlines the key differences between centralized exchanges and dYdX Chain, focusing on the decentralized limit order book and matching engine. ### Blockchain Overview dYdX Chain is a p2p blockchain network using [CosmosSDK](https://github.com/cosmos/cosmos-sdk) and [CometBFT](https://github.com/cometbft/cometbft) (formerly Tendermint). Anyone can run a full node using the open-source software. Full nodes with sufficient delegated governance tokens participate in block building as validators. The software repository is: [https://github.com/dydxprotocol/v4-chain/](https://github.com/dydxprotocol/v4-chain/) ### Limit Order Book Each full node in the network maintains an in-memory order book, which undergoes state changes in real time as traders submit order instructions. Block proposers use trades from their local order book to build blocks, with matches generated by price-time priority. Since message arrival order varies between nodes, the order book may differ across the network at any given point in time. To address this, upon seeing a new consensus-commited block, nodes sync their local books with the block contents. Clients can subscribe to a node's book state using the [Full Node Streaming API](/nodes/full-node-streaming). ### Matching and Block Processing The order matching logic is broadly similar to centralized exchanges, with some key differences: 1. On receiving a cancel instruction: * The node cancels the order unless it's already matched locally. * The cancel instruction is stored until it expires (based on the [GTB field](https://github.com/dydxprotocol/v4-chain/blob/4780b4cba2cab75e0af5675c3e87e551162ecf33/proto/dydxprotocol/clob/tx.proto#L90)). 2. On receiving an order: * The order fails to place if it has already been cancelled. * Otherwise, it is matched and/or placed on the order book, with optimistic matches stored locally. 3. Validator nodes propose blocks that include all their local matches. 4. When processing a new block: * The node starts from the state of the prior block (local state used to propose is temporarily disregarded). * The block’s changes are applied. * The node replays its local state on top of the new state, during which: * Cancels are preserved. * Orders matched in the prior local state are re-placed. * Orders may match differently, fail to place due to cancellation, or not match at all in the new state. ##### Source Code References For further details on how the protocol handles these actions, refer to the following source code references: * See [here](https://github.com/dydxprotocol/v4-chain/blob/dc6e0a004fd81e3139a24f88b10605ab5ce16cfd/protocol/x/clob/ante/clob.go#L90) and [here](https://github.com/dydxprotocol/v4-chain/blob/2d5dfa55357abd5ead46f8baa03ed76d420849cc/protocol/x/clob/memclob/memclob.go#L103) for how the protocol reacts when (1) a cancel is seen. * When (2) [an order is placed](https://github.com/dydxprotocol/v4-chain/blob/dc6e0a004fd81e3139a24f88b10605ab5ce16cfd/protocol/x/clob/ante/clob.go#L132) and [checked to not be already cancelled](https://github.com/dydxprotocol/v4-chain/blob/749dff9cbca56eb2a6ab3a19feeb338de8db80e6/protocol/x/clob/keeper/orders.go#L780). * When (3) [proposing a block](https://github.com/dydxprotocol/v4-chain/blob/189b11217490aa5a87a4108dde0f679b0190511b/protocol/app/prepare/prepare_proposal.go#L157). * And (4) when [nodes process blocks committed by consensus](https://github.com/dydxprotocol/v4-chain/blob/4780b4cba2cab75e0af5675c3e87e551162ecf33/protocol/x/clob/abci.go#L152). ### Order Messages Order instructions are limit order placements, replacements, and cancellations. > Note: This section covers short-term orders which live off-chain, in node memory, until matched. > > Stateful orders (on-chain, consensus-speed placement) exist for longer-lived limit orders but aren't recommended for API traders. > More info: [Short-term vs Long-term Orders](/concepts/trading/orders#short-term-vs-long-term). #### Finality and GTB Each limit order placement or cancellation [includes a GTB (good-til-block) field](https://github.com/dydxprotocol/v4-chain/blob/dc6e0a004fd81e3139a24f88b10605ab5ce16cfd/proto/dydxprotocol/clob/order.proto#L114-L146), which specifies the block height after which the instruction expires. While rare, it is possible for a cancel instruction to be seen by the current block proposer but not by one or more subsequent proposers (if the instruction isn't gossiped to them in time through the p2p network). In such cases, the order could still match after the sender expects it to have been cancelled. Therefore, we recommend that API traders consider setting tight GTB values on order placements (e.g. the current chain height + 3) because expiry due to GTB is the only guaranteed way for an order to become unfillable. Consensus does not permit any order to fill at a height greater than its GTB. #### Replacements We recommend using replacement instructions over cancelling and placing new orders. Replacing prevents accidental double-fills that can occur with a ‘place order A, cancel order A, place order B’ approach, where both A and B might fill simultaneously unless the chain height has already passed A’s GTB. For example, after the following messages are sent: 1. Place A: Sell 1 @ $100, client id = 123 2. Cancel A 3. Place B: Sell 1 @ $101, client id = 456 If a proposer sees messages 1 and 3, but not 2, it sees both orders A and B as open. If it also sees marketable bids for qty >= 2, both could fill simultaneously. ##### Replacement Instruction Fields To replace an order, send a placement with the same order ID **and a larger GTB value**. Orders have the same ID if these client-specified fields match ([OrderId proto definition](https://github.com/dydxprotocol/v4-chain/blob/dcd2d9c2f6170bd19218d92cf6f2f88216b2ffe1/proto/dydxprotocol/clob/order.proto#L9-L41)): * [Subaccount ID](https://github.com/dydxprotocol/v4-chain/blob/dcd2d9c2f6170bd19218d92cf6f2f88216b2ffe1/proto/dydxprotocol/subaccounts/subaccount.proto#L10-L17) (owner: signing address, number: 0 unless different subaccount) * Client ID * Order flags (0 for short-term orders) * CLOB pair ID ## Liquidations As part of the default settings of the v4 open source software (”dYdX Chain”), accounts whose total value falls below their maintenance margin requirement may have their positions automatically closed by the liquidation engine. Positions are closed via protocol-generated liquidation matches where a protocol-generated liquidation order uses a calculated “Fillable Price” as the limit price to match against liquidity resting on the order book.\ Profits or losses from liquidations are taken on by the insurance fund. A liquidated subaccount may have its position partially or fully closed. v4 open source software includes a liquidations configuration which — as determined by the applicable Governance Community — will determine how much of the position is liquidated. ### Liquidation Penalty As part of the default settings of the v4 open source software, when an account is liquidated, up to the entire remaining value of the account may be taken as penalty and transferred to an insurance fund. The liquidation engine will attempt to leave funds in accounts of positive value where possible after they have paid the Maximum Liquidation Penalty of 1.5%. The 1.5% fee contemplated in the default v4 software will be subject to adjustments by the applicable Governance Community. ### Isolated Liquidation Price This is the price at which a specific position reaches the point of liquidation. 1. **Formula Explanation:** * The liquidation price `p'` is calculated using: ``` p' = (e - s * p) / (|s| * MMF - s) ``` * Here: * `e` is the current equity in the account. * `s` is the size of the position. * `p` is the original price of the position. * `MMF` is the maintenance margin fraction, a percentage that indicates the minimum equity required to keep the position open. 2. **Example:** * Suppose a trader deposits $1,000 (`e = 1000`). * The trader shorts 3 ETH contracts (`s = -3`) at $3,000 per contract, with a maintenance margin fraction of 5% (`MMF = 0.05`). * The formula becomes: `p' = (1000 - (-3 * 3000)) / (3 * 0.05 - (-3))` * This simplifies to: `p' = (1000 + 9000) / (0.15 + 3) = 10000 / 3.15 ≈ 3174.60` * This means if the price of ETH rises to $3,174.60, the position will reach the liquidation threshold. * At this price, the trader's remaining equity would be 5% of the notional value of the position or $476.2 based on the calculation `(3 * 3174.6 * 0.05 ≈ 476.2)`. ### Cross Liquidation Price For cross-margining (multiple positions sharing the same margin), the calculation is adjusted to account for the margin used by other positions. 1. **Key Terms:** * **Total Maintenance Margin Requirement (`MMR_t`):** Calculate the maintenance margin needed for all positions at current prices: `MMR_t = |s| · p · MMF` * **Other Positions' Margin Requirement (`MMR_o`)**: Subtract the margin requirement of the position in question from MMR\_t: `MMR_o = MMR_t - |s| * p * MMF` * **New Margin Requirement at Price `p'`**: Add `MMR_o` to the margin requirement of the position at the new price: `MMR_o + |s| * p' * MMF` * **Liquidation Price Formula**: Substitute into the equation to find the liquidation price for the position: `p' = (e - s * p - MMR_o) / (|s| * MMF - s)` 2. **Example:** * Suppose a trader deposits $1,000 (`e = 1000`). * The trader shorts 1.5 ETH (`s = -1.5`) at $3,000 and buys 1,000 STRK contracts at $1.75 (`MMF = 10%` for STRK). * **Calculate Other Positions' Margin Requirement**: `MMR_o = 1000 * 1.75 * 0.10 = 175` * Compute the Liquidation Price for ETH: `p' = (1000 - (-1.5 * 3000) - 175) / (1.5 * 0.05 + 1.5)` * This simplifies to: `p' = (1000 + 4500 - 175) / 1.575 ≈ 3380.95`. * If the ETH price reaches $3,380.95, the equity would fall to the required margin level. ### “Fillable Price” for Liquidations As part of the default settings of dYdX Chain, the “fillable price” (or the limit price of a liquidation order) for a position being liquidated is calculated as follows. For both short and long position: ``` Fillable Price (Short or Long) = P x (1 - ((SMMR x MMF) x (BA x (1 - Q))) ``` Where (provided as genesis parameters): * `P` is the oracle price for the market * `SMMR` is the spread to maintenance margin ratio * `MMR`= `Config.FillablePriceConfig.SpreadToMaintenanceMarginRatioPpm` * `MMF` is the maintenance margin fraction for the position * `BA` is the bankruptcy adjustment * `A` = `Config.FillablePriceConfig.BankruptcyAdjustmentPpm`. Is ≥ 1. * `Q = V / TMMR` where `V` is the total account value, and `TMMR` is the total maintenance margin requirement On the other hand, the “Close Price” will be the sub-ticks of whatever maker order(s) the liquidation order matches against. For more information on Margin fractions and calculations, see [Margin](/concepts/trading/margin). ### FAQ > What price is used to determine liquidations? As part of the default settings, Oracle Price is used to estimate the value of an account’s positions. If the account’s value falls below the account’s maintenance margin requirement, the account is liquidatable. > Who receives the liquidation fees? The insurance fund would receive liquidation fees / penalty. Please note that the applicable Governance Community needs to initially fund the insurance fund from the applicable community treasury. > How liquidation engine works? Our liquidation engine automatically closes positions that fall below the maintenance margin. > Does dYdX apply a penalty in the event of liquidation? Yes. The liquidation engine will attempt to leave funds in accounts of positive value where possible after they have paid the Maximum Liquidation Penalty of 1.5%. The 1.5% fee contemplated in the default v4 software will be subject to adjustments by the applicable Governance Community. > How to avoid liquidation? In order to avoid liquidation, you can deposit more assets to your account, as while opening a position the key point is to have enough assets to cover the maintenance margin requirements. You can also close the part of the position and the liquidation price will change. ## Margining As part of default settings on the dYdX Chain open source software, each market has two risk parameters, Initial Margin Fraction (IMF) and Maintenance Margin Fraction (MMF): * **Initial Margin Fraction**: A percentage (fixed until [certain level](#open-interest-based-imf) of Open Interest) that determines the minimum collateral required to open or increase positions. * **Maintenance Margin Fraction**: A percentage (fixed) that determines the minimum collateral required to maintain positions and avoid liquidation. ### Open-Interest-Based IMF The IMF of a perpetual market scales linearly according to the current `open_notional` in the market, starting at `open_notional_lower_cap` to `open_notional_upper_cap` (USDC denominated): ``` open_notional = open_interest * oracle_price scaling_factor = (open_notional - open_notional_lower_cap) / (open_notional_upper_cap - open_notional_lower_cap) IMF_increase = scaling_factor * (1 - base_IMF) effective_IMF = Min(base_IMF + Max(IMF_increase, 0), 100%) ``` I.e. the effective IMF is the base IMF while `open_notinal < lower_cap`, and increases linearly until `open_notional = upper_cap`, at which point the IMF stays at 100% (requiring 1:1 collateral for trading). Importantly, the MMF (Maintenance Margin Fraction) does not change. The [Open Notional Lower Cap](https://github.com/dydxprotocol/v4-chain/blob/b829b28b0d71e754ac553fbeec29ce5309bd79f7/proto/dydxprotocol/perpetuals/perpetual.proto#L133) and [Open Notional Upper Cap](https://github.com/dydxprotocol/v4-chain/blob/b829b28b0d71e754ac553fbeec29ce5309bd79f7/proto/dydxprotocol/perpetuals/perpetual.proto#L138) are parameters defined as part of the market's [Liquidity Tier](https://github.com/dydxprotocol/v4-chain/blob/b829b28b0d71e754ac553fbeec29ce5309bd79f7/proto/dydxprotocol/perpetuals/perpetual.proto#L100). ### Margin Calculation The margin requirement for a single position is calculated as follows:
  Initial Margin Requirement = abs(S × P × I) Maintenance Margin
  Requirement = abs(S × P × M)
Where: * `S` is the size of the position (positive if long, negative if short) * `P` is the oracle price for the market * `I` is the initial margin fraction for the market * `M` is the maintenance margin fraction for the market The margin requirement for the account as a whole is the sum of the margin requirement over each market `i` in which the account holds a position:
  Total Initial Margin Requirement = Σ abs(Si × Pi{" "}
  × Ii) Total Maintenance Margin Requirement = Σ abs(S
  i × Pi × Mi)
The total margin requirement is compared against the total value of the account, which incorporates the quote asset (USDC) balance of the account as well as the value of the positions held by the account:
  Total Account Value = Q + Σ (Si × Pi)
The Total Account Value is also referred to as equity. Where: * `Q` is the account's USDC balance (note that `Q` may be negative). In the API, this is called `quoteBalance`. Every time a transfer, deposit or withdrawal occurs for an account, the balance changes. Also, when a position is modified for an account, the `quoteBalance` changes. Also funding payments and liquidations will change an account's `quoteBalance`. * `S` and `P` are as defined above (note that `S` may be negative) An account cannot open new positions or increase the size of existing positions if it would lead the total account value of the account to drop below the total initial margin requirement. If the total account value ever falls below the total maintenance margin requirement, the account may be liquidated. Free collateral is calculated as:
  Free collateral = Total Account Value - Total Initial Margin Requirement
Equity and free collateral can be tracked over time using the latest oracle price (obtained from the markets websocket). ## MegaVault MegaVault is a live feature on the dYdX that allows users to deposit **USDC** and earn yield by providing liquidity to various markets. It operates as an automated liquidity provisioning system, using deposited funds to run **automated market-making (AMM)** strategies across multiple trading pairs. Depositors can contribute USDC at any time and begin earning yield immediately. Withdrawals are also supported, though factors such as market conditions, leverage, or open positions may affect the timing and value of withdrawals (via slippage). MegaVault is designed to benefit both sides of the protocol: users earn potential returns, and markets benefit from deeper liquidity and more efficient trading. Yield for depositors can originate from: * Profit and loss (PnL) on market-making positions * A share of trading fees generated by the vault’s activity * Funding payments and incentives configured by governance or software deployers An **operator**, elected through governance, currently manages certain manual operations like reallocating funds between sub-vaults and tuning quoting parameters. In future versions, these processes may be automated. ### Vault Mechanics & User Experience * **Sub-vault Architecture**: MegaVault is composed of multiple “sub-vaults,” each assigned to a specific market. When users deposit USDC, the funds are distributed among these sub-vaults based on liquidity needs. Each sub-vault runs its own AMM strategy, tailored for that market’s dynamics. * **Yield Aggregation & Distribution**: Returns generated from all sub-vaults are pooled and distributed proportionally among depositors. Users' deposits represent a fractional ownership of the vault’s total net equity — including both USDC and active trading positions. * **Deposits and Withdrawals**: Users can deposit USDC at any time with no minimums. Withdrawals are also available on-demand, but the amount received may be affected by the vault’s exposure and market volatility. This can result in **slippage** — especially during large withdrawals or volatile periods. * **Risks and Future Features**: * Yield is **not guaranteed**. Depositors bear market risk, and negative PnL from trading positions can reduce vault equity. * Withdrawals may be subject to **future restrictions**, such as **lockup periods** for specific strategies or new market listings. * Currently, users cannot interact directly with sub-vaults; all deposits and withdrawals are routed through MegaVault. ## Oracle Prices Oracle prices are aggregated prices that provide up-to-date price data for different assets. The oracle price for each trading pair is used for the following: * Ensuring that each account is well-collateralized after each trade * Determining when an account should be liquidated * Triggering "triggerable" order types such as Stop-Limit and Take-Profit orders ### How are oracle prices determined on dYdX Chain? Oracle prices are determined by the current validator set of the network. 1. each validator runs a sidecar that pulls prices from oracle providers and external exchanges, such as Binance, Bitfinex, Bitstamp, Bybit, Coinbase, crypto.com, GateIO, ... the entire list is shown [here](https://github.com/skip-mev/connect-mmu/blob/main/local/config-dydx-mainnet.json) 2. each validator submits their view of oracle prices through vote extensions (some caveats here where there's a max number of markets that can be updated per block, minimum threshold on price changes, etc.) 3. proposer proposes prices by aggregating these vote extensions 4. network accepts a block 5. oracle prices update ## Orders An order is the way a trader manages positions in the dYdX markets. Different types of orders exist to support different trading strategies. ### Short-term vs Long-term **Short-term** orders are short-lived orders that are not stored on-chain unless filled. These orders stay in-memory of the network validators, for up to 20 blocks, with only their fill amount and expiry block height being committed to state. Short-term orders are mainly intended for use by market makers with high throughput or for market orders. **Long-term orders** are “stateful orders” that are committed to the blockchain. Long-term orders encompass any order that lives on the orderbook for longer than the short block window. The short block window represents the maximum number of blocks past the current block height that a short-term `MsgPlaceOrder` or `MsgCancelOrder` message will be considered valid by a validator. Currently the default short block window is 20 blocks. #### Comparison | | Short-term | Stateful | | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | purpose | Short-lived orders which are meant to placed immediately (in the same block the order was received). These orders stay in-memory up to 20 blocks, with only their fill amount and expiry block height being committed to state. Intended for use by market makers with high throughput, or for market orders. IoC and FoK orders are also considered short-term orders. Short-term orders do not survive a network restart.
  • • User would send a short-term transaction to a validator
  • • The transaction needs to contain exactly one Cosmos msg, and that msg is a [MsgPlaceOrder](https://github.com/dydxprotocol/v4-chain/blob/c092bf0166d1a111dcd9c2e4153334865c8fe553/proto/dydxprotocol/clob/tx.proto#L78)
  • • Each validator has a [MsgProposedOperations](https://github.com/dydxprotocol/v4-chain/blob/c092bf0166d1a111dcd9c2e4153334865c8fe553/proto/dydxprotocol/clob/tx.proto#L68), which is one validator’s view of the operations queue
  • • In the context of short-term orders, the block proposer should eventually be gossiped the short-term order and have it in their MsgProposedOperations
  • • The block proposer would then optimistically place the short-term order in [CheckTx](https://docs.cosmos.network/sdk/v0.50/learn/beginner/tx-lifecycle)
  • • Matches that short term orders were included in block during MsgProposedOperations would be included in block for all the validators in the network during [DeliverTx](https://docs.cosmos.network/sdk/v0.50/learn/beginner/tx-lifecycle)
| Long-lived orders which may execute far in the future. These orders should not be lost during a validator restart (placed in the block after the order was received). In the event a validator restarts, all stateful orders are [placed back onto the in-memory orderbook](https://github.com/dydxprotocol/v4-chain/blob/95b59028af247c0a93ef72de9bfd09a645d30eb1/protocol/app/app.go#L1125). Likely to be used primarily by retail traders. The front end would be sending stateful orders for all order types other than market orders.

Two types of stateful orders:

1. Long-Term Orders
• meant to be added to the orderbook as soon as possible. Due to certain technical limitations, long-term orders are placed in the block after they are written to state. E.g. if `MsgPlaceOrder` is included in block N, taker order matching would occur for the long-term order in block N+1.
• Order types requiring immediate execution such as fill-or-kill / immediate-or-cancel are disallowed as these should be placed as short term orders; Long-term FoK/IoC orders would never be maker orders, so there is no benefit to writing them to state.

2. Conditional Orders
• execute when the oracle price becomes either LTE or GTE to specified trigger price, depending on the type of conditional order (e.g. stop loss sell = LTE, take profit buy = GTE)
• orders are placed in the block after their condition is met and they become triggered
• it is possible for a conditional order to become triggered in the same block they are initially written to state in. Conditional orders are placed in block ≥ N+1. | | placement message | MsgPlaceOrder | `MsgPlaceOrder`, long term or conditional order flag enabled on `MsgPlaceOrder.Order.OrderId.OrderFlags`
• valid OrderFlags values are 32 (conditional), 64 (long-term), and 128 (TWAP) for stateful orders | | cancellation message | MsgCancelOrder

Short term cancellations are handled best-effort, meaning they are only gossiped and not included in MsgProposedOperations | `MsgCancelOrder`, long term or conditional order flag enabled on `MsgCancelOrder.OrderId.OrderFlags` | | expirations | Good-Till-Block (GTB)

Short term orders have a maximum GTB of current block height + [ShortBlockWindow](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/protocol/x/clob/types/constants.go#L9). Currently this value is 20 blocks, or about 30 seconds. Short term orders can only be GTB because in the interest of being resilient to chain halts or slowdowns. | Good-Till-Block-Time (GTBT)

Stateful orders have a maximum GTBT of current block time + [StatefulOrderTimeWindow](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/protocol/x/clob/types/constants.go#L17). Currently this value is 95 days.

GTBT is used instead of GTB to give a more meaningful expiration time for stateful orders. | | inclusion in block | `OperationRaw_ShortTermOrderPlacement` inside `MsgProposedOperations.OperationsQueue` which is an app-injected message in the proposal. Included if and only if the short term order is included in a match. | Normal cosmos transaction. The original Tx which included the `MsgPlaceOrder` or `MsgCancelOrder` would be included directly in the block. | | signature verification | Short-term orders must undergo custom signature verification because they are included in an app-injected transaction.

The memclob stores each short term order placement’s raw transaction bytes in the memclob. When the order is included in a match, an `OperationRaw_ShortTermOrderPlacement` operation is included in `MsgProposedOperations` which contains these bytes.

During `DeliverTx`, we decode the raw transaction bytes into a transaction object and pass the transaction through the app’s antehandler which executes signature verification. If signature verification fails, the `MsgProposedOperations` execution returns an error and none of the operations are persisted to state. Operations for a given block is all-or-nothing, meaning all operations execute or none of them execute. | Normal cosmos transaction signature verification, executed by the app’s antehandler. | | replay prevention | Keep orders in state until after Good-Till-Block aka expiry (even if fully-filled or cancelled) | Cosmos SDK sequence numbers, verified to be strictly increasing in the app’s antehandler.

Note that their use of sequence numbers requires stateful orders to be received in order otherwise they would fail. If placing multiple stateful orders they should be sent to the same validator to prevent issues. | | time placed (matching logic) | `CheckTx`, immediately after placement transaction is received by the validator.

Short term orders are only included in a block when matched. See “time added to state” below. | long-term: Block N+1 in [PrepareCheckState](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/protocol/x/clob/abci.go#L136) where `MsgPlaceOrder` was included in block N
conditional: Block N+1 `PrepareCheckState` where the order was triggered in `EndBlocker` of block N | | what is stored in state | `OrderAmountFilledKeyPrefix`:
• key = OrderId
• value = OrderFillAmount & PrunableBlockHeight

`BlockHeightToPotentiallyPrunableOrdersPrefix`:
• key = block height
• value = list of potentially prunable OrderIds

PrunableBlockHeight holds the block height at which we can safely remove this order from state. BlockHeightToPotentiallyPrunableOrders stores a list of order ids which we can prune for a certain block height. These are used in conjunction for replay prevention of short term orders | `StatefulOrderPlacementKeyPrefix`:

• key = OrderId
• value = Order

`StatefulOrdersTimeSlice`:
• key = time
• value = list of OrderIds expiring at this GTBT

`OrderAmountFilledKeyPrefix`:
• key = OrderId
• value = OrderFillAmount & PrunableBlockHeight (prunable block height unused for stateful orders) | | time added to state | `DeliverTx` when part of a match included in `MsgProposedOperations` | `StatefulOrderPlacementKeyPrefix` and `StatefulOrdersTimeSlice`: `DeliverTx`, the [MsgPlaceOrder](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/protocol/x/clob/keeper/msg_server_place_order.go#L22) is executed for `MsgPlaceOrder` msgs included in the block. The handler performs stateful validation, a collateralization check, and writes the order to state. Stateful orders are also written to the checkState in CheckTx for spam mitigation purposes.

`OrderAmountFilledKeyPrefix`: DeliverTx, when part of a match included in `MsgProposedOperations` | | time removed from state | Always in [EndBlocker](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/protocol/x/clob/abci.go#L60) based off of prunable block height | • cancelled by user: removed from state in `DeliverTx` for `MsgCancelOrder`
• forcefully-cancelled by protocol: removed from state in `DeliverTx` when processing `OperationRaw_OrderRemoval` operation. This operation type is included by the proposer in `MsgProposedOperations` when a stateful order is no longer valid. Removal reasons listed [here](https://github.com/dydxprotocol/v4-chain/blob/4eb219b1b726df9ba17c9939e8bb9296f5e98bb3/protocol/x/clob/types/order_removals.pb.go#L28)
• fully-filled: removed from state in `DeliverTx` in the block in which they become fully filled. The order is added to `RemovedStatefulOrderIds` of `processProposerMatchesEvents` to be used in `EndBlocker` to remove from the in-memory orderbook.
• expired: pruned during EndBlocker based off of GTBT

also removed from state in CheckTx for cancellations. This is for spam mitigation purposes. | | time added to in-memory orderbook | When placed in `CheckTx`, if not fully-matched | When placed in `PrepareCheckState`, if not fully-matched | | time removed from in-memory orderbook | • when fully-filled: removed in `PrepareCheckState` where invalid memclob state is purged via fully filled orders present in `OrderIdsFilledInLastBlock`
• when cancelled: (CheckTx)
• when expired: PrepareCheckState, removed using memclob.openOrders.blockExpirationsForOrders data structure which stores expiration times for short term orders based off of GTB | • when fully-filled: removed in `PrepareCheckState` where we purge invalid memclob state based off of `RemovedStatefulOrderIds`
• when cancelled: removed in `PrepareCheckState` based off of `PlacedStatefulCancellations`
• when expired: removed in `PrepareCheckState` by `PurgeInvalidMemclobState`, using the list of `ExpiredStatefulOrderIds` produced in `EndBlocker` | ### Types Currently, dYdX supports 6 different order types: * Market Order * Limit Order * Stop Market Order * Stop Limit Order * Take Profit Market Order * Take Profit Limit Order #### Market Order A Market Order is an order to buy or sell a given asset and will execute immediately at the best price dependent on the liquidity on the other side of the order book. By default, the front end submits market orders as Immediate-or-Cancel orders, meaning the order will fill immediately (matched against the other side of the order book) and any part that isn’t filled will be canceled. Market orders are also used to close positions. For closing positions, the order is submitted as an Immediate-or-Cancel order. #### Limit Order A Limit Order is an order to buy or sell a given asset at a specified (or better) price. A limit order to buy will only execute at the limit price or lower, and a limit order to sell will only execute at the limit price or higher. #### Stop Market Order A Stop Market Order protects against losses by closing a trader’s position once the Oracle Price or the last traded price\* crosses the trigger price. The trigger price can be triggered by either the Oracle Price or the last traded price\*. Stop market orders can be used to limit losses on a trader’s positions by automatically closing them when the price falls below (for longs) or rises above (for shorts) the trigger price. Once triggered, the resulting market order will be immediately filled at the best price on the books. #### Stop Limit Order A Stop Limit Order will execute only when the Oracle Price or the last traded price\* crosses a specified Trigger Price. The trigger price can be triggered by either the Oracle Price or the last traded price\*. Stop limit orders can be used to limit losses on a trader’s positions by automatically closing them when the price falls below (for longs) or rises above (for shorts) the trigger price. Once triggered, the resulting limit order may either be immediately filled or may rest on the orderbook at the limit price. The limit price operates exactly the same as for normal limit orders. #### Take Profit Market Order Take Profit Market orders allow traders to set targets and protect profits on positions by specifying a price at which to close an open position for profit. Take profit market orders lock in profits by closing a trader’s position once the Oracle Price or last traded price\* crosses the trigger price. For a long position, a trader places a stop above the current market price. For a short position, a trader places the stop below the current market price. Stop limit orders can be used to limit losses on a trader’s positions by automatically closing them when the price falls below (for longs) or rises above (for shorts) the trigger price. #### Take Profit Limit Order Take Profit Limit orders allow traders to set targets and protect profits on positions by specifying a price at which to close an open position for profit. Take profit limit orders enable profit taking like take profit market orders, but with the versatility and control of a limit order. For a long position, a trader places a take profit limit above the current market price. For a short position, a trader places the trigger below the current market price. If the Oracle Price or last traded price\* rises/drops to take-profit point, the T/P order changes from 'Untriggered' -> 'Open', and then behaves as a traditional limit order. Take-profit orders are best used by short-term traders interested in managing their risk. This is because they can get out of a trade as soon as their planned profit target is reached and not risk a possible future downturn in the market. #### TWAP Orders (release in v9.0) TWAP (Time weighted average price) orders enable users to submit orders that will be executed at certain time intervals at the current market price. When submitting a TWAP order with TWAP parameters supplied, the `OrderFlags` value must be set to 128 (TWAP) on `MsgPlaceOrder.Order.OrderId.OrderFlags`. ## Quantums and Subticks In dYdX, quantities and prices are represented in quantums (for quantities) and subticks (for prices), which need conversion for practical understanding. ### Quantums The smallest increment of position size. Determined from `atomicResolution`. atomicResolution - Determines the size of a quantum. [For example](https://github.com/dydxprotocol/v4-testnets/blob/aa1c7ac589d6699124942a66c2362acad2e6f50d/dydx-testnet-4/genesis.json#L5776), an `atomicResolution` of -10 for `BTC`, means that 1 quantum is `1e-10` `BTC`. ### Subticks Human-readable units: `USDC/` e.g. USDC/BTC Units in V4 protocol: `quote quantums/base quantums` e.g. (`1e-14 USDC/1e-10 BTC`) Determined by `quantum_conversion_exponent`, this allows for flexibility in the case that an asset’s prices plummet, since prices are represent in subticks, decreasing `subticks_per_tick` would allow for ticks to denote smaller increments between prices. E.g. 1 `subtick` = `1e-14 USDC/1e-10 BTC` and if BTC was at 20,000 USDC/BTC, a `tick` being 100 USDC/BTC (`subtick_per_tick` = 10000) may make sense. If BTC drops to 200 USDC/BTC, a `tick` being 100 USDC/BTC no longer makes sense, and we may want a `tick` to be 1 USDC/BTC, which lets us set `subtick_per_tick` to 100 to get to a `tick` size of 1 USDC/BTC. ### Interpreting block data ![Interpret1](/interpret_block_data_1.png) :::steps #### Buy or Sell First, notice row I is negative. That means this trade is a sell by the taker account. If It was positive, it would be a buy. #### Market determination Next, look at row N. The perpetual\_id is 7, which maps to AVAX-USD market. You can see all the mappings from this endpoint for the dYdX Chain deployment by dYdX Operations Services Ltd. [https://indexer.dydx.trade/v4/perpetualMarkets](https://indexer.dydx.trade/v4/perpetualMarkets) where the clobPairId is the perpetual\_id. #### Quantity determination Next, we need to get the decimals for this market. First, get the atomicResolution from that endpoint above which we see is -7. Now we can get the size of the trade. From row I and J, take this number -500000000 and multiply by 10^(AtomicResolution) and you get: -500000000 x 10^-7 = 50, so the quantity is 50. #### Price determination Next, look at row, E, F, G, H, I, and J ![Interpret2](/interpret_block_data_2.png) The price of the trade is either `abs((G+E)/I)*10e(-6 - AtomicResolution)`, or `abs((H+F)/J)*10e(-6 - AtomicResolution)`, either one is the same. Note that the ‘-6’ is because the AtomicResolution of USDC is -6. `abs((1479130125 + 369875)/-500000000)*10e(-6 + 7) = 29.59` `abs((-1479337255 - 162745)/500000000)*10e(-6 +7) = 29.59` #### Conclusion In conclusion, we have determined that this trade is SELL 50 AVAX-USD at price $29.59 ::: import Accounts from './accounts/index.mdx' import Markets from './markets/index.mdx' import Utility from './utility/index.mdx' import Vaults from './vaults/index.mdx' ## HTTP API import BatchedArray from '../../../components/BatchedArray'; import Details from '../../../components/Details'; #### Block Height Data feed of current block height. Data contains the last block height and time. ##### Method Declaration :::code-group ```python [Python] ``` ```typescript [TypeScript] ``` ```rust [Rust] // struct `Feeds` pub async fn block_height( &mut self, batched: bool, ) -> Result, FeedError> // The stream is unsubscribed when the `Feed` object is dropped ``` ```url [Channel] v4_block_height ``` :::
* Add feed to Python, TS clients.
##### Schema The field `id` is not employed in the subscribe/unsubscribe schemas. The field `contents` is serialized using the following schemas. ##### Messages | Initial | Update | | ----------------------------- | --------------------------------------------- | | [`BlockHeightInitialMessage`] | [`BlockHeightUpdateMessage`] | [`BlockHeightInitialMessage`]: /types/block_height_initial_message [`BlockHeightUpdateMessage`]: /types/block_height_update_message import BatchedArray from '../../../components/BatchedArray'; #### Candles Data feed of the [candles](https://en.wikipedia.org/wiki/Candlestick_chart) of a market. Data contains updates for open, low, high, and close prices, trade volume, for a certain time resolution. ##### Method Declaration :::code-group ```python [Python] # class `Candles` def subscribe(self, id: str, resolution: CandlesResolution, batched: bool = True) -> Self def unsubscribe(self, id: str, resolution: CandlesResolution) ``` ```typescript [TypeScript] // class `IndexerSocket` subscribeToCandles(market: string, resolution: CandlesResolution): void unsubscribeFromCandles(market: string, resolution: CandlesResolution): void ``` ```rust [Rust] // struct `Feeds` pub async fn candles( &mut self, ticker: &Ticker, resolution: CandleResolution, batched: bool, ) -> Result, FeedError> // The stream is unsubscribed when the `Feed` object is dropped ``` ```url [Channel] v4_candles ``` ::: ##### Schema The field `id` is a string containing the market and candle resolution. It is formatted as `{market}/{resolution}`. The field `contents` is serialized using the following schemas. ##### Messages | Initial | Update | | ------------------------- | ----------------------------------------- | | [`CandlesInitialMessage`] | [`CandlesUpdateMessage`] | [`CandlesInitialMessage`]: /types/candles_initial_message [`CandlesUpdateMessage`]: /types/candles_update_message import Feeds from './intro.mdx' import Subaccounts from './subaccounts.mdx' import Markets from './markets.mdx' import Trades from './trades.mdx' import Orders from './orders.mdx' import Candles from './candles.mdx' import ParentSubaccounts from './parent_subaccounts.mdx' import BlockHeight from './block_height.mdx' import Details from '../../../components/Details'; ## WebSockets API The WebSockets API provides data feeds providing the trader real-time information. See the [guide](/interaction/data/feeds) for examples on how to use the WebSockets API. ### Common schemas Interactions with the WebSockets endpoint is done using common base JSON schemas for all channels/feed types. For specific feeds, see the following [subsections](#feeds). #### Subscribe Use the following schema to subscribe to a channel. ##### JSON Schema | Parameter | Type | Description | | --------- | ------ | --------------------------------------------------------------- | | `type` | string | Message type (`subscribe`). | | `channel` | string | Feed type identifier. | | `id` | string | Selector for channel-specific data. Only used in some channels. | | `batched` | bool | Reduce incoming messages by batching contents. |
```tsx { "type": "subscribe", "channel": "v4_trades", "id": "BTC-USD", "batched": false } ```
##### Response | Parameter | Type | Description | | --------------- | ------ | ------------------------------------------------- | | `type` | string | Message type (`subscribed`). | | `connection_id` | string | String identifying the subscription. | | `message_id` | int | Message sequence number sent on the subscription. | | `id` | string | Selector for channel-specific data. | | `contents` | value | Channel-specific initial data. | #### Unsubscribe Use the following schema to unsubscribe from a channel. Similar scheme to the `subscribe` schema, however with the `unsubscribe` type, and without the `batched` field. ##### JSON Schema | Parameter | Type | Description | | --------- | ------ | ----------------------------------- | | `type` | string | Message type (`unsubscribe`). | | `channel` | string | Feed type identifier. | | `id` | string | Selector for channel-specific data. |
```tsx { "type": "unsubscribe", "channel": "v4_trades", "id": "BTC-USD" } ```
##### Response | Parameter | Type | Description | | --------------- | ------ | --------------------------------------------------------------- | | `type` | string | Message type (`unsubscribed`). | | `connection_id` | string | String identifying the subscription. | | `channel` | string | Feed type identifier. | | `message_id` | int | Message sequence number sent on the subscription. | | `id` | string | Selector for channel-specific data. Only used in some channels. | #### Data After subscription, the incoming messages will be serialized using the following schema. ##### JSON Schema | Parameter | Type | Description | | --------------- | ------ | --------------------------------------------------------------- | | `connection_id` | string | String identifying the subscription. | | `channel` | string | Feed type identifier. | | `id` | string | Selector for channel-specific data. Only used in some channels. | | `message_id` | int | Message sequence number sent on the subscription. | | `version` | string | Protocol identifier. | | `contents` | value | Channel-specific message data. | ### Channels The available clients API is presented below. For each, the subscription and unsubscription functions are shown. Internally, these functions send messages serialized in the [subscribe](#json-schema) and [unsubscribe](#json-schema-1) JSON schemas above. For each channel/feed type the sub-schemas employed in the `contents` field of the received [Data](#json-schema-2) (after subscription) are shown. import BatchedArray from '../../../components/BatchedArray'; #### Markets Data feed of all dYdX markets. Data contains updates to all markets, including market parameters and oracle prices. ##### Method Declaration :::code-group ```python [Python] # class `Markets` def subscribe(self, batched: bool = True) -> Self def unsubscribe(self) ``` ```typescript [TypeScript] // class `IndexerSocket` subscribeToMarkets(): void unsubscribeFromMarkets(): void ``` ```rust [Rust] // struct `Feeds` pub async fn markets( &mut self, batched: bool, ) -> Result, FeedError> // The stream is unsubscribed when the `Feed` object is dropped ``` ```url [Channel] v4_markets ``` ::: ##### Schema The field `id` is not employed in the subscribe/unsubscribe schemas. The field `contents` is serialized using the following schemas. ##### Messages | Initial | Update | | ------------------------- | ----------------------------------------- | | [`MarketsInitialMessage`] | [`MarketsUpdateMessage`] | [`MarketsInitialMessage`]: /types/markets_initial_message [`MarketsUpdateMessage`]: /types/markets_update_message import BatchedArray from '../../../components/BatchedArray'; #### Orders Data feed of the orders of a market. Data contains lists of the bids and asks of the order book. ##### Method Declaration :::code-group ```python [Python] # class `OrderBook` def subscribe(self, id: str, batched: bool = True) -> Self def unsubscribe(self, id: str) ``` ```typescript [TypeScript] // class `IndexerSocket` subscribeToOrderbook(market: string): void unsubscribeFromOrderbook(market: string): void ``` ```rust [Rust] // struct `Feeds` pub async fn orders( &mut self, ticker: &Ticker, batched: bool, ) -> Result, FeedError> // The stream is unsubscribed when the `Feed` object is dropped ``` ```url [Channel] v4_orderbook ``` ::: ##### Schema The field `id` is the market/ticker as a string. The field `contents` is serialized using the following schemas. ##### Messages | Initial | Update | | ------------------------ | ---------------------------------------- | | [`OrdersInitialMessage`] | [`OrdersUpdateMessage`] | [`OrdersInitialMessage`]: /types/orders_initial_message [`OrdersUpdateMessage`]: /types/orders_update_message import BatchedArray from '../../../components/BatchedArray'; #### Parent Subaccounts Data feed of a parent subaccount. This channel returns similar data to the [subaccount channel](/indexer-client/websockets/subaccounts). A parent subaccount is a subaccount numbered between 0 and 127. Used for isolated position management by the dYdX frontend (web). ##### Method Declaration :::code-group ```python [Python] # Coming soon. ``` ```typescript [TypeScript] // Coming soon. ``` ```rust [Rust] // struct `Feeds` pub async fn parent_subaccounts( &mut self, subaccount: ParentSubaccount, batched: bool, ) -> Result, FeedError> // The stream is unsubscribed when the `Feed` object is dropped ``` ```url [Channel] v4_parent_subaccounts ``` ::: ##### Schema The field `id` is a string containing the subaccount ID (address and subaccount number). It is formattted as `{address}/{subaccount-number}`. The field `contents` is serialized using the following schemas. ##### Messages | Initial | Update | | ----------------------------------- | --------------------------------------------------- | | [`ParentSubaccountsInitialMessage`] | [`ParentSubaccountsUpdateMessage`] | [`ParentSubaccountsInitialMessage`]: /types/parent_subaccounts_initial_message [`ParentSubaccountsUpdateMessage`]: /types/parent_subaccounts_update_message import BatchedArray from '../../../components/BatchedArray'; #### Subaccounts Data feed of a subaccount. Data contains updates to the subaccount such as position, orders and fills updates. ##### Method Declaration :::code-group ```python [Python] # class `Subaccounts` def subscribe(self, address: str, subaccount_number: int) -> Self def unsubscribe(self, address: str, subaccount_number: int) ``` ```typescript [TypeScript] // class `IndexerSocket` subscribeToSubaccount(address: string, subaccountNumber: number): void unsubscribeFromSubaccount(address: string, subaccountNumber: number): void ``` ```rust [Rust] // struct `Feeds` pub async fn subaccounts( &mut self, subaccount: Subaccount, batched: bool, ) -> Result, FeedError> // The stream is unsubscribed when the `Feed` object is dropped ``` ```url [Channel] v4_subaccounts ``` ::: ##### Schema The field `id` is a string containing the subaccount ID (address and subaccount number). It is formattted as `{address}/{subaccount-number}`. The field `contents` is serialized using the following schemas. ##### Messages | Initial | Update | | ----------------------------- | --------------------------------------------- | | [`SubaccountsInitialMessage`] | [`SubaccountsUpdateMessage`] | [`SubaccountsInitialMessage`]: /types/subaccounts_initial_message [`SubaccountsUpdateMessage`]: /types/subaccounts_update_message import BatchedArray from '../../../components/BatchedArray'; #### Trades Data feed of the trades on a market. Data contains order fills updates, such as the order side, price and size. ##### Method Declaration :::code-group ```python [Python] # class `Trades` def subscribe(self, id: str, batched: bool = True) -> Self def unsubscribe(self, id: str) ``` ```typescript [TypeScript] // class `IndexerSocket` subscribeToTrades(market: string): void unsubscribeFromTrades(market: string): void ``` ```rust [Rust] // struct `Feeds` pub async fn trades( &mut self, ticker: &Ticker, batched: bool, ) -> Result, FeedError> // The stream is unsubscribed when the `Feed` object is dropped ``` ```url [Channel] v4_trades ``` ::: ##### Schema The field `id` is the market/ticker as a string. The field `contents` is serialized using the following schemas. ##### Messages | Initial | Update | | ------------------------ | ---------------------------------------- | | [`TradesInitialMessage`] | [`TradesUpdateMessage`] | [`TradesInitialMessage`]: /types/trades_initial_message [`TradesUpdateMessage`]: /types/trades_update_message ## Quick Start with Python This guide will walk you through the steps to set up and start using the dYdX API Python library. :::steps ### Install Python3 and Poetry Choose and install [Python 3.9+](https://www.python.org/downloads/) and [Poetry](https://python-poetry.org/docs#installing-with-the-official-installer) for your system. ### Clone the dydx client repo ```bash git clone https://github.com/dydxprotocol/v4-clients.git ``` ### Install all dependencies Go to the Python client library. ```bash cd v4-clients/v4-client-py-v2 ``` Install the project dependencies using the following command: ```bash poetry install ``` ### Run an example Now, we can run an example file. Let's run `example/accounts_endpoint.py` file. ```bash poetry run python -m examples.account_endpoints ``` ::: **Now, you can play around with all the available examples. Happy trading!** :::tip[Python Package] The Python client is also available through the PyPI [package](https://pypi.org/project/dydx-v4-client/) `dydx-v4-client`. ```bash [Installation] pip install dydx-v4-client ``` ::: ## Quick Start with Rust This guide will walk you through the steps to set up and start using the dYdX API Rust library. :::steps ### Install Rust and Cargo Choose and install [Rust](https://www.rust-lang.org/tools/install) and [Cargo](https://doc.rust-lang.org/cargo/getting-started/installation.html) for your system. ### Clone the dydx client repo ```bash git clone https://github.com/dydxprotocol/v4-clients.git ``` ### Run an example Go to the Rust client library. ```bash cd v4-clients/v4-client-rs ``` Now, we can run an example file. Let's run `accounts_endpoint` example. ```bash cargo run --example account_endpoint ``` ::: **Now, you can play around with all the available examples. Happy trading!** ::::tip[Rust Crate] The Rust client is also available through the crates.io [crate](https://crates.io/crates/dydx) `dydx`. :::details[Installation] ```bash [Terminal] cargo add dydx ``` Or add it manually to `Cargo.toml`. ```toml [Cargo.toml] [dependencies] dydx = "0.2.0" # Replace with the latest version // [!code ++] ``` ::: :::: :::::note[Configuration File] The Rust client uses a TOML configuration file to configure several required parameters. ::::details[Examples] :::code-group ```toml [mainnet.toml] [node] endpoint = "https://dydx-ops-grpc.kingnodes.com:443" chain_id = "dydx-mainnet-1" fee_denom = "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5" [indexer] http.endpoint = "https://indexer.dydx.trade" ws.endpoint = "wss://indexer.dydx.trade/v4/ws" [noble] # optional endpoint = "http://noble-grpc.polkachu.com:21590" chain_id = "noble-1" fee_denom = "uusdc" ``` ```toml [testnet.toml] [node] endpoint = "https://test-dydx-grpc.kingnodes.com" chain_id = "dydx-testnet-4" fee_denom = "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5" [indexer] http.endpoint = "https://indexer.v4testnet.dydx.exchange" ws.endpoint = "wss://indexer.v4testnet.dydx.exchange/v4/ws" [noble] # optional endpoint = "http://noble-testnet-grpc.polkachu.com:21590" chain_id = "grand-1" fee_denom = "uusdc" [faucet] # optional endpoint = "https://faucet.v4testnet.dydx.exchange" ``` ::: :::: ::::: ## Quick Start with TypeScript This guide will walk you through the steps to set up and start using the dYdX API TypeScript library. :::steps ### Install Node and npm Choose and install [node](https://nodejs.org/en/download) for your system. ### Clone the dydx client repo ```bash git clone https://github.com/dydxprotocol/v4-clients.git ``` ### Run an example Go to the TypeScript client library. ```bash cd v4-clients/v4-client-js ``` Install and use required node version using `nvm` ```bash nvm install nvm use ``` Install and build the examples ```bash npm install npm run build ``` Now, we can run an example file. Let's run `example/accounts_endpoint.js` file. ```bash node ../build/examples/account_endpoints.js ``` ::: **Now, you can play around with all the available examples. Happy trading!** :::tip[JavaScript Package] The JavaScript/TypeScript client is also available through the npm [package](https://www.npmjs.com/package/@dydxprotocol/v4-client-js) `v4-client-js`. ```bash [Installation] npm i @dydxprotocol/v4-client-js ``` ::: import Details from '../../../components/Details'; ## Accounts All of your trading activity is associated with your account which corresponds to an address. In dYdX, accounts are also composed by subaccounts. All trading is done through a subaccount. See more on the [Accounts and Subaccounts](/concepts/trading/accounts) page. ### Account Data An account can have multiple subaccounts. To fetch all known (with some activity) subaccounts associated with an account the account's address is required. :::code-group ```python [Python] response = await indexer.account.get_subaccounts(ADDRESS) ``` ```typescript [TypeScript] const response = await indexer.account.getSubaccounts(ADDRESS); ``` ```rust [Rust] let subaccounts = indexer.accounts().get_subaccounts(address).await?; ``` ::: To fetch a specific subaccount, use the account's address the the subaccount number. :::code-group ```python [Python] # Fetch subaccount '0' information. subaccount_resp = await indexer.account.get_subaccount(ADDRESS, 0) ``` ```typescript [TypeScript] // Fetch subaccount '0' information. const subaccountResp = await indexer.account.getSubaccount(ADDRESS, 0); ``` ```rust [Rust] // Fetch subaccount '0' information. let subaccount_resp = indexer.accounts().get_subaccount(&subaccount).await?; ``` ::: #### Balance The responses above will contain information such as the subaccount's equity, also known as the total account value. Your equity is a combination of the account's USDC balance and sum of the open positions values. A minimum amount of funds is required to trade, see more on [Margin](/concepts/trading/margin) and [Equity Tier Limits](/concepts/trading/limits/equity-tier-limits). :::code-group ```python [Python] subaccount = subaccount_resp["subaccount"] print("Equity: ", subaccount["equity"]) print("Open positions: ", subaccount["openPerpetualPositions"]) ``` ```typescript [TypeScript] const subaccount = subaccountResp.subaccount; console.log('Equity: ', subaccount.equity); console.log('Open positions: ', subaccount.openPerpetualPositions); ``` ```rust [Rust] println!("Equity: {:?}", subaccount_resp.equity); println!("Open positions: {:?}", subaccount_resp.open_perpetual_positions); ``` ::: ::::note dYdX is built on the Cosmos SDK and therefore has related methods available. To see the balances of your assets/tokens please see the methods below.
Get the account balance of all assets types (currently USDC and dYdX tokens). :::code-group ```python [Python] response = await node.get_account_balances(ADDRESS) ``` ```typescript [TypeScript] const coins = await node.get.getAccountBalances(ADDRESS); ``` ```rust [Rust] let balance = client .get_account_balances(&address) .await?; ``` :::
The balance of a specific asset can also be fetched instead. :::code-group ```python [Python] # `adv4tnt` is the dYdX token (testnet) denomination. response = await node.get_account_balance(ADDRESS, "adv4tnt") # [!code focus] ``` ```typescript [TypeScript] // `adv4tnt` is the dYdX token (testnet) denomination. const coins = await node.get.getAccountBalance(ADDRESS, "adv4tnt"); // [!code focus] ``` ```rust [Rust] // `adv4tnt` is the dYdX token (testnet) denomination. let balance = node // [!code focus] .get_account_balance(&address, &"adv4tnt".parse()?) // [!code focus] .await?; // [!code focus] ``` ::: :::: ### Asset Transfers Methods are available to transfer [assets](/concepts/trading/assets#assets-and-collateral) among accounts and subaccounts. See the table below for the different transfer paths. Links point to the API reference. | Source | Destination | Method | | ---------- | ----------- | --------------------------------------------- | | Account | Subaccount | [Deposit](/node-client/private#deposit) | | Subaccount | Account | [Withdraw](/node-client/private#withdraw) | | Subaccount | Subaccount | [Transfer](/node-client/private#transfer) | | Account | Account | [Send Token](/node-client/private#send-token) | :::info To transfer assets in and out of the dYdX network, please see the [Deposits and Withdawals](/interaction/deposits-withdrawals/overview) page. If using the **testnet**, please the [Faucet client](/interaction/endpoints#faucet-client) on how to request test funds. ::: import Details from '../../../components/Details'; ## WebSockets The Indexer can provide realtime data through its WebSockets endpoint. Below an example is provided of how to establish a connection and watch realtime **trades** updates. See the full API specification [here](/indexer-client/websockets) for other data feeds. ::::steps ### Connect To get realtime updates, we first need to establish a connection with the WebSockets endpoint. :::code-group ```python [Python] # The message handler, triggered when a message is received. def handler(ws: IndexerSocket, message: dict): print(message) ``` ```typescript [TypeScript] // The message handler, triggered when a message is received. function handler(message) { console.log(message); } // Create a socket. const mySocket = new SocketClient( Network.testnet().indexerConfig, // On-connection callback () => { console.log('socket opened'); }, // On-disconnection callback () => { console.log('socket closed'); }, // Message handler (message) => { handler(message); }, // WebSockets event handler (event) => { console.error('Encountered error:', event.message); }, ); // Establish the connection. mySocket.connect(); ``` ```rust [Rust] // Establish the connection. // An internal loop is spawned which handles the connection state. let mut indexer = IndexerClient::new(config.indexer); ``` ::: Upon a successful connection you will receive an initial connection message. This message maybe abstracted away, depending on the client.
```tsx { "type": "connected", "connection_id": "004a1efa-21bb-4b19-a2e9-a8ffadd6dc53", "message_id": 0 } ```
### Subscribe After a connection is established, you may subscribe to several feeds, containing different types of data. WebSockets include information on **markets**, **trades**, **orders**, **candles**, and **subaccounts**. :::code-group ```python [Python] # Modify the `handler()` function. # Subscribe only after a succesful connection. def handler(ws: IndexerSocket, message: dict): if message["type"] == "connected": # Subscribe. ws.trades.subscribe(ETH_USD) print(message) ``` ```typescript [TypeScript] // Modify the `handler()` function. // Subscribe only after a succesful connection. function handler(message) { console.log(message); if (typeof message.data === 'string') { const jsonString = message.data as string; try { const data = JSON.parse(jsonString); if (data.type === IncomingMessageTypes.CONNECTED) { // Subscribe. mySocket.subscribeToTrades('ETH-USD'); } console.log(data); } catch (e) { console.error('Error parsing JSON message:', e); } } } ``` ```rust [Rust] // Subscribe. let trades_feed = indexer.feed().trades(&"ETH-USD", false).await?; ``` ::: ### Handling the data After subscription, you will start receiving the update messages. Here, the update messages contain the finalized trades (matched orders) for the `ETH-USD` ticker. For each received message, the `handler()` function will be called on it. Modify it to implement your desired logic.
In Rust, callbacks are not used. Intead, the handle returned on subscription must be polled. ```rust [Rust] // Continuous loop running until the feed is stopped. while let Some(msg) = trades_feed.recv().await { println!("New trades update: {msg:?}"); } ```
### Unsubscribe When the data feed is not needed anymore, you may stop it and unsubscribe from it. :::code-group ```python [Python] ws.trades.unsubscribe(ETH_USD) ``` ```typescript [TypeScript] mySocket.unsubscribeFromTrades('ETH-USD'); ``` ```rust [Rust] // To unsubscribe, drop the feed handle (here `trades_feed`). ``` ::: :::: ### Details #### Rate Limiting The default rate limiting config for WebSockets is: * 2 subscriptions per (connection + channel + channel ID) per second. * 2 invalid messages per connection per second. #### Maintaining a Connection Every 30 seconds, the WebSockets API will send a [heartbeat `ping` control frame](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API/Writing_WebSocket_servers#pings_and_pongs_the_heartbeat_of_websockets) to the connected client. If a `pong` event is not received within 10 seconds back, the websocket API will disconnect. #### CLI example You can use a command-line WebSockets client such as [`interactive-websocket-cli`](https://www.npmjs.com/package/interactive-websocket-cli) to connect and subscribe to channels. Example (with `interactive-websocket-cli`): ```tsx # For the deployment by DYDX token holders (mainnet), use # wscli connect wss://indexer.dydx.trade/v4/ws wscli connect wss://indexer.v4testnet.dydx.exchange/v4/ws { "type": "subscribe", "channel": "v4_trades", "id": "BTC-USD" } ``` ## Trading Data This section guides you on how to fetch some important data points. We focus here on getting data using spontaneous (single) requests. For continuous data streams of data see also the [WebSockets guide](/interaction/data/feeds). ### List Positions Assets are used to trade and manage (perpetual) positions opened and closed by [issuing orders](/interaction/trading#place-an-order). See the example below on how to check your perpetual positions. :::code-group ```python [Python] from dydx_v4_client.indexer.rest.constants import PositionStatus # Fetch all subaccount '0' positions. [!code focus] perpetual_positions_response = await indexer # [!code focus] .account # [!code focus] .get_subaccount_perpetual_positions(ADDRESS, 0) # [!code focus] # Fetch only open positions. # [!code focus] perpetual_positions_response = await indexer .account .get_subaccount_perpetual_positions(address, 0, PositionStatus.OPEN) # [!code focus] ``` ```typescript [TypeScript] // Fetch all subaccount '0' positions. const response = await indexer .account .getSubaccountPerpetualPositions(ADDRESS, 0); // Fetch only open positions. const response = await indexer .account .getSubaccountPerpetualPositions(ADDRESS, 0, PositionStatus.OPEN); ``` ```rust [Rust] use dydx::indexer::PerpetualPositionStatus; // Fetch all subaccount '0' positions. [!code focus] let positions = indexer // [!code focus] .accounts() // [!code focus] .get_subaccount_perpetual_positions(&subaccount, None) // [!code focus] .await?; // [!code focus] // Fetch only open positions. // [!code focus] let opts = ListPositionsOpts { // [!code focus] status: PerpetualPositionStatus::Open.into(), // [!code focus] ..Default::default() // [!code focus] }; // [!code focus] let positions = indexer // [!code focus] .accounts() // [!code focus] .get_subaccount_perpetual_positions(&subaccount, Some(opts)) // [!code focus] .await?; // [!code focus] ``` ::: See the [API reference](/indexer-client/http/accounts/list_positions) for the complete method definition. ### Market List A market (sometimes referred by the ticker name, e.g., `ETH-USD`) is associated with a perpetual and it is the place where trading happens. To fetch the available markets see the code below. :::code-group ```python [Python] response = await indexer.markets.get_perpetual_markets() # [!code focus] print(response["markets"]) # [!code focus] ``` ```typescript [TypeScript] const response = await indexer.markets.getPerpetualMarkets(); // [!code focus] console.log(response.markets); // [!code focus] ``` ```rust [Rust] let markets = indexer // [!code focus] .markets() // [!code focus] .get_perpetual_markets(None) // `None`: Use default options. // [!code focus] .await?; // [!code focus] println!("{markets:?}"); // [!code focus] ``` ::: See the [API reference](/indexer-client/http/markets/get_perpetual_markets) for the complete method definition. ### List Orders Retrieve orders for a specific subaccount, with various filtering options to narrow down the results based on order characteristics. :::info It is recommended to set specific clientID's when placing different orders, which will be useful to retrieve back a specific order on the `v4/orders` endpoint ::: :::code-group ```python [Python] orders_response = indexer.account.get_subaccount_orders(address, 0) # [!code focus] ``` ```typescript [TypeScript] const response = await indexer.account.getSubaccountOrders(ADDRESS, 0); ``` ```rust [Rust] let orders = indexer // [!code focus] .accounts() // [!code focus] .get_subaccount_orders(&subaccount, None) // [!code focus] .await?; // [!code focus] ``` ::: See the [API reference](/indexer-client/http/accounts/list_orders) the complete method definition. :::tip[Orderbook] For some trading strategies it is useful to have a continouos view of the [orderbook](https://en.wikipedia.org/wiki/Order_book) up-to-date. See the [Watch Orderbook](/interaction/data/watch-orderbook) guide on how to set this up. ::: ### Get Fills Retrieve order fill records for a specific subaccount on the exchange. Fills are matched orders. :::code-group ```python [Python] fills_response = indexer.account.get_subaccount_fills(address, 0) # [!code focus] ``` ```typescript [TypeScript] const response = await indexer.account.getSubaccountFills(ADDRESS, 0); ``` ```rust [Rust] let fills = indexer // [!code focus] .accounts() // [!code focus] .get_subaccount_fills(&env.subaccount, None) // [!code focus] .await?; // [!code focus] ``` ::: See the [API reference](/indexer-client/http/accounts/get_fills) the complete method definition. ### Price History Price history in the classic [candlestick](https://en.wikipedia.org/wiki/Candlestick_chart) can also be fetched. Data will be organized into a *open*, *high*, *low*, and *close* (OHLC) prices for some *period*. :::code-group ```python [Python] from dydx_v4_client.indexer.candles_resolution import CandlesResolution response = await indexer.markets.get_perpetual_market_candles( # [!code focus] market="BTC-USD", resolution=CandlesResolution.ONE_MINUTE # [!code focus] ) # [!code focus] print(response["candles"]) # [!code focus] ``` ```typescript [TypeScript] const response = await indexer.markets.getPerpetualMarketCandles('BTC-USD', '1MIN'); // [!code focus] console.log(response.candles); // [!code focus] ``` ```rust [Rust] use dydx::indexer::CandleResolution; let candles = indexer // [!code focus] .markets() // [!code focus] .get_perpetual_market_candles(&"BTC-USD".into(), CandleResolution::M1, None) // [!code focus] .await?; // [!code focus] println!("{candles:?}"); // [!code focus] ``` ::: See the [API reference](/indexer-client/http/markets/get_candles) for the complete method definition. :::info[Sparklines] See also the [sparklines method](/indexer-client/http/markets/get_sparklines) for price history. ::: ### Get User Fee Tier The Get User Fee Tier function retrieves the perpetual fee tier associated with a specific wallet address, providing information on the user's current fee structure. :::code-group ```python [Python] user_fee_tier = await node.get_user_fee_tier(ADDRESS) ``` ```typescript [TypeScript] const userfeeTier = await node.get.getUserFeeTier(ADDRESS); ``` ```rust [Rust] let user_fee_tier = node.get_user_fee_tier(address.clone()).await?; ``` ::: See the [API reference](/node-client/public/get_user_fee_tier) for the complete method definition. ### Get Rewards Params The Get Rewards Params function retrieves the parameters for the rewards system, providing insight into the set configurations for earning and distributing rewards. :::code-group ```python [Python] rewards_params = await node.get_rewards_params() ``` ```typescript [TypeScript] const rewardsParams = await node.get.getRewardsParams(); ``` ```rust [Rust] let reward_params = node.get_rewards_params().await?; ``` ::: See the [API reference](/node-client/public/get_rewards_params) for the complete method definition. ### Trading Rewards Retrieve historical block trading rewards for the specified address. :::code-group ```python [Python] response = await indexer.account.get_historical_block_trading_rewards(test_address, limit) ``` ```typescript [TypeScript] const response = await indexer.account.getHistoricalBlockTradingRewards(ADDRESS, limit); ``` ```rust [Rust] indexer .accounts() .get_rewards(&env.address, None) .await?; ``` ::: See the [API reference](/indexer-client/http/accounts/get_rewards) for the complete method definition. ### Get Latest Block Height Retrieve the most recent block's height. This can serve to see if the blockchain node you are connected to is in sync. :::code-group ```python [Python] height = await node.latest_block_height() ``` ```typescript [TypeScript] const height = await node.get.latestBlockHeight(); ``` ```rust [Rust] let height = node.latest_block_height().await?; ``` ::: See the [API reference](/node-client/public/get_latest_block_height) for the complete method definition. ## Watch orderbook Depending on your trading strategy, keeping track of the current orderbook can be essential. The orderbook is a list of all the unmatched orders, divided into the **bids** (buy orders) and the **asks** (sell orders). We'll use the Indexer WebSockets data streams for this. ::::steps ### Subscribe to the Orders channel Lets take as reference the previous [section](/interaction/data/feeds). Subscribe to the Orders channel. :::code-group ```python [Python] def handler(ws: IndexerSocket, message: dict): if message["type"] == "connected": # Subscribe. [!code focus] ws.orders.subscribe(ETH_USD) # [!code focus] print(message) ``` ```typescript [TypeScript] // In the `handler()` function [!code focus] // ... if (data.type === IncomingMessageTypes.CONNECTED) { // Subscribe. [!code focus] mySocket.subscribeToOrders('ETH-USD'); // [!code focus] } // ... ``` ```rust [Rust] // Subscribe. let trades_feed = indexer.feed().orders(&"ETH-USD", false).await?; ``` ::: ### Parse the update messages Grab the bids and asks lists from the incoming messages. Each incoming bid and ask entry is the updated *level* in the orderbook. Each level is associated with a certain price and a total size. The total size is the current aggregated orders size for that price. :::code-group ```python [Python] def handler(ws: IndexerSocket, message: dict): if message["type"] == "connected": ws.order_book.subscribe(ETH_USD, False) elif message["channel"] == "v4_orderbook" and "contents" in message: # [!code focus] # Bids levels. # [!code focus] if "bids" in contents: # [!code focus] for bid in contents["bids"]: # [!code focus] price = bid["price"] # [!code focus] size = bid["size"] # [!code focus] # Asks levels. # [!code focus] if "asks" in contents: # [!code focus] for ask in contents["asks"]: # [!code focus] price = ask["price"] # [!code focus] size = ask["size"] # [!code focus] ``` ```typescript [TypeScript] // In the `handler()` function [!code focus] // ... if (data.type === IncomingMessageTypes.CONNECTED) { // Subscribe. mySocket.subscribeToOrders('ETH-USD'); } const orderBookDataList = data.contents; // [!code focus] if (Array.isArray(orderBookDataList)) { // [!code focus] orderBookDataList.forEach(entry => { // [!code focus] const bids = entry.bids?.[0]; // [!code focus] const asks = entry.asks?.[0]; // [!code focus] }); // [!code focus] } // [!code focus] // ... ``` ```rust [Rust] // Continuous handler loop. while let Some(msg) = self.traders_feed.recv().await { match msg { // [!code focus] // The initial message (on-subscription). // [!code focus] OrdersMessage::Initial(init) => { // [!code focus] let bids = init.contents.bids; // [!code focus] let asks = init.contents.asks; // [!code focus] } // [!code focus] // Update messages. Bids and asks are optional. // [!code focus] OrdersMessage::Update(upd) => { // [!code focus] if let Some(bids) = upd.contents.bids { } // [!code focus] if let Some(asks) = upd.contents.asks { } // [!code focus] } // [!code focus] } // [!code focus] } ``` ::: ### Keeping track On a continuous loop, keep recording all the incoming bids and asks and update your local orderbook. :::code-group ```python [Python] def handler(ws: IndexerSocket, message: dict): if message["type"] == "connected": ws.order_book.subscribe(ETH_USD, False) elif message["channel"] == "v4_orderbook" and "contents" in message: # Modify the above snippet. # [!code focus] # For full snapshot (initial subscribed message), reset the orderbook. # [!code focus] if message["type"] == "subscribed": # [!code focus] orderbook["bids"] = {} # [!code focus] orderbook["asks"] = {} # [!code focus] # Process bids levels. # [!code focus] if "bids" in contents: for bid in contents["bids"]: process_price_level(bid, "bids") # [!code focus] # Process asks levels. # [!code focus] if "asks" in contents: for ask in contents["asks"]: process_price_level(ask, "asks") # [!code focus] # Orderbook state. Levels are stored as [price, size, offset]. # [!code focus] orderbook = { # [!code focus] "bids": {}, # [!code focus] "asks": {} # [!code focus] } # [!code focus] def process_price_level(level, side): # [!code focus] """Process a single price level (bid or ask)""" # [!code focus] if isinstance(level, dict): # [!code focus] # Full snapshot format # [!code focus] price = level["price"] # [!code focus] size = level["size"] # [!code focus] offset = level.get("offset", "0") # [!code focus] else: # [!code focus] # Incremental update format # [!code focus] price = level[0] # [!code focus] size = level[1] # [!code focus] offset = level[2] if len(level) > 2 else "0" # [!code focus] # Update local orderbook. # [!code focus] if float(size) > 0: # [!code focus] orderbook[side][price] = [price, size, offset] # [!code focus] elif price in orderbook[side]: # [!code focus] del orderbook[side][price] # [!code focus] ``` ```typescript [TypeScript] // In the `handler()` function// [!code focus] // ... mySocket.subscribeToOrders('ETH-USD'); } const orderBookDataList = data.contents; // Modify above snippet.// [!code focus] const messageId = data.message_id;// [!code focus] if (orderBookDataList instanceof Array) {// [!code focus] // common orderBook data // [!code focus] [bidList, askList] = updateOrderBook(// [!code focus] orderBookDataList,// [!code focus] bidList,// [!code focus] askList,// [!code focus] messageId,// [!code focus] );// [!code focus] // Sort. // [!code focus] bidList.sort((a, b) => b[0] - a[0]); // descending // [!code focus] askList.sort((a, b) => a[0] - b[0]); // ascending // [!code focus] } else if (orderBookDataList !== null && orderBookDataList !== undefined) {// [!code focus] // initial OrderBook data// [!code focus] setInitialOrderBook(orderBookDataList, bidList, askList, messageId);// [!code focus] }// [!code focus] // ... type Level = [number, number, number];// [!code focus] const setInitialOrderBook = (// [!code focus] orderBookDataList: { bids: { price: string; size: string }[]; asks: { price: string; size: string }[] }, // [!code focus] bidList: Level[],// [!code focus] askList: Level[],// [!code focus] messageId: number// [!code focus] ): void => {// [!code focus] const convertToLevel = (item: { price: string; size: string }): Level => [// [!code focus] Number(item.price),// [!code focus] Number(item.size),// [!code focus] messageId,// [!code focus] ];// [!code focus] bidList.push(...orderBookDataList.bids.map(convertToLevel));// [!code focus] askList.push(...orderBookDataList.asks.map(convertToLevel));// [!code focus] };// [!code focus] const updateOrderBook = (// [!code focus] updates: { bids: number[][]; asks: number[][] }[],// [!code focus] bidList: Level[],// [!code focus] askList: Level[],// [!code focus] messageId: number// [!code focus] ): [Level[], Level[]] => {// [!code focus] const updateList = (updateEntries: number[][], list: Level[]) => {// [!code focus] updateEntries.forEach(([price, size]) => {// [!code focus] const index = list.findIndex(([p]) => p === price);// [!code focus] if (size === 0) {// [!code focus] if (index !== -1) list.splice(index, 1);// [!code focus] } else if (index !== -1) {// [!code focus] list[index] = [price, size, messageId];// [!code focus] } else {// [!code focus] list.push([price, size, messageId]);// [!code focus] }// [!code focus] });// [!code focus] };// [!code focus] updates.forEach(({ bids, asks }) => {// [!code focus] if (bids.length) updateList(bids, bidList);// [!code focus] if (asks.length) updateList(asks, askList);// [!code focus] });// [!code focus] return [bidList, askList];// [!code focus] };// [!code focus] ``` ```rust [Rust] // Update the previous loop to record the incoming bids/asks. let mut order_book = OrderBook::default(); while let Some(msg) = traders_feed.recv().await { match msg { OrdersMessage::Initial(init) => { order_book.update_bids(init.contents.bids); order_book.update_asks(init.contents.asks); } OrdersMessage::Update(upd) => { if let Some(bids) = upd.contents.bids { order_book.update_bids(bids); } if let Some(asks) = upd.contents.asks { order_book.update_asks(asks); } } } } // ... // Keep track of the orderbook using this struct. #[derive(Default, Debug)] pub struct OrderBook { // Use `BTreeMap` for easier sorting. pub bids: BTreeMap, pub asks: BTreeMap, pub offset: u64, } impl OrderBook { pub fn update_bids(&mut self, bids: Vec) { Self::update(&mut self.bids, bids, &mut self.offset); } pub fn update_asks(&mut self, asks: Vec) { Self::update(&mut self.asks, asks, &mut self.offset); } fn update(map: &mut BTreeMap, levels: Vec, offset: &mut u64) { for level in levels { if level.size.is_zero() { map.remove(&level.price); } else { map.insert(level.price, (level.size, *offset)); *offset += 1; } } } } ``` ::: ### Uncrossing the orderbook Given the decentralized nature of dYdX, sometimes, some of the bids will be higher than some of the asks. :::info There is no guarantee that prices do not cross (a bid higher than a ask) because there is no centralized orderbook. For that reason, the software does not include a global offset. The *correct* orderbook at any given time is whatever the current block proposer has in its mempool, which is not what the indexer or the front end can directly see. The block proposer changes every block, so there is a new canonical mempool, and therefore, a new canonical orderbook every block. Due to the particulars of message propagation, that means there will be slight differences in the canonical orderbook every block. The prices will uncross eventually. ::: If trader needs the orderbook uncrossed, then one way is to use the order of messages as a logical timestamp. That is, when a message is received, update a global locally-held offset. Each WebSockets update has a `message-id` which is a logical offset to use. Using a timestamp is also an option. :::code-group ```python [Python] # In the handler() function # ... # Process asks levels. if "asks" in contents: for ask in contents["asks"]: process_price_level(ask, "asks") # Uncross the orderbook.# [!code focus] uncross_orderbook()# [!code focus] def get_sorted_book():# [!code focus] """Get sorted lists of bids and asks"""# [!code focus] bids_list = list(orderbook["bids"].values())# [!code focus] asks_list = list(orderbook["asks"].values())# [!code focus] bids_list.sort(key=lambda x: float(x[0]), reverse=True)# [!code focus] asks_list.sort(key=lambda x: float(x[0]))# [!code focus] return bids_list, asks_list# [!code focus] def uncross_orderbook():# [!code focus] """Remove crossed orders from the orderbook"""# [!code focus] bids_list, asks_list = get_sorted_book()# [!code focus] if not bids_list or not asks_list:# [!code focus] return# [!code focus] top_bid = float(bids_list[0][0])# [!code focus] top_ask = float(asks_list[0][0])# [!code focus] while bids_list and asks_list and top_bid >= top_ask:# [!code focus] bid = bids_list[0]# [!code focus] ask = asks_list[0]# [!code focus] bid_price = float(bid[0])# [!code focus] ask_price = float(ask[0])# [!code focus] bid_size = float(bid[1])# [!code focus] ask_size = float(ask[1])# [!code focus] bid_offset = int(bid[2]) if len(bid) > 2 else 0# [!code focus] ask_offset = int(ask[2]) if len(ask) > 2 else 0# [!code focus] if bid_price >= ask_price:# [!code focus] # Remove older entry.# [!code focus] if bid_offset < ask_offset:# [!code focus] bids_list.pop(0)# [!code focus] elif bid_offset > ask_offset:# [!code focus] asks_list.pop(0)# [!code focus] else:# [!code focus] # Same offset, handle based on size.# [!code focus] if bid_size > ask_size:# [!code focus] asks_list.pop(0)# [!code focus] bid[1] = str(bid_size - ask_size)# [!code focus] elif bid_size < ask_size:# [!code focus] ask[1] = str(ask_size - bid_size)# [!code focus] bids_list.pop(0)# [!code focus] else:# [!code focus] # Both filled exactly.# [!code focus] asks_list.pop(0)# [!code focus] bids_list.pop(0)# [!code focus] else:# [!code focus] # No crossing.# [!code focus] break# [!code focus] if bids_list and asks_list:# [!code focus] top_bid = float(bids_list[0][0])# [!code focus] top_ask = float(asks_list[0][0])# [!code focus] # Update the orderbook with uncrossed data.# [!code focus] orderbook["bids"] = {bid[0]: bid for bid in bids_list}# [!code focus] orderbook["asks"] = {ask[0]: ask for ask in asks_list}# [!code focus] ``` ```typescript [TypeScript] // In the handler() function // [!code focus] // ... // Sort. bidList.sort((a, b) => b[0] - a[0]); // descending askList.sort((a, b) => a[0] - b[0]); // ascending // Resolving crossed orderbook. // [!code focus] [bidList, askList] = resolveCrossedOrderBook( // [!code focus] bidList, // [!code focus] askList, // [!code focus] ); // [!code focus] // ... // Uncross the orderbook. // [!code focus] const resolveCrossedOrderBook = (// [!code focus] bidList: Level[],// [!code focus] askList: Level[],// [!code focus] ): [Level[], Level[]] => {// [!code focus] while (bidList.length && askList.length && bidList[0][0] >= askList[0][0]) {// [!code focus] const bid = bidList[0];// [!code focus] const ask = askList[0];// [!code focus] // Compare message IDs to resolve duplicates// [!code focus] if (bid[2] < ask[2]) {// [!code focus] bidList.shift();// [!code focus] } else if (bid[2] > ask[2]) {// [!code focus] askList.shift();// [!code focus] } else {// [!code focus] const tradedAmount = Math.min(bid[1], ask[1]);// [!code focus] bid[1] -= tradedAmount;// [!code focus] ask[1] -= tradedAmount;// [!code focus] if (bid[1] === 0) bidList.shift();// [!code focus] if (ask[1] === 0) askList.shift();// [!code focus] }// [!code focus] }// [!code focus] return [bidList, askList];// [!code focus] };// [!code focus] ``` ```rust [Rust] // Update the previous loop to uncross the orderbook.// [!code focus] while let Some(msg) = traders_feed.recv().await { match msg { // ... } order_book.uncross();// [!code focus] } // ... impl OrderBook { // ... pub fn uncross(&mut self) {// [!code focus] while !self.bids.is_empty() && !self.asks.is_empty() {// [!code focus] let highest_bid_price = match self.bids.keys().last() {// [!code focus] Some(price) => price.clone(),// [!code focus] None => break,// [!code focus] };// [!code focus] // [!code focus] let lowest_ask_price = match self.asks.keys().next() {// [!code focus] Some(price) => price.clone(),// [!code focus] None => break,// [!code focus] };// [!code focus] // [!code focus] // Check if there's a cross.// [!code focus] if highest_bid_price >= lowest_ask_price {// [!code focus] // Get entries for the crossed orders// [!code focus] let (highest_bid_qty, highest_bid_offset) = self.bids.get(&highest_bid_price).unwrap().clone();// [!code focus] let (lowest_ask_qty, lowest_ask_offset) = self.asks.get(&lowest_ask_price).unwrap().clone();// [!code focus] // [!code focus] // Remove oldest entry.// [!code focus] if highest_bid_offset < lowest_ask_offset {// [!code focus] self.bids.remove(&highest_bid_price);// [!code focus] } else if highest_bid_offset > lowest_ask_offset {// [!code focus] self.asks.remove(&lowest_ask_price);// [!code focus] } else {// [!code focus] // Same offset, compare sizes// [!code focus] if highest_bid_qty > lowest_ask_qty {// [!code focus] let new_qty = highest_bid_qty - lowest_ask_qty;// [!code focus] if new_qty.is_zero() {// [!code focus] self.bids.remove(&highest_bid_price);// [!code focus] } else {// [!code focus] self.bids.insert(highest_bid_price, (new_qty, highest_bid_offset));// [!code focus] }// [!code focus] } else if highest_bid_qty < lowest_ask_qty {// [!code focus] self.bids.remove(&highest_bid_price);// [!code focus] let new_qty = lowest_ask_qty - highest_bid_qty;// [!code focus] if new_qty.is_zero() {// [!code focus] self.asks.remove(&lowest_ask_price);// [!code focus] } else {// [!code focus] self.asks.insert(lowest_ask_price, (new_qty, lowest_ask_offset));// [!code focus] }// [!code focus] } else {// [!code focus] // Equal sizes, remove both// [!code focus] self.bids.remove(&highest_bid_price);// [!code focus] self.asks.remove(&lowest_ask_price);// [!code focus] }// [!code focus] }// [!code focus] } else {// [!code focus] // No crossing.// [!code focus] break;// [!code focus] }// [!code focus] }// [!code focus] }// [!code focus] // ... } ``` ::: ### Additional logic Now with an always up-to-date orderbook, implement your trading strategy based on this data. For simplicity here, we'll just print the current state. :::code-group ```python [Python] def print_orderbook():# [!code focus] """Print n levels"""# [!code focus] bids_list, asks_list = get_sorted_book()# [!code focus] print(f"\n--- Orderbook for {ETH_USD} ---")# [!code focus] # Print asks. # [!code focus] for price, size in reversed(asks_list): # [!code focus] print(f"ASK: {price:<12} | {size:<16}") # [!code focus] print("----------------") # [!code focus] # Print bids. # [!code focus] for price, size in bids_list: # [!code focus] print(f"BID: {price:<12} | {size:<16}") # [!code focus] print("")# [!code focus] # ... print_orderbook()# [!code focus] ``` ```typescript [TypeScript] const printOrderBook = ( bidList: Level[], askList: Level[] ): void => { console.log(`OrderBook for ETH-USD:`); console.log(`Price Qty`); const printRow = (price: number, qty: number) => { console.log(`${String(price).padEnd(10)}${qty}`); }; // Print asks (in reverse order). askList.reverse().forEach(([price, qty]) => { printRow(price, qty); }); console.log('---------------------'); // Print bids. bidList.forEach(([price, qty]) => { printRow(price, qty); }); console.log(''); }; ``` ```rust [Rust] impl fmt::Display for OrderBook { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // Print asks. for (price, (size, _)) in &self.inner.asks { writeln!(f, "ASK: {} - {}", price, size)?; } println!("---------------------"); // Print bids. for (price, (size, _)) in &self.inner.bids { writeln!(f, "BID: {} - {}", price, size)?; } Ok(()) } } // ... println!("{}", order_book); ``` ::: :::: :::info See more examples in the repository: [Python (TODO)](), [TypeScript](https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-js/examples/websocket_orderbook_example.ts), [Rust](https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/examples/support/order_book.rs). ::: ## Other deposit methods ### Deposit with Coinbase **Coinbase deposits** involve an **automatic Noble Wallet to dYdX IBC transfer** without needing a third-party bridge. #### Process Flow (Deposit) 1. **On dYdX, select "Coinbase" to display the Noble address associated with the trader's dYdX address.** * The trader can then scan the QR code on Coinbase using the Coinbase QR code scanner, or copy and paste the Noble address into the destination address on the Coinbase withdrawal modal. Traders should make sure the Noble address is correct when depositing from Coinbase. 2. **Open Coinbase app** and navigate to the USDC asset page 3. **Select "Send"** and choose "Coinbase Pay" as the destination 4. **Enter your Noble address wallet address** (starts with "noble1...") 5. **Enter the amount** you wish to transfer 6. **Confirm the transaction** in Coinbase 7. **Wait for confirmation** (typically 1-3 minutes) * Note: This process relies on Coinbase's infrastructure and Noble's IBC integration * Coinbase handles the initial funds transfer to Noble's USDC hub 8. **Funds will automatically route** through Noble to dYdX via IBC * This automatic routing uses the IBC relayer network * No swaps occur in this process as USDC moves directly between compatible chains #### Example Deposit Coinbase → dYdX ```json { "operations": [ { "transfer": { "from_chain_id": "noble-1", "to_chain_id": "dydx-mainnet-1", "denom_in": "uusdc", "denom_out": "ibc/USDC", "bridge_id": "IBC" } } ] } ``` ### Direct IBC Transfer For users familiar with the Cosmos ecosystem, direct IBC transfers provide a straightforward method to deposit funds. Supported Cosmos Chains: Osmosis, Cosmos Hub, Kujira, Noble, Injective and other IBC-enabled chains #### Process Flow (Deposit) 1. **Open your Cosmos wallet** (Keplr, Leap, etc.) 2. **Navigate to the IBC Transfer section** 3. **Select dYdX Chain as the destination** 4. **Enter your dYdX wallet address** 5. **Enter the amount** you wish to transfer 6. **Confirm the transaction** 7. **IBC relayer network processes the transfer:** * IBC relayers run by validators and third-party services handle the cross-chain message delivery * No centralized entity controls this process; it's based on the Cosmos IBC protocol * If transferring a non-native token, ensure it's an IBC-supported asset on both chains 8. **Wait for confirmation** (typically 15-30 seconds) * Faster than bridging solutions as it doesn't require multi-chain consensus ## Deposits and Withdrawals This guide provides a **step-by-step** explanation of deposit and withdrawal processes on the dYdX Chain. It includes instructions for Skip Go Fast (`Instant`), Skip Go (`regular`), Coinbase deposits, and direct IBC transfers, along with troubleshooting methods and best practices to ensure seamless transactions. :::note Note that for deposits > $20, skip go fast is the default option. ::: ### Deposit & Withdrawal Methods | Method | Description | Finality | Chains Supported | Fee Range (USD) | | :----------------------------------------------- | :------------------------------------------------------------------------ | :------------ | :---------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------- | | **Skip Go Fast (`Instant Deposit`)** | The fastest bridging option for rapid deposits | 10-30 seconds | Ethereum Mainnet, Arbitrum, Optimism, Base, Polygon, Avalanche | 10 bps on the transfer size + source chain fee: Ethereum: \~$2.5 L2s: $0.01-$0.10. No Fees for Fast deposits > $100 (eth), > $20 (L2s) | | **Skip Go (`regular`)** | A universal interoperability platform supporting multiple bridges | 2-30 minutes | Ethereum Mainnet, Arbitrum, Optimism, Base, Polygon, Avalanche, Solana, Neutron, Osmosis, Noble | Deposits \~$0.02 Withdrawals: \~$0.1-$7 + source chain gas fee: Ethereum: gas price L2s: gas price Cosmos: gas price Solana: gas price | | **Deposit / Withdrawal with Coinbase via Noble** | Direct deposit or withdrawal via Noble Wallet with automatic IBC transfer | 1-3 minutes | Coinbase to dYdX only | Coinbase withdrawal fee + IBC fee ($0.1-$0.2) | | **Direct IBC Transfer** | For users familiar with Cosmos ecosystem transfers | 15-30 seconds | All Cosmos chains with IBC enabled | \~$0.1-0.5 | ## Skip Go (`regular deposit`) Skip Go API is a **universal interoperability platform**, allowing **seamless swaps and transfers** across multiple blockchain ecosystems via **bridges such as CCTP and Axelar**. ### Supported Chains & Assets * **Chains:** Ethereum Mainnet, Arbitrum, Avalanche, Base, Optimism, Polygon, Solana, and Cosmos chains * **Assets:** USDC, ETH, POL ### Min & Max Transfer Sizes | Source Chain | Min Transfer (USD) | Max Transfer (USD) | | ---------------- | ------------------ | ------------------ | | Ethereum Mainnet | \~$0.05 | \~$1,000,000 | | Other EVM Chains | \~$0.05 | \~$1,000,000 | | Solana | \~$0.05 | \~$1,000,000 | | Cosmos Chains | \~$0.05 | \~$1,000,000 | ### Fees Source chain gas fees + Deposits \~$0.02 Withdrawals: \~$0.1-$7 | Source Chain | Fee (USD) | | ---------------- | --------------------------------------- | | Ethereum Mainnet | Deposits \~$0.02 Withdrawals: \~$0.1-$7 | | Other EVM Chains | Deposits \~$0.02 Withdrawals: \~$0.1-$7 | | Solana | Deposits \~$0.02 Withdrawals: \~$0.1-$7 | | Cosmos Chains | Deposits \~$0.02 Withdrawals: \~$0.1-$7 | ### Process Flow (Deposit) 1. **Connect your wallet** to the dYdX interface and navigate to the "Deposit" section 2. **Enter the amount** you wish to transfer (First time 1.25 USDC will be kept in wallet for gas fees) 3. **Choose source chain and asset** you wish to deposit 4. **Review the transaction details** including estimated fees and finality time 5. **Confirm and sign** the transaction from your wallet 6. **Third-party protocol interactions begin:** * If your source token is not USDC, an automatic swap occurs via integrated DEXs * Funds are sent to bridge contracts (CCTP, Axelar, etc.) based on optimal route * These bridges rely on external validators and relayers to verify cross-chain transactions 7. **Wait for confirmation** across all involved networks (may take 10-20 minutes) * Multiple relayer networks and validators must reach consensus * Each bridge and network has its own finality period 8. **Once confirmed**, funds are available in your dYdX account * Relayers monitor and trigger the final IBC transfer to dYdX Chain ### Example Deposit Ethereum → dYdX via Skip Go ```json { "operations": [ { "cctp_transfer": { "from_chain_id": "1", "to_chain_id": "noble-1", "denom_in": "USDC", "denom_out": "uusdc", "bridge_id": "CCTP" } }, { "transfer": { "from_chain_id": "noble-1", "to_chain_id": "dydx-mainnet-1", "denom_in": "uusdc", "denom_out": "ibc/USDC", "bridge_id": "IBC" } } ] } ``` ## Skip Go Fast (`Instant Deposit`) Skip Go Fast is a **decentralized bridging protocol**, developed by **Skip**, that enables **rapid and secure cross-chain transactions** across major blockchain ecosystems such as Ethereum, Cosmos, and Solana. Go Fast accelerates cross-chain actions by up to 25 times, **reducing onboarding times from 10+ minutes to seconds**. Learn more [here](https://docs.skip.build/go/advanced-transfer/go-fast#go-fast). ### Supported Chains & Assets * **Chains:** Ethereum Mainnet, Arbitrum, Avalanche, Base, Optimism, Polygon * **Assets:** USDC, ETH, POL ### Min & Max Transfer Sizes | Source Chain | Min Transfer (USD) | Max Transfer (USD) | | ---------------- | ------------------ | ------------------ | | Ethereum Mainnet | 50 | 100,000 | | Arbitrum | 10 | 100,000 | | Base | 10 | 100,000 | | Optimism | 10 | 100,000 | | Polygon | 10 | 100,000 | For the latest Minimum & Maximum Transfer Sizes, checkout the Skip API [documents](https://docs.skip.build/go/advanced-transfer/go-fast#what-are-the-minimum-and-maximum-transfer-sizes-for-go-fast%3F). :::info If starting with an asset that is not USDC, Skip Go will swap the asset to USDC on the source chain, and the post-swap amount is used to determine if it falls within the min/max transfer size limits. ::: ### Fees :::info * As of 24/6 Ethereum deposits > $100 route via skip go fast by default and are free of fees. * As of 8/7 EVM L2 deposits > $20 route via skip go fast by default and are free of fees. All other routes go via skip regular ::: All Skip Go Fast `10 bps` on the transfer size + source chain fee: | Source Chain | Fee (USD) | | ---------------- | ---------------------------------- | | Ethereum Mainnet | \~$2.5 (depends on gas fees) | | Arbitrum | \~$0.01-$0.1 (depends on gas fees) | | Base | \~$0.01-$0.1 (depends on gas fees) | | Optimism | \~$0.01-$0.1 (depends on gas fees) | | Polygon | \~$0.01-$0.1 (depends on gas fees) | For the latest source chain fees, checkout the Skip API [documents](https://docs.skip.build/go/advanced-transfer/go-fast#what-is-the-fee-model-for-go-fast%3F). ### Process Flow (Deposit) 1. **Connect your wallet** to the dYdX interface and navigate to the "Deposit" section 2. **Enter the amount** you wish to transfer (ensure it meets minimum requirements) 3. **Choose the source chain and asset** you wish to deposit 4. **Review the transaction details** including estimated fees and finality time 5. **Confirm and sign** the transaction from your wallet 6. **Skip Go Fast protocol's solvers** monitor for confirmation of fund arrival at Noble * This process relies on Skip's decentralized solver network * If your source token is not USDC, an automatic swap occurs via integrated DEXs 7. Once confirmed, funds are automatically sent to dYdX Chain via IBC transfer * This step uses IBC relayers to complete the cross-chain transfer 8. Final step Funds are moved from the main account to the subaccount for trading * This internal transfer is handled by dYdX Chain’s infrastructure ### How Skip Go Fast Works Skip Go Fast uses a solver-based approach to achieve near-instant finality: :::steps ##### **User Intent Submission** * When you initiate a transfer, you call the `submitOrder` function on the protocol contract * This broadcasts your intent to transfer assets across chains * Your intent specifies the assets, destination address, and any additional message payload ##### **Solver Network** * Permissionless participants called "solvers" watch for these intents * Solvers evaluate whether they can fulfill your intent based on: * Their available liquidity on the destination chain * Potential reward for fulfilling the intent * When a solver agrees to fulfill your intent, they use their own capital to front the assets ##### **Instant Fulfillment** * The solver calls the `fillOrder` function on the destination chain * This transfers the specified assets and processes any additional actions * From your perspective, the assets appear on the destination chain almost instantly ##### **Settlement Process** * After fulfilling your transfer, the solver initiates settlement to recover their fronted assets * The protocol verifies the solver's actions via a cross-chain messaging system * Once confirmed, the solver receives their assets back plus any earned fees * This settlement happens in the background and doesn't affect your user experience. ::: ### Example deposit Base → dYdX via Skip Go Fast ```json { "operations": [ { "evm_swap": { "from_chain_id": "8453", "denom_in": "base-native", "denom_out": "USDC", "amount_in": "10511954965182950", "amount_out": "21430265", "swap_venues": [{ "name": "base-uniswap" }] } }, { "cctp_transfer": { "from_chain_id": "8453", "to_chain_id": "noble-1", "denom_in": "USDC", "denom_out": "uusdc", "bridge_id": "CCTP" } }, { "transfer": { "from_chain_id": "noble-1", "to_chain_id": "dydx-mainnet-1", "denom_in": "uusdc", "denom_out": "ibc/USDC", "bridge_id": "IBC" } } ] } ``` ## Withdrawal and Deposit Troubleshooting ### **Common Deposit Issues** :::steps ##### **Funds not appearing in your dYdX account** * Verify transaction succeeded on source blockchain explorer * Check Noble explorer for IBC transfer confirmation * Ensure you've connected the correct wallet to dYdX interface; this is important for the autosweeping to happen from noble to dYdX chain and to sweep into your dYdX subaccount * Check [skip explorer](https://explorer.skip.build/) or [Range Tracker Tool](https://usdc.range.org/usdc) to see if relayers have picked up your transaction * Wait at least 30 minutes for all confirmations to complete ##### **Transaction stuck or pending** * For EVM chains, check if gas price was too low * Verify if transaction was rejected on source chain * For Skip bridges, check status on [skip explorer](https://explorer.skip.build/) or [Range](https://usdc.range.org/usdc) to see if relayers have picked up your transaction * Check if relayer networks are experiencing delays or outages * Verify all involved third-party services are operational ##### **Insufficient funds error** * Ensure you're accounting for network fees in addition to transfer amount * Verify minimum transfer requirements are met * For swaps, account for price impact and slippage ##### **Failed at swap stage** * Check if the DEX had sufficient liquidity for your swap * Verify slippage settings were appropriate for market conditions * Consider trying another deposit method that doesn't require a swap ::: ### **Bridge-Specific Troubleshooting** For detailed troubleshooting guides specific to each bridge, please refer to: #### Bridge Troubleshooting guides :::steps ##### **Skip Transaction Troubleshooting** * [Skip Documentation Portal](https://docs.skip.build/go/general/transaction-support) * Input *tx\_hash* and source chain *chain\_id* into [Skip API](https://explorer.skip.build/) ##### **CCTP Troubleshooting Guide** * [skip explorer](https://explorer.skip.build/) * [Circle CCTP Status Page](https://status.circle.com) * [USDC Tracker Tool](https://usdc.range.org/usdc) * [dYdX Chain Explorer](https://www.mintscan.io/dydx) * [Noble Chain Explorer](https://www.mintscan.io/noble) * [dYdX CCTP Documentation](https://dydx.exchange/blog/cctp) * **Source Chain Explorer**: Etherscan, Arbiscan, etc. ##### **IBC Transfer Issues** * [Mintscan IBC Explorer](https://www.mintscan.io/ibc) * [IBC Relayer Status](https://ibc.range.org) ##### **Relayer issues** * Check status pages for relayer networks involved in your transaction * Wait for relayer networks to resume normal operation if experiencing downtime * Consider using an alternative deposit method if persistent issues occur ::: #### Troubleshooting bridging issues If you encounter persistent bridging issues, follow these steps: :::steps ##### **Identify where your funds are currently located** * Use block explorers for each relevant chain (source, Noble, dYdX) * For Skip transactions, check the `transfer_asset_release` field in the [API response](https://docs.skip.build/go/api-reference/prod/transaction/get-v2txstatus) ##### **Try manual recovery methods if needed** * For IBC: Use Keplr or Leap wallet to manually complete pending transfers * For CCTP: Follow the manual process described in the CCTP section * For Skip: Contact Skip support through their Discord ##### **Contact appropriate support** * **Check the [dYdX Status Page](https://status.dydx.exchange)** for any known issues * **Join the [dYdX Discord](https://discord.gg/dydx)** for community support * **Open a support ticket** via the dYdX interface * **Skip issues:** [Skip Discord](https://discord.gg/skip-protocol) * **Noble issues:** [Noble Discord](https://discord.gg/noble) ::: Remember to include transaction details, wallet addresses, and a clear description of the issue for faster resolution. ### **Best Practices** 1. **Always start with a small test transaction** when using a new deposit or withdrawal method 2. **Keep your wallet connected** to dYdX frontend for auto-sweeping 3. **Save transaction hashes** for all operations 4. **Double-check all addresses** before confirming transactions 5. **Ensure your destination wallet supports** the asset you're withdrawing 6. **For large transfers, use Skip Go** instead of Skip Go Fast for better reliability 7. **Always move funds from sub account to main account** before initiating withdrawals 8. **Understand third-party dependencies** in your chosen transfer route: * Skip relies on their own solver network and DEX integrations * IBC transfers depend on the Cosmos relayer infrastructure * Coinbase deposits rely on Coinbase's infrastructure and Noble's IBC integration 9. **Monitor relayer and bridge status** during high network congestion periods 10. **Have backup withdrawal methods** in case one bridge or relayer network experiences issues ## Withdrawal Process Withdrawing from dYdX Chain requires first moving funds from your trading subaccount to your main account before bridging to your destination chain. ### Process Flow (Withdrawal) 1. **Connect your wallet** to the dYdX interface and Navigate to "Withdraw" section 2. **Choose destination chain and enter destination address** 3. **Enter withdrawal amount** 4. **Review transaction details** * Pay attention to the relayers and bridges involved in your specific route 5. **Confirm and sign the transaction** 6. **Third-party services process your withdrawal:** * For Skip methods: Relayers monitor for your transaction and execute cross-chain transfers * For IBC transfers: IBC relayer network handles the IBC * Multiple validators may need to confirm your transaction depending on the route 7. **Wait for confirmation** across all networks * Timeframes vary based on network congestion and the third-party services involved ### Withdrawal Timeframes | Withdrawal Method | Approximate Time | | ----------------- | ---------------- | | Skip Go | 1-5 minutes | | Direct IBC | 30 seconds | ## Builder Codes Builder codes enables external parties to submit orders to dYdX and collect fees (per-order) for building and routing an order to the exchange. The address and fee, in parts per million, needs to be configured via the `BuilderCodeParameters` in the order message itself. The fee will be paid out when the given order is filled. **Important:** Builder code fees are added **on top of each fill**, as opposed to revenue share where the fee revenue is split. No governance proposal is required to use builder codes. Builder fees and addresses can be queried via the indexer using the `/orders` and `/fills` endpoints as usual. `/orders` contains details on the fee rate and builder address. `/fills` also contains the builder address as well as details on the amount charged per-fill. ## Placing orders and verifying Order Router Address in Fills :::steps ### BuilderCodeParameters `BuilderCodeParameters` is an addition to the order message which will specify: * `builderAddress` - where fees will be routed * `feePpm (in ppm)` that will be charged on order matching ```python [Python] import asyncio import random from dydx_v4_client import MAX_CLIENT_ID, OrderFlags from v4_proto.dydxprotocol.clob.order_pb2 import Order from dydx_v4_client.indexer.rest.constants import OrderType from dydx_v4_client.indexer.rest.indexer_client import IndexerClient from dydx_v4_client.network import TESTNET from dydx_v4_client.node.client import NodeClient from dydx_v4_client.node.market import Market from dydx_v4_client.wallet import Wallet from tests.conftest import DYDX_TEST_MNEMONIC, TEST_ADDRESS MARKET_ID = "ETH-USD" node = await NodeClient.connect(TESTNET.node) indexer = IndexerClient(TESTNET.rest_indexer) market = Market( (await indexer.markets.get_perpetual_markets(MARKET_ID))["markets"][MARKET_ID] ) wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC, TEST_ADDRESS) order_id = market.order_id( TEST_ADDRESS, 0, random.randint(0, MAX_CLIENT_ID), OrderFlags.SHORT_TERM ) current_block = await node.latest_block_height() new_order = market.order( order_id=order_id, order_type=OrderType.MARKET, side=Order.Side.SIDE_SELL, size=size, price=0, # Recommend set to oracle price - 5% or lower for SELL, oracle price + 5% for BUY time_in_force=Order.TimeInForce.TIME_IN_FORCE_UNSPECIFIED, reduce_only=False, good_til_block=current_block + 10, builder_address=TEST_ADDRESS, fee_ppm=500, ) transaction = await node.place_order( wallet=wallet, order=new_order, ) print(transaction) wallet.sequence += 1 await asyncio.sleep(5) fills = await indexer.account.get_subaccount_fills( address=TEST_ADDRESS, subaccount_number=0, ticker=MARKET_ID, limit=1 ) print(f"Fills: {fills}") assert fills["fills"][0]["builderAddress"] == TEST_ADDRESS ``` ### Order Validation Checks * Ensure the `builder address` is valid * Ensure the `feePpm` is in the range `(0, 10,000]` ::: ## Compliance ### Terms of Service Per the [terms of service](https://dydx.exchange/v4-terms) of the open-source software, the following categories of persons are currently prohibited from using dYdX Software: * Persons or entities who reside in, are located in, are incorporated in, have a registered office in, or are operated or controlled from the United States or Canada; * Persons or entities who reside in, are citizens of, are located in, are incorporated in, have a registered office in, or are operated or controlled from Iran, Cuba, North Korea, Syria, Myanmar (Burma), the regions of Crimea, Donetsk or Luhansk, or any other country or region that is the subject of comprehensive country-wide or region-wide economic sanctions by the United States; * Persons or entities subject to any economic or trade sanctions administered or enforced by any governmental authority or otherwise designated on any list of prohibited or restricted parties (including the list maintained by the Office of Foreign Assets Control of the U.S. Department of the Treasury); * Any other persons or entities whose use of dYdX Software is contrary to any applicable law. Third parties integrating with the dYdX open-source software are expected to comply with these terms. The client application should block the user from certain countries (see the [list of countries](https://github.com/dydxprotocol/v4-web/blob/633d38dfb837cd80bf2e3e007ecdcaeee2acc658/src/constants/geo.ts#L246) currently blocked). ### Geo-Blocking Implementation :::steps #### /v4/geo Third parties are expected to implement geo-blocking themselves but can rely on location information provided by the indexer’s `/v4/geo` endpoint: ```bash curl https://api.dydx.exchange/v4/geo {"geo":{"country":"JP","region":"Tokyo","regionCode":"13","city":"Tokyo","timezone":"Asia/Tokyo","ll":[35.6164,139.7425],"metro":null,"blocked":false} ``` This will show the user's compliance status based on the requesting IP. #### `/v4/screen` The client can additionally query the compliant status of the user by calling the indexer’s `/v4/screen` endpoint: ```bash curl https://indexer.v4testnet.dydx.exchange/v4/compliance/screen/dydx14y0wd820uzr5rd6xu85q5475rg5sk03fsuke3m {"status":"COMPLIANT","reason":null,"updatedAt":"2024-10-24T20:26:15.714Z"} ``` For further information or questions, we can set up a meeting with dYdX’s Legal to discuss specific implementations. ::: ## Data sources The client application would need the obtain the data from the following sources: ### Data sources :::steps #### [Indexer](/indexer-client) The Indexer aggregates blockchain data to provide a Web2 interface for client applications. Clients access updated market data, order books, and user account information through REST and WebSocket endpoints. All data from the Indexer is read-only. #### [Node Client](/node-client) The client application submits transactions to the blockchain through the validator and also reads data from it. It uses v4-client-js functions to streamline read and write operations. #### Metadata Service Static market data, including market icons, names, and various URLs, can be retrieved from this REST service. There are two endpoints (use POST instead of GET): * `metadata-service/v1/info`: Returns a list of market metadata: ```bash curl --request POST 'https://66iv2m87ol.execute-api.ap-northeast-1.amazonaws.com/mainnet/metadata-service/v1/info ``` * `metadata-service/v1/prices`: Returns a list of spot market pricing information. Sample curl request: ```bash curl --request POST 'https://66iv2m87ol.execute-api.ap-northeast-1.amazonaws.com/mainnet/metadata-service/v1/prices' ``` ::: ## Market data ### Market data The client application typically displays a list of tradable and launchable markets as follows: ![markets1](/markets1.png) ![markets2](/markets2.png) The market list can be obtained from the Indexer in two ways: 1. REST API: Call `GET` [/perpetualMarkets](/indexer-client/http#get-perpetual-markets). 2. WebSocket Subscription: Subscribe to the [v4-markets](/indexer-client/websockets#markets) channel (preferred, as it provides real-time updates). Additionally, the client should call `metadata-service/v1/info` to retrieve static market data, such as market icons and other metadata. #### Market Data Display Fields (a)`Market Icon`: Extracted from the logo field in the `metadata-service/v1/info` response. (b) `Market Name`: Derived from the name field in `metadata-service/v1/info` or directly from the market token name in the Indexer data. (c) `Oracle Price`: Provided by the Indexer. (d) `Perp 24h Price Change (%)`: Available in the Indexer’s `priceChange24H` field. (e) `Perp Trade Volume`: Extracted from the Indexer’s `volume24H` field. (f) `Spot Market Cap`: Found in the `market_cap` field of the `metadata-service/v1/prices` response. (g) `User’s Buying Power`: Determined by applying collateral to the market (explained in the user-specific data section). (h) `Perp Open Interest`: Retrieved from the Indexer’s `openInterest` field. (i)` Perp Funding Rate`: Available in the Indexer’s `nextFundingRate` field. (j) `Market Descriptions`: Sourced from the `v4-localization` repository, using the token ID as the key. (k) `Market Info Links`: Obtained from the url field in `metadata-service/v1/info`. ### Candles The candles data come from the websocket `v4-candles` channel. To subscribe the channel, supply the following parameters: ```bash { “id”: “ETH-USD/1HOUR” “batch”: “true } ``` Supply the id field with the market ID and the [candle resolution](/types/candle_resolution). Parse [CandleResponse](/types/candle_response_object) to get the list of the candle values. ![candles](/candles.png) (a) `Candlestick`. Use the `high`, `low`, `open` and `close` field from each data point. See [here](https://www.investopedia.com/trading/candlestick-charting-what-is-it/) for candles. (b) `Volume`. Use the `usdVolume` field. (c) `Candle resolution` [candle resolution](/types/candle_resolution). ### Orderbook The orderbook data come from the websocket [v4\_orderbook](/indexer-client/websockets#orders) channel, which gives a list of asks and bids: ```bash "bids": [ { "price": "28194", "size": "4.764826096" }, ... ], "asks": [ { "price": "28294", "size": "5.764826096" }, ... ], ``` #### Order Book Displaying Fields ![orderbook](/orderbook.png) (a) `Size of the order` (b) `Price of the order` #### Order Book Color * `Darker green bar`: Represents the size of an `individual order`. * `Light green bar`: Represents the `depth at this price`, calculated as the `sum of all order sizes` that would have been taken before this order is crossed. See [here](https://academy.youngplatform.com/en/trading/order-book/) for depth. #### Handling WebSocket Updates When the `WebSocket channel` sends an update, the client `must update the order book` in memory using the order price as the key. :::steps ##### Add/Update/Delete Entries * `Add or update` an entry if the backend sends an order with a non-zero size. * `Remove` an entry if the backend sends an order with `zero size` (indicating the order was canceled or taken). ##### Remove Crossed Entries * After updating the order book, the client should `remove any crossed entries` when the lowest ask price > highest bid price. * This ensures that only valid bid-ask pairs remain in the order book. ::: ## Onboarding ### dYdX address The client application onboards the user by asking the user to sign a message using an existing wallet address. The v4-client-js library provides a [function](https://github.com/dydxprotocol/v4-clients/blob/a2c7adcc64b33fefaf56ffb6fc1d2bb8b174601e/v4-client-js/src/lib/onboarding.ts#L43) that deterministically generates the dYdX chain address (see [accounts](/concepts/trading/accounts#main-account)) and keys from the signed message. Once the keys are generated, the client application can use them to sign transactions on the dYdX chain on the user’s behalf, enabling secure and seamless interaction with the platform. The payload doesn't matter to generate a dYdX address, but if you want to deterministically generate a dYdX address that matches what the dYdX Frontend generates, it is best to have the same payload message ```json "message": { "action": "dYdX Chain Onboarding" } ``` The payload typically looks like this: :::details ```ts const ethersWallet = new ethers.Wallet(''); const provider = new JsonRpcProvider('https://ethereum.publicnode.com', 1); const signer = ethersWallet.connect(provider); const signature = await signer.signTypedData( { name: 'dYdX Chain', chainId: 1, }, { dYdX: [{ name: 'action', type: 'string' }], }, { action: 'dYdX Chain Onboarding', }, ); const { mnemonic, privateKey, publicKey } = deriveHDKeyFromEthereumSignature(signature); ``` ::: #### Subaccounts Each main account has 128,001 [subaccounts](/concepts/trading/accounts#subaccounts) Subaccounts 0 to 127 are parent subaccounts. Parent subaccounts can have multiple positions opened and all positions are cross-margined. Subaccounts 128 to 128,000 are child subaccounts and are isolated margin. Child subaccounts will only ever have up to 1 position open. ### Deposits and Withdrawals :::note Skip has an `allow_unsafe` parameter, which is set to False by default. It is recommended to keep it to False and **NOT** recommended to be set to True, as it can lead to bad trade execution for users, see more on Skip go best practises [here](https://docs.skip.build/go/advanced-swapping/allow_unsafe-preventing-handling-bad-execution) and [here](https://docs.skip.build/go/advanced-swapping/safe-swapping-how-to-protect-users-from-harming-themselves). ::: #### Deposits Deposits are in three steps. :::steps #### Deposit Tx First, the client calls Skip’s API to construct the transaction that routes the fund from the source chain to Noble. Users would need to sign and send this transaction from their wallet. #### Noble poll The client then polls the balance at the user's Noble address, and when the fund arrives, it would sign (using the user’s dYdX key pair) and send it over to the dYdX chain (see [here](https://github.com/dydxprotocol/v4-web/blob/71bd9c7f85512fe1893fd656968011cf75b106e6/src/bonsai/lifecycles/nobleBalanceSweepLifecycle.ts#L119)). #### Transfer Subaccount Lastly, after the fund arrives at dYdX, the client moves the fund from the user's main account to the [subaccount](/node-client/private#transfer) for trading. ::: #### Withdrawals The withdrawal is the reverse process of deposit. :::steps #### Transfer Main Account First, the fund needs to be moved from the user's subaccount to the main account. #### Withdrawal Tx Then, a withdrawal transaction is constructed to send the fund to the destination chain/address. The withdrawal transaction contains two child transactions: 1. sending from dYdX chain to Noble chain, 2. Noble to destination chain (obtained from the Skip route API). See [here](https://github.com/dydxprotocol/v4-web/blob/71bd9c7f85512fe1893fd656968011cf75b106e6/src/hooks/useSubaccount.tsx#L156). ::: ### Skip Go API Users must deposit Noble USDC as collateral into the dYdX chain to begin trading. The dYdX chain uses USDC. To construct the route for deposits and withdrawals, we recommend using Skip Go, either through the [Typescript client](https://docs.skip.build/go/client/getting-started) or by calling the [APIs](https://docs.skip.build/go/general/getting-started) directly. There are endpoints to get the supported [chains](https://docs.skip.build/go/api-reference/prod/info/get-v2infochains) and [tokens](https://docs.skip.build/go/api-reference/prod/fungible/get-v2fungibleassets). To initiate a deposit or withdrawal, :::steps #### Call endpoint first call the [route](https://docs.skip.build/go/api-reference/prod/fungible/post-v2fungibleroute) endpoint to fetch the route. Sample route fetch request for deposit looks like this: ```bash curl 'https://api.skip.build/v2/fungible/route' \ -H 'accept: application/json, text/plain, */*' \ -H 'content-type: application/json' \ --data-raw '{"source_asset_denom":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","source_asset_chain_id":"1","dest_asset_denom":"ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5","dest_asset_chain_id":"dydx-mainnet-1","cumulative_affiliate_fee_bps":"0","allow_unsafe":true,"smart_relay":true,"smart_swap_options":{"split_routes":false,"evm_swaps":true},"amount_in":"10000000"}' ``` Here `ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5` is the USDC denom of the dYdX Chain. #### Set Bridge option Set the [bridges](https://docs.skip.build/go/api-reference/prod/fungible/post-v2fungibleroute#body-bridges) option to include `“CCTP”`, `“GO_FAST”`, `“IBC”`, and `“AXELAR”`.\ You can also set the [go\_fast](https://docs.skip.build/go/api-reference/prod/fungible/post-v2fungibleroute#body-go-fast) to `true` for selected chains/and tokens (see [here](https://docs.skip.build/go/advanced-transfer/go-fast)), which would return a route that can be completed within 10 seconds. The route endpoint will return the estimated time and fees for the transfer that can be shown to the user. #### Msgs endpoint After user accepts the route, call the [msgs](https://docs.skip.build/go/api-reference/prod/fungible/post-v2fungiblemsgs) endpoint and passing the operations returned from the `route` response payload to fetch the transaction data: ```bash curl 'https://api.skip.build/v2/fungible/msgs' \ -H 'accept: application/json, text/plain, */*' \ -H 'content-type: application/json' \ --data-raw '{"source_asset_denom":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","source_asset_chain_id":"1","dest_asset_denom":"ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5","dest_asset_chain_id":"dydx-mainnet-1","amount_in":"10000000","amount_out":"9980000","address_list":["0x0765CA6d3DC4fa6d6638781BA8414A1f5eFbfAd8","dydx1k93udthd0vtzjk465f846qzea3fzq7axnmfqyz"],"operations":[{"tx_index":0,"amount_in":"10000000","amount_out":"9980000","cctp_transfer":{"from_chain_id":"1","to_chain_id":"noble-1","burn_token":"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48","bridge_id":"CCTP","denom_in":"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48","denom_out":"uusdc","smart_relay":true,"smart_relay_fee_quote":{"fee_amount":"20000","relayer_address":"noble1dyw0geqa2cy0ppdjcxfpzusjpwmq85r5a35hqe","expiration":"2025-03-14T00:52:56Z","fee_payment_address":"0xBC8552339dA68EB65C8b88B414B5854E0E366cFc","fee_denom":"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"}}},{"tx_index":0,"amount_in":"9980000","amount_out":"9980000","transfer":{"port":"transfer","channel":"channel-33","from_chain_id":"noble-1","to_chain_id":"dydx-mainnet-1","pfm_enabled":true,"supports_memo":true,"denom_in":"uusdc","denom_out":"ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5","bridge_id":"IBC","dest_denom":"ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5","chain_id":"noble-1","smart_relay":false}}],"estimated_amount_out":"9980000","slippage_tolerance_percent":"1"}' ``` Here, `dydx1k93udthd0vtzjk465f846qzea3fzq7axnmfqyz` is the user's dYdX chain address obtained from the Onboarding step. To obtain the relay address for Noble, Osmosis and Neutron, do conversion from the dYdX address as follows ```bash dydx_address="dydx1kgjgvl3xer7rwskp6tlynmjrd2juas6nqxn8yg" noble_address=bech32.bech32_encode("noble", bech32.bech32_decode(dydx_address)[1]) osmosis_address=bech32.bech32_encode("osmo", bech32.bech32_decode(dydx_address)[1] neutron_address=bech32.bech32_encode("neutron", bech32.bech32_decode(dydx_address)[1]) ``` #### Submit TX The `route` and `msgs` calls can be combined into one call to [msgs\_direct](https://docs.skip.build/go/api-reference/prod/fungible/post-v2fungiblemsgs_direct). To submit the transaction, the transaction payload data from the `msgs` or `msgs_direct` should be submitted via the user wallet. ::: #### Go Fast Supported Tokens & Chains * Go Fast supports `any EVM` asset on the supported source chains that can be swapped to USDC. * Any asset on the `Cosmos side` that can be swapped from USDC is also supported. * This includes `instant deposit` support for assets like ETH on Ethereum, which can be instantly converted to USDC on dYdX. * If a chain has `instant finality` and `IBC compatibility`, transfers can be near-instant, making it functionally similar to Instant Deposit. * `Chains not supported by CCTP` will not have Instant Deposit functionality but can still use IBC for transfers. * For an exhaustive list of supported tokens, reference the [Skip Go documentation](https://docs.skip.build/go/advanced-transfer/go-fast). ## User Portfolio After onboarding to dYdX and depositing funds, the app displays the portfolio view. Once the user places a trade, the app updates to show open positions. ### Portfolio Data Display Fields ![portfolio1](/portfolio1.png) ![portfolio2](/portfolio2.png) (a) `Account equity`: Indexer data from the websocket `v4_parent_subaccounts` channel. [response](/types/parent_subaccounts_update_message). This is equity of subaccount 0 (e.g., the sum of all child subaccount equities). (b) `Account PnL`: `Account PNL` of the selected time range (e.g., it’s “7 days” on the screenshot). Indexer data [GetHistoricalPnlForParentSubaccount](/indexer-client/http#get-parent-historical-pnl). Sample curl request: ```bash curl --location 'https://indexer.v4testnet.dydx.exchange/v4/historical-pnl/parentSubaccountNumber?address=dydx1k93udthd0vtzjk465f846qzea3fzq7axnmfqyz&parentSubaccountNumber=0' ``` (c) `max buying power`: The endpoint returns the PNL ticks of the given time range. The value is the difference between the last and first tick. Account level buying power. The Indexer provides data via the WebSocket `v4_parent_subaccounts` channel. [response](/types/parent_subaccounts_update_message). :::steps ##### Calculate max leverage Each market has a configured maximum leverage factor ranging from 1x to 50x. The leverage factor is derived from the `initialMarginFraction` field in the market info received from the WebSocket [v4-markets](/indexer-client/websockets#markets) channel. ```bash max leverage = 1 / initialMarginFraction ``` ##### Max Buying power The `max buying power` is then calculated as follows: * Take the `freeCollateral` of `subaccount 0` * Multiply it by the `maximum leverage` across all markets (50x) This represents the user's maximum buying power if all remaining free collateral is applied to the market with the highest leverage. ::: (d) `Account level risk` The account-level risk is derived from Indexer data via the WebSocket `v4_parent_subaccounts` channel. [sample response](/types/parent_subaccounts_update_message).\ This metric represents the `percentage of the user's total collateral` that has been allocated to support open positions (also referred to as `margin`). The account-level risk is derived from Indexer data via the WebSocket `v4_parent_subaccounts` channel. [sample response](/types/parent_subaccounts_update_message). To compute `total collateral`, follow these steps: :::steps ##### Collateral per position Calculate the collateral for each open position: ```bash position collateral = (current value of the position) * leverage = (position size * current oracle price) * leverage ``` ##### Sum collateral Sum up the collaterals for all open positions. ##### Total collateral Add the USDC asset position of subaccount 0, which represents the remaining USDC balance that hasn’t been used as collateral for open positions. The resulting `total collateral` is then used to determine the `margin percentage`, reflecting the level of risk associated with the account’s open positions. ::: (e) `Historical PNL`. Indexer data [GetHistoricalPnlForParentSubaccount](/indexer-client/http#get-parent-historical-pnl). The chart shows the PNL tickets of the selected time range. (f) `Position size, and position side` Indexer data from the websocket `v4_parent_subaccounts` channel. [sample response](/types/parent_subaccounts_update_message). Subaccount -> ChildSubaccounts -> openPerpetualPositions -> size/side. (g) `Current oracle price`. From market data off the Websocket [v4-markets](/indexer-client/websockets#markets) channel. (i (img 1)) `Perp 24h price change percentage`: From market data off the Websocket [v4-markets](/indexer-client/websockets#markets) channel, priceChange24H field of childSubaccount. (i (img 2)) `Position USDC size`. Position size \* oracle price (j) `Position PNL`. (Position size \* oracle price) - (Position size \* entry\_price) (k) `Current funding rate`. netFunding, of childSubaccount. (l) `Entry price`. entryPrice, of childSubaccount (m) `Liquidation price`. When the market price moves across this estimated price, the user's position would be liquidated. See [margining requirements](/concepts/trading/margin) ### History The trade history data come from the Indexer’s [getfillsforparentsubaccount](/indexer-client/http#get-parent-fills) endpoint. Transfer history data come from the Indexer’s [gettransfersforparentsubaccount](/indexer-client/http#get-parent-transfers) endpoint. ![history](/history.png) ## Revenue Share Order Router Rev Share enables third-party order routers to direct orders to dYdX and earn a portion of the trading fees (maker and taker). The revenue share, specified in parts per million (ppm), must be voted in via Governance and set in the `order_router_address` field in the order message itself. The revenue will be distributed based on filled orders that were routed through the participating order router. Order router details and revenue share percentages can be monitored through the indexer using the `/orders` and `/fills` endpoints. The `/orders` endpoint provides information on the order router address, while the `/fills` endpoint shows the order router address and the specific revenue amount distributed per fill. To participate in the Order Router Rev Share program, users need to propose and have a passing vote for their order router address & revenue split. * Affiliate revenue takes priority in the distribution hierarchy. * If there is an active affiliate split that has not reached its maximum within a 30-day rolling window, no revenue will be shared with the order router ### Implementation Details :::steps ### Voting in via Governance To participate in the Order Router Rev Share program, you need to create and submit a governance proposal. Since governance proposals require adding gas, deposit, and other parameters, it's recommended to create the proposal using a JSON file and submit it via CLI command. For an example of the governance proposal JSON structure, see [proposal 311 on Mintscan](https://www.mintscan.io/dydx/proposals/311). The key components of the proposal message are: * `address` - The address of the order router that will receive the revenue share. This is also the id you place in your order message * `share_ppm` - The revenue share percentage in parts per million (ppm) After submitting the proposal, it must go through the standard governance voting process and receive a passing vote before the order router address and revenue share percentage are activated in the system. ### Placing orders with order rev share address and verifying Order Router Address in Fills The `order_router_address` field is set when an order is placed * `order_router_address` - the ID of the order router and where fees will be sent to ```python [Python] import asyncio import random import time from dydx_v4_client import MAX_CLIENT_ID, OrderFlags from dydx_v4_client.indexer.rest.constants import OrderType from dydx_v4_client.indexer.rest.indexer_client import IndexerClient from dydx_v4_client.key_pair import KeyPair from dydx_v4_client.network import TESTNET from dydx_v4_client.node.client import NodeClient from dydx_v4_client.node.market import Market from dydx_v4_client.node.message import order_id from dydx_v4_client.wallet import Wallet from tests.conftest import TEST_ADDRESS_2, TEST_ADDRESS, DYDX_TEST_MNEMONIC from v4_proto.dydxprotocol.clob.order_pb2 import Order node = await NodeClient.connect(TESTNET.node) indexer = IndexerClient(TESTNET.rest_indexer) MARKET_ID = "ETH-USD" market = Market( (await indexer.markets.get_perpetual_markets(MARKET_ID))["markets"][ MARKET_ID ] ) wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC, TEST_ADDRESS) order_id = market.order_id( TEST_ADDRESS, 0, random.randint(0, MAX_CLIENT_ID), OrderFlags.SHORT_TERM ) current_block = await node.latest_block_height() new_order = market.order( order_id=order_id, order_type=OrderType.MARKET, side=Order.Side.SIDE_SELL, size=0.0001, price=0, # Recommend set to oracle price - 5% or lower for SELL, oracle price + 5% for BUY time_in_force=Order.TimeInForce.TIME_IN_FORCE_UNSPECIFIED, reduce_only=False, good_til_block=current_block + 10, order_router_address=TEST_ADDRESS_2, ) transaction = await node.place_order( wallet=wallet, order=new_order, ) print(transaction) await asyncio.sleep(5) fills = await indexer.account.get_subaccount_fills( address=TEST_ADDRESS, subaccount_number=0, limit=1 ) print(f"Fills: {fills}") response = await node.get_market_mapper_revenue_share_param() print(response) response = await node.get_order_router_revenue_share(TEST_ADDRESS_2) print(response) ``` ### Order Validation Checks * Ensure the `order_router_address`(TEST\_ADDRESS\_2) field is valid and already voted in via governance ::: ## Placing a Trade ### Market Order (Simple Trade) Market trade lets users execute an order based on the current state of the orderbook. ![marketorder](/marketorder.png) (a) `Trade size in USD`. (b) `Trade size in native token`. This value should be dynamically updated based on the trade size in USD and the current state of the order book. Order Fulfillment Process (See [link](https://github.com/dydxprotocol/v4-abacus/blob/f3802563c06422eb5a3de0b1e48719fb279fab71/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/TradeInput/TradeInputMarketOrderCalculator.kt#L379) for an implementation): 1. Iterate through the order book from the top (best price first). 2. Attempt to fill the order by consuming liquidity until the requested USDC size is met. 3. Sum up the native token size from the orders used in the process. The toggle button to the right would toggle the input between (a) and (b). (c) `Maximum amount` that can be entered into (a). The maximum order amount is constrained by: 1. The maximum leverage of the selected token market. 2. The user’s remaining free collateral. This is similar to previous calculations, but with an additional consideration for current pending orders. #### Adjustments for Existing Positions * If the proposed order modifies an existing position in the same token market, the displayed buying power must be adjusted accordingly: * If the new order is in the same direction (LONG on top of LONG, SHORT on top of SHORT): The buying power remains unchanged. * If the new order is in the opposite direction (LONG on top of SHORT, SHORT on top of LONG): The buying power increases since closing the existing position frees up collateral. Example Calculation: * Current buying power: $500 * Existing position: LONG ETH-USD, worth $100 | New Order Type | Adjusted Buying Power | | -------------- | ------------------------ | | LONG ETH-USD | $500 (unchanged) | | SHORT ETH-USD | $700 (increased by $200) | This adjustment ensures that users see the correct available buying power based on their current open positions and pending orders. (d) `Account level risk`. Same as here, except taking into account the current pending order. (e) `Fees`. Each user belongs to a fee tier depending on the volume of trades that have executed. The actual fee is the USD size of the trade multiplied by the fee rate associated with the fee tier. To get the [current fee tier](/node-client/public/get_user_fee_tier) of the user, call validator’s RPC UserFeeTier endpoint (see [example](https://github.com/dydxprotocol/v4-clients/blob/9e77056451944c15c4625854da96de5e023e429d/v4-client-js/src/clients/modules/get.ts#L99)). #### Market Order Submission To submit a market order, the client can use the [placeOrder](/node-client/private#place-order) function of the v4-client-js library with the following parameters: * type=”MARKET” * timeInForce = “IOC” * price = value from (a) / value from (b) \* (1+slippage\_threhold), where slippage\_threhold = 0.05 * clientId = \[some unique random id] The validator will return an error if the input is invalid, meaning client-side validation is not strictly necessary. However, it is recommended that the client application validates user input before submitting an order to enhance the user experience by preventing unnecessary errors and reducing failed transactions [sample validation logic](https://github.com/dydxprotocol/v4-abacus/blob/f3802563c06422eb5a3de0b1e48719fb279fab71/src/commonMain/kotlin/exchange.dydx.abacus/validator/TradeInputValidator.kt#L26). Pending orders will be returned from the websocket `v4_parent_subaccounts` channel sample response. Each [order](/types/order) in the list will contain a `clientId` field, which the client can match against the trade submission payload’s clientId. Once the order is executed, the `openPerpetualPositions` field from the websocket `v4_parent_subaccount` channel will have the position added or updated. #### Closing a Position To close an existing position, submit a market order using the [placeOrder](/node-client/private#place-order) function of the `v4-client-js` library with the position size as the “size” field. Make the “side” to be the opposite of the position side, and reduceOnly to “true”. Once the order is executed, the openPerpetualPositions field from the websocket `v4_parent_subaccount` channel will have the existing position removed. #### Pro Trade (Limit, Take Profit, Stop Loss, etc) \[TO-DO] ### Trigger Orders Users can add take profit and stop loss triggers to existing positions. ![triggerorder](/triggerorder.png) To find existing trigger orders associated with the current position, examine the `“orders”` list returned from the websocket `v4_parent_subaccounts` channel. A sample response: ```bash "orders": [ { "id": "ff6d83c1-a2e7-5fa9-9362-3fbc4771aec3", "subaccountId": "4adcda50-be50-596e-b3b0-d70cd3ca193d", "clientId": "1741547162", "clobPairId": "0", "side": "SELL", "size": "0.001", "totalFilled": "0", "price": "97510", "type": "TAKE_PROFIT", "status": "UNTRIGGERED", "timeInForce": "IOC", "reduceOnly": true, "orderFlags": "32", "goodTilBlockTime": "2025-06-09T17:00:16.000Z", "createdAtHeight": "34142063", "clientMetadata": "1", "triggerPrice": "102643", "updatedAt": "2025-03-11T17:00:16.521Z", "updatedAtHeight": "34142063", "postOnly": false, "ticker": "BTC-USD", "subaccountNumber": 0 } ], ``` * `ticker` field: Must match the `market ID` of the` open position`. * `side` field: Should be the `opposite` of the `open position` side: * If the open position is `LONG`, the order side should be `SELL`. * If the open position is `SHORT`, the order side should be `BUY`. * status field: Must be set to `UNTRIGGERED`. * type field: Can be either `TAKE_PROFIT` or `STOP_MARKET`. Existing trigger order associated with the current position should be displayed as follows: (a) The price field of the `TAKE_PROFIT` order (b) The gain percent is calculated from the `entry_price` field of the open position and (a). For example, if `entry_price` is $100, and (a) is $110, the gain percentage is 10%. (c) Same as (a) except for a `STOP_MARKET` order (d) Same as (b) except for a `STOP_MARKET` order The client can modify a trigger order state by performing two operations: 1. `Add` a new trigger order 2. `Cancel` an existing order #### Updating the Price of an Existing Trigger Order If the user updates the price of an existing trigger order, the client must first cancel the existing order before adding the new one. This ensures that only the latest order remains active and prevents conflicting trigger conditions. To submit a trigger order, the client can use the [placeOrder](/node-client/private#place-order) function of the v4-client-js library with the following parameters: * type=”TAKE\_PROFIT\_MARKET”, or “STOP\_MARKET” * triggerPrice = Trigger price entered by the user * price = triggerPrice \* (1+slippage\_threhold), where slippage\_threhold = \[0.05, 0.1] * execution = “IOC” * timeInForce = null * reduceOnly = “true” * clientId = \[some unique random id] To cancel an existing trigger order, the client can call the [cancelOrder](/node-client/private#cancel-order) function of the `v4-client-js` library. ## Integration Guide This guide outlines the development of a perpetual trading front-end application using the dYdX v4 application chain APIs. It covers backend data interpretation and rendering for end users, as well as trade execution. While maintaining a high-level perspective, it addresses specific details as needed. For in-depth implementation details, refer to the dYdX source repository, where dYdX Trading Inc. maintains the trading application's source code for web, iOS, and Android platforms. ### Overview of dYdX Repositories The following repos contain the source code for the dYdX client applications. They can serve as a good reference if you are wondering about the implementation details: ### Main applications :::steps #### [v4-web](https://github.com/dydxprotocol/v4-web). The web application. This is the React application that powers the [https://dydx.trade](https://dydx.trade) site. It references the v4-client-js repo for read/write data from the validator. #### [v4-native-ios](https://github.com/dydxprotocol/v4-native-ios). The iOS application written in Swift It’s powered by the abacus library for its core trading logic. It also uses the v4-client-js for read/write data from the validator, and the cartera-ios library to interact with mobile wallet apps. #### [v4-native-android](https://github.com/dydxprotocol/v4-native-android). The Android application written in Kotlin. Same with the iOS app, it uses abacus at its core, and use the v4-client-js for read/write data from the validator. Cartera-android is used to interact with mobile wallet apps. ::: ### Support libraries :::steps #### [v4-client-js](https://github.com/dydxprotocol/v4-clients/tree/main/v4-client-js). Typescript library that wraps the common functions to query and write to the validator/blockchain. It takes a user’s mnemonic and then can send transactions to the validator on the user's behalf. It’s responsible for all data directly flowing in between the client applications and the validator. #### [abacus](https://github.com/dydxprotocol/v4-abacus). Kotlin library that powers the core trading logic of the iOS and Android applications. The library pulls the data from the indexer and executes transactions by calling v4-client-js. #### [v4-localization](https://github.com/dydxprotocol/v4-localization). Contains localization data used by all client applications. There is no application logic. ::: ## Permissioned Keys Permissioned Keys provide a way for different traders to share the same account. Using this mechanism, the owner of an account can provide different types of permissions, allowing flexibility to what and what not the permissioned users can do with its account. A permission, or set of permissions, is also known as an **authenticator**. See all available authenticator types [here](/concepts/trading/authenticators). This section will guide you through authenticators use and management. We show the two sides of this authenticating interaction. We'll label the main user, which gives the permissiones, as the **owner**, and the permissioned user as the **trader**. ### Owner There are 2 ways to setup Permissioned API Keys: 1/ Via the Trade interface (default permissions to trade on all cross-margin pairs) 2/ Via API (customisable) ::::steps #### Setup Permissioned API Keys via Trade Interface ![Setup Permissioned API Key](/APIKey.png) On the dYdX.trade, after signing in with your wallet or socials: * click `More → API Trading Keys`. * Click `Generate New API Key`. This generates a new keypair: * API Wallet Address * Private Key :::note This is a one-time view, make sure to save your Private Key immediately. It will not be shown again and is not stored by dYdX ::: * Check the `terms` and click `Authorize API Key`. * Now you can go to [Import private key](/interaction/permissioned-keys#trader) to start trading via API keys. #### Create an authenticator Alternatively, you can create a custom authenticator which allows only the **trader** to place orders. To create this authenticator, we use two sub-authenticators: `signatureVerification` and `messageFilter`: * the `signatureVerification` authenticator must be present in all authenticator sets and contains the **trader**'s public key; * the `messageFilter` authenticator must contain the gRPC message ID of the allowed transaction (here `MsgPlaceOrder`). We compose everything together using the composable authenticator `AllOf`, stating that some **trader**'s transaction is only allowed if it complies with all the authenticators. The **trader** also needs to send the **owner** the its public key, associated with its address. This can be done by encoding the public key (e.g., hex string) and sending it. The **owner** then decodes it, serving it as input below. :::code-group ```python [Python] # trader_key = trader_wallet.public_key.key auth = Authenticator.compose( # All sub-authenticators must be valid. AuthenticatorType.AllOf, [ # The allowed account. Authenticator.signature_verification(trader_key), # The allowed action. Authenticator.message_filter("/dydxprotocol.clob.MsgPlaceOrder"), ], ) ``` ```typescript [TypeScript] // const traderKey = traderWallet.pubKey!.value; const auth = [ // The allowed account. { type: AuthenticatorType.SIGNATURE_VERIFICATION, config: traderKey, }, // The allowed action. { type: AuthenticatorType.MESSAGE_FILTER, config: toBase64(new TextEncoder().encode("/dydxprotocol.clob.MsgPlaceOrder")), }, ]; // Encode the authenticator. const jsonString = JSON.stringify(auth); const encodedData = new TextEncoder().encode(jsonString);; ``` ```rust [Rust] // let trader_key = trader_account.public_key().to_bytes(); // All sub-authenticators must be valid. let authenticator = Authenticator::AllOf(vec![ // The allowed account. Authenticator::SignatureVerification(trader_key), // The allowed action. Authenticator::MessageFilter("/dydxprotocol.clob.MsgPlaceOrder".into()), ]); ``` ::: #### Add the authenticator Now we need to push the authenticator to the network. :::code-group ```python [Python] # Add the authenticator. [!code focus] response = await node.add_authenticator(wallet, auth) # [!code focus] ``` ```typescript [TypeScript] try { // Add the authenticator. [!code focus] await client.addAuthenticator(subaccount, AuthenticatorType.ALL_OF, encodedData); // [!code focus] } catch (error) { console.log(error.message); } ``` ```rust [Rust] // Add the authenticator. [!code focus] client // [!code focus] .authenticators() // [!code focus] .add_authenticator(&account, account.address.clone(), authenticator) // [!code focus] .await?; // [!code focus] ``` ::: #### List authenticators You can confirm if the authenticator was added by listing all the authenticators associated with your (owner) address. The added authenticator, identified by an ID (integer), will appear last on the list. The **trader** will then need to use this ID. :::code-group ```python [Python] # List authenticators. authenticators = await node.get_authenticators(wallet.address) # Grab the last authenticator ID. id = authenticators.account_authenticators[-1] ``` ```typescript [TypeScript] // List authenticators. const authenticators = await client.getAuthenticators(wallet1.address!); // Grab the last authenticator ID. const lastElement = authenticators.accountAuthenticators.length - 1; const id = authenticators.accountAuthenticators[lastElement].id; ``` ```rust [Rust] // List authenticators. let id = client .authenticators() .get_authenticators(account.address().clone()) .await? .last() // Grab the last authenticator ID. .unwrap() .id; ``` ::: #### Remove the authenticator Authenticators can be removed if they are needed anymore, or if the **trader** goes rogue. :::code-group ```python [Python] # Remove the authenticator. response = await node.remove_authenticator(wallet, id) ``` ```typescript [TypeScript] // Remove the authenticator. await client.removeAuthenticator(subaccount, id); ``` ```rust [Rust] // Remove the authenticator. client .authenticators() .remove_authenticator(&mut account, account.address.clone(), id) .await?; ``` ::: :::: ### Trader ::::steps #### Get the authenticator ID and permissioned API Key Grab the `authenticatorId` created by the **owner** in [Add authenticator](/interaction/permissioned-keys#add-the-authenticator), either by: * request the **owner** for the `authenticatorId` or by using the list authenticators method to fetch the to be used authenticator (see [list authenticators](/interaction/permissioned-keys#last-authenticators)). * Get the private key `DYDX_TEST_PRIVATE_KEY` from the owner in [via the Trade Interface](/interaction/permissioned-keys#get-the-private-key). #### Setup the permissioned wallet If you got a private key from the trading interface load in via `fromPrivateKey`, otherwise use `fromMnemonic` if you didn't create via the trading interface by assigning to mnemonic. ```typescript const fromWallet = await LocalWallet.fromPrivateKey(DYDX_TEST_PRIVATE_KEY, BECH32_PREFIX); const authenticatedSubaccount = SubaccountInfo.forPermissionedWallet( fromWallet, address, //owner dydx address subaccountNumber, //subaccount to trade onbehalf of [authenticatorId], ) ``` #### Using the authenticator The **trader** can now use the permissioned API key to perform the allowed trading actions. In this example, the **trader** can use the authenticator to issue orders on the behalf of the **owner**. :::code-group ```typescript [TypeScript] const client = await CompositeClient.connect(network); // Place an order using the authenticator. const tx = await client.placeShortTermOrder( authenticatedSubaccount, 'ETH-USD', side, price, 0.01, clientId, goodTilBlock, timeInForce, false, undefined, ); ``` ```python [Python] id = ... # authenticator ID # [!code focus] # Create the order ID, using the owner address # [!code focus] order_id = market.order_id( # [!code focus] OWNER_ADDRESS, # address # [!code focus] 0, # subaccount number random.randint(0, 100000000), # client ID, can be random OrderFlags.SHORT_TERM # short-term order ) # [!code focus] # Create the order as usual # [!code focus] order = market.order( # [!code focus] order_id, # [!code focus] # ... ) # [!code focus] # The TxOptions should have the authenticator ID to be used. # [!code focus] tx_options = TxOptions([id], wallet.sequence, wallet.account_number) # [!code focus] # Place the order. # [!code focus] place = await node.place_order(wallet, order, tx_options) # [!code focus] ``` ```rust [Rust] // Create a `PublicAccount` representing the owner. // [!code focus] let owner_account = // [!code focus] PublicAccount::updated(owner_address.clone(), &mut client).await?; // [!code focus] // Register the public account. // [!code focus] account // [!code focus] .authenticators_mut() // [!code focus] .add_authenticator(external_account, id); // [!code focus] // Create an order for the owner subaccount. // [!code focus] let owner_subaccount = Subaccount { // [!code focus] address: owner_address.clone(), // [!code focus] number: 0.try_into()?, }; // [!code focus] let (_, order) = OrderBuilder::new(market, owner_subaccount) // [!code focus] //... .build(123456)?; // Place an order using the authenticator. // [!code focus] let tx_hash = client.place_order(&mut account, order).await?; // [!code focus] ``` ::: :::: ## Trading Interacting with the dYdX perpetual markets and managing your positions is done by placing orders. Enter `LONG` positions by placing buy orders and `SHORT` positions by placing sell orders. ### Place an order To place an [order](/concepts/trading/orders), you'll need your wallet ready to sign transactions. Please check the [Wallet Setup](/interaction/wallet-setup) guide to check how to set up a wallet. In this guide we'll be creating a short-term buy order for the ETH-USD market. ::::steps #### Get market parameters To create an order for a specific market (identified by its ticker, or *CLOB pair ID*), we should first fetch the market parameters that allows us to do data conversions associated with that specific market. Other parameters such as the current price can also be fetched this way. :::code-group ```python [Python] MARKET_ID = 1 # ETH-USD identifier # Fetch market parameters. market = Market( (await indexer.markets.get_perpetual_markets(MARKET_ID))["markets"][MARKET_ID] ) # Current price. print(market["oraclePrice"]) ``` ```typescript [TypeScript] const ETH_USD: string = 'ETH-USD'; // Fetch market parameters. const market = await indexer.markets.getPerpetualMarkets(ETH_USD).markets[ETH_USD]; // Current price. console.log(market.oraclePrice); ``` ```rust [Rust] let ETH_USD_TICKER = "ETH-USD"; // Fetch market parameters. let market = indexer .markets() .get_perpetual_market(Ð_USD_TICKER.into()) .await?; // Current price. println!("ETH-USD current price: {:?}", market.oracle_price); ``` ::: #### Creating an order ##### Identifying the order Every order created has an unique identifier, referred to as the **order ID**. It can be calculated locally and is composed of, * **Subaccount ID**: The account address plus the integer identifying the subaccount; * **Client ID**: A 32-bit integer chosen by the user to identify the order. Two orders can't have the same client ID; * **Order flags**: An integer identifying if the order is short-term (`0`), long-term (`64`), conditional (`32`), or TWAP (`128`); * **CLOB Pair ID**: The ID of the underlying market/perpetual. :::info It is recommended to set specific clientID's when placing different orders, which will be useful to retrieve back a specific order on the `v4/orders` endpoint ::: :::code-group ```python [Python] order_id = market.order_id( ADDRESS, # address 0, # subaccount number random.randint(0, 100000000), # chosen client ID, can be random OrderFlags.SHORT_TERM # short-term order ) ``` ```typescript [TypeScript] import { OrderId, SubaccountId } from '@dydxprotocol/v4-client-js'; const subaccountId: SubaccountId = { owner: ADDRESS, number: 0, }; const orderId: OrderId = { subaccountId, randomInt(100000000), // chosen client ID, can be random 0, // order flags (0 = short term) 1, // CLOB pair ID (ETH-USD) }; ``` ```rust [Rust] use dydx::indexer::ClientId; use dydx_proto::dydxprotocol::{ clob::OrderId, subaccounts::SubaccountId }; // The order ID is also returned on `OrderBuilder::build()` let order_id = OrderId { subaccount_id: Some(SubaccountId { owner: account.address().to_string(), number: 0, }), ClientId::random().0, // chosen client ID, can be random order_flags: 0_u32, // short-term clob_pair_id: 1_u32, // ETH-USD }; ``` ::: ##### Building the order In dYdX, orders can be either short-term or long-term, see a comparison [here](/concepts/trading/orders). A wide range of order types and parameters are provided to allow for different types of trading strategies. * [**Type**](/types/order_type): Market, Limit, and Stop, and Take Profit orders are supported; * [**Side**](/types/order_side): Purchase (`BUY`) or sell (`SELL`); * **Size**: A decimal value corresponding to the quantity being traded; * **Price**: A decimal value corresponding to the chosen price; * [**Time in Force**](/types/time_in_force): Execution option, defining conditions for order placements; * **Reduce Only**: A boolean value, used to label orders that can only reduce your position size. For example, a 1.25 BTC Sell Reduce Only order on a 1 BTC long: * If **true**: can only decrease your position size by 1. The remaining .25 BTC sell will not fill and be cancelled; * Else: The remaining .25 BTC sell can fill and the position become .25 BTC short. * **Good until Block**: Validity of the order. It is an integer value, different for short-term and long-term orders: * **Short-term**: Short term orders have a maximum validity of current block height + `ShortBlockWindow`. Currently, `ShortBlockWindow` is 20 blocks, or about 30 seconds; * **Long-term**: Stateful orders have a maximum validity of current block time + `StatefulOrderTimeWindow`. Currently, `StatefulOrderTimeWindow` this value is 95 days. :::code-group ```python [Python] # Order valid for the next 10 blocks. good_til_block = await node.latest_block_height() + 10 # Create the order. order = market.order( order_id, OrderType.LIMIT, Order.Side.SIDE_BUY, size=0.01, # ETH price=1000, # USDC time_in_force=Order.TimeInForce.TIME_IN_FORCE_UNSPECIFIED, reduce_only=False, good_til_block=good_til_block, # valid until this (future) block ) ``` ```typescript [TypeScript] // If using the Composite client, the order ID and order are builtinternally, // when broadcasting the order. ``` ```rust [Rust] // Get current block number. let current_block_height = client.latest_block_height().await?; let size = BigDecimal::from_str("0.01")?; // 0.01 ETH // Create the order. The order ID is also returned let (_id, order) = OrderBuilder::new(market, subaccount) .market(OrderSide::Buy, size) .reduce_only(false) .price(1000) // USDC .time_in_force(TimeInForce::Unspecified) .until(current_block_height.ahead(10)) .short_term() // or .long_term() for long term order .build(123456)?; // client ID ``` ::: #### Broadcasting an order With the order now built, we can push it to the dYdX network. :::code-group ```python [Python] # Broadcast the order. place = await node.place_order(wallet, order) ``` ```typescript [TypeScript] const goodTilBlock = await client.validatorClient.get.latestBlockHeight() + 10; // order valid until this block const clientId = randomInt(100000000); // a random client ID // Create and broadcast the order. const tx = await client.placeShortTermOrder( subaccount, 'ETH-USD', OrderSide.BUY, 1000, // USDC 0.01, // ETH clientId, goodTilBlock, Order_TimeInForce.TIME_IN_FORCE_UNSPECIFIED, false, // (not) reduce only ); ``` ```rust [Rust] // Broadcast the order. let tx_hash = node.place_order(&mut account, order).await?; ``` ::: :::: ### Cancel an order An unfilled order can be cancelled. The **order ID** is required to cancel an order. :::code-group ```python [Python] cancel_tx = await node.cancel_order( wallet, order_id, good_til_block ) ``` ```typescript [TypeScript] // The order ID is calculated internally. const tx = await client.cancelOrder( subaccount, // subaccount ID clientId, OrderFlags.SHORT_TERM, 'ETH-USD', // market goodTilBlock, 0, // good-until-time not used in short-term orders ); ``` ```rust [Rust] let cancel_tx_hash = node .cancel_order(&mut account, order_id, good_until) .await?; ``` ::: #### Add Add an authenticator. ##### Method Declaration :::code-group ```python [Python] async def add_authenticator( self, wallet: Wallet, authenticator: Authenticator, ): ``` ```typescript [TypeScript] async addAuthenticator( subaccount: SubaccountInfo, authenticatorType: AuthenticatorType, data: Uint8Array, ) ``` ```rust [Rust] pub async fn add_authenticator( &mut self, account: &mut Account, address: Address, authenticator: Authenticator, ) -> Result ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------------- | -------- | --------------- | -------- | ---------------------------------------------------------------------------- | | `wallet` | query | [Wallet] | true | The wallet to use for signing the transaction or authenticating the request. | | `authenticator` | query | [Authenticator] | true | The authenticator to be added. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | The transaction hash. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [Guide - Add an Authenticator] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/authenticator_management.py#L53 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/permissioned_keys_example.ts#L58 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/authenticator.rs#L79 [Guide - Add an Authenticator]: ../../interaction/permissioned-keys#add-the-authenticator [Wallet]: /types/wallet [Authenticator]: /types/authenticator [TxHash]: /types/tx_hash [OK]: /types/ok [Bad Request]: /types/bad-request import Add from './add.mdx' import Remove from './remove.mdx' import List from './list.mdx' ## Permissioned Keys ### Authenticators Access the authenticators/permissioned keys requests dispatcher. :::code-group ```rust [Rust] let authenticators = node_client.authenticators(); ``` ```python [Python] ``` ```typescript [TypeScript] ``` ::: #### List List down added authenticators. ##### Method Declaration :::code-group ```python [Python] async def get_authenticators( self, address: str, ) -> accountplus_query.GetAuthenticatorsResponse ``` ```typescript [TypeScript] async getAuthenticators( address: string, ): Promise ``` ```rust [Rust] pub async fn get_authenticators( &mut self, address: Address, ) -> Result, Error> ``` ```url [API] /dydxprotocol.accountplus.Query/GetAuthenticators ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | --------- | -------- | ----------------------------------------- | | `account` | query | [Address] | true | The wallet address that owns the account. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------------------------ | ------------------------------------- | | `200` | [OK] | [AccountAuthenticator] ⛁ | The account authenticators. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [Guide - List Authenticators] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/authenticator_management.py#L62 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/authenticator_management.py#L62 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/authenticator.rs#L93 [Guide - List Authenticators]: ../../interaction/permissioned-keys#list-authenticators [Address]: /types/address [OK]: /types/ok [AccountAuthenticator]: /types/account_authenticator [Bad Request]: /types/bad-request #### Remove Remove an added authenticator. ##### Method Declaration :::code-group ```python [Python] async def remove_authenticator(self, wallet: Wallet, authenticator_id: int) ``` ```typescript [TypeScript] async removeAuthenticator( subaccount: SubaccountInfo, id: Long, ): Promise ``` ```rust [Rust] pub async fn remove_authenticator( &mut self, account: &mut Account, address: Address, id: u64, ) -> Result ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------------ | -------- | -------- | -------- | ---------------------------------------------------------------------------- | | `wallet` | query | [Wallet] | true | The wallet to use for signing the transaction or authenticating the request. | | `authenticator_id` | query | int | true | The authenticator identifier. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | The transaction hash. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The authenticator was not found. | Examples: [Python] | [TypeScript] | [Rust] | [Guide - Remove an Authenticator] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/authenticator_management.py#L68 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/permissioned_keys_example.ts#L50 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/authenticator.rs#L146 [Guide - Remove an Authenticator]: ../../interaction/permissioned-keys#remove-the-authenticator [Wallet]: /types/wallet [TxHash]: /types/tx_hash [Bad Request]: /types/bad-request [Not Found]: /types/not-found [OK]: /types/ok import Details from '../../../components/Details'; #### Batch Cancel Orders Cancel a batch of short-terms orders. ##### Method Declaration :::code-group ```python [Python] async def batch_cancel_orders( self, wallet: Wallet, subaccount_id: SubaccountId, short_term_cancels: List[OrderBatch], good_til_block: int, tx_options: Optional[TxOptions] = None, ) ``` ```typescript [TypeScript] async batchCancelShortTermOrdersWithMarketId( subaccount: SubaccountInfo, shortTermOrders: OrderBatchWithMarketId[], goodTilBlock: number, broadcastMode?: BroadcastMode, ): Promise ``` ```rust [Rust] pub async fn batch_cancel_orders( &mut self, account: &mut Account, subaccount: Subaccount, short_term_cancels: Vec, until_block: Height, ) -> Result ``` ```url [API] /dydxprotocol.clob.Msg/BatchCancel ``` :::
* `TxOptions` is not available in Rust
##### Parameters | Parameter | Location | Type | Required | Description | | -------------------- | -------- | -------------- | -------- | --------------------------------------------------------------------- | | `wallet` | query | [Wallet] | true | The wallet to use for signing the transaction. | | `subaccount_id` | query | [SubaccountId] | true | The subaccount ID for which to cancel orders. | | `short_term_cancels` | query | [OrderBatch] ⛁ | true | List of OrderBatch objects containing the orders to cancel. | | `good_til_block` | query | [Height] | true | The last block the short term order cancellations can be executed at. | | `tx_options` | query | [TxOptions] | false | Options for transaction to support authenticators. | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | Examples: [Python] | [TypeScript] | [Rust] [Python]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-py-v2/examples/batch_cancel_example.py [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-js/examples/batch_cancel_orders_example.ts [Rust]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/examples/batch_cancel_orders.rs [Wallet]: /types/wallet [OK]: /types/ok [SubaccountId]: /types/subaccount_id [OrderBatch]: /types/order_batch [TxOptions]: /types/tx_options [Height]: /types/height [Bad Request]: /types/bad-request [Not Found]: /types/not-found [TxHash]: /types/tx_hash #### Broadcast Transaction The `Broadcast Transaction` method is used to send a transaction to the network for processing. The key parameters include the transaction itself and the mode of broadcasting, which is optional and defaults to synchronous broadcasting mode. ##### Method Declaration :::code-group ```python [Python] async def broadcast(self, transaction: Tx, mode=BroadcastMode.BROADCAST_MODE_SYNC) ``` ```typescript [TypeScript] async sendSignedTransaction( signedTransaction: Uint8Array, ): Promise ``` ```rust [Rust] pub async fn broadcast_transaction( &mut self, tx_raw: Raw, ) -> Result ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------- | -------- | --------------- | -------- | -------------------------------------------------------------------- | | `transaction` | query | [Tx] | true | The transaction to broadcast. | | `mode` | query | [BroadcastMode] | false | The broadcast mode. Defaults to BroadcastMode.BROADCAST\_MODE\_SYNC. | \::: ##### Response | Status | Meaning | Schema | | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The transaction was not found. | Examples: [Rust] [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/withdraw_other.rs#L71 [Tx]: /types/tx [BroadcastMode]: /types/broadcast_mode [OK]: /types/ok [TxHash]: /types/tx_hash [Bad Request]: /types/bad-request [Not Found]: /types/not-found import Details from '../../../components/Details'; #### Cancel Order Terminate an existing order using the provided order ID and related parameters, such as block validity periods and transaction options. ##### Method Declaration :::code-group ```python [Python] async def cancel_order( self, wallet: Wallet, order_id: OrderId, good_til_block: int = None, good_til_block_time: int = None, tx_options: Optional[TxOptions] = None, ) ``` ```typescript [TypeScript] async cancelOrder( subaccount: SubaccountInfo, clientId: number, orderFlags: OrderFlags, marketId: string, goodTilBlock?: number, goodTilTimeInSeconds?: number, ): Promise ``` ```rust [Rust] pub async fn cancel_order( &mut self, account: &mut Account, order_id: OrderId, until: impl Into, ) -> Result ``` ```url [API] ``` :::
* Check the `marketId` is really needed (used in `TypeScript`)
##### Parameters | Parameter | Location | Type | Required | Description | | --------------------- | -------- | ----------- | -------- | ------------------------------------------------------------------ | | `wallet` | query | [Wallet] | true | The wallet to use for signing the transaction. | | `order_id` | query | [OrderId] | true | The ID of the order to cancel. | | `good_til_block` | query | [i32] | false | The block number until which the order is valid. Defaults to None. | | `good_til_block_time` | query | [i32] | false | The block time until which the order is valid. Defaults to None. | | `tx_options` | query | [TxOptions] | false | Options for transaction to support authenticators. | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | The transaction hash. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The order was not found. | Examples: [Rust] | [Guide - Cancel an order] [Rust]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/examples/cancel_order.rs [Guide - Cancel an order]: ../../interaction/trading#cancel-an-order [OK]: /types/ok [Wallet]: /types/wallet [OrderId]: /types/order_id_obj [i32]: /types/i32 [TxOptions]: /types/tx_options [TxHash]: /types/tx_options [Bad Request]: /types/bad-request [Not Found]: /types/not-found #### Close Position Close position for a given market. Opposite short-term market orders are used. If provided, the position is only reduced by a size of reduce\_by. Note that at the moment dYdX doesn’t support spot trading. ##### Method Declaration :::code-group ```python [Python] ``` ```typescript [TypeScript] ``` ```rust [Rust] pub async fn close_position( &mut self, account: &mut Account, subaccount: Subaccount, market_params: impl Into, reduce_by: Option, client_id: impl Into, ) -> Result, NodeError> ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------------- | -------- | ------------------- | -------- | -------------------------------------------- | | `account` | query | [Account] | true | The wallet address that owns the subaccount. | | `subaccount` | query | [Subaccount] | true | Subaccount to close | | `market_params` | query | [OrderMarketParams] | true | Market parameters | | `reduced_by` | query | [BigDecimal] | false | | | `client_id` | query | [ClientId] | true | | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | Examples: [Rust] [Rust]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/examples/close_position.rs [Account]: /types/account [Subaccount]: /types/subaccount [OrderMarketParams]: /types/order_market_params [BigDecimal]: /types/big_decimal [ClientId]: /types/client_id [TxHash]: /types/tx_hash [Bad Request]: /types/bad-request [Not Found]: /types/not-found [OK]: /types/ok #### Create Market Permissionless Create a market permissionless. ##### Method Declaration :::code-group ```python [Python] async def create_market_permissionless( self, wallet: Wallet, ticker: str, address: str, subaccount_id: int ) -> Any ``` ```typescript [TypeScript] async createMarketPermissionless( subaccount: SubaccountInfo, ticker: string, broadcastMode?: BroadcastMode, gasAdjustment?: number, memo?: string, ): Promise ``` ```rust [Rust] pub async fn create_market_permissionless( &mut self, account: &mut Account, ticker: &Ticker, subaccount: &Subaccount, ) -> Result ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Description | | ------------ | -------- | ---------------- | ---------------------- | | `account` | query | [Account] | Account information | | `ticker` | query | [Ticker] | Ticker information | | `subaccount` | query | [SubaccountInfo] | Subaccount information | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | [Account]: /types/account [Ticker]: /types/ticker [SubaccountInfo]: /types/subaccount_info [OK]: /types/ok [TxHash]: /types/tx_hash [Bad Request]: /types/bad-request [Not Found]: /types/not-found #### Create Transaction Create a transaction. ##### Method Declaration :::code-group ```python [Python] async def create_transaction(self, wallet: Wallet, message: Message) -> Tx ``` ```typescript [TypeScript] async signTransaction( messages: EncodeObject[], transactionOptions: TransactionOptions, fee?: StdFee, memo: string = '', publicKey?: Secp256k1Pubkey, ): Promise ``` ```rust [Rust] pub async fn create_transaction( &mut self, account: &mut Account, msg: impl ToAny, auth: Option<&Address>, ) -> Result ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Mandatory | Description | | --------- | -------- | --------- | --------- | --------------------------------- | | `account` | query | [Account] | true | Owner's account information | | `msg` | query | Any | true | Message during create transaction | | `auth` | query | [Address] | false | | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | --------- | ------------------------------------- | | `200` | [OK] | [tx::Raw] | | | `400` | [Bad Request] | | The request was malformed or invalid. | [Account]: /types/account [Address]: /types/address [OK]: /types/ok [tx::Raw]: /types/cosmos [Bad Request]: /types/bad-request #### Delegate Delegate tokens from a delegator to a validator. ##### Method Declaration :::code-group ```python [Python] async def delegate( self, wallet: Wallet, delegator: str, validator: str, quamtums: int, denomination: str, ) ``` ```typescript [TypeScript] async delegate( subaccount: SubaccountInfo, delegator: string, validator: string, amount: string, broadcastMode?: BroadcastMode, ): Promise ``` ```rust [Rust] pub async fn delegate( &mut self, account: &mut Account, delegator: Address, validator: Address, token: impl Tokenized, ) -> Result ``` ```uri ``` ::: ##### Parameters | Parameter | Location | Type | Mandatory | Description | | ------------- | -------- | ---------------- | --------- | --------------------- | | subaccount | query | [SubaccountInfo] | true | Subaccount number | | delegator | query | string | true | Delegator information | | validator | query | string | true | validator information | | amount | query | string | true | Amount to delegate | | broadcastMode | query | [BroadcastMode] | false | Mode of broadcast | ##### Response | Status | Meaning | Schema | | | | ------ | ------- | -------------------------- | ------------------------- | ----------- | | `200` | [OK] | [BroadcastTxAsyncResponse] | [BroadcastTxSyncResponse] | [IndexedTx] | [SubaccountInfo]: /types/subaccount_info [BroadcastMode]: /types/broadcast_mode [BroadcastTxAsyncResponse]: /types/cosmos [BroadcastTxSyncResponse]: /types/cosmos [IndexedTx]: /types/cosmos [OK]: /types/ok #### Deposit Deposit funds (USDC) from the address to the subaccount. ##### Method Declaration :::code-group ```python [Python] async def deposit( self, wallet: Wallet, sender: str, recipient_subaccount: SubaccountId, asset_id: int, quantums: int, ) ``` ```typescript [TypeScript] async deposit( subaccount: SubaccountInfo, assetId: number, quantums: Long, broadcastMode?: BroadcastMode, ): Promise ``` ```rust [Rust] pub async fn deposit( &mut self, account: &mut Account, sender: Address, recipient: Subaccount, amount: impl Into, ) -> Result ``` ```url [API] /dydxprotocol.sending.Msg/DepositToSubaccount ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ---------------------- | -------- | -------------- | -------- | ------------------------------------------------------------ | | `wallet` | query | [Wallet] | true | The wallet to use for signing the transaction. | | `sender` | query | string | true | The sender address. | | `recipient_subaccount` | query | [SubaccountId] | true | The recipient subaccount ID. | | `asset_id` | query | [AssetId] | true | The asset ID. | | `quantums` | query | int | true | The amount of quantums to deposit. [See more about quantums] | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | [Wallet]: /types/wallet [SubaccountId]: /types/Subaccount_id [OK]: /types/ok [TxHash]: /types/tx_hash [See more about quantums]: ../../concepts/trading/quantums#quantums [Bad Request]: /types/bad-request [Not Found]: /types/not-found [AssetId]: /types/asset_id_node import PrivateApi from './intro.mdx' import PlaceOrder from './place_order.mdx' import CancelOrder from './cancel_order.mdx' import BatchCancelOrders from './batch_cancel_orders.mdx' import Deposit from './deposit.mdx' import Withdraw from './withdraw.mdx' import Transfer from './transfer.mdx' import SendToken from './send_token.mdx' import Delegate from './delegate.mdx' import Undelegate from './undelegate.mdx' import WithdrawDelegatorReward from './withdraw_delegator_reward.mdx' import RegisterAffiliate from './register_affiliate.mdx' import ClosePosition from './close_position.mdx' import CreateMarketPermissionless from './create_market_permissionless.mdx' import Simulate from './simulate.mdx' import CreateTransaction from './create_transaction.mdx' import BroadcastTransaction from './broadcast_transaction.mdx' import MegaVault from './megavault/index.mdx' ### Private Node API import Details from '../../../components/Details'; #### Place Order Execute a transaction that places an order on a market. This function takes parameters for wallet authentication, various order details, and optional transaction options to manage specific order types and behaviors. ##### Method Declaration :::code-group ```python [Python] async def place_order( self, wallet: Wallet, order: Order, tx_options: Optional[TxOptions] = None, ) ``` ```typescript [TypeScript] async placeOrder( subaccount: SubaccountInfo, marketId: string, type: OrderType, side: OrderSide, price: number, size: number, clientId: number, timeInForce?: OrderTimeInForce, goodTilTimeInSeconds?: number, execution?: OrderExecution, postOnly?: boolean, reduceOnly?: boolean, triggerPrice?: number, marketInfo?: MarketInfo, currentHeight?: number, goodTilBlock?: number, memo?: string, ): Promise ``` ```rust [Rust] pub async fn place_order( &mut self, account: &mut Account, order: Order, ) -> Result ``` ```url [API] ``` :::
* Use a convenient `Wallet` and `Order` pair for all clients * TypeScript doesn't use authenticators * In Python we use them explicitly * Consider to do the same like in Rust (set it automatically)
##### Parameters | Parameter | Location | Type | Required | Description | | ------------ | -------- | ----------- | -------- | -------------------------------------------------- | | `wallet` | query | [Wallet] | true | The wallet to use for signing the transaction. | | `order` | query | [Order] | true | The order to place. | | `tx_options` | query | [TxOptions] | false | Options for transaction to support authenticators. | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [Guide - Place an order] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/short_term_order_cancel_example.py#L36 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-js/examples/short_term_order_composite_example.ts [Rust]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/examples/place_order_short_term.rs [Guide - Place an order]: ../../interaction/trading#place-an-order [Wallet]: /types/wallet [OK]: /types/ok [Order]: /types/order [TxHash]: /types/tx_hash [TxOptions]: /types/tx_options [Bad Request]: /types/bad-request #### Register Affiliate Register affiliate. ##### Method Declaration :::code-group ```python [Python] async def register_affiliate( self, wallet: Wallet, referee: str, affiliate: str ) -> Any ``` ```typescript [TypeScript] async registerAffiliate( subaccount: SubaccountInfo, affiliate: string, broadcastMode?: BroadcastMode, gasAdjustment: number = 2, ): Promise ``` ```rust [Rust] pub async fn register_affiliate( &mut self, account: &mut Account, referee: Address, affiliate: Address, ) -> Result ``` ```uri ``` ::: ##### Parameters | Parameter | Location | Type | Mandatory | Description | | --------------- | -------- | ---------------- | --------- | ---------------------- | | `subaccount` | query | [SubaccountInfo] | true | Subaccount information | | `affiliate` | query | string | true | Affiliate information | | `broadcastMode` | query | [BroadcastMode] | false | Mode of broadcast | | `gasAdjustment` | query | int | false | Gas adjustment value | ##### Response | Status | Meaning | Schema | | | | ------ | ------- | -------------------------- | ------------------------- | ----------- | | `200` | [OK] | [BroadcastTxAsyncResponse] | [BroadcastTxSyncResponse] | [IndexedTx] | [SubaccountInfo]: /types/subaccount_info [BroadcastMode]: /types/broadcast_mode [OK]: /types/ok [BroadcastTxAsyncResponse]: /types/cosmos [BroadcastTxSyncResponse]: /types/cosmos [IndexedTx]: /types/cosmos import Details from '../../../components/Details'; #### Send Token Transfer a specified token from one account/address to another. It requires details such as the wallet for signing the transaction, sender and recipient addresses, and the quantum amount or denomination of the token. ##### Method Declaration :::code-group ```python [Python] async def send_token( self, wallet: Wallet, sender: str, recipient: str, quantums: int, denomination: str, ) ``` ```typescript [TypeScript] async sendToken( subaccount: SubaccountInfo, recipient: string, coinDenom: string, quantums: string, zeroFee: boolean = true, broadcastMode?: BroadcastMode, ): Promise ``` ```rust [Rust] pub async fn send_token( &mut self, account: &mut Account, sender: Address, recipient: Address, token: impl Tokenized, ) -> Result ``` ```url [API] ``` :::
* All the types are different, revision is needed * Standard types like strings are used * Broadcast mode? * Zero fee?
##### Parameters | Parameter | Location | Type | Required | Description | | -------------- | -------- | -------- | -------- | ---------------------------------------------- | | `wallet` | query | [Wallet] | true | The wallet to use for signing the transaction. | | `sender` | query | String | true | The sender address. | | `recipient` | query | String | true | The recipient address. | | `quantums` | query | [i32] | true | The amount of quantums to send. | | `denomination` | query | [i32] | true | The denomination of the token. | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Rust] [Rust]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/examples/send_token.rs [Wallet]: /types/wallet [TxHash]: /types/tx_hash [i32]: /types/i32 [OK]: /types/ok [Bad Request]: /types/bad-request import Details from '../../../components/Details'; #### Simulate Pre-execution simulation of a transaction, predicting its execution cost and resource usage without committing any changes. This method typically returns information like estimated gas fees or other transaction-related metrics to anticipate the impact of operations before they are executed on the blockchain. ##### Method Declaration :::code-group ```python [Python] async def simulate(self, transaction: Tx) ``` ```typescript [TypeScript] async simulate( wallet: LocalWallet, messaging: () => Promise, gasPrice: GasPrice = this.getGasPrice(), memo?: string, account?: () => Promise, ): Promise ``` ```rust [Rust] pub async fn simulate(&mut self, tx_raw: &Raw) -> Result ``` ```url [API] ``` :::
* Some extra parameters in TypeScript? What to do with them?
##### Parameters | Parameter | Location | Type | Required | Description | | ------------- | -------- | ---- | -------- | ---------------------------- | | `transaction` | query | [Tx] | true | The transaction to simulate. | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | --------- | ------------------------------------- | | `200` | [OK] | [GasInfo] | | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/transfer_example_withdraw_other.py#L20 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/transfer_example_withdraw_other.ts#L37 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/withdraw_other.rs#L52 [Tx]: /types/tx [GasInfo]: /types/gas_info [OK]: /types/ok [Bad Request]: /types/bad-request #### Transfer Transfer funds (USDC) between subaccounts. ##### Method Declaration :::code-group ```python [Python] async def transfer( self, wallet: Wallet, sender_subaccount: SubaccountId, recipient_subaccount: SubaccountId, asset_id: int, amount: int, ) ``` ```typescript [TypeScript] async transfer( subaccount: SubaccountInfo, recipientAddress: string, recipientSubaccountNumber: number, assetId: number, amount: Long, broadcastMode?: BroadcastMode, ): Promise ``` ```rust [Rust] pub async fn transfer( &mut self, account: &mut Account, sender: Subaccount, recipient: Subaccount, amount: impl Into, ) -> Result ``` ```url [API] /dydxprotocol.sending.Msg/CreateTransfer ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ---------------------- | -------- | -------------- | -------- | ----------------------------------------------------------------------------------------------- | | `wallet` | query | [Wallet] | true | The wallet to use for signing the transaction. | | `sender_subaccount` | query | [SubaccountId] | true | The sender subaccount ID. | | `recipient_subaccount` | query | [SubaccountId] | true | The recipient subaccount ID. | | `asset_id` | query | [AssetId] | true | The asset ID. | | `amount` | query | int | true | The amount to transfer in quote quantums (e.g. `1_000_000` = 1 USDC). [See more about quantums] | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | The transaction hash. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | Examples: [Python] | [TypeScript] | [Rust] [Python]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-py-v2/examples/transfer_example_transfer.py [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-js/examples/transfer_example_subaccount_transfer.ts [Rust]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/examples/transfer.rs [Wallet]: /types/wallet [SubaccountId]: /types/subaccount_id [AssetId]: /types/asset_id_node [OK]: /types/ok [TxHash]: /types/tx_hash [Bad Request]: /types/bad-request [Not Found]: /types/not-found [See more about quantums]: ../../concepts/trading/quantums#quantums #### Undelegate Undelegate coins from a delegator to a validator. ##### Method Declaration :::code-group ```python [Python] async def undelegate( self, wallet: Wallet, delegator: str, validator: str, quamtums: int, denomination: str, ) ``` ```typescript [TypeScript] async undelegate( subaccount: SubaccountInfo, delegator: string, validator: string, amount: string, broadcastMode?: BroadcastMode, ): Promise ``` ```rust [Rust] pub async fn undelegate( &mut self, account: &mut Account, delegator: Address, validator: Address, token: impl Tokenized, ) -> Result ``` ```uri ``` ::: ##### Parameters | Parameter | Location | Type | Mandatory | Description | | ------------- | -------- | ---------------- | --------- | --------------------- | | subaccount | query | [SubaccountInfo] | true | Subaccount number | | delegator | query | string | true | Delegator information | | validator | query | string | true | validator information | | amount | query | string | true | Amount to delegate | | broadcastMode | query | [BroadcastMode] | false | Mode of broadcast | ##### Response | Status | Meaning | Schema | | | | ------ | ------- | -------------------------- | ------------------------- | ----------- | | `200` | [OK] | [BroadcastTxAsyncResponse] | [BroadcastTxSyncResponse] | [IndexedTx] | [SubaccountInfo]: /types/subaccount_info [BroadcastMode]: /types/broadcast_mode [BroadcastTxAsyncResponse]: /types/cosmos [BroadcastTxSyncResponse]: /types/cosmos [IndexedTx]: /types/cosmos [OK]: /types/ok #### Withdraw Withdraw funds (USDC) from the subaccount to the address. ##### Method Declaration :::code-group ```python [Python] async def withdraw( self, wallet: Wallet, sender_subaccount: SubaccountId, recipient: str, asset_id: int, quantums: int, ) ``` ```typescript [TypeScript] async withdraw( subaccount: SubaccountInfo, assetId: number, quantums: Long, recipient?: string, broadcastMode?: BroadcastMode, ): Promise ``` ```rust [Rust] pub async fn withdraw( &mut self, account: &mut Account, sender: Subaccount, recipient: Address, amount: impl Into, ) -> Result ``` ```url [API] /dydxprotocol.sending.Msg/WithdrawFromSubaccount ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------------- | -------- | -------------- | -------- | ---------------------------------------------- | | `wallet` | query | [Wallet] | true | The wallet to use for signing the transaction. | | `sender_subaccount` | query | [SubaccountId] | true | The sender subaccount ID. | | `recipient` | query | string | true | The recipient subaccount ID. | | `asset_id` | query | [AssetId] | true | The asset ID. | | `quantums` | query | int | true | The amount of quantums to withdraw. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | The transaction hash. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | Examples: [Python] | [TypeScript] | [Rust] [Python]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-js/examples/transfer_example_subaccount_transfer.ts [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-js/examples/transfer_example_withdraw.ts [Rust]: https://github.com/dydxprotocol/v4-clients/blob/main/v4-client-rs/client/examples/withdraw.rs [Wallet]: /types/wallet [SubaccountId]: /types/subaccount_id [OK]: /types/ok [AssetId]: /types/asset_id_node [TxHash]: /types/tx_hash [Bad Request]: /types/bad-request [Not Found]: /types/not-found #### Withdraw Delegator Reward Withdraw delegator reward. ##### Method Declaration :::code-group ```python [Python] async def withdraw_delegate_reward( self, wallet: Wallet, delegator: str, validator: str ) -> Any ``` ```rust [Rust] pub async fn withdraw_delegator_reward( &mut self, account: &mut Account, delegator: Address, validator: Address, ) -> Result ``` ```typescript [TypeScript] async withdrawDelegatorReward( subaccount: SubaccountInfo, delegator: string, validator: string, broadcastMode?: BroadcastMode, ): Promise ``` ```uri ``` ::: ##### Parameters | Parameter | Location | Type | Mandatory | Description | | ------------- | -------- | ---------------- | --------- | --------------------- | | subaccount | query | [SubaccountInfo] | true | Subaccount number | | delegator | query | string | true | Delegator information | | validator | query | string | true | validator information | | broadcastMode | query | [BroadcastMode] | false | Mode of broadcast | ##### Response | Status | Meaning | Schema | | | | ------ | ------- | -------------------------- | ------------------------- | ----------- | | `200` | [OK] | [BroadcastTxAsyncResponse] | [BroadcastTxSyncResponse] | [IndexedTx] | [SubaccountInfo]: /types/subaccount_info [BroadcastMode]: /types/broadcast_mode [BroadcastTxAsyncResponse]: /types/cosmos [BroadcastTxSyncResponse]: /types/cosmos [IndexedTx]: /types/cosmos [OK]: /types/ok #### Get Account Retrieves an account using its unique wallet address, returning detailed account information encapsulated in the `BaseAccount` structure. See the [definition](https://github.com/cosmos/cosmos-sdk/tree/main/x/auth#account-1). ##### Method Declaration :::code-group ```python [Python] async def get_account(self, address: str) -> BaseAccount ``` ```typescript [TypeScript] async getAccount(address: string): Promise ``` ```rust [Rust] pub async fn get_account( &mut self, address: &Address, ) -> Result ``` ```url [API] /cosmos.auth.v1beta1.Query/BaseAccount ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | --------- | -------- | ----------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the account. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------------- | ------------------------------------- | | `200` | [OK] | [BaseAccount] | The account information. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [API] | [Guide - Get Account] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L12 [typescript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L9 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L32 [API]: https://test-dydx-rest.kingnodes.com/cosmos/auth/v1beta1/accounts/dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art [Guide - Get Account]: ../../interaction/data/accounts#get-account [Address]: /types/address [OK]: /types/ok [BaseAccount]: /types/base_account [Bad Request]: /types/bad-request #### Get Account Balance Retrieves the balance of a specified account address for a particular denomination/token. See the [definition](https://github.com/cosmos/cosmos-sdk/tree/main/x/bank#balance). ##### Method Declaration :::code-group ```python [Python] async def get_account_balance( self, address: str, denom: str, ) -> bank_query.QueryBalanceResponse ``` ```typescript [TypeScript] async getAccountBalance( address: string, denom: string, ): Promise ``` ```rust [Rust] pub async fn get_account_balance( &mut self, address: &Address, denom: &Denom, ) -> Result ``` ```url [API] /cosmos.bank.v1beta1.Query/Balance ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | --------- | -------- | ------------------------------------------------------ | | `address` | query | [Address] | true | The wallet address that owns the account. | | `denom` | query | [Denom] | true | Denomination of the token associated with the request. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------ | ------------------------------------- | | `200` | [OK] | [Coin] | The account balance. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [Rust] | [API] | [Guide - Get Account Balance] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L28 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L38 [API]: https://test-dydx-rest.kingnodes.com/cosmos/bank/v1beta1/balances/dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art/by_denom?denom=adv4tnt [Guide - Get Account Balance]: ../../interaction/data/accounts#get-account-balance [Address]: /types/address [Denom]: /types/denom [OK]: /types/ok [Coin]: /types/coin [Bad Request]: /types/bad-request #### Get Account Balances Retrieves the balance for a specific wallet address across all currency denominations within the account. See the [definition](https://github.com/cosmos/cosmos-sdk/tree/main/x/bank#allbalances). ##### Method Declaration :::code-group ```python [Python] async def get_account_balances( self, address: str, ) -> bank_query.QueryAllBalancesResponse ``` ```typescript [TypeScript] async getAccountBalances(address: string): Promise ``` ```rust [Rust] pub async fn get_account_balances( &mut self, address: &Address, ) -> Result, Error> ``` ```url [API] /cosmos.bank.v1beta1.Query/AllBalances ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | --------- | -------- | ----------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the account. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------- | --------------------------------------------- | | `200` | [OK] | [Coin] ⛁ | The response containing all account balances. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [API] | [Guide - Get Account Balances] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L20 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L17 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L35o [API]: https://test-dydx-rest.kingnodes.com/cosmos/bank/v1beta1/balances/dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art [Guide - Get Account Balances]: ../../interaction/data/accounts#get-account-balances [Address]: /types/address [OK]: /types/ok [Coin]: /types/coin [Bad Request]: /types/bad-request #### Get Affiliate Info Get affiliate info by address. ##### Method Declaration :::code-group ```python [Python] async def get_affiliate_info( self, address: str ) -> affiliate_query.AffiliateInfoResponse ``` ```rust [Rust] pub async fn get_affiliate_info( &mut self, address: &Address, ) -> Result ``` ```typescript [TypeScript] async getAffiliateInfo(address: string): Promise ``` ```uri /dydxprotocol.affiliates.Query/AffiliateInfo ``` ::: ##### Parameters | Parameter | Location | Type | Mandatory | Description | | --------- | -------- | --------- | --------- | ----------------------------- | | `address` | Query | [Address] | true | Address to get affiliate info | ##### Response | Status | Meaning | Schema | | ------ | ------- | --------------------------------------- | | `200` | [OK] | [AffiliateModule.AffiliateInfoResponse] | Examples: [API] [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/affiliates/affiliate_info/dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art [OK]: /types/ok [AffiliateModule.AffiliateInfoResponse]: /types/cosmos [Address]: /types/address #### Get Affiliate White List Get affiliate white list. ##### Method Declaration :::code-group ```python [Python] async def get_affiliate_whitelist( self, ) -> affiliate_query.AffiliateWhitelistResponse ``` ```rust [Rust] pub async fn get_affiliate_whitelist(&mut self) -> Result ``` ```typescript [TypeScript] async getAffiliateWhitelist(): Promise ``` ```uri /dydxprotocol.affiliates.Query/AffiliateWhitelist ``` ::: ##### Parameters ##### Response | Status | Meaning | Schema | | ------ | ------- | -------------------------------------------- | | `200` | [OK] | [AffiliateModule.AffiliateWhitelistResponse] | Examples: [API] [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/affiliates/affiliate_whitelist [OK]: /types/ok [AffiliateModule.AffiliateWhitelistResponse]: /types/cosmos #### Get All Affiliate Tiers Get all affiliate tiers. ##### Method Declaration :::code-group ```python [Python] async def get_all_affiliate_tiers( self, ) -> affiliate_query.AllAffiliateTiersResponse ``` ```rust [Rust] pub async fn get_all_affiliate_tiers(&mut self) -> Result ``` ```typescript [TypeScript] async getAllAffiliateTiers(): Promise ``` ```uri /dydxprotocol.affiliates.Query/AllAffiliateTiers ``` ::: ##### Parameters ##### Response | Status | Meaning | Schema | | ------ | ------- | ------------------------------------------- | | `200` | [OK] | [AffiliateModule.AllAffiliateTiersResponse] | Examples: [API] [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/affiliates/all_affiliate_tiers [OK]: /types/ok [AffiliateModule.AllAffiliateTiersResponse]: /types/cosmos #### Get All Gov Proposals Get all gov proposals. ##### Method Declaration :::code-group ```python [Python] async def get_all_gov_proposals( self, proposal_status: Optional[str] = None, voter: Optional[str] = None, depositor: Optional[str] = None, key: Optional[bytes] = None, offset: Optional[int] = None, limit: Optional[int] = None, count_total: Optional[bool] = False, reverse: Optional[bool] = False, ) -> gov_query.QueryProposalsResponse ``` ```rust [Rust] pub async fn get_all_gov_proposals( &mut self, status: ProposalStatus, voter: Address, depositor: Address, pagination: Option, ) -> Result, Error> ``` ```typescript [TypeScript] async getAllGovProposals( proposalStatus: ProposalStatus = ProposalStatus.PROPOSAL_STATUS_VOTING_PERIOD, voter: string = '', depositor: string = '', ): Promise ``` ```url /cosmos.gov.v1.Query/Proposals ``` ::: ##### Parameters | Parameter | Location | Type | Mandatory | Description | | -------------- | -------- | ---------------- | --------- | ------------------------------------ | | proposalStatus | Query | [ProposalStatus] | true | Status of the proposal to filter by. | | voter | Query | string | false | Voter to filter by | | depositor | Query | string | false | Depositor to filter by. | ##### Response | Status | Meaning | Schema | | ------ | ------- | ------------------------------------ | | `200` | [OK] | [GovV1Module.QueryProposalsResponse] | Examples: [API] [API]: https://test-dydx-rest.kingnodes.com/cosmos/gov/v1/proposals?proposalStatus=0 [ProposalStatus]: /types/cosmos [OK]: /types/ok [GovV1Module.QueryProposalsResponse]: /types/cosmos #### Get All Validators Fetches a list of all validators, optionally filtering them by a specified status. See the [definition](https://github.com/cosmos/cosmos-sdk/tree/main/x/staking#validators-2). ##### Method Declaration :::code-group ```python [Python] async def get_all_validators( self, status: str = "", ) -> staking_query.QueryValidatorsResponse ``` ```typescript [TypeScript] async getAllValidators(status: string = ''): Promise ``` ```rust [Rust] pub async fn get_all_validators( &mut self, status: Option, ) -> Result, Error> ``` ```url [API] /cosmos.staking.v1beta1.Query/Validators ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | ------ | -------- | ---------------------------------------- | | `status` | query | string | false | Status to filter out the matched result. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------------- | --------------------------------------- | | `200` | [OK] | [Validator] ⛁ | The response containing all validators. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [API] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L60 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L131 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L62 [API]: https://test-dydx-rest.kingnodes.com/cosmos/staking/v1beta1/validators [OK]: /types/ok [Validator]: /types/validator [Bad Request]: /types/bad-request #### Get Clob Pair Fetches the order book pair identified by a given ID, allowing users to retrieve detailed information about a specific trading pair within a Central Limit Order Book (CLOB) system. The response includes data structured within the `ClobPair` schema. ##### Method Declaration :::code-group ```python [Python] async def get_clob_pair(self, pair_id: int) -> clob_pair_type.ClobPair ``` ```typescript [TypeScript] async getClobPair(pairId: number): Promise ``` ```rust [Rust] pub async fn get_clob_pair(&mut self, pair_id: u32) -> Result ``` ```url [API] /dydxprotocol.clob.Query/ClobPair ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | ----- | -------- | ------------ | | `id` | query | [u32] | true | Clob pair ID | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ---------- | ------------------------------------- | | `200` | [OK] | [ClobPair] | The clob pair information. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The clob pair was not found. | Examples: [Python] | [Rust] | [API] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L86 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L71 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/clob/clob_pair/1 [u32]: /types/u32 [OK]: /types/ok [ClobPair]: /types/clob_pair [Bad Request]: /types/bad-request [Not Found]: /types/not-found import Details from '../../../components/Details'; #### Get Clob Pairs Obtain a comprehensive list of all available order book trading pairs. ##### Method Declaration :::code-group ```python [Python] async def get_clob_pairs(self) -> QueryClobPairAllResponse ``` ```typescript [TypeScript] async getAllClobPairs(): Promise ``` ```rust [Rust] pub async fn get_clob_pairs( &mut self, pagination: Option, ) -> Result, Error> ``` ```url [API] /dydxprotocol.clob.Query/ClobPairAll ``` :::
* Python and TypeScript don't contain options
##### Parameters | Parameter | Location | Type | Required | Description | | ------------ | -------- | ------------- | -------- | ------------------------- | | `pagination` | query | [PageRequest] | false | Parameters of pagination. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------------ | --------------------------------------- | | `200` | [OK] | [ClobPair] ⛁ | The response containing all clob pairs. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [API] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L94 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L49 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L74 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/clob/clob_pair [OK]: /types/ok [ClobPair]: /types/clob_pair [PageRequest]: /types/page_request [Bad Request]: /types/bad-request #### Get Delayed Complete Bridge Messages Retrieve delayed bridge messages associated with a specified wallet address, focusing on messages that have not yet reached completion. It requires the address of the wallet to access its related subaccount messages and returns a structured response detailing the delayed messages. ##### Method Declaration :::code-group ```python [Python] async def get_delayed_complete_bridge_messages( self, address: str = "", ) -> bridge_query.QueryDelayedCompleteBridgeMessagesResponse ``` ```typescript [TypeScript] async getDelayedCompleteBridgeMessages( address: string = '', ): Promise ``` ```rust [Rust] pub async fn get_delayed_complete_bridge_messages( &mut self, address: Address, ) -> Result, Error> ``` ```url [API] /dydxprotocol.bridge.Query/DelayedCompleteBridgeMessages ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | --------- | -------- | ----------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the account. | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | -------------------------------- | ------------------------------------- | | `200` | [OK] | [DelayedCompleteBridgeMessage] ⛁ | | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [API] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L160 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L139 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L104 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/v4/bridge/delayed_complete_bridge_messages [Address]: /types/address [OK]: /types/ok [DelayedCompleteBridgeMessage]: /types/delayed_complete_bridge_messages [Bad Request]: /types/bad-request #### GetDelegationTotalRewards Get all unbonding delegations from a delegator. ##### Method Declaration :::code-group ```python [Python] async def get_delegation_total_rewards( self, address: str ) -> distribution_query.QueryDelegationTotalRewardsResponse ``` ```rust [Rust] pub async fn get_delegation_total_rewards( &mut self, delegator_address: Address, ) -> Result ``` ```typescript [TypeScript] async getDelegationTotalRewards( delegatorAddress: string, ): Promise ``` ```url /cosmos.distribution.v1beta1.Query/DelegationTotalRewards ``` ::: :::unification-plan Implement Python and Rust method ::: ##### Parameters | Parameter | Location | Type | Mandatory | Description | | ---------------- | -------- | ------ | --------- | ------------------------ | | delegatorAddress | Query | string | true | Address of the delegator | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------------------------------------------------------- | ------------------------------------------- | | `200` | [OK] | [DistributionModule.QueryDelegationTotalRewardsResponse] | All unbonding delegations from a delegator. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [API] [API]: https://test-dydx-rest.kingnodes.com/cosmos/distribution/v1beta1/delegators/dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art/rewards [OK]: /types/ok [DistributionModule.QueryDelegationTotalRewardsResponse]: /types/cosmos [Bad Request]: /types/bad-request import Details from '../../../components/Details'; #### Get Delegator Delegations Retrieves all delegations associated with a specific delegator address. See the [definition](https://github.com/cosmos/cosmos-sdk/tree/main/x/staking#delegatordelegations). ##### Method Declaration :::code-group ```python [Python] async def get_delegator_delegations( self, delegator_addr: str ) -> staking_query.QueryDelegatorDelegationsResponse ``` ```typescript [TypeScript] async getDelegatorDelegations( delegatorAddr: string, ): Promise ``` ```rust [Rust] pub async fn get_delegator_delegations( &mut self, delegator_address: Address, pagination: Option, ) -> Result, Error> ``` ```url [API] /cosmos.staking.v1beta1.Query/DelegatorDelegations ``` :::
* Python and TypeScript don't contain options * Options in protocol are not optional * Two types of options (v4 and cosmos, are the same, please use cosmos)
##### Parameters | Parameter | Location | Type | Required | Description | | ---------------- | -------- | ------------- | -------- | ----------------------------------------- | | `delegator_addr` | query | [Address] | true | The wallet address that owns the account. | | `pagination` | query | [PageRequest] | false | Parameters of pagination. | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | ---------------------- | ------------------------------------- | | `200` | [OK] | [DelegationResponse] ⛁ | | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [API] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L142 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L105 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L92 [API]: https://test-dydx-rest.kingnodes.com/cosmos/staking/v1beta1/delegations/dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art [Address]: /types/address [OK]: /types/ok [PageRequest]: /types/page_request [DelegationResponse]: /types/delegation_response [Bad Request]: /types/bad-request import Details from '../../../components/Details'; #### Get Delegator Unbonding Delegations Query all unbonding delegations associated with a specific delegator address. See the [definition](https://github.com/cosmos/cosmos-sdk/tree/main/x/staking#delegatorunbondingdelegations). ##### Method Declaration :::code-group ```python [Python] async def get_delegator_unbonding_delegations( self, delegator_addr: str ) -> staking_query.QueryDelegatorUnbondingDelegationsResponse ``` ```typescript [TypeScript] async getDelegatorUnbondingDelegations( delegatorAddr: string, ): Promise ``` ```rust [Rust] pub async fn get_delegator_unbonding_delegations( &mut self, delegator_address: Address, pagination: Option, ) -> Result, Error> ``` ```url [API] /cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations ``` :::
* Python and TypeScript don't contain options * Options in protocol are not optional * Two types of options (v4 and cosmos, are the same, please use cosmos)
##### Parameters | Parameter | Location | Type | Required | Description | | ---------------- | -------- | ------------- | -------- | ----------------------------------------- | | `delegator_addr` | query | [Address] | true | The wallet address that owns the account. | | `pagination` | query | [PageRequest] | false | Parameters of pagination. | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | ----------------------- | ------------------------------------- | | `200` | [OK] | [UnbondingDelegation] ⛁ | | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [API] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L150 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L113 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L98 [API]: https://test-dydx-rest.kingnodes.com/cosmos/staking/v1beta1/delegators/dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art/unbonding_delegations [Address]: /types/address [OK]: /types/ok [PageRequest]: /types/page_request [UnbondingDelegation]: /types/unbonding_delegations [Bad Request]: /types/bad-request #### Get Equity Tier Limit Config Fetch the configuration details that outline the limits set for different tiers of equity. ##### Method Declaration :::code-group ```python [Python] async def get_equity_tier_limit_config( self, ) -> equity_tier_limit_config_type.EquityTierLimitConfiguration: ``` ```typescript [TypeScript] async getEquityTierLimitConfiguration(): Promise ``` ```rust [Rust] pub async fn get_equity_tier_limit_configuration( &mut self, ) -> Result ``` ```url [API] /dydxprotocol.clob.Query/EquityTierLimitConfiguration ``` ::: ##### Parameters ##### Response | Status | Meaning | Schema | | | ------ | ------------- | ------------------------------ | ------------------------------------- | | `200` | [OK] | [EquityTierLimitConfiguration] | | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [API] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L134 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L89 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L89 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/clob/equity_tier [OK]: /types/ok [EquityTierLimitConfiguration]: /types/equity_tier_limit_configuration [Bad Request]: /types/bad-request #### Get Fee Tiers Retrieves current fee tiers associated with perpetual trading, helping to understand the fees applied based on trading volume or other criteria. ##### Method Declaration :::code-group ```python [Python] async def get_fee_tiers(self) -> fee_tier_query.QueryPerpetualFeeParamsResponse ``` ```typescript [TypeScript] async getFeeTiers(): Promise ``` ```rust [Rust] pub async fn get_fee_tiers(&mut self) -> Result, Error> ``` ```url [API] /dydxprotocol.feetiers.Query/PerpetualFeeParams ``` ::: ##### Response | Status | Meaning | Schema | | | ------ | ------------- | -------------------- | ------------------------------------- | | `200` | [OK] | [PerpetualFeeTier] ⛁ | | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [API] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L168 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L65 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L110 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/v4/feetiers/perpetual_fee_params [OK]: /types/ok [PerpetualFeeTier]: /types/perpetual_fee_tier [Bad Request]: /types/bad-request #### Get Latest Block Retrieves the most recent block from the blockchain network. This is useful for obtaining the latest state or data committed on the chain, ensuring the application operates with up-to-date information. ##### Method Declaration :::code-group ```python [Python] async def latest_block(self) -> tendermint_query.GetLatestBlockResponse ``` ```typescript [TypeScript] async latestBlock(): Promise ``` ```rust [Rust] pub async fn latest_block(&mut self) -> Result ``` ```url [API] ``` ::: ##### Parameters ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------- | ------------------------------------- | | `200` | [OK] | [Block] | The latest block. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [Rust] | [API]| [Guide - Get Latest Block] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L36 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L53 [API]: https://test-dydx-rest.kingnodes.com/cosmos/base/tendermint/v1beta1/blocks/latest [Guide - Get Latest Block]: ../../interaction/data/market#get-latest-block-height [OK]: /types/ok [Block]: /types/block [Bad Request]: /types/bad-request #### Get Latest Block Height Retrieves the most recent block height from a blockchain network. Internally it uses the [`Get Latest Block`](#get-latest-block) API call. ##### Method Declaration :::code-group ```python [Python] async def latest_block_height(self) -> int ``` ```typescript [TypeScript] async latestBlockHeight(): Promise ``` ```rust [Rust] pub async fn latest_block_height(&mut self) -> Result ``` ::: ##### Parameters ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [Height] | The latest block height. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [Rust] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L44 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L56 [OK]: /types/ok [Height]: /types/height [Bad Request]: /types/bad-request #### Get Market Mapper Revenue Share Details Retrieves market mapper revenue share details ##### Method Declaration :::code-group ```python [Python] async def get_market_mapper_revenue_share_details(self, market_id: int) -> QueryMarketMapperRevShareDetailsResponse ``` ```typescript [TypeScript] ``` ```rust [Rust] pub async fn get_market_mapper_rev_share_details( &mut self, market_id: u32, ) -> Result ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ----------- | -------- | ---- | -------- | ----------- | | `market_id` | query | int | true | Market id | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------------------------------------------ | ------------------------------------- | | `200` | [OK] | [QueryMarketMapperRevShareDetailsResponse] | Market mapper revenue share details | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] [Python]: https://github.com/dydxprotocol/v4-clients/blob/95f2ad4b7d87e2c8f819138fcbe903826af47230/v4-client-py-v2/examples/revenue_share_example.py [OK]: /types/ok [QueryMarketMapperRevShareDetailsResponse]: /types/query_market_mapper_revenue_share_details_response [Bad Request]: /types/bad-request #### Get Market Mapper Revenue Share Parmas Retrieves market mapper revenue share params ##### Method Declaration :::code-group ```python [Python] async def get_market_mapper_revenue_share_param(self) -> QueryMarketMapperRevenueShareParamsResponse ``` ```typescript [TypeScript] ``` ```rust [Rust] pub async fn get_market_mapper_revenue_share_params( &mut self, ) -> Result ``` ```url [API] ``` ::: ##### Parameters ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | --------------------------------------------- | ------------------------------------- | | `200` | [OK] | [QueryMarketMapperRevenueShareParamsResponse] | Market mapper revenue share params | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] [Python]: https://github.com/dydxprotocol/v4-clients/blob/95f2ad4b7d87e2c8f819138fcbe903826af47230/v4-client-py-v2/examples/revenue_share_example.py [OK]: /types/ok [QueryMarketMapperRevenueShareParamsResponse]: /types/query_market_mapper_revenue_share_params_response [Bad Request]: /types/bad-request #### Get Node Info Query for node info. ##### Method Declaration :::code-group ```python [Python] async def get_node_info(self) -> tendermint_query.GetNodeInfoResponse ``` ```typescript [TypeScript] ``` ```rust [Rust] pub async fn get_node_info(&mut self) -> Result ``` ```url [API] ``` ::: ##### Parameters ##### Response | Status | Meaning | Schema | | | ------ | ------------- | --------------------- | ------------------------------------- | | `200` | [OK] | [GetNodeInfoResponse] | | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Rust] | [API] [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L44 [API]: https://test-dydx-rest.kingnodes.com/cosmos/base/tendermint/v1beta1/node_info [GetNodeInfoResponse]: /types/get_node_info_response [OK]: /types/ok [Bad Request]: /types/bad-request #### Get Order Router Revenue share Retrieves order router revenue share ##### Method Declaration :::code-group ```python [Python] async def get_order_router_revenue_share(self, address: str) -> QueryOrderRouterRevShareResponse ``` ```typescript [TypeScript] ``` ```rust [Rust] pub async fn get_order_router_rev_share( &mut self, address: Address, ) -> Result ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Mandatory | Description | | --------- | -------- | ------ | --------- | -------------------------------------- | | address | Query | string | true | Address of the revenue share recipient | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ---------------------------------- | ------------------------------------- | | `200` | [OK] | [QueryOrderRouterRevShareResponse] | Order router revenue share | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] [Python]: https://github.com/dydxprotocol/v4-clients/blob/95f2ad4b7d87e2c8f819138fcbe903826af47230/v4-client-py-v2/examples/revenue_share_example.py [OK]: /types/ok [QueryOrderRouterRevShareResponse]: /types/query_order_router_revenue_share_response [Bad Request]: /types/bad-request #### Get Perpetual Queries a specific perpetual contract by its unique perpetual ID, returning details about the contract. ##### Method Declaration :::code-group ```python [Python] async def get_perpetual(self, perpetual_id: int) -> QueryPerpetualResponse ``` ```typescript [TypeScript] async getPerpetual(perpetualId: number): Promise ``` ```rust [Rust] pub async fn get_perpetual( &mut self, perpetual_id: u32, ) -> Result ``` ```url [API] /dydxprotocol.perpetuals.Query/Perpetual ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | ----- | -------- | ------------ | | `id` | query | [u32] | true | Perpetual ID | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | ----------- | ------------------------------------- | | `200` | [OK] | [Perpetual] | | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The perpetual was not found. | Examples: [Python] | [Rust] | [API] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L118 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L83 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/perpetuals/perpetual/1 [u32]: /types/u32 [OK]: /types/ok [Perpetual]: /types/perpetual [Bad Request]: /types/bad-request [Not Found]: /types/not-found import Details from '../../../components/Details'; #### Get Perpetuals Retrieve a list of all perpetuals currently available in the system. ##### Method Declaration :::code-group ```python [Python] async def get_perpetuals(self) -> QueryAllPerpetualsResponse ``` ```typescript [TypeScript] async getAllPerpetuals(): Promise ``` ```rust [Rust] pub async fn get_perpetuals( &mut self, pagination: Option, ) -> Result, Error> ``` ```url [API] /dydxprotocol.perpetuals.Query/AllPerpetuals ``` :::
* Python and TypeScript don't contain options * Options in protocol are not optional
##### Parameters | Parameter | Location | Type | Required | Description | | ------------ | -------- | ------------- | -------- | ------------------------- | | `pagination` | query | [PageRequest] | false | Parameters of pagination. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------------- | --------------------------------------- | | `200` | [OK] | [Perpetual] ⛁ | The response containing all perpetuals. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [Rust] | [API] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L126 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L86 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/perpetuals/perpetual [OK]: /types/ok [PageRequest]: /types/page_request [Perpetual]: /types/perpetual [Bad Request]: /types/bad-request #### Get Price Retrieve the current market price for a specified market, identified by its market ID. ##### Method Declaration :::code-group ```python [Python] async def get_price(self, market_id: int) -> market_price_type.MarketPrice ``` ```typescript [TypeScript] async getPrice(marketId: number): Promise ``` ```rust [Rust] pub async fn get_price(&mut self, market_id: u32) -> Result ``` ```url [API] /dydxprotocol.prices.Query/MarketPrice ``` ::: :::warning In `dYdX`, `market id` and `perpetual id` may not match. For example, ```text dydxprotocold query perpetuals show-perpetual 145 perpetual: funding_index: "6530" open_interest: "0" params: atomic_resolution: -5 default_funding_ppm: 100 id: 145 liquidity_tier: 4 market_id: 71 market_type: PERPETUAL_MARKET_TYPE_ISOLATED ticker: LUCE-USD ``` Here, `perpetual id` is 145 but the `market id` is 71. ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ----------- | -------- | ----- | -------- | ---------------------------------- | | `market_id` | query | [u32] | true | ID of the market to fetch price of | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | ------------- | ------------------------------------- | | `200` | [OK] | [MarketPrice] | | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The market was not found. | Examples: [Python] | [Rust] | [API] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L102 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L102 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/prices/market/1 [u32]: /types/u32 [OK]: /types/ok [MarketPrice]: /types/market_price [Bad Request]: /types/bad-request [Not Found]: /types/not-found import Details from '../../../components/Details'; #### Get Prices Query all market prices from the system, providing an overview of current market values. ##### Method Declaration :::code-group ```python [Python] async def get_prices(self) -> QueryAllMarketPricesResponse ``` ```typescript [TypeScript] async getAllPrices(): Promise ``` ```rust [Rust] pub async fn get_prices( &mut self, pagination: Option, ) -> Result, Error> ``` ```url [API] /dydxprotocol.prices.Query/AllMarketPrices ``` :::
* Python and TypeScript don't contain options
##### Parameters | Parameter | Location | Type | Required | Description | | ------------ | -------- | ------------- | -------- | ------------------------- | | `pagination` | query | [PageRequest] | false | Parameters of pagination. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | --------------- | ------------------------------------------ | | `200` | [OK] | [MarketPrice] ⛁ | The response containing all market prices. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [API] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L110 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L57 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L80 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/prices/market [OK]: /types/ok [PageRequest]: /types/page_request [MarketPrice]: /types/market_price [Bad Request]: /types/bad-request #### Get Referred By Get referred by. ##### Method Declaration :::code-group ```python [Python] async def get_referred_by(self, address: str) -> affiliate_query.ReferredByResponse ``` ```rust [Rust] pub async fn get_referred_by(&mut self, address: Address) -> Result ``` ```typescript [TypeScript] async getReferredBy(address: string): Promise ``` ```uri /dydxprotocol.affiliates.Query/ReferredBy ``` ::: ##### Parameters | Parameter | Location | Type | Mandatory | Description | | --------- | -------- | --------- | --------- | -------------------------- | | `address` | Query | [Address] | true | Address to get referred by | ##### Response | Status | Meaning | Schema | | ------ | ------- | ------------------------------------ | | `200` | [OK] | [AffiliateModule.ReferredByResponse] | Examples: [API] [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/affiliates/referred_by/dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art [OK]: /types/ok [AffiliateModule.ReferredByResponse]: /types/cosmos [Address]: /types/address import Details from '../../../components/Details'; #### Get Rewards Params Retrieves the parameters for the rewards system, providing insight into the set configurations for earning and distributing rewards. ##### Method Declaration :::code-group ```python [Python] async def get_rewards_params(self) -> rewards_query.QueryParamsResponse ``` ```typescript [TypeScript] async getRewardsParams(): Promise ``` ```rust [Rust] pub async fn get_rewards_params(&mut self) -> Result ``` ```url [API] /dydxprotocol.rewards.Query/Params ``` :::
* Rename `Params` to `RewardsParams` in Rust
##### Parameters ##### Response | Status | Meaning | Schema | | ------ | ------- | --------------- | | `200` | [OK] | [RewardsParams] | Examples: [Python] | [TypeScript] | [Rust] | [API] | [Guide - Get Rewards Params] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L184 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L97 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L116 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/v4/rewards/params [Guide - Get Rewards Params]: ../../interaction/data/market#get-rewards-params [OK]: /types/ok [RewardsParams]: /types/rewards_params import Details from '../../../components/Details'; #### Get Subaccount Fetches details of a specific subaccount based on its owner's address and the subaccount's unique number. ##### Method Declaration :::code-group ```python [Python] async def get_subaccount( self, address: str, account_number: int, ) -> Optional[subaccount_type.Subaccount] ``` ```typescript [TypeScript] async getSubaccount( address: string, accountNumber: number, ): Promise ``` ```rust [Rust] pub async fn get_subaccount( &mut self, subaccount: &Subaccount, ) -> Result ``` ```url [API] /dydxprotocol.subaccounts.Query/Subaccount ``` :::
* Rust uses `Subaccount` - a wrapper that combines `Address` with `SubaccountNumber`, it's safer to create the same type for Python and TypeScript
##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | ------------------ | -------- | ---------------------------------------------------------------------- | | `owner` | query | [Address] | true | The wallet address that owns the account. | | `number` | query | [SubaccountNumber] | true | The identifier for the specific subaccount withing the wallet address. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ---------------- | --------------------------------------- | | `200` | [OK] | [SubaccountInfo] | The response containing the subaccount. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | Examples: [Python] | [TypeScript] | [Rust] | [API] | [Guide - Get Subaccount] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L68 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L33 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L65 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/subaccounts/subaccount/dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art/0 [Guide - Get Subaccount]: ../../interaction/data/accounts#get-account-subaccount [Address]: /types/address [OK]: /types/ok [SubaccountNumber]: /types/subaccount_number [SubaccountInfo]: /types/subaccount_info [Bad Request]: /types/bad-request [Not Found]: /types/not-found #### Get Subaccounts Retrieves a comprehensive list of all subaccounts, returning structured information encapsulated in a response object. ##### Method Declaration :::code-group ```python [Python] async def get_subaccounts(self) -> QuerySubaccountAllResponse ``` ```typescript [TypeScript] async getSubaccounts(): Promise ``` ```rust [Rust] pub async fn get_subaccounts(&mut self) -> Result, Error> ``` ```url [API] /dydxprotocol.subaccounts.Query/SubaccountAll ``` ::: ##### Parameters ##### Response | Status | Meaning | Schema | | ------ | ------- | ------------------ | | `200` | [OK] | [SubaccountInfo] ⛁ | Examples: [Python] | [TypeScript] | [Rust] | [API] | [Guide - Get Subaccounts] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L77 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L25 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L68 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/subaccounts/subaccount [Guide - Get Subaccounts]: ../../interaction/data/accounts#get-account-subaccounts [OK]: /types/ok [SubaccountInfo]: /types/subaccount_info #### Get Unconditional Revenue Sharing Config Retrieves unconditional revenue share config ##### Method Declaration :::code-group ```python [Python] async def get_unconditional_revenue_sharing_config(self) -> QueryUnconditionalRevShareConfigResponse ``` ```typescript [TypeScript] ``` ```rust [Rust] pub async fn get_unconditional_rev_share_config( &mut self, ) -> Result ``` ```url [API] ``` ::: ##### Parameters ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------------------------------------------ | ------------------------------------- | | `200` | [OK] | [QueryUnconditionalRevShareConfigResponse] | Unconditional revenue share config | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] [Python]: https://github.com/dydxprotocol/v4-clients/blob/95f2ad4b7d87e2c8f819138fcbe903826af47230/v4-client-py-v2/examples/revenue_share_example.py [OK]: /types/ok [QueryUnconditionalRevShareConfigResponse]: /types/query_unconditional_revenue_share_config_response [Bad Request]: /types/bad-request #### Get User Fee Tier Retrieves the perpetual fee tier associated with a specific wallet address, providing information on the user's current fee structure. ##### Method Declaration :::code-group ```python [Python] async def get_user_fee_tier( self, address: str ) -> fee_tier_query.QueryUserFeeTierResponse ``` ```typescript [TypeScript] async getUserFeeTier(address: string): Promise ``` ```rust [Rust] pub async fn get_user_fee_tier( &mut self, address: Address, ) -> Result ``` ```url [API] /dydxprotocol.feetiers.Query/UserFeeTier ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | --------- | -------- | ----------------------------------------- | | `user` | query | [Address] | true | The wallet address that owns the account. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------------------ | ------------------------------------------ | | `200` | [OK] | [PerpetualFeeTier] | The response containing the user fee tier. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [API]| [Guide - Get User Fee Tier] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L176 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L73 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L113 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/v4/feetiers/user_fee_tier?user=dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art [Guide - Get User Fee Tier]: ../../interaction/data/market#get-user-fee-tier [Address]: /types/address [OK]: /types/ok [PerpetualFeeTier]: /types/perpetual_fee_tier [Bad Request]: /types/bad-request #### Get User Stats Retrieves statistical data for a user's Maker and Taker positions associated with a specified wallet address. ##### Method Declaration :::code-group ```python [Python] async def get_user_stats( self, address: str, ) -> stats_query.QueryUserStatsResponse ``` ```typescript [TypeScript] async getUserStats( address: string, ): Promise<{ takerNotional: Long; makerNotional: Long } | undefined> ``` ```rust [Rust] pub async fn get_user_stats( &mut self, address: &Address, ) -> Result ``` ```url [API] /dydxprotocol.stats.Query/UserStats ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | --------- | -------- | ----------------------------------------- | | `user` | query | [Address] | true | The wallet address that owns the account. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ----------- | --------------------------------------- | | `200` | [OK] | [UserStats] | The response containing the user stats. | | `400` | [Bad Request] | | The request was malformed or invalid. | Examples: [Python] | [TypeScript] | [Rust] | [API] [Python]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-py-v2/examples/validator_get_example.py#L52 [TypeScript]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-js/examples/validator_get_example.ts#L81 [Rust]: https://github.com/dydxprotocol/v4-clients/blob/3e8c7e1b960291b7ef273962d374d9934a5c4d33/v4-client-rs/client/examples/validator_get.rs#L59 [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/v4/stats/user_stats?user=dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art [Address]: /types/Address [OK]: /types/ok [UserStats]: /types/user_stats [Bad Request]: /types/bad-request #### Get Withdrawal and Transfer Gating Status Get withdrawal and transfer gating status. ##### Method Declaration :::code-group ```python [Python] async def get_withdrawal_and_transfer_gating_status( self, perpetual_id: int ) -> subaccount_query.QueryGetWithdrawalAndTransfersBlockedInfoResponse ``` ```rust [Rust] pub async fn get_withdrawal_and_transfer_gating_status( &mut self, perpetual_id: u32, ) -> Result ``` ```typescript [TypeScript] async getWithdrawalAndTransferGatingStatus( perpetualId: number, ): Promise ``` ```url /dydxprotocol.subaccounts.Query/GetWithdrawalAndTransfersBlockedInfo ``` ::: ##### Parameters | Parameter | Location | Type | Mandatory | Description | | ----------- | -------- | ---- | --------- | ------------ | | perpetualId | Query | int | true | Perpetual id | ##### Response | Status | Meaning | Schema | | ------ | ------- | --------------------------------------------------------------------- | | `200` | [OK] | [SubaccountsModule.QueryGetWithdrawalAndTransfersBlockedInfoResponse] | Examples: [API] [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/subaccounts/withdrawals_and_transfers_blocked_info/1 [OK]: /types/ok [SubaccountsModule.QueryGetWithdrawalAndTransfersBlockedInfoResponse]: /types/cosmos #### Get Withdrawal Capacity By Denom Get withdrawal capacity by denom. ##### Method Declaration :::code-group ```python [Python] async def get_withdrawal_capacity_by_denom( self, denom: str ) -> rate_query.QueryCapacityByDenomResponse ``` ```rust [Rust] pub async fn get_withdrawal_capacity_by_denom(&mut self, denom: Denom) -> Result, Error> ``` ```typescript [TypeScript] async getWithdrawalCapacityByDenom( denom: string, ): Promise ``` ```uri /dydxprotocol.ratelimit.Query/CapacityByDenom ``` ::: ##### Parameters | Parameter | Location | Type | Mandatory | Description | | --------- | -------- | ------ | --------- | ------------------ | | denom | Query | string | true | Denomination value | ##### Response | Status | Meaning | Schema | | ------ | ------- | ---------------------------------------------- | | `200` | [OK] | [RateLimitModule.QueryCapacityByDenomResponse] | Examples: [API] [API]: https://test-dydx-rest.kingnodes.com/dydxprotocol/v4/ratelimit/capacity_by_denom?denom=adv4tnt [OK]: /types/ok [RateLimitModule.QueryCapacityByDenomResponse]: /types/cosmos import PublicApi from './intro.mdx' import GetAccountBalances from './get_account_balances.mdx' import GetAccountBalance from './get_account_balance.mdx' import GetAccount from './get_account.mdx' import GetNodeInfo from './get_node_info.mdx' import GetLatestBlock from './get_latest_block.mdx' import GetLatestBlockHeight from './get_latest_block_height.mdx' import GetUserStats from './get_user_stats.mdx' import GetAllValidators from './get_all_validators.mdx' import GetSubaccounts from './get_subaccounts.mdx' import GetSubaccount from './get_subaccount.mdx' import GetClobPair from './get_clob_pair.mdx' import GetClobPairs from './get_clob_pairs.mdx' import GetPrice from './get_price.mdx' import GetPrices from './get_prices.mdx' import GetPerpetual from './get_perpetual.mdx' import GetPerpetuals from './get_perpetuals.mdx' import GetEquityTierLimitConfig from './get_equity_tier_limit_config.mdx' import GetDelegatorDelegations from './get_delegator_delegations.mdx' import GetDelegatorUnbondingDelegations from './get_delegator_unbonding_delegations.mdx' import GetDelayedCompleteBridgeMessages from './get_delayed_complete_bridge_messages.mdx' import GetFeeTiers from './get_fee_tiers.mdx' import GetUserFeeTier from './get_user_fee_tier.mdx' import GetRewardsParams from './get_rewards_params.mdx' import GetAffiliateInfo from './get_affiliate_info.mdx' import GetAffiliateWhiteList from './get_affiliate_whitelist.mdx' import GetAllAffiliateTiers from './get_all_affiliate_tiers.mdx' import GetAllGovProposals from './get_all_gov_proposals.mdx' import GetDelegationTotalRewards from './get_delegation_total_rewards.mdx' import GetReferredBy from './get_referred_by.mdx' import GetWithdrawalAndTransferGatingStatus from './get_withdrawal_and_transfer_gating_status.mdx' import GetWithdrawalCapacityByDenom from './get_withdrawal_capacity_by_denom.mdx' import QueryAddress from './query_address.mdx' import GetMarketMapperRevenueShareDetails from './get_market_mapper_revenue_share_details.mdx' import GetMarketMapperRevenueShareParam from './get_market_mapper_revenue_share_param.mdx' import GetOrderRouterRevenueShare from './get_order_router_revenue_share.mdx' import GetUnconditionalRevenueShareConfig from './get_unconditional_revenue_sharing_config.mdx' ### Public Node API #### Query Address Fetch account’s number and sequence number from the network. ##### Method Declaration :::code-group ```python [Python] async def query_address(self, address: str) -> (int, int) ``` ```typescript [TypeScript] ``` ```rust [Rust] pub async fn query_address( &mut self, address: &Address, ) -> Result<(u64, u64), Error> ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Description | | --------- | -------- | --------- | ----------------------------------------- | | `address` | query | [Address] | The wallet address that owns the account. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ---------------------- | ------------------------------------- | | `200` | [OK] | Pair of ([u64], [u64]) | The response containing the account. | | `400` | [Bad Request] | | The request was malformed or invalid. | [Address]: /types/address [OK]: /types/ok [Bad Request]: /types/bad-request [u64]: /types/u64 #### Query Transaction Query the network for a transaction. ##### Method Declaration :::code-group ```python [Python] async def query_transaction(self, tx_hash: str) -> Tx ``` ```typescript [TypeScript] ``` ```rust [Rust] pub async fn query_transaction(&mut self, tx_hash: &TxHash) -> Result ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Description | | --------- | -------- | -------- | ---------------- | | `tx_hash` | query | [TxHash] | Transaction Hash | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------ | ---------------------------------------- | | `200` | [OK] | [Tx] | The response containing the transaction. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The transaction was not found. | [TxHash]: /types/tx_hash [OK]: /types/ok [Tx]: /types/tx [Bad Request]: /types/bad-request [Not Found]: /types/not-found #### Query Transaction Result Query the network for a transaction result. ##### Method Declaration :::code-group ```python [Python] ``` ```typescript [TypeScript] ``` ```rust [Rust] pub async fn query_transaction_result( &mut self, tx_hash: Result, ) -> Result, Error> ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Description | | --------- | -------- | -------- | ---------------- | | `tx_hash` | query | [TxHash] | Transaction Hash | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | ------ | ------------------------------------- | | `200` | [OK] | [Tx] | | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The transaction was not found. | [TxHash]: /types/tx_hash [OK]: /types/ok [Tx]: /types/tx [Bad Request]: /types/bad-request [Not Found]: /types/not-found ## gRPC Streaming Example [Indexer-based orderbook streaming](/interaction/data/watch-orderbook), due to the increased latency introduced by the Indexer, can cause issues like more outdated orders or a crossed orderbook. In a full node, the orderbook available will be more up-to-date and should be preferred over the Indexer-based solution. This requires a full node with [gRPC streaming enabled](/nodes/full-node-streaming). :::note While more up-to-date than the Indexer, the orderbook state can vary slightly between nodes due to dYdX's offchain orderbook design. ::: In this example, we'll guide on how to connect and handle the gRPC data in order to enable use-cases such as orderbook watching. While full node streaming is provided both using gRPC and WebSockets, we'll focus here on gRPC-based streaming due to its higher efficiency. :::tip[Full Example] This guide is only a general walkthrough of the important methods on how to establish and maintain a gRPC connection, and maintain the orderbook state. For the worked example see the [repository](https://github.com/dydxprotocol/grpc-stream-client) (Python). ::: :::::steps ### Install dependencies gRPC uses structured and serialized data using [Protocol Buffers](https://en.wikipedia.org/wiki/Protocol_Buffers). For Python, install the package [`v4-proto`](https://pypi.org/project/v4-proto/) which already contains the messages and generated code used in gRPC. This is the main dependency used in this guide, allowing us to deserialize the incoming stream messages. ::::info :::details[Dependency list] The full dependency list used in this guide. ```bash [Terminal] grpcio>=1.67.0 grpcio-tools==1.64.1 protobuf==5.28.1 PyYAML==6.0.1 sortedcontainers==2.4.0 v4-proto==6.0.8 ``` ::: :::: ### Establish a connection With a full node with gRPC streaming available, we can now try to establish a connection to it. We'll need to define a gRPC configuration to maintain an healthy connection. Lets also define here the CLOB pairs IDs (markets) that we are interested in, as well as the relevant subaccounts. ```python [Python] import grpc from v4_proto.dydxprotocol.clob.query_pb2 import StreamOrderbookUpdatesRequest from v4_proto.dydxprotocol.clob.query_pb2_grpc import QueryStub from v4_proto.dydxprotocol.subaccounts.subaccount_pb2 import SubaccountId GRPC_OPTIONS = [ # Send keepalive ping every 30 seconds ("grpc.keepalive_time_ms", 3000), # Wait 10 seconds for ping ack before considering the connection dead ("grpc.keepalive_timeout_ms", 1000,), # Allow keepalive pings even when there are no calls ("grpc.keepalive_permit_without_calls", True,), # Minimum allowed time between pings ("grpc.http2.min_time_between_pings_ms", 3000,), # Minimum allowed time between pings with no data ("grpc.http2.min_ping_interval_without_data_ms", 3000,), ] endpoint = "your-node-address:9090" clob_pair_ids = [0, 1] # ETH-USD subaccount_ids = [] # All subaccounts # Establish async connection async with grpc.aio.insecure_channel(endpoint, GRPC_OPTIONS) as channel: tasks = [ listen_to_grpc_stream( channel, clob_pair_ids, subaccount_ids, feed_handler, ), ] await asyncio.gather(*tasks) ``` ### Streaming The streaming function `listen_to_grpc_stream()` processes the continuous stream of orderbook updates. Each message contains batched updates that must be processed sequentially to maintain correct state. ```python [Python] async def listen_to_grpc_stream( channel: grpc.Channel, clob_pair_ids: List[int], subaccount_ids: List[str], feed_handler: FeedHandler, ): """Subscribe to gRPC stream and handle orderbook updates.""" stub = QueryStub(channel) # Parse subaccount ids (format: owner_address/subaccount_number) subaccount_protos = [ SubaccountId(owner=sa.split('/')[0], number=int(sa.split('/')[1])) for sa in subaccount_ids ] request = StreamOrderbookUpdatesRequest( clob_pair_id=clob_pair_ids, subaccount_ids=subaccount_protos ) async for response in stub.StreamOrderbookUpdates(request): fill_events = feed_handler.handle(response) # Process fills and other updates for fill in fill_events: print(f"Fill: {fill.quantums} @ {fill.subticks}") ``` ### Maintaining Orderbook and Subaccount State Lets add a component `FeedHandler` that maintains local state by processing streaming updates. It will handle different message types and ensure state consistency. ```python [Python] class FeedHandler: def __init__(self): self.books: Dict[int, LimitOrderBook] = {} self.subaccounts: Dict[SubaccountId, StreamSubaccount] = {} self.has_seen_first_snapshot = False def handle(self, message: StreamOrderbookUpdatesResponse) -> List[Fill]: """Handle incoming stream messages and update state.""" collected_fills = [] for update in message.updates: update_type = update.WhichOneof('update_message') if update_type == 'orderbook_update': self._handle_orderbook_update(update.orderbook_update) elif update_type == 'order_fill': fills = self._handle_fills(update.order_fill, update.exec_mode) collected_fills += fills elif update_type == 'subaccount_update': self._handle_subaccounts(update.subaccount_update) return collected_fills ``` #### Snapshots Snapshots provide the complete current state and serve as the foundation for processing subsequent incremental updates. The client should wait for snapshots before processing any other messages to ensure state consistency. > Discard order messages until you receive a `StreamOrderbookUpdate` with `snapshot` set to `true`. This message contains the full orderbook state for each clob pair. > Similarly, discard subaccount messages until you receive a `StreamSubaccountUpdate` with `snapshot` set to `true`. This message contains the full subaccount state for each subscribed subaccount. ```python def _handle_orderbook_update(self, update: StreamOrderbookUpdate): """Handle orderbook snapshots and incremental updates.""" # Skip messages until the first snapshot is received if not self.has_seen_first_snapshot and not update.snapshot: return # Skip subsequent snapshots if update.snapshot and self.has_seen_first_snapshot: logging.warning("Skipping subsequent snapshot") return if update.snapshot: # This is a new snapshot of the book state if not self.has_seen_first_snapshot: self.has_seen_first_snapshot = True # Process each update in the batch for u in update.updates: update_type = u.WhichOneof('update_message') if update_type == 'order_place': self._handle_order_place(u.order_place) elif update_type == 'order_update': self._handle_order_update(u.order_update) elif update_type == 'order_remove': self._handle_order_remove(u.order_remove) def _handle_subaccounts(self, update: StreamSubaccountUpdate): """Handle subaccount snapshots and updates.""" parsed_subaccount = parse_subaccounts(update) subaccount_id = parsed_subaccount.subaccount_id if update.snapshot: # Skip subsequent snapshots if subaccount_id in self.subaccounts: logging.warning(f"Saw multiple snapshots for subaccount {subaccount_id}") return self.subaccounts[subaccount_id] = parsed_subaccount else: # Skip messages until the first snapshot is received if subaccount_id not in self.subaccounts: return # Update the existing subaccount existing_subaccount = self.subaccounts[subaccount_id] existing_subaccount.perpetual_positions.update(parsed_subaccount.perpetual_positions) existing_subaccount.asset_positions.update(parsed_subaccount.asset_positions) ``` #### Orderbook Management The orderbook is implemented as a Level 3 (L3) order book that maintains individual orders with their full details. This provides maximum granularity for trading applications that need to track specific orders and their execution. ::::info :::details[Order Data] ```python from dataclasses import dataclass from typing import Dict, Iterator, Optional from sortedcontainers import SortedDict @dataclass(frozen=True) # frozen=True allows use as dict keys class OrderId: """Unique identifier for orders within a CLOB pair.""" owner_address: str # Account that placed the order subaccount_number: int # Subaccount index within the account client_id: int # Client-assigned order ID (can be reused) order_flags: int # Order type flags (conditional, short-term, etc.) @dataclass class Order: """Individual order with pricing and quantity information.""" order_id: OrderId is_bid: bool # True for buy orders, False for sell orders original_quantums: int # Original order size (integer, needs conversion) quantums: int # Remaining size after fills (integer, needs conversion) subticks: int # Price level (integer, needs conversion) ``` ::: :::: Lets implement an efficient Orderbook data structure named `LimitOrderBook` suitable for high-frequency trading. ::::info :::details[Orderbook] ```python class LimitOrderBook: """ Level 3 orderbook with O(log N) insertion, O(1) updates and removal. Architecture: - SortedDict maps price levels to order queues - Each price level is a doubly-linked list (FIFO order execution) - Hash map provides O(1) order lookup by OrderId """ def __init__(self): # Fast order lookup by ID self.oid_to_order_node: Dict[OrderId, ListNode] = {} # Price-ordered asks (lowest price first) self._asks: SortedDict[int, DoublyLinkedList] = SortedDict() # Price-ordered bids (highest price first) self._bids: SortedDict[int, DoublyLinkedList] = SortedDict(lambda x: -x) def add_order(self, order: Order) -> Order: """ Add order to the end of its price level queue. Orders at the same price level execute in time priority (FIFO). New orders are always placed at the back of the queue. """ # Determine which side of the book book_side = self._bids if order.is_bid else self._asks # Get or create the price level level = self._get_or_create_level(order.subticks, book_side) # Add to end of price level queue and index for fast lookup order_node = level.append(order) self.oid_to_order_node[order.order_id] = order_node return order def remove_order(self, oid: OrderId) -> Order: """ Remove order from book and clean up empty price levels. Returns the removed order for processing (e.g., logging cancellations). """ # Find and remove the order node order_node = self.oid_to_order_node.pop(oid) order = order_node.data # Remove from the appropriate price level book_side = self._bids if order.is_bid else self._asks level: DoublyLinkedList = book_side[order.subticks] level.remove(order_node) # Clean up empty price levels to save memory if level.head is None: del book_side[order.subticks] return order def get_order(self, oid: OrderId) -> Optional[Order]: """O(1) order lookup by ID.""" node = self.oid_to_order_node.get(oid) return node.data if node else None def asks(self) -> Iterator[Order]: """Iterate asks from best (lowest) to worst (highest) price.""" for price, level in self._asks.items(): for order in level: # Time priority within price level yield order def bids(self) -> Iterator[Order]: """Iterate bids from best (highest) to worst (lowest) price.""" for price, level in self._bids.items(): for order in level: # Time priority within price level yield order @staticmethod def _get_or_create_level(subticks: int, book_side: SortedDict) -> DoublyLinkedList: """Lazily create price levels as orders arrive.""" if subticks not in book_side: book_side[subticks] = DoublyLinkedList() return book_side[subticks] ``` ::: :::: Each price level maintains orders in a doubly-linked list for efficient insertion and removal. ::::info :::details[Order Queue] ```python class ListNode: """Node in the doubly-linked list representing an order.""" def __init__(self, data): self.data = data # The Order object self.prev = None # Previous order in queue self.next = None # Next order in queue class DoublyLinkedList: """ FIFO queue for orders at the same price level. Provides O(1) append and remove operations essential for high-frequency order book updates. """ def __init__(self): self.head = None # First order (next to execute) self.tail = None # Last order (most recently added) def append(self, data) -> ListNode: """Add new order to the end of the queue.""" new_node = ListNode(data) if self.head is None: # First order at this price level self.head = self.tail = new_node else: # Add to end, maintaining FIFO order self.tail.next = new_node new_node.prev = self.tail self.tail = new_node return new_node def remove(self, node_to_remove: ListNode): """Remove specific order from anywhere in the queue.""" # Update previous node's forward link if node_to_remove.prev: node_to_remove.prev.next = node_to_remove.next else: self.head = node_to_remove.next # Update next node's backward link if node_to_remove.next: node_to_remove.next.prev = node_to_remove.prev else: self.tail = node_to_remove.prev # Clean up the removed node node_to_remove.prev = node_to_remove.next = None ``` ::: :::: Orders progress through a lifecycle of placement, updates, and removal. Your client must handle each stage correctly to maintain accurate book state. ::::info :::details[Order Management] ```python def _handle_order_place(self, order_place: OrderPlaceV1) -> int: """ Insert new orders at the end of their price level queue. Track both initial quantity and cumulative filled amount. """ order = helpers.parse_indexer_order(order_place.order) clob_pair_id = order_place.order.order_id.clob_pair_id book = self._get_book(clob_pair_id) # Verify order doesn't already exist (should see remove before place) if book.get_order(order.order_id) is not None: raise AssertionError(f"Order {order.order_id} already exists") # Store initial quantums for fill tracking order.original_quantums = order.quantums book.add_order(order) return clob_pair_id def _handle_order_update(self, order_update: OrderUpdateV1) -> int: """ Update an order's total filled amount. Note: total_filled_quantums is cumulative, not incremental. Remaining quantity = original_quantums - total_filled_quantums """ clob_pair_id = order_update.order_id.clob_pair_id oid = helpers.parse_indexer_oid(order_update.order_id) order = self._get_book(clob_pair_id).get_order(oid) if order is None: # Order may not exist yet due to message ordering return clob_pair_id # Calculate remaining quantity after fills order.quantums = order.original_quantums - order_update.total_filled_quantums return clob_pair_id def _handle_order_remove(self, order_remove: OrderRemoveV1) -> int: """ Remove orders from the book. Removal reasons include: - User cancellation - Order expiry (Good-Til-Block/Good-Til-BlockTime) - Complete fill - Best-effort cancellation (order may still fill until expiry) """ clob_pair_id = order_remove.order_id.clob_pair_id oid = helpers.parse_indexer_oid(order_remove.order_id) book = self._get_book(clob_pair_id) # Remove order if it exists (may have already been removed) if book.get_order(oid) is not None: book.remove_order(oid) return clob_pair_id ``` ::: :::: Handle both optimistic and finalized trades correctly. Optimistic fills update book state immediately, while only finalized fills should be treated as confirmed trades. ::::info :::details[Fills and Trade Confirmation] ```python def _handle_fills(self, order_fill: StreamOrderbookFill, exec_mode: int) -> List[fills.Fill]: """ Process trade fills and update maker order states. Important: Use ALL ClobMatch messages to update book state (optimistic), but only treat execMode=7 (finalized) fills as confirmed trades. """ # Skip fills until we have orderbook snapshots if not self.has_seen_first_snapshot: return [] parsed_fills = fills.parse_fill(order_fill, exec_mode) for fill in parsed_fills: clob_pair_id = fill.clob_pair_id maker_oid = fill.maker order = self._get_book(clob_pair_id).get_order(maker_oid) if order is not None: # Update maker order's remaining quantity # fill_amounts represents total filled, not incremental order.quantums = order.original_quantums - fill.maker_total_filled_quantums # Log different treatment for optimistic vs finalized status = "finalized" if exec_mode == 7 else "optimistic" logging.debug(f"({status}) Fill processed: {fill.quantums} @ {fill.subticks}") return parsed_fills def is_trade_finalized(fill: fills.Fill) -> bool: """ Only treat fills with exec_mode=7 as consensus-confirmed trades. Other exec modes are optimistic and may be reverted. """ return fill.exec_mode == 7 ``` ::: :::: ### Additional logic Process informational taker order messages without updating state. ::::info :::details[Taker Orders] ```python def _handle_taker_order(self, stream_taker_order: StreamTakerOrder, block_height: int): """ Handle taker order messages (informational only). Taker orders are emitted when orders enter the matching engine, regardless of success/failure. No state updates required. """ order = helpers.parse_protocol_order(stream_taker_order.order) # Log for analytics/monitoring but don't update book state logging.debug(f"Taker order: {order.order_id} size={order.quantums}") # Optional: Track taker order metrics self.taker_order_metrics.process_order(order, block_height) ``` ::: :::: Convert protocol integers to decimal values for display and analysis. [Fetch the market information](/indexer-client/http#get-perpetual-markets) from the Indexer, containing data required for integer conversion. ::::info :::details[Data Conversion] ```python def subticks_to_price(subticks: int, atomic_resolution: int, quantum_conversion_exponent: int) -> float: """ Convert integer subticks to human-readable price. Formula: subticks * 10^(-atomic_resolution) * 10^(-quantum_conversion_exponent) """ return subticks * (10 ** (-atomic_resolution - quantum_conversion_exponent)) def quantums_to_size(quantums: int, atomic_resolution: int) -> float: """ Convert integer quantums to human-readable quantity. Formula: quantums * 10^(-atomic_resolution) """ return quantums * (10 ** (-atomic_resolution)) def format_fill_for_display(fill: fills.Fill, market_info: dict) -> str: """Format fill for human consumption.""" ar = market_info['atomicResolution'] qce = market_info['quantumConversionExponent'] price = subticks_to_price(fill.subticks, ar, qce) size = quantums_to_size(fill.quantums, ar) side = "buy" if fill.taker_is_buy else "sell" status = "finalized" if fill.exec_mode == 7 else "optimistic" return f"({status}) {side} {size} @ {price}" ``` ::: :::: Validate orderbook state consistency in order to detect any errors, here related with crossed orderbook (a bid larger than a ask). ::::info :::details[Validate Orderbook] ```python def _validate_books(self): """ Validate orderbook state consistency. Each node maintains subjective state until block finalization. Regular validation helps detect processing errors. """ for cpid, book in self.books.items(): best_ask = next(book.asks(), None) best_bid = next(book.bids(), None) if best_ask and best_bid: if best_ask.subticks <= best_bid.subticks: # Crossed book indicates state error raise AssertionError( f"Crossed book for market {cpid}: " f"ask {best_ask.subticks} <= bid {best_bid.subticks}" ) ``` ::: :::: ::::: ## Full Node gRPC Streaming Enable full node streaming to expose a stream of orderbook updates (L3), fills, taker orders, and subaccount updates, allowing clients to maintain a full view of the orderbook and various exchange activities. Note that the orderbook state can vary slightly between nodes due to dYdX's offchain orderbook design. Last updated for: `v7.0.2` :::warning We recommend you use this exclusively with your own node, as supporting multiple public gRPC streams with unknown client subscriptions may result in degraded performance. ::: :::info[Example] If you already have access to a full node with gRPC streaming enabled, see the [example](/nodes/full-node-streaming/example). ::: ### Enabling Streaming Full node streaming supports two streaming protocols. Information can be streamed via gRPC or Websockets. Use the following flags to configure full node streaming features: | CLI Flag | Type | Default | Short Explanation | | ---------------------------------------- | ---- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `grpc-streaming-enabled` | bool | false | Toggle on to enable grpc-based full node streaming. | | `grpc-streaming-flush-interval-ms` | int | 50 | Buffer flush interval for batch emission of protocol-side updates. | | `grpc-streaming-max-batch-size` | int | 2000 | Maximum protocol-side update buffer before dropping all streaming connections. | | `grpc-streaming-max-channel-buffer-size` | int | 2000 | Maximum channel size before dropping slow or erroring grpc connections. Decreasing this will more aggressively drop slow client connections. | | `websocket-streaming-enabled` | bool | false | Toggle on to enable websocket-based streaming. Must be used in conjunction with `grpc-streaming-enabled`. | | `websocket-streaming-port` | int | 9092 | Port number to expose for websocket streaming. | | `fns-snapshot-interval` | int | 0 | If set to a nonzero number, snapshots will be sent out at this block interval. Used for debugging purposes. | ### Connecting to the Stream After setting up a full node with gRPC streaming enabled, you can connect to the stream using any gRPC client. To follow along with [Google's documentation on gRPC streaming clients](https://grpc.io/docs/languages/go/basics/#client): 1. Clone the [github.com/dydxprotocol/v4-chain](https://github.com/dydxprotocol/v4-chain) repository at the same version as your full node. 2. Generate the protos: `make proto-gen && make proto-export-deps`. 3. The generated protos are now in the `.proto-export-deps` directory. 4. Use the protobuf compiler (protoc) to [generate stubs](https://protobuf.dev/getting-started/) in any supported language. 5. Follow [Google's documentation](https://grpc.io/docs/languages/go/basics/#client) to write a client that can read from the stream. 6. Connect to the stream defined in the `dydxprotocol.clob.Query` service ([StreamOrderbookUpdates](https://github.com/dydxprotocol/v4-chain/blob/4199c3e7b00ded24774d49ce8adcbaaa8325ddc1/proto/dydxprotocol/clob/query.proto#L63-L67)). For Python, the corresponding code is already generated in the package [`v4-proto`](https://pypi.org/project/v4-proto/). For Rust, install the crate [`dydx-proto`](https://crates.io/crates/dydx-proto). To connect via websocket, connect to the specified websocket server port at endpoint `/ws`. Default port number is `9092`, but it can be configured via CLI flag. There are two query parameters. `clobPairIds` is a list of CLOB pair IDs to subscribe to, and `subaccountIds` are a list of subaccount ids to subscribe to. ### Maintaining Orderbook and Subaccount State #### Overview 1. Connect to the stream and subscribe to updates for a series of CLOB pair IDs, each of which corresponds to a tradeable instrument, and subaccount ids, each of which corresponds to a subaccount. 2. Discard order messages until you receive a `StreamOrderbookUpdate` with `snapshot` set to `true`. This message contains the full orderbook state for each CLOB pair. 3. Similarly, discard subaccount messages until you receive a `StreamSubaccountUpdate` with `snapshot` set to `true`. This message contains the full subaccount state for each subscribed subaccount. 4. When you see an `OrderPlaceV1` message, insert the order into the book at the end of the queue on its price level. Track the order's initial quantums (quantity) and total filled quantums. 5. When you see an `OrderUpdateV1` message, update the order's total filled quantums. 6. When you see a `ClobMatch` (trade) message, update the total filled quantums for each maker order filled using the `fill_amounts` field. * Note that, similar to `OrderUpdateV1`, the `fill_amounts` field represents the order's total filled quantity up to this point. This is not the amount filled in this specific match, but rather the cumulative amount filled across all matches for this order. * The order's quantity remaining is always its initial quantity minus its total filled quantity. * Note that both `OrderUpdateV1` and `ClobMatch` messages must be processed to maintain the correct book state. See [OrderUpdateV1](#orderupdatev1) for details. 7. When you see an `OrderRemoveV1` message, remove the order from the book. 8. When you see a `StreamSubaccountUpdate` message with `snapshot` set to `false`, incrementally update the subaccount's balances and positions. 9. When you see a `StreamTakerOrder` message, state does not need to be updated. Taker orders are purely informational and are emitted whenever a taker order enters the matching loop, regardless of success or failure. Note: * The order subticks (price) and quantums (quantity) fields are encoded as integers and require [translation to human-readable values](https://github.com/dydxprotocol/grpc-stream-client/blob/d8cbbc3c6aeb454078c72204491727b243c26e19/src/market_info.py#L1). * Each node's view of the book is subjective, because order messages arrive at different nodes in different orders. When a block is proposed, nodes "sync" subsets of their book states to cohere with the trades seen by the block proposer. * Only `ClobMatch` messages with `execModeFinalize` are trades confirmed by consensus. * Use all `ClobMatch` messages to update the orderbook state. The node's book state is optimistic, and reverts if fills are not confirmed, in which case a series of `OrderRemoveV1`, `OrderPlaceV1` and `OrderUpdateV1` messages are sent to represent the modifications to the full node's book state. * Treat only `ClobMatch` messages with `execModeFinalize` as confirmed trades. * See [Reference Material](#reference-material) for more information. #### Request / Response To subscribe to the stream, the client can send a 'StreamOrderbookUpdatesRequest' specifying the CLOB pair IDs and subaccount ids to subscribe to.
Protobuf Structs ```protobuf // StreamOrderbookUpdatesRequest is a request message for the // StreamOrderbookUpdates method. message StreamOrderbookUpdatesRequest { // Clob pair ids to stream orderbook updates for. repeated uint32 clob_pair_id = 1; // Subaccount ids to stream subaccount updates for. repeated dydxprotocol.subaccounts.SubaccountId subaccount_ids = 2; } ```
  Response will contain a `oneof` field that contains either: * `StreamOrderbookUpdate` * Contains one or more `OffChainUpdateV1` orderbook updates (Add/Remove/Update) * boolean field indicating if the updates are coming from a snapshot or not. * `StreamOrderbookFill` * Contains a singular `ClobMatch` object describing a fill (order or liquidation). * Represents one taker order matched with 1 or more maker orders. * Matched quantums are provided for each pair in the match. * `orders` field contains full order information at time of matching. Contains all maker and taker orders involved in the `ClobMatch` object. * Prices within a Match are matched at the maker order price. * `fill_amounts` contains the absolute, total filled quantums of each order as stored in state. * fill\_amounts should be zipped together with the `orders` field. Both arrays should have the same length. * `StreamTakerOrder` * Contains a oneof `TakerOrder` field which represents the order that entered the matching loop. * Could be a regular order or a Liquidation Order. * Contains a `StreamTakerOrderStatus` field which represents the status of a taker order after it has finished the matching loop. * `OrderStatus` is a uint32 describing the result of the taker order matching. Only value `0` indicates success. Possible values found [here](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/types/orderbook.go#L118-L152). * @jonfung to update to static link * `RemainingQuantums` represents the remaining amount of non-matched quantums for the taker order. * `OptimisticallyFilledQuantums` represents the number of quantums filled *during this matching loop*. It does not include quantums filled before this matching loop, if the order was a replacement order and was previously filled. * `StreamSubaccountUpdate` * Contains a singular `SubaccountId` object to identify the subaccount. * multiple `SubaccountPerpetualPosition`s to represent the perpetual positions of the subaccount. * each `SubaccountPerpetualPosition` contains a perpetual id and the size of the position in base quantums. * multiple `SubaccountAssetPosition`s to represent the asset positions of the subaccount. (i.e, usdc collateral positions) * each `SubaccountAssetPosition` contains an asset id and the size of the position in base quantums. as well as `block_height` and `exec_mode` (see [Exec Modes Reference](#exec-mode-reference)).
Protobuf Structs ```protobuf // StreamOrderbookUpdatesResponse is a response message for the // StreamOrderbookUpdates method. message StreamOrderbookUpdatesResponse { // Batch of updates for the clob pair. repeated StreamUpdate updates = 1 [ (gogoproto.nullable) = false ]; } // StreamUpdate is an update that will be pushed through the // gRPC stream. message StreamUpdate { // Contains one of an StreamOrderbookUpdate, // StreamOrderbookFill, StreamTakerOrderStatus, StreamSubaccountUpdate. oneof update_message { StreamOrderbookUpdate orderbook_update = 1; StreamOrderbookFill order_fill = 2; StreamTakerOrder taker_order = 3; dydxprotocol.subaccounts.StreamSubaccountUpdate subaccount_update = 4; } // Block height of the update. uint32 block_height = 5; // Exec mode of the update. uint32 exec_mode = 6; } // StreamOrderbookUpdate provides information on an orderbook update. Used in // the full node gRPC stream. message StreamOrderbookUpdate { // Orderbook updates for the clob pair. Can contain order place, removals, // or updates. repeated dydxprotocol.indexer.off_chain_updates.OffChainUpdateV1 updates = 1 [ (gogoproto.nullable) = false ]; // Snapshot indicates if the response is from a snapshot of the orderbook. // This is true for the initial response and false for all subsequent updates. // Note that if the snapshot is true, then all previous entries should be // discarded and the orderbook should be resynced. bool snapshot = 2; } // StreamOrderbookFill provides information on an orderbook fill. Used in // the full node gRPC stream. message StreamOrderbookFill { // Clob match. Provides information on which orders were matched // and the type of order. ClobMatch clob_match = 1; // All orders involved in the specified clob match. Used to look up // price of a match through a given maker order id. repeated Order orders = 2 [ (gogoproto.nullable) = false ]; // Resulting fill amounts for each order in the orders array. repeated uint64 fill_amounts = 3 [ (gogoproto.nullable) = false ]; } // StreamTakerOrder provides information on a taker order that was attempted // to be matched on the orderbook. // It is intended to be used only in full node streaming. message StreamTakerOrder { // The taker order that was matched on the orderbook. Can be a // regular order or a liquidation order. oneof taker_order { Order order = 1; StreamLiquidationOrder liquidation_order = 2; } // Information on the taker order after it is matched on the book, // either successfully or unsuccessfully. StreamTakerOrderStatus taker_order_status = 3; } // StreamTakerOrderStatus is a representation of a taker order // after it is attempted to be matched on the orderbook. // It is intended to be used only in full node streaming. message StreamTakerOrderStatus { // The state of the taker order after attempting to match it against the // orderbook. Possible enum values can be found here: // https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/types/orderbook.go#L105 uint32 order_status = 1; // The amount of remaining (non-matched) base quantums of this taker order. uint64 remaining_quantums = 2; // The amount of base quantums that were *optimistically* filled for this // taker order when the order is matched against the orderbook. Note that if // any quantums of this order were optimistically filled or filled in state // before this invocation of the matching loop, this value will not include // them. uint64 optimistically_filled_quantums = 3; } // StreamSubaccountUpdate provides information on a subaccount update. Used in // the full node GRPC stream. message StreamSubaccountUpdate { SubaccountId subaccount_id = 1; // updated_perpetual_positions will each be for unique perpetuals. repeated SubaccountPerpetualPosition updated_perpetual_positions = 2; // updated_asset_positions will each be for unique assets. repeated SubaccountAssetPosition updated_asset_positions = 3; // Snapshot indicates if the response is from a snapshot of the subaccount. // All updates should be ignored until snapshot is received. // If the snapshot is true, then all previous entries should be // discarded and the subaccount should be resynced. // For a snapshot subaccount update, the `updated_perpetual_positions` and // `updated_asset_positions` fields will contain the full state of the // subaccount. bool snapshot = 4; } // SubaccountPerpetualPosition provides information on a subaccount's updated // perpetual positions. message SubaccountPerpetualPosition { // The `Id` of the `Perpetual`. uint32 perpetual_id = 1; // The size of the position in base quantums. Negative means short. int64 quantums = 2; } // SubaccountAssetPosition provides information on a subaccount's updated asset // positions. message SubaccountAssetPosition { // The `Id` of the `Asset`. uint32 asset_id = 1; // The absolute size of the position in base quantums. uint64 quantums = 2; } ```
After subscribing to the orderbook updates, use the orderbook in the snapshot as the starting orderbook. Similarly, use the subaccount state in the snapshot as the starting subaccount state. #### OrderPlaceV1 When `OrderPlaceV1` is received, add the corresponding order to the end of the price level. * This message is only used to modify the orderbook data structure (Bids, Asks). * This message is sent out whenever an order is added to the in-memory orderbook. * This may occur in various places such as when an order is initially placed, or when an order is replayed during the ProcessCheckState step. * An `OrderPlaceV1` message is always be followed by an `OrderUpdateV1` message, which sets the intial fill amount (typically zero).
Code Snippet ```go func (l *LocalOrderbook) AddOrder(order v1types.IndexerOrder) { l.Lock() defer l.Unlock() if _, ok := l.OrderIdToOrder[order.OrderId]; ok { l.Logger.Error("order already exists in orderbook") } subticks := order.GetSubticks() if order.Side == v1types.IndexerOrder_SIDE_BUY { if _, ok := l.Bids[subticks]; !ok { l.Bids[subticks] = make([]v1types.IndexerOrder, 0) } l.Bids[subticks] = append(l.Bids[subticks], order) } else { if _, ok := l.Asks[subticks]; !ok { l.Asks[subticks] = make([]v1types.IndexerOrder, 0) } l.Asks[subticks] = append(l.Asks[subticks], order) } l.OrderIdToOrder[order.OrderId] = order l.OrderRemainingAmount[order.OrderId] = 0 } ```
#### OrderUpdateV1 When `OrderUpdateV1` is received, update the order's fill amount to the amount specified. * This message is only used to update fill amounts. It carries information about an order's updated fill amount. * This message is emitted when an order's fill amount changes due to something other than a `ClobMatch`. * This includes when deliverState is reset to the checkState from last block, or when branched state is written to and then discarded if there was a matching error. * For example, this could happen if the full node sees the order filled, and then the next block committed by consensus does not contain the expected fill, so the order’s quantity remaining resets to its state from the previous block. * An update message will always accompany an order placement message. * It's possible for an update message to be sent before a placement message. You can safely ignore update messages with order ids not in the orderbook. * **Note that you must handle both `OrderUpdateV1` and `ClobMatch` messages to maintain the correct book state**.
Code Snippet ```go func (l *LocalOrderbook) SetOrderFillAmount( orderId *v1types.IndexerOrderId, fillAmount uint64, ) { l.Lock() defer l.Unlock() if fillAmount == 0 { delete(l.FillAmounts, *orderId) } else { l.FillAmounts[*orderId] = fillAmount } } ```
#### OrderRemoveV1 When `OrderRemoveV1` is received, remove the order from the orderbook. * This message is only used to modify the orderbook data structure (Bids, Asks). * This message is emitted when an order is removed from the in-memory orderbook. * Note that this does not mean the fills are removed from state yet. * When fills are removed from state, a separate Update message will be sent with 0 quantum.
Code Snippet ```go func (l *LocalOrderbook) RemoveOrder(orderId v1types.IndexerOrderId) { l.Lock() defer l.Unlock() if _, ok := l.OrderIdToOrder[orderId]; !ok { l.Logger.Error("order not found in orderbook") } order := l.OrderIdToOrder[orderId] subticks := order.GetSubticks() if order.Side == v1types.IndexerOrder_SIDE_BUY { for i, o := range l.Bids[subticks] { if o.OrderId == order.OrderId { l.Bids[subticks] = append( l.Bids[subticks][:i], l.Bids[subticks][i+1:]..., ) break } } if len(l.Bids[subticks]) == 0 { delete(l.Bids, subticks) } } else { for i, o := range l.Asks[subticks] { if o.OrderId == order.OrderId { l.Asks[subticks] = append( l.Asks[subticks][:i], l.Asks[subticks][i+1:]..., ) break } } if len(l.Asks[subticks]) == 0 { delete(l.Asks, subticks) } } delete(l.OrderIdToOrder, orderId) } ```
#### StreamOrderbookFill/ClobMatch This message is only used to update fill amounts, it does not add or remove orders from the book but can change the quantity remaining for open orders. The `ClobMatch` data structure contains either a `MatchOrders` or a `MatchPerpetualLiquidation` object. Match Deleveraging events are not emitted. Within each Match object, a `MakerFill` array contains the various maker orders that matched with the singular taker order and the amount of quantums matched. Note that all matches occur at the maker order price. The `orders` field in the `StreamOrderbookFill` object allow for price lookups based on order id. It contains all the maker order ids, and in the case of non-liquidation orders, it has the taker order. Mapping each order in `orders` to the corresponding value in the `fill_amounts` field provides the absolute filled amount of quantums that each order is filled to after the ClobMatch was processed.
Code Snippet ```go // fillAmountMap is a map of order ids to fill amounts. // The SetOrderFillAmount code can be found in `the `OrderUpdateV1` section. func (c *GrpcClient) ProcessMatchOrders( matchOrders *clobtypes.MatchOrders, orderMap map[clobtypes.OrderId]clobtypes.Order, fillAmountMap map[clobtypes.OrderId]uint64, ) { takerOrderId := matchOrders.TakerOrderId clobPairId := takerOrderId.GetClobPairId() localOrderbook := c.Orderbook[clobPairId] indexerTakerOrder := v1.OrderIdToIndexerOrderId(takerOrderId) localOrderbook.SetOrderFillAmount(&indexerTakerOrder, fillAmountMap[takerOrderId]) for _, fill := range matchOrders.Fills { makerOrder := orderMap[fill.MakerOrderId] indexerMakerOrder := v1.OrderIdToIndexerOrderId(makerOrder.OrderId) localOrderbook.SetOrderFillAmount(&indexerMakerOrder, fillAmountMap[makerOrder.OrderId]) } } func (c *GrpcClient) ProcessMatchPerpetualLiquidation( perpLiquidation *clobtypes.MatchPerpetualLiquidation, orderMap map[clobtypes.OrderId]clobtypes.Order, fillAmountMap map[clobtypes.OrderId]uint64, ) { localOrderbook := c.Orderbook[perpLiquidation.ClobPairId] for _, fill := range perpLiquidation.GetFills() { makerOrder := orderMap[fill.MakerOrderId] indexerMakerOrderId := v1.OrderIdToIndexerOrderId(makerOrder.OrderId) localOrderbook.SetOrderFillAmount(&indexerMakerOrderId, fillAmountMap[makerOrder.OrderId]) } } ```
#### StreamSubaccountUpdate This message is used to update subaccount balances and positions. The initial message for a subaccount will have `snapshot` set to `true`. This message contains the full state of the subaccount. All updates should be ignored until the snapshot is received. Subsequent updates will contain updates to the positions and balances of the subaccount. They should be merged in with the existing state of the subaccount. Apart from the initial snapshot, this mesage will only be sent out for subaccount updates that are in consensus.
Code Snippet ```go type SubaccountId struct { Owner string Number int } type SubaccountPerpetualPosition struct { PerpetualId int Quantums int } type SubaccountAssetPosition struct { AssetId int Quantums int } type SubaccountState struct { SubaccountId SubaccountId PerpetualPositions map[int]SubaccountPerpetualPosition AssetPositions map[int]SubaccountAssetPosition } func (c *GrpcClient) ProcessSubaccountUpdate( subaccountUpdate *satypes.StreamSubaccountUpdate, subaccountMap map[satypes.SubaccountId]SubaccountState, ) { // Extract the subaccount ID from the update subaccountId := *subaccountUpdate.SubaccountId // Check if this is a snapshot if subaccountUpdate.Snapshot { // Replace the entire subaccount state with the snapshot data subaccountState := SubaccountState{ SubaccountId: subaccountId, PerpetualPositions: make(map[int]SubaccountPerpetualPosition), AssetPositions: make(map[int]SubaccountAssetPosition), } // Populate perpetual positions from snapshot for _, perpPositionUpdate := range subaccountUpdate.UpdatedPerpetualPositions { subaccountState.PerpetualPositions[perpPositionUpdate.PerpetualId] = *perpPositionUpdate } // Populate asset positions from snapshot for _, assetPositionUpdate := range subaccountUpdate.UpdatedAssetPositions { subaccountState.AssetPositions[assetPositionUpdate.AssetId] = *assetPositionUpdate } // Update the map with the new snapshot state subaccountMap[subaccountId] = subaccountState } else { // If not a snapshot, retrieve or initialize the current subaccount state subaccountState, exists := subaccountMap[subaccountId] if !exists { subaccountState = SubaccountState{ SubaccountId: subaccountId, PerpetualPositions: make(map[int]SubaccountPerpetualPosition), AssetPositions: make(map[int]SubaccountAssetPosition), } } // Update perpetual positions for _, perpPositionUpdate := range subaccountUpdate.UpdatedPerpetualPositions { if perpPositionUpdate.Quantums != 0 { subaccountState.PerpetualPositions[perpPositionUpdate.PerpetualId] = *perpPositionUpdate } else { // Delete the entry if the position size is zero delete(subaccountState.PerpetualPositions, perpPositionUpdate.PerpetualId) } } // Update asset positions for _, assetPositionUpdate := range subaccountUpdate.UpdatedAssetPositions { if assetPositionUpdate.Quantums != 0 { subaccountState.AssetPositions[assetPositionUpdate.AssetId] = *assetPositionUpdate } else { // Delete the entry if the asset quantity is zero delete(subaccountState.AssetPositions, assetPositionUpdate.AssetId) } } // Update the map with the modified state subaccountMap[subaccountId] = subaccountState } } ```
#### StreamTakerOrder This message is purely an informational message used to indicate whenever a taker order is matched against the orderbook. No internal state in clients need to be updated. Information provided in the struct: * One of (taker order, liquidation order) entering matching loop * Status of order after matching. If order failed to match, status code provides the reason for failure (i.e post only order crosses book) * Remaining non-matched quantums for the taker order * Quantity of optimistically matched quantums during this matching order loop. Note that by protocol design, all `StreamTakerOrderStatus` emissions will be optimistic from CheckTx state. This is due to the fact that each node maintains it's own orderbook, thus all matching operations when a taker order enters the matching loop will be optimistic. If confirmed fill amounts in consensus are desired, `StreamOrderbookFill` objects will be emitted during DeliverTx for proposed blocks.
Code Snippet ```go type StreamTakerOrderStatus struct { OrderStatus uint32 RemainingQuantums uint64 OptimisticallyFilledQuantums uint64 } func (c *GrpcClient) ProcessStreamTakerOrder( streamTakerOrder *satypes.StreamTakerOrder, ) { takerOrder := streamTakerOrder.GetOrder() takerOrderLiquidation := streamTakerOrder.GetLiquidationOrder() takerOrderStatus := streamTakerOrder.GetTakerOrderStatus() if takerOrderStatus.OrderStatus == 0 || takerOrderStatus.OrderStatus == 0 { if takerOrder != nil { // Process success of regular taker order } if takerOrderLiquidation != nil { // Process success of liquidation taker order } } } ```
### Reference Material #### Optimistic Orderbook Execution By protocol design, each validator has their own version of the orderbook and optimistically processes orderbook matches. As a result, you may see interleaved sequences of order removals, placements, and state fill amount updates when optimistically processed orderbook matches are removed and later replayed on the local orderbook. ![full node streaming diagram](/full_node_streaming_diagram.jpg) Note that DeliverTx maps to exec mode `execModeFinalize`. #### Staged DeliverTx Validation In DeliverTx, all of the updates emitted are finalized and in consensus. A batch of updates will be sent out in the same `StreamOrderbookUpdatesResponse` object. In consensus, fills and subaccount updates will be emitted. #### Finalized Subaccount Updates Only finalized subaccount updates are sent. Snapshots are sent during PrepareCheckState, so the execMode will be set to `102` for subaccount snapshots. Finalized incremental subaccount updates are sent with execMode `7`. #### Exec Mode Reference
Exec Modes ```go execModeCheck = 0 // Check a transaction execModeReCheck = 1 // Recheck a (pending) transaction after a commit execModeSimulate = 2 // Simulate a transaction execModePrepareProposal = 3 // Prepare a block proposal execModeProcessProposal = 4 // Process a block proposal execModeVoteExtension = 5 // Extend or verify a pre-commit vote execModeVerifyVoteExtension = 6 // Verify a vote extension execModeFinalize = 7 // Finalize a block proposal ExecModeBeginBlock = 100 ExecModeEndBlock = 101 ExecModePrepareCheckState = 102 ```
#### Taker Order Status Reference Values are defined in code [here](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/x/clob/types/orderbook.go#L118-L152). * @jonfung to update to static link | Value | Status | Description | | ----- | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 0 | Success | Order was successfully matched and/or added to the orderbook. | | 1 | Undercollateralized | Order failed collateralization checks when matching or placed on orderbook. Order was cancelled. | | 2 | InternalError | Order caused internal error and was cancelled. | | 3 | ImmediateOrCancelWouldRestOnBook | Order is an IOC order that would have been placed on the orderbook. Order was cancelled. | | 4 | ReduceOnlyResized | Order was resized since it would have changed the user's position size. | | 5 | LiquidationRequiresDeleveraging | Not enough liquidity to liquidate the subaccount profitably on the orderbook. Order was not fully matched because insurance fund did not have enough funds to cover losses from performing liquidation. Subaccount requires deleveraging. | | 6 | LiquidationExceededSubaccountMaxNotionalLiquidated | Liquidation order could not be matched because it exceeds the max notional liquidated in this block. | | 7 | LiquidationExceededSubaccountMaxInsuranceLost | Liquidation order could not be matched because it exceeds the max funds lost for hte insurance fund in this block. | | 8 | ViolatesIsolatedSubaccountConstraints | Matching this order would lead to the subaccount violating isolated perpetual constraints. Order was cancelled. | | 9 | PostOnlyWouldCrossMakerOrder | Matching this order would lead to the post only taker order crossing the orderbook. Order wasa cancelled. | #### Example Scenario * Trader places a bid at price 100 for size 1 * OrderPlace, price = 100, size = 1 * OrderUpdate, total filled amount = 0 * Trader replaces that original bid to be price 99 at size 2 * OrderRemove * OrderPlace, price = 99, size = 2 * OrderUpdate, total filled amount = 0 * Another trader submits an IOC ask at price 100 for size 1. * Full node doesn't see this matching anything so no updates. * Block is confirmed that there was a fill for the trader's original order at price 100 for size 1 (block proposer didn't see the order replacement) * OrderUpdate, set total fill amount to be 0 (no-op) from checkState -> deliverState reset * MatchOrder emitted for block proposer's original order match, total filled amount = 1 #### Metrics and Logs | Metric | Type | Explanation | | ----------------------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `grpc_send_orderbook_updates_latency.quantile` | histogram | Latency for each orderbook cache buffer enqueue | | `grpc_send_orderbook_updates_latency.count` | count | number orderbook updates enqueued in cache buffer | | `grpc_send_orderbook_snapshot_latency.quantile` | histogram | Latency for each snapshot orderbook emission | | `grpc_send_orderbook_snapshot_latency.count` | count | number of order book snapshots emitted | | `grpc_send_subaccount_update_count` | count | Number of subaccount updates emitted | | `grpc_send_orderbook_fills_latency.quantile` | histogram | Latency for each orderbook fill cache buffer enqueue | | `grpc_send_orderbook_fills_latency.count` | count | number orderbook snapshots enqueued in cache buffer | | `grpc_add_update_to_buffer_count` | count | Number of total update objects added to the cache buffer | | `grpc_add_to_subscription_channel_count` | count | Number of updates added to each per-subscription channel buffer. Tagged by `subscription_id`. | | `grpc_send_response_to_subscriber_count` | count | Number of updates sent from each per-subscription channel buffer to the client. Tagged by `subscription_id`. | | `grpc_stream_subscriber_count` | count | number of streaming connections currently connected to the full node | | `grpc_stream_num_updates_buffered` | histogram | number of updates in the full node's buffer cache of updates. Once this hits `grpc-streaming-max-batch-size`, all subscriptions will be dropped. Use with `quantile:0.99` in order to observe maximum amount of updates. | | `grpc_flush_updates_latency.count` | count | number of times the buffer cache is flushed. | | `grpc_flush_updates_latency.quantile` | histogram | Latency of each buffer cache flush call into subscription channel. | | `grpc_subscription_channel_length.quantile` | histogram | Length of each subscription's channel buffer. Tagged by `subscription_id`. Use with `quantile:0.99` in order to observe subscription channel length for subscription ids. Once this hits `grpc-streaming-max-channel-buffer-size`, the offending subscription will be dropped. | All logs from grpc streaming are tagged with `module: full-node-streaming`. #### Protocol-side buffering and Slow gRPC Client Connections The full node maintains a length-configurable buffer cache of streaming updates to ensure bursts of protocol updates do not induce full node lag. If the buffer reaches maximum capacity, all connections and updates are dropped, and subscribers will have to re-subscribe. The buffer is periodically flushed into each per-subscription golang channel at a configurable set interval of time, defaulting to 50ms. To ensure slow client connections do not induce full node lag, each client subscription has a unique goroutine and golang channel that pushes updates through the grpc stream. If the channel buffer grows beyond the configurable `grpc-streaming-max-channel-buffer-size` parameter, the goroutine will be stopped. With the poller gone, the channel buffer will eventually grow and hit the max buffer size, at which the lagging subscription is pruned. Metrics and logs are emitted to help tune both of these parameters. #### FAQs > Q: Suppose the full node saw the cancellation of order X at t0 before the placement of the order X at t1. What would the updates be like? > A: No updates because the order was never added to the book. *** > Q: A few questions because it often results in crossed books: > In which cases shall we not expect to see OrderRemove message? > * Post only reject? → PO reject won’t have a removal since they were never added to the book; > * IOC/FOK auto cancel? → IOC/FOK also won’t have a removal message for similar reason; > * Order expired outside of block window? → Expired orders will generate a removal message; > * Passive limit order was fully filled → Fully filled maker will generate a removal message; > * Aggressive limit order was fully filled? → Fully filled taker won’t have a removal. *** > Q: Why does `StreamOrderbookUpdate` use IndexerOrderId and `StreamOrderbookFill` use dydxprotocol.OrderId? > A: gRPC streaming exposes inner structs of the matching engine and our updates are processed differently from fills. The two data structures have equivalent fields, and a lightweight translation layer to go from Indexer OrderId to Protocol OrderId can be written. *** > Q: I only want to listen to confirmed updates. I do not want to process optimistic fills. > A: You will want to only process messages from DeliverTx stage (`execModeFinalize`). This step is when we save proposed matches from the block proposer into state. These updates will have exec mode execModeFinalize. *** > Q: Why do I see an Order Update message for a new OrderId before an Order Place message? > A: During DeliverTx, the first step we do is to reset fill amounts (via OrderUpdate messages) for all orders involved in the proposed and local operations queue due to the deliver state being reset to the check state from last block. We "reset" fill order amounts to 0 for orders that the block proposer has seen but has not gossiped to our full node yet. In the future, we may reduce the number of messages that are sent, but for now we are optimizing for orderbook correctness. *** > Q: How do I print the gRPC stream at the command line? > A: Use the [grpcurl](https://github.com/fullstorydev/grpcurl) tool. Connect to a full node stream with: ``` grpcurl -plaintext -d '{"clobPairId":[0,1], "subaccountIds": [{"owner": "dydx1nzuttarf5k2j0nug5yzhr6p74t9avehn9hlh8m", "number": 0}]}' 127.0.0.1:9090 dydxprotocol.clob.Query/StreamOrderbookUpdates ``` *** > Q: Is there a sample client? > A: Example client which subscribes to the stream and maintains a local orderbook: [dydxprotocol/grpc-stream-client](https://github.com/dydxprotocol/grpc-stream-client/) ### Changelog #### v7.0.2 * perp position to signed int for tracking long/short positions #### v6.0.8 * added taker order message to stream * added subaccount update message to stream * Finalized DeliverTx updates are all batched together in a single message * Metrics modifications * Websocket support #### v5.0.5 * added update batching and per-channel channel/goroutines to not block full node on laggy subscriptions * Protobuf breaking change: Shifted block height and exec mode from `StreamOrderbookUpdatesResponse` to `StreamUpdate` * Metrics ## Hardware Requirements ### Minimum Specs The minimum recommended specs for running a node is the following: * 16-core, x86\_64 architecture processor * 64 GiB RAM * 500 GiB of locally attached SSD storage For example, an AWS instance like the `r6id.4xlarge`, or equivalent. ## Optimize Your Full Node Optimizing your full node helps keep it online, up to date, and operating quickly. Faster nodes have an advantage over slower nodes because they tend to receive new data first and they minimize the time between placing and resolving orders. Optimize your full node by connecting to trusted nodes, taking precautions against falling out of sync with the network, and configuring storage settings. ### Prerequisites You need a running, non-validating full node that is connected to a network. * If you created a system service for your node by following the instructions on the previous page, [Set Up a Full Node](/nodes/running-node/setup), start your node with the following command: ```bash stystemctl start dydxprotocold ``` * To start your node with Cosmovisor or with the `dydxprotocold` binary, you must include the flag `--non-validating-full-node=true`. The flag disables the functionality intended for validator nodes and enables additional logic for reading data. Your CLI may prompt you to configure additional variables in your environment or include them in your command. To start your node with Cosmovisor, run the following command: ```bash cosmovisor run start --non-validating-full-node=true ``` To start your node with `dydxprotocold`, run the following command: ```bash dydxprotocold run start --non-validating-full-node=true ``` ### Save a List of Trusted Nodes Specify a list of healthy, stable nodes that you trust. Your node prioritizes connecting to those nodes, speeding up the process of connecting or re-connecting to the network. Connecting directly with a peer node is faster than connecting to a seed node and then finding new peers. #### Save a List of Persistent Peers You can save a list of healthy, stable nodes in the `persistent_peers` field of your `config.toml` file. Request a list of healthy peers for your deployment from a [Live Peer Node](/nodes/resources#live-peer-node-providers) provider. From the list of healthy peers that you retrieve from peer node provider, choose any 5 for your node to query for the latest state. Add a comma-separated list of those peer addresses to the `persistent_peers` field in your `config.toml`, like in the following example: ```yaml # config.toml # Example values from Polkachu for dydx-mainnet-1 persistent_peers=83c299de2052db247f08422b6592e1383dd7a104@136.243.36.60:23856,1c64b35055d34ff3dd199bb4a5a3ae46b9c10c89@3.114.126.71:26656,3651c82a89f8f4d6fc30fb27b91159f0de092031@202.8.9.134:26656,580ec248de1f41d4e50abe132b7838348db55b80@176.9.144.40:23856,febe75fb6e70a60ce6344b82ff14903bcb53a209@38.122.229.90:26656 ``` #### Replace Your Address Book File As an alternative to persistent peers, you can replace your node's local address book with the latest address book from a trusted provider. The address book file contains the latest connection information for peers from that provider. Download an up-to-date `addrbook.json` file for your deployment from an [Address Book](/nodes/resources#address-book-providers) provider. Save it in your `/.dydxprotocol/config` directory, replacing the existing `addrbook.json` file. ### Prepare to Restore Your Node To minimize downtime in case your node falls out of sync, make preparations to restore your node quickly. Your full node can fall out of sync with the rest of the network for a variety of reasons, including a bad software upgrade, unexpected node crashes, or human operational error. To re-sync with the network, your full node needs to replay the history of the network, which can take a long time. You can speed up the re-syncing process significantly by providing your node with a snapshot. A snapshot contains a compressed copy of the application state at the time the snapshot was taken. If your node falls out of sync, a snapshot allows it to recover to that saved state before replaying the rest of the history of the network, saving you time. #### Configure Your Node's State Sync Setting You can use state sync, a configuration setting that allows your node to retrieve a snapshot from the network, to ensure that your node can be restored quickly if it falls out of sync. To use state sync for quick recovery in case your node falls out of sync, follow the instructions for your deployment from a [State Sync](/nodes/resources#state-sync-service) service. :::info Cosmos SDK 0.40 release will include automatic support for state sync, and developers only need to enable it in their applications to make use of it. Replace above with a procedure. ::: #### Save a Snapshot on Your System As an alternative to state sync, you can use a snapshot that you have saved on your node's system to restore your node if it falls out of sync. To save a snapshot on your system for quick recovery in case your node falls out of sync, install a snapshot for your deployment from a [Snapshot Service](/nodes/resources#snapshot-service). ### Configure a Pruning Strategy To reduce the amount of storage your node requires, dYdX recommends the following pruning setting, configured in your `app.toml` file: ```bash # app.toml pruning = "everything" # 2 latest states will be kept; pruning at 10 block intervals ``` However, if you want to use your node to query historical data, configure a custom pruning strategy to retain more states. Retaining more states increases storage requirements. ## Peering Directly with Order Gateway For improved order latency of the network, the community might spin up an order gateway node to directly peer with. A chain coordination party may share this in the form of `$gateway_node_id@$gateway_ip_address:$port`. There are 2 options to peer directly with the gateway node: ### Option A: Gateway -> Validator * Share the full peering info of your validator node (`node_id@ip_address:port`) with the coordination party, which can be added as a `persistent_peer` to the gateway node. It's important that raw IP address (as opposed to a loadbalancer URL) of the validator node (as opposed to a sentry node) is shared. This ensures that the a direction connection can be maintained across node restarts. * If your IP or node ID changes due to node migration, please inform the coordination party. * Add the gateway `node_id` as a private and unconditional peer. This ensure that the gateway node is not subject to regualr peer # limits, and is not broadcasted to the rest of the network. ```bash --p2p.private_peer_ids="$gateway_node_id,..." --p2p.unconditional_peer_ids="$gateway_node_id,..." ``` ### Option B: Validator -> Gateway * Share the `node_id` (IP not required) of your validator node with the coordination party. It's important to share the `node_id` of the validator node, as opposed to a sentry node. This can be added to the gateway node as `unconditional_peer`. * Add the gateway node as a persistent and private peer to the validator node: ```bash --p2p.private_peer_ids="$gateway_node_id,..." --p2p.persistent_peers="$gateway_node_id@$gateway_ip_address:$port,..." ``` ## Addendum CometBFT [documentation](https://docs.cometbft.com/v0.38/spec/p2p/legacy-docs/config) on P2P configs ## Required Node Configs These configurations must be applied for both full nodes and validators. 💡Note: failure to set up below configurations on a validator node may compromise chain functionality. ### Node Configs The dYdX Chain has important node configurations required for normal chain operation. This includes: * The `config.toml` file read by CometBFT * ([Full documentation](https://docs.cometbft.com/v0.38/core/configuration)) * The `app.toml` file read by CosmosSDK * ([Full documentation](https://docs.cosmos.network/sdk/v0.50/learn/advanced/config)) #### `config.toml` ##### Consensus Configs ``` [consensus] timeout_commit = "500ms" ``` #### `app.toml` ##### Base Configuration Replace `$NATIVE_TOKEN_DENOM` at the end of the field with the correct value from [Network Constants](/nodes/network-constants) ``` ### Gas Prices ### minimum-gas-prices = "0.025ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5,12500000000$NATIVE_TOKEN_DENOM" ``` ``` ### Pruning ### pruning = "custom" # Small numbers >= "2" for validator nodes. # Larger numbers could be used for full-nodes if they are used for historical queries. pruning-keep-recent = "7" # Any prime number between "13" and "97", inclusive. pruning-interval = "17" ``` ##### gRPC Configs ``` [grpc] # Enable grpc. The Cosmos gRPC service is used by various daemon processes, # and must be enabled in order for the protocol to operate: enable = true # Non-standard gRPC ports are not supported at this time. Please run on port 9090, which is the default # port specified in the config file. # Note: grpc can be also be configured via start flags. Be careful not to change the default settings # with either of the following flags: `--grpc.enable`, `--grpc.address`. address = "0.0.0.0:9090" ``` ##### Oracle Configuration ```toml # ... other sections [oracle] # Enabled indicates whether the oracle is enabled. enabled = "true" # Oracle Address is the URL of the out of process oracle sidecar. This is used to # connect to the oracle sidecar when the application boots up. oracle_address = "SLINKY_ADDRESS_HERE:SLINKY_PORT_HERE" # default Slinky port is 8080. # Client Timeout is the time that the application is willing to wait for responses from # the oracle before timing out. client_timeout = "250ms" # MetricsEnabled determines whether oracle metrics are enabled. Specifically, # this enables instrumentation of the oracle client and the interaction between # the oracle and the app. metrics_enabled = "true" # PriceTTL is the maximum age of the latest price response before it is considered stale. # The recommended max age is 10 seconds (10s). If this is greater than 1 minute (1m), the app # will not start. price_ttl = "10s" # Interval is the time between each price update request. The recommended interval # is the block time of the chain. Otherwise, 0.6 seconds (600ms) is a good default. If this # is greater than 1 minute (1m), the app will not start. interval = "600ms" ``` ### Slinky Configs Sidecar can be configured by both flags and environment variables. #### Env Vars | Key | Default | Description | | ------------------------------------------------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `CONNECT_CONFIG_HOST` | `"0.0.0.0"` | The address Slinky will serve requests from. For single-machine deployments, consider using `127.0.0.1` for enhanced security. WARNING: changing this value requires updating the oracle\_address in the app.toml configuration. | | `CONNECT_CONFIG_PORT` | `"8080"` | The port Slinky will serve requests from. WARNING: changing this value requires updating the oracle\_address in the app.toml configuration. | | `CONNECT_CONFIG_METRICS_ENABLED` | `"true"` | Enables prometheus metrics. | | `CONNECT_CONFIG_METRICS_PROMETHEUSSERVERADDRESS` | `"0.0.0.0:8002"` | The address of your prometheus server instance. | | `STORK_PUB_KEY` | **No default - must set** | Public key (hex) for verifying Stork price feed signatures on the sidecar. Slinky ships unset (`""`). Recommended: `0x0a803F9b1CCe32e2773e0d2e98b37E0775cA5d44` (confirm against the aggregator key in [Stork's public keys](https://docs.stork.network/resources/public-keys)). | | `SLINKY_CONFIG_PROVIDERS_STORK_API_API_ENDPOINTS_0_URL` | **No default - must set** | HTTPS URL of the Stork price feed Slinky pulls from. Slinky ships unset (`""`). Recommended: `https://oracle-relay.dydx.trade/prices`. | | `PYTH_PUB_KEY` | **No default - must set** | Public key for verifying Pyth price data on the sidecar. Slinky ships unset (`""`). Recommended: `9gKEEcFzSd1PDYBKWAKZi4Sq4ZCUaVX5oTr8kEjdwsfR`. You can confirm the trusted signer by querying the Pyth Lazer storage account as described in [Use Pyth Lazer SDK in smart contracts](https://docs.pyth.network/price-feeds/pro/integrate-as-consumer/svm#use-pyth-lazer-sdk-in-smart-contracts). | | `SLINKY_CONFIG_PROVIDERS_PYTH_API_API_ENDPOINTS_0_URL` | **No default - must set** | HTTPS URL of the Pyth price feed Slinky pulls from. Slinky ships unset (`""`). Recommended: `https://oracle-relay.dydx.trade/prices`. |
Terms of Use for `oracle-relay.dydx.trade` **Oracle Proxy Terms of Use** These Terms of Use (the "**Terms**") govern your access to and use of the oracle proxy service operated by dYdX Operations Services Ltd. ("**DOS**") that provides access to third-party price data for infrastructure supporting the dYdX Protocol. By accessing, cloning, pulling data from, or otherwise using the data relay service or this repository (the "**Service**"), you acknowledge that you have read, understood, and agree to be bound by these Terms. If you do not agree to these Terms, you must not access or use the Service. **1. Nature of the Service** The Service is a technical relay and caching layer deployed and operated by DOS that retrieves, caches, and redistributes certain price and market data originating from third-party data providers. The Service may utilize software components developed by third parties and may depend on infrastructure, networks, and systems operated by entities other than DOS. The Service is provided solely to facilitate the operation and maintenance of infrastructure supporting the dYdX Protocol. DOS does not generate price data, determine asset values, publish official price feeds, verify the correctness of the underlying data, or operate upstream data networks. The Service merely retrieves and redistributes data originating from third-party sources. The Service must not be interpreted as an authoritative or canonical source of price information. **2. Third-Party Data Sources** Data made available through the Service may originate from one or more third-party data providers and may depend on infrastructure and systems that are outside the control of DOS. DOS does not control, operate, or guarantee the availability, accuracy, or performance of any third-party data provider or any upstream infrastructure used to generate or transmit the data. DOS shall have no responsibility or liability for any act or omission of any third-party data provider, including Douro Labs or the Pyth network, in connection with the provision, interruption, or accuracy of any Data made available through the Service. **3. Incorporation of Third-Party Terms** By accessing or using the Service, you confirm that you have read, understood, and expressly agree to be bound by the Douro Labs Subscription Terms available at [here](https://www.dourolabs.xyz/Subscription-Terms.pdf) as in effect at the time of your acceptance of these Terms. DOS will notify you of any material changes to the Douro Labs Subscription Terms that affect your use of the Service. In the event of any conflict between these Terms and the Douro Labs Subscription Terms, these Terms shall prevail except where the Douro Labs Subscription Terms impose stricter restrictions on the use of data, in which case the stricter restriction shall apply. Data made available through the Service may also originate from other Data Providers whose terms govern your use of that data. You are responsible for reviewing and complying with the applicable terms of each Data Provider whose data you access through the Service. **4. Disclaimer of Warranties** THE SERVICE AND ALL DATA MADE AVAILABLE THROUGH IT ARE PROVIDED "AS IS" AND "AS AVAILABLE". TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, DOS AND ITS AFFILIATES DISCLAIM ALL WARRANTIES, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING WARRANTIES OF ACCURACY, COMPLETENESS, TIMELINESS, AVAILABILITY, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. DATA MAY BE INCOMPLETE, INACCURATE, STALE, OR DELAYED DUE TO FACTORS OUTSIDE DOS'S CONTROL. YOU MUST NOT RELY ON THE SERVICE OR THE DATA AS A SOLE OR AUTHORITATIVE SOURCE OF PRICE INFORMATION AND ARE SOLELY RESPONSIBLE FOR INDEPENDENTLY VERIFYING ANY DATA OBTAINED THROUGH THE SERVICE. **5. Restriction on Use** You shall not use the Data obtained through the Service to create, calculate, publish, contribute to, or otherwise maintain any index, rate, or other data series that would constitute a "benchmark" within the meaning of Regulation (EU) 2016/1011 (the EU Benchmarks Regulation, as amended), the UK Benchmarks Regulation (as retained and amended in UK law), or any comparable benchmark regulation in any applicable jurisdiction (collectively, "**Benchmark Laws**"). You further shall not use the Data in any manner that would result in DOS being deemed a benchmark administrator, contributor, or otherwise subject to obligations under any Benchmark Laws. For the avoidance of doubt, the Data made available through the Service is not intended or approved for use as a financial benchmark, a regulated market data product, or as a reference rate in any financial instrument, contract, or investment fund within the meaning of any Benchmark Laws or any applicable financial markets regulation. DOS makes no representation that the Data satisfies the requirements of any regulated data product or that its use in connection with any financial instrument or product is permissible under applicable law. You are solely responsible for ensuring that your use of the Data complies with all applicable laws and regulations, including any licensing or authorisation requirements applicable to your use of financial or market data. Furthermore, you shall not, and shall not permit any third party to: (i) reverse engineer, decompile, or otherwise attempt to discern the source code or protocols of the Service or any components thereof; (ii) copy, modify, adapt, distribute, publish, resell, or sublicense the data obtained through the Service to any third party; (iii) remove or modify any proprietary markings on the data; (iv) use the data in violation of any applicable law; (v) seek to circumvent the Service to access upstream data sources directly; or (vi) use the data for any purpose other than operating and maintaining infrastructure supporting the dYdX Protocol. **6. Suspension and Termination** DOS may suspend or terminate your access to the Service at any time, with or without notice, if: (i) you breach these Terms or any applicable Data Provider terms; (ii) your use exceeds applicable rate or usage limits; (iii) DOS's agreement with any upstream data provider terminates or expires; or (iv) DOS reasonably determines that suspension is necessary to protect the integrity of the Service or comply with applicable legal or contractual obligations. DOS shall not be liable for any loss or damage arising from any such suspension or termination. **7. Limitation of Liability** TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, DOS AND ITS AFFILIATES SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES ARISING FROM YOUR USE OF OR INABILITY TO USE THE SERVICE OR THE DATA. DOS'S TOTAL AGGREGATE LIABILITY TO YOU SHALL NOT EXCEED USD $100, WHETHER ARISING IN CONTRACT, TORT, OR OTHERWISE. THESE LIMITATIONS SURVIVE TERMINATION OF THESE TERMS AND APPLY REGARDLESS OF THE FORM OF ACTION. NOTHING IN THESE TERMS EXCLUDES OR LIMITS LIABILITY THAT CANNOT BE EXCLUDED OR LIMITED UNDER APPLICABLE LAW. **8. Indemnification** You agree to defend, indemnify, and hold harmless DOS and its affiliates and their respective officers, directors, employees, and agents from and against any claims, liabilities, damages, losses, costs, and expenses (including reasonable attorneys' fees) arising out of or relating to: (i) your use of the Service in violation of these Terms or any applicable Data Provider terms; (ii) your violation of any applicable law; or (iii) your gross negligence or willful misconduct in connection with your use of the Service. The foregoing indemnification obligation shall not apply to the extent that a claim arises from DOS's own gross negligence or willful misconduct. **9. Amendments** DOS reserves the right to modify these Terms at any time and will, where practicable, provide reasonable notice of material changes. Continued use of the Service after the effective date of any amendment constitutes acceptance. If you do not agree to the amended Terms, you must cease using the Service. **10. Governing Law and Disputes** These Terms are governed by the laws of the Cayman Islands, without regard to conflict of laws principles. Any dispute arising out of or relating to these Terms shall be finally resolved by binding arbitration administered by the Cayman Islands Mediation & Arbitration Centre ("**CI-MAC**") under its then-current rules and the Arbitration Act of the Cayman Islands. The seat of arbitration shall be George Town, Grand Cayman. Proceedings shall be conducted in English before a sole arbitrator. Any award shall be final, binding, and enforceable in any court of competent jurisdiction.
#### Flags | Flag | Default Value | Description | | -------------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------- | | `--market-map-endpoint` | `""` | The listen-to endpoint for market-map. This is typically the blockchain node's gRPC endpoint. | | `--oracle-config` | `""` | Overrides part of the Oracle configuration. This does not override the entire configuration, only the specified part. | | `--run-pprof` | `false` | Run pprof server. | | `--pprof-port` | `"6060"` | Port for the pprof server to listen on. | | `--log-std-out-level` | `"info"` | Log level (debug, info, warn, error, dpanic, panic, fatal). | | `--log-file-level` | `"info"` | Log level for the file logger (debug, info, warn, error, dpanic, panic, fatal). | | `--log-file` | `"sidecar.log"` | Write logs to a file. | | `--log-max-size` | `100` | Maximum size in megabytes before log is rotated. | | `--log-max-backups` | `1` | Maximum number of old log files to retain. | | `--log-max-age` | `3` | Maximum number of days to retain an old log file. | | `--log-file-disable-compression` | `false` | Compress rotated log files. | | `--log-disable-file-rotation` | `false` | Disable writing logs to a file. | | `--metrics-enabled` | `true` | Enables the Oracle client metrics. | | `--metrics-prometheus-address` | `"0.0.0.0:8002"` | Sets the Prometheus server address for the Oracle client metrics. | | `--host` | `"0.0.0.0"` | The address the Oracle will serve from. | | `--port` | `"8080"` | The port the Oracle will serve from. | | `--update-interval` | `250000000` | The interval at which the oracle will fetch prices from providers. | | `--max-price-age` | `120000000000` | Maximum age of a price that the oracle will consider valid. | ## Running a Validator 💡Note: failure to set up below configurations on a validator node may compromise chain functionality. ### Disabled ETH bridge For the chain to process bridge transactions from Ethereum, Ethereum testnet, or other chain that supports the `eth_getLogs` RPC method, the bridge daemon queries an RPC endpoint for logs emitted by the bridge contract. By default, a node will use a public testnet endpoint that may have rate-limiting, low reliability, or other restricted functionality. As a validator run the flags `--bridge-daemon-enabled=false` in the command you run when starting the node, since the bridge has been disabled ### Slinky Starting in [`v5.0.0`](https://www.mintscan.io/dydx/proposals/59), running a validating full node requires the Slinky sidecar to be running in order to fetch Oracle prices. Slinky is a sidecar that pulls price data from external sources and caches them for the validator to use. #### Running Slinky First, please ensure you have received your API keys for the relevant decentralized provider nodes. If you have not received API keys, please reach out to the provider team in the relevant communication channels. Next, place your API keys under their corresponding URLs in the example file below and save it to your system. Keep the file path handy as it needs to be passed into a flag when running Slinky. The `oracle.json` file also supplies an edited configuration for the `dydx_migration_api` which facilitates graceful migration from dydx's `x/prices` module to `x/marketmap` module. For the `dydx_migration_api` provider, make sure to fill in the URL for the REST endpoint and gRPC endpoint of your node (in that order). The migration API will not work unless the REST API endpoint is the first endpoint in the endpoints list.
Example `oracle.json` file ```json { "providers": { "dydx_migration_api": { "api": { "endpoints": [ { "url": "http://" }, { "url": ":" } ] } }, "raydium_api": { "api": { "endpoints": [ { "url": "https://connect-solana.kingnodes.com", "authentication": { "apiKeyHeader": "x-api-key", "apiKey":"API KEY" } }, { "url": "https://dydx.helius-rpc.com", "authentication": { "apiKeyHeader": "x-api-key", "apiKey": "API KEY" } }, { "url": "https://dydx-solanam-525a.mainnet.rpcpool.com", "authentication": { "apiKeyHeader": "x-token", "apiKey": "API KEY" } } ] } }, "uniswapv3_api-ethereum": { "api": { "endpoints": [ { "url": "https://ethereum.polkachu.com", "authentication": { "apiKeyHeader": "x-api-key", "apiKey": "API KEY" } }, { "url": "https://connect-eth.kingnodes.com", "authentication": { "apiKeyHeader": "x-api-key", "apiKey": "API KEY" } }, { "url": "https://ethereum-rpc.rhino-apis.com", "authentication": { "apiKeyHeader": "x-api-key", "apiKey": "API KEY" } } ] } }, "uniswapv3_api-base": { "api": { "endpoints": [ { "url": "https://base-rpc.rhino-apis.com", "authentication": { "apiKeyHeader": "x-api-key", "apiKey": "API KEY" } }, { "url": "https://connect-base.kingnodes.com", "authentication": { "apiKeyHeader": "x-api-key", "apiKey": "API KEY" } }, { "url": "https://base.polkachu.com", "authentication": { "apiKeyHeader": "x-api-key", "apiKey": "API KEY" } } ] } } } } ```
With the `oracle.json` file path, enter the following command to run Slinky. ```bash connect \ --marketmap-provider dydx_migration_api \ --oracle-config path/to/oracle.json ``` For more information on the available configuration parameters for Slinky, please refer to the [Slinky Configuration](/nodes/running-node/required-node-configs.mdx#slinky-configs) section. #### Verifying Slinky is Running To verify that Slinky is running, enter the following command. ```bash curl 'http://localhost:8080/slinky/oracle/v1/prices' | jq . ``` The output of the command should look similar to this: ```json { "prices": { "ATOM/USD": "920650000", "BITCOIN/USD": "3980283250000", "DYDX/USD": "273682500", "ETHEREUM/BITCOIN": "5842000", "ETHEREUM/USD": "232550500000", "POLKADOT/USD": "638800000", "SOLANA/USD": "8430350000" }, "timestamp": "2024-01-23T01:15:09.776890Z" } ``` #### Connecting a dYdX Validator node to Slinky In order for the application to get prices from Slinky, we need to add the following lines under the `[oracle]` heading in the `app.toml`. Remember to change the `oracle_address` value to the address of your Slinky instance. ```toml (app.toml) # ... other sections [oracle] enabled = "true" # if you are not running a full node, set this to "false" oracle_address = ":8080" client_timeout = "250ms" metrics_enabled = "true" interval = "1500ms" price_ttl = "10s" ``` For more information on the available configuration parameters for the oracle, please refer to the [Oracle Configuration](/nodes/running-node/required-node-configs.mdx#oracle-configuration) section. #### Advanced Slinky Configuration In case you are using a remote signer or have another distributed validator setup, you may need to configure Slinky differently. ##### Using Remote Signers Remote signers can be used with Connect, however only certain versions are compatible **Horcrux** Required Version: `v3.3.0+` [link](https://github.com/strangelove-ventures/horcrux/releases) **With `Horcrux-Proxy`** Required Version: `v1.0.0+` [link](https://github.com/strangelove-ventures/horcrux-proxy/releases) **TMKMS** Required Version: `v0.13.1+` [link](https://github.com/iqlusioninc/tmkms/tags) ##### Using Distributed Validators Connect can be used within a distributed validator setup. To do so, simply apply the same `app.toml` changes to each validator node. Head over to [configuration](/nodes/running-node/required-node-configs.mdx) to see the necessary application-side configurations. #### Upgrading Slinky The recommended version of Slinky is the latest released version in the GitHub repository [link](https://github.com/dydxprotocol/slinky). Instructions on upgrading sidecar can be found [here](/nodes/upgrades/upgrading-sidecar). #### Slinky FAQ
Can I run Slinky on the same machine as my validator? * Yes, you can run it anywhere - but please defer to any chain-specific recommendations if there are any!
Can I use IPv6? * No, IPv6 is currently not supported for sidecar-node communication.
Can I get slashed for running Connect? * No.
Does Connect take up a lot of resources? * No, the Connect binary is very lightweight. On a 36 GB Macbook Pro M3, a Connect instance fetching 125 markets took up only 50MB of memory, and 6% of the CPU.
## Set Up a Full Node Installing and running a full node allows you to read orderbook and onchain data from a network, as well as place, confirm and cancel orders directly on that network. ### Prerequisites The minimum recommended specs for running a node is the following: * 16-core, x86\_64 architecture processor * 64 GiB RAM * 500 GiB of locally attached SSD storage For example, an AWS instance like the `r6id.4xlarge`, or equivalent. ### Choose a Method To set up a full node, you can either: 1. Use [this script](https://github.com/dydxprotocol/v4-chain/blob/main/protocol/scripts/create_full_node.sh), provided by dYdX, to automate setup. Save the script with an `.sh` extension in your `$HOME` directory. Edit the script, replacing default values in fields such `VERSION` and `CHAIN-ID` with your own. Run the script with the following commands: :::note To find the current version of the [dYdX Foundation](https://www.dydx.foundation/) mainnet, see the recommended protocol version on [mintscan.io](https://www.mintscan.io/dydx/parameters). To find network constants such as chain IDs, see the [Network Configuration](/nodes/network-constants) section of the documentation. ::: ```bash cd $HOME bash create_full_node.sh ``` 2. Or, follow the steps on this page to manually set up a full node. ### Manual Installation Steps The following steps will guide you through manually setting up a full node. Run the commands in this procedure from your home directory unless otherwise specified. To change directories to your home folder, run the following command: ```bash cd $HOME ``` ::::steps #### Update your system and prepare to install dependencies To download system updates and install [curl](https://curl.se/), [jq](https://jqlang.github.io/jq/), and [lz4](https://lz4.org/), run the following commands: ```bash sudo apt-get -y update sudo apt-get install -y curl jq lz4 ``` #### Install Go To install [Go](https://go.dev/), run the following commands using the latest version of Go: ```bash # Example for AMD64 architecture and Go version 1.22.2 wget https://golang.org/dl/go1.22.2.linux-amd64.tar.gz # Download the compressed file sudo tar -C /usr/local -xzf go1.22.2.linux-amd64.tar.gz # Extract the file to /usr/local rm go1.22.2.linux-amd64.tar.gz # Delete the installer package ``` Add the Go directory to your system `$PATH`: ```bash echo 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin' >> $HOME/.bashrc # Write to your .bashrc profile ``` #### Install Cosmovisor and create data directories [Cosmovisor](https://docs.cosmos.network/sdk/v0.50/build/tooling/cosmovisor) is a process manager for Cosmos SDK-based blockchains that enables automatic binary updates without downtime. To install the latest version of Cosmovisor, run the following command: ```bash go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest ``` To create data directories for Cosmovisor, run the following commands: ```bash mkdir -p $HOME/.dydxprotocol/cosmovisor/genesis/bin mkdir -p $HOME/.dydxprotocol/cosmovisor/upgrades ``` #### Download the `dydxprotocold` binary The `dydxprotocold` binary contains the software you need to operate a full node. **You must use the same version of the software as the network to which you want to connect.** To find the current version of the [dYdX Foundation](https://www.dydx.foundation/) mainnet, see the recommended protocol version on [mintscan.io](https://www.mintscan.io/dydx/parameters). **Option 1**: Find and download that protocol binary from the [v4 Chain Releases](https://github.com/dydxprotocol/v4-chain/releases/) page. > For example, for protocol version 5.0.5 on an AMD system, download `dydxprotocold-v5.0.5-linux-amd64.tar.gz`. **Option 2**: Download the binary with `curl`, replacing the version numbers and architecture of the package as needed: ```bash # curl example for protocol version 5.0.5 on AMD64 architecture curl -L -O https://github.com/dydxprotocol/v4-chain/releases/download/protocol/v5.0.5/dydxprotocold-v5.0.5-linux-amd64.tar.gz ``` #### Move `dydxprotocold` to your Cosmovisor `/genesis` directory After you download the binary, moving `dydxprotocold` into your Cosmovisor data directory allows you to use Cosmovisor for no-downtime binary upgrades. To extract, rename, and move the file to your Cosmovisor data directory, run the following commands: ```bash # Example for AMD64 architecture sudo tar -xzvf dydxprotocold-v5.0.5-linux-amd64.tar.gz # Extract the file sudo mv ./build/dydxprotocold-v5.0.5-linux-amd64 ./.dydxprotocol/cosmovisor/genesis/bin/dydxprotocold # Move the file to /.dydxprotocol and rename it rm dydxprotocold-v5.0.5-linux-amd64.tar.gz # Delete the installer package rm -rf build # Delete the now-empty /build directory ``` Add the `dydxprotocold` directory to your system `$PATH`: ```bash echo 'export PATH=$PATH:$HOME/.dydxprotocol/cosmovisor/genesis/bin' >> $HOME/.bashrc # Write to your .bashrc profile ``` #### Initialize your node To initialize your node, provide the ID of the chain to which you want to connect and create a name for your node. The dYdX home directory is created in `$HOME/.dydxprotocol` by default. Replace the example values `dydx-mainnet-1` and `my-node` with your own and run the following command: ```bash # Example for DYDX token holders on mainnet dydxprotocold init --chain-id=dydx-mainnet-1 my-node ``` :::note See the [Network Configuration](/nodes/network-constants) section of the documentation for chain IDs and other network constants. ::: When you initialize your node, `dydxprotocold` returns your default node configuration in JSON. #### Update your node configuration with a list of seed nodes A seed node acts as an address book and helps your node join the network. To update `config.toml` with a list of seed nodes, run the following command: :::note Check the [Resources](/nodes/resources#seed-nodes) page for an up-to-date list of seed nodes for the network to which you want to connect. ::: ```bash # Example for DYDX token holders on mainnet SEED_NODES=("ade4d8bc8cbe014af6ebdf3cb7b1e9ad36f412c0@seeds.polkachu.com:23856", "df1f145848d253800d4e4216e8793158688912f1@seeds.kingnodes.com:23856", "6a720a1e5e8be9acf2752b22dc868ea2f95aaaf7@dydx-seeds.enigma-validator.com:1490", "c2c2fcb5e6e4755e06b83b499aff93e97282f8e8@tenderseed.ccvalidators.com:26401" ) sed -i 's/seeds = ""/seeds = "'"${SEED_NODES[*]}"'"/' $HOME/.dydxprotocol/config/config.toml ``` The preceding command updates the `seeds` variable of `config.toml` with the list you provide. #### Use a snapshot as your node's initial state Using snapshots to restore or sync your full node's state saves time and effort. Using a snapshot avoids replaying all the blocks from genesis and does not require multiple binary versions for network upgrades. Instead, your node uses the snapshot as its initial state. ##### Clear your data directory If you already have a data directory at `$HOME/.dydxprotocol/data`, you must clear it before installing a snapshot, which comes with its own data directory. To clear your data directory while retaining files you need, follow these steps: First, make a backup copy of `priv_validator_state.json` in your `.dydxprotocol` directory by running the following command: ```bash # Make a copy of priv_validator_state.json and append .backup cp $HOME/.dydxprotocol/data/priv_validator_state.json $HOME/.dydxprotocol/priv_validator_state.json.backup ``` Next, confirm the following: * A backup file, `priv_validator_state.json.backup`, exists in your current directory. * The original `priv_validator_state.json` exists in the `/data` directory to be deleted. * No other files exist in the `/data` directory to be deleted. ```bash ls $HOME/.dydxprotocol # Confirm that the backup exists in /.dydxprotocol ls $HOME/.dydxprotocol/data # Confirm that only priv_validator_state.json exists in /data ``` Finally, to clear the data directory, removing it and all files inside, run the following command: ```bash # WARNING: This command recursively deletes files and directories in the dydxprotocol /data directory. Make sure you know what you are deleting before running the command. rm -rf $HOME/.dydxprotocol/data ``` Installing a snapshot will create a new `/data` directory. ##### Install the Snapshot To download and extract the snapshot contents to the default dydxprotocol home directory, first **change directories into /.dydxprotocol**. To change directories, run the following command: ```bash cd $HOME/.dydxprotocol ``` Next, find a provider for your use case on the [Snapshot Service](/nodes/resources#snapshot-service) page. Use the provider's instructions to download the snapshot into your `$HOME/.dydxprotocol` directory. > For example, if you are connecting to `dydx-mainnet-1`, you may use the provider [Polkachu](https://polkachu.com/tendermint_snapshots/dydx). In most cases, you can run `wget `. Next, run the following command in your `$/HOME/.dydxprotocol` directory, replacing the example value `your-snapshot-filename`: ```bash lz4 -dc < your-snapshot-filename.tar.lz4 | tar xf - ``` Extracting the snapshot creates a new `/data` folder in your current directory, `.dydxprotocol`. Next, use the backup file `priv_validator_state.json.backup` you created to reinstate `/data/priv_validator_state.json` with the following command: ```bash mv $HOME/.dydxprotocol/priv_validator_state.json.backup $HOME/.dydxprotocol/data/priv_validator_state.json ``` Finally, **change directories back to your `$HOME` directory for the rest of the procedure**. Run the following command: ```bash cd $HOME ``` When you start your full node, it will automatically use the snapshot in your data directory to begin syncing your full node's state with the network. #### Create a system service to start your full node automatically To create a `systemd` service that starts your full node automatically, run the following commands: ```bash sudo tee /etc/systemd/system/dydxprotocold.service > /dev/null << EOF [Unit] Description=dydxprotocol node service After=network-online.target [Service] User=$USER ExecStart=/$HOME/go/bin/cosmovisor run start --non-validating-full-node=true WorkingDirectory=$HOME/.dydxprotocol Restart=always RestartSec=5 LimitNOFILE=4096 Environment="DAEMON_HOME=$HOME/.dydxprotocol" Environment="DAEMON_NAME=dydxprotocold" Environment="DAEMON_ALLOW_DOWNLOAD_BINARIES=false" Environment="DAEMON_RESTART_AFTER_UPGRADE=true" Environment="UNSAFE_SKIP_BACKUP=true" [Install] WantedBy=multi-user.target EOF sudo systemctl daemon-reload sudo systemctl enable dydxprotocold ``` The system service definition above holds environment variables. When you start it, the service will run the command `/$HOME/go/bin/cosmovisor run start --non-validating-full-node=true`. > The flag `--non-validating-full-node` is required. It disables the functionality intended for validator nodes and enables additional logic for reading data. #### Start the service To start your node using the `systemd` service that you created, run the following command: ```bash sudo systemctl start dydxprotocold ``` When you want to stop the service, run the following command: ```bash sudo systemctl stop dydxprotocold ``` When you start your full node it must sync with the history of the network. If you initialized your full node using a snapshot, your node must update its state only with blocks created after the snapshot was taken. If your node's state is empty, it must sync with the entire history of the network. #### Check your service logs to confirm that your node is running ```bash sudo journalctl -u dydxprotocold -f ``` If your system service `dydxprotocold` is running, the preceding command streams updates from your node to your command line. Press `Ctrl + C` to stop viewing updates. Finally, confirm that your full node is properly synchronized by comparing its current block to the dYdX Chain: * To find the network's current block, see the **Block Height** of your network with a block explorer, such as [mintscan.io](https://www.mintscan.io/dydx). * To find your full node's height, query your node with the following command: ```bash curl localhost:26657/status ``` When your full node's latest block is the same as the network's latest block, your full node is ready to participate in the network. :::: ### Next Steps When your full node is up to date with the network, you can use it to read live data and configure additional settings. Learn more on the [Optimizing Your Full Node](/nodes/running-node/optimize) and [Full Node Streaming](/nodes/full-node-streaming) pages. ## Snapshots Services that provide snapshots for the network can be found [here](/nodes/resources#snapshot-service). These snapshots will be used as a backup point in case an upgrade fails or a new node wants to start up and does not want to start from block 1 to catchup. ## Voting ### Save your Chain ID in `dydxprotocold` config Save the [chain-id](/nodes/network-constants#chain-id). This will make it so you do not have to manually pass in the chain-id flag for every CLI command. ```bash dydxprotocold config set client chain-id [chain_id] ``` ### View the status of a proposal To view the status of a proposal, use the following command: ```bash dydxprotocold query gov proposal [proposal_id] ``` The status of the proposal will be returned: ```bash deposit_end_time: "2023-04-02T19:21:27.467932675Z" final_tally_result: abstain_count: "0" no_count: "0" no_with_veto_count: "0" yes_count: "0" id: "1" messages: - '@type': /cosmos.upgrade.v1beta1.MsgSoftwareUpgrade authority: dydx10d07y265gmmuvt4z0w9aw880jnsr700jnmapky plan: height: "60400" info: "" name: v0.1.0 time: "0001-01-01T00:00:00Z" upgraded_client_state: null metadata: "" proposer: dydx199tqg4wdlnu4qjlxchpd7seg454937hjrknju4 status: PROPOSAL_STATUS_VOTING_PERIOD submit_time: "2023-03-31T19:21:27.467932675Z" summary: This is a proposal to schedule v0.1.0 software upgrade at block height 60400, estimated to occur on Tuesday April 4th at 1PM EDT. title: dYdX Protocol v1.0.0 Upgrade total_deposit: - amount: "10000000" denom: stake voting_end_time: "2023-03-31T19:22:27.467932675Z" voting_start_time: "2023-03-31T19:21:27.467932675Z" ``` ### Voting for a proposal To vote for a governance proposal, use the following command: ```bash dydxprotocold tx gov vote [proposal_id] [option] --from [key] ``` The option can be either `Yes`, `No`, `NoWithVeto`, `Abstain`. See [here](https://docs.cosmos.network/sdk/v0.47/build/modules/gov#option-set) for the descriptions of the these options. ### To see the votes ```bash dydxprotocold query gov votes [proposal_id] ``` ```bash pagination: next_key: null total: "0" votes: - metadata: "" options: - option: VOTE_OPTION_YES weight: "1.000000000000000000" proposal_id: "1" voter: dydx199tqg4wdlnu4qjlxchpd7seg454937hjrknju4 ... ``` ### To see the tally of votes To query tally of votes on a proposal: ```bash dydxprotocold query gov tally [proposal_id] ``` This will return something like: ```bash abstain_count: "0" no_count: "0" no_with_veto_count: "0" yes_count: "0" ``` ## Cosmovisor `cosmovisor` is a small process manager for Cosmos SDK application binaries that monitors the governance module for incoming chain upgrade proposals. If it sees a proposal that gets approved, `cosmovisor` can automatically download the new binary, stop the current binary, switch from the old binary to the new one, and finally restart the node with the new binary. We recommend validators to use `cosmovisor` to run their nodes. This will make low-downtime upgrades smoother, as validators don’t have to manually upgrade binaries during the upgrade. Instead, they can pre-install new binaries, and `cosmovisor` will automatically update them based on the onchain software upgrade proposals. ### Configuration When Cosmovisor activates an upgrade, it does a backup of the entire data directory by default. This backup can take a very long time to process unless the user does aggressive historical-state-pruning using the `pruning` [configuration on the node](../running-node/required-node-configs.mdx). As long as you have access to a previous state [snapshot](../running-node/snapshots.mdx), we recommend setting the environment variable `UNSAFE_SKIP_BACKUP` to `true` which skips the data backup and allows a much faster upgrade. If your node is configured to only keep a small amount of historical state, then you may be able to get away with running the backup quickly. More information about Cosmovisor settings can be found in the [Cosmovisor documentation](https://docs.cosmos.network/sdk/v0.50/build/tooling/cosmovisor). ### Installation #### Using go install To install the latest version of `cosmovisor`, run the following command: ```bash go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest ``` #### Manual Build You can also install from source by pulling the cosmos-sdk repository and switching to the correct version and building as follows: ```bash git clone https://github.com/cosmos/cosmos-sdk.git cd cosmos-sdk git checkout cosmovisor/vx.x.x make cosmovisor ``` This will build Cosmovisor in `/cosmovisor` directory. Afterwards you may want to put it into your machine's PATH like as follows: ```bash cp cosmovisor/cosmovisor ~/go/bin/cosmovisor ``` To check your Cosmovisor version, run ```bash cosmovisor version ``` ### Directory structure ``` . ├── current -> genesis or upgrades/ ├── genesis │ └── bin │ └── $DAEMON_NAME └── upgrades └── ├── bin │ └── $DAEMON_NAME └── upgrade-info.json ``` ### Initializing Cosmovisor 1. Rename binary to `dydxprotocold` ```bash mv dydxprotocold.- dydxprotocold ``` 2. Set the environment variables ```bash export DAEMON_NAME=dydxprotocold export DAEMON_HOME= ``` 3. The directory structure can be initialized with ```bash cosmovisor init ``` * `DAEMON_HOME` should be set to the **validator’s home directory** since Cosmovisor polls `/data/` for upgrade info. * `DAEMON_NAME` should be set to `dydxprotocold` ### How to run `cosmovisor` is simply a thin wrapper around Cosmos applications. Use the following command to start a testnet validator using `cosmovisor` . ```bash cosmovisor run arg1 arg2 arg3 ... ``` All arguments passed to `cosmovisor run` will be passed to the application binary (as a subprocess). `cosmovisor` will return `/dev/stdout` and `/dev/stderr` of the subprocess as its own. Example: ```bash cosmovisor run start —log-level info —home /dydxprotocol/chain/.alice ``` runs ```bash dydxprotocold start —log-level info —home /dydxprotocol/chain/.alice ``` as its subprocess. ## Performing Upgrades ### Managing Upgrades Validators can choose how to run a validator and manage software upgrades according to their preferred option: 1. Using [Cosmovisor](./cosmovisor.mdx) 2. Manual ### Voting for Upgrade Proposals See [Voting](../running-node/voting.mdx) ### Upgrades Releases for the dYdX Chain will use [semantic versioning](https://semver.org/). See [here](./types-of-upgrades.mdx) for details. #### ⚒️ Cosmovisor Users ##### Upgrading to a new Major/Minor Version (e.g. v0.1.0) 1. Download the [binary](https://github.com/dydxprotocol/v4-chain) for the new release, rename the binary to `dydxprotocold`. ```bash mv dydxprotocold.- dydxprotocold ``` 2. Make sure that the new binary is executable. ```bash chmod 755 dydxprotocold ``` 3. Create a new directory `$DAEMON_HOME/cosmovisor/upgrades//bin` where `` is the URI-encoded name of the upgrade as specified in the Software Upgrade Plan. ```bash mkdir -p $DAEMON_HOME/cosmovisor/upgrades//bin ``` 4. Place the new binary under `$DAEMON_HOME/cosmovisor/upgrades//bin` before the upgrade height. ```bash mv $DAEMON_HOME/cosmovisor/upgrades//bin ``` 💡 **IMPORTANT**: Do this before the upgrade height, so that `cosmovisor` can make the switch. That's it! The old binary will stop itself at the upgrade height, and `cosmovisor` will switch to the new binary automatically. For a `Plan` with name `v0.1.0`, your `cosmovisor/` directory should look like this: ``` cosmovisor/ ├── current/ # either genesis or upgrades/ ├── genesis │ └── bin │ └── dydxprotocold └── upgrades └── v0.1.0 ├── bin └── dydxprotocold ``` ##### Upgrading to a Patch Version (e.g. v0.0.2) 1. Download the [binary](https://github.com/dydxprotocol/v4-chain/releases) for the new patch release, rename the binary to `dydxprotocold`. ```bash mv dydxprotocold.- dydxprotocold ``` 2. Make sure that the new binary is executable. ```bash chmod 755 dydxprotocold ``` 3. Replace the binary under `$DAEMON_HOME/cosmovisor/current/bin` with the new binary. ```bash mv $DAEMON_HOME/cosmovisor/current/bin ``` 4. Stop the current binary (e.g. Ctrl+C) 5. Restart `cosmovisor` ```bash cosmovisor run start --p2p.seeds="[seed_node_id]@[seed_node_ip_addr]:26656" --bridge-daemon-eth-rpc-endpoint="" ``` #### 🦾 Manual Users ##### Upgrading to a Major/Minor Version (e.g. v0.1.0) 1. Download the [binary](https://github.com/dydxprotocol/v4-chain/releases) for the new release. 1. Ideally also before the upgrade height to minimize downtime 2. Make sure that the new binary is executable. ```bash chmod 755 dydxprotocold ``` 3. Wait for the old binary to stop at the upgrade height (this should happen automatically). 4. Restart the application using the **new binary from step 1**. ```bash ./dydxprotocold start --p2p.seeds="[seed_node_id]@[seed_node_ip_addr]:26656" --bridge-daemon-eth-rpc-endpoint="" ``` ##### Upgrading to a Patch Version (e.g. v0.0.2) 1. Download the [binary](https://github.com/dydxprotocol/v4-chain/releases) for the new release. 2. Make sure that the new binary is executable. ```bash chmod 755 dydxprotocold ``` 3. Stop the current binary (e.g. Ctrl+C) 4. Restart the application using the new binary from step 1. ```bash ./dydxprotocold start --p2p.seeds="[seed_node_id]@[seed_node_ip_addr]:26656" --bridge-daemon-eth-rpc-endpoint="" ``` *** ### Rollback In the case of an unsuccessful chain upgrade, an incorrect `AppHash` might get persisted by Tendermint. To move forward, validators will need to rollback to the previous state so that upon restart, Tendermint can replay the last block to get the correct `AppHash`. **Please note:** validators should never rollback further than the last invalid block. In extreme edge cases, transactions could be reverted / re-applied for the last block and cause issues. #### ⚒️ Cosmovisor Users Cosmovisor backs up the `data` directory before attempting an upgrade. To restore to a previous version: 1. Stop the node (e.g. Ctrl+C) 2. Then, copy the contents of your backup data directory back to `~/.dydxprotocol` ```bash rm -rf ~/.dydxprotocol/data mv ~/.dydxprotocol/data-backup-YYYY-MM-DD ~/.dydxprotocol/data ``` 3. Restart your node. ```bash cosmovisor run start --p2p.seeds="[seed_node_id]@[seed_node_ip_addr]:26656" --bridge-daemon-eth-rpc-endpoint="" ``` #### 🦾 Manual Users If you don't have a data backup: 1. Stop the node (e.g. Ctrl+C) 2. Rollback the application and Tendermint state by one block height. ```bash ./dydxprotocold rollback ``` 3. Restart your node. ```bash ./dydxprotocold start --p2p.seeds="[seed_node_id]@[seed_node_ip_addr]:26656" --bridge-daemon-eth-rpc-endpoint="" ``` ## Types of upgrades ### Major and minor versions Major and minor changes can be consensus breaking. These upgrades usually go through a governance proposal and happen at specific heights. ### Patch versions Patch versions are backwards compatible changes. These typically can be applied in a rolling fashion and don’t need to go through a governance proposal. ### Hard-forks One of the limitations of the normal upgrade procedure via governance is that it requires waiting for the entire voting period, which makes them unsuitable for emergency situations. For such cases, hard forks are usually required. The high-level strategy for coordinating an upgrade is as follows: 1. The vulnerability is fixed on a private branch that contains breaking changes. 2. A new patch release (e.g. `v8.0.0` -> `v8.0.1`) needs to be created that contains a hard fork logic and performs an upgrade to the next breaking version (e.g. `v9.0.0`) at a predefined block height. 3. Validators upgrade their nodes to the patch release (e.g. `v8.0.1`). In order to perform the hard fork successfully, it’s important that enough validators upgrade to the patch release so that they make up at least 2/3 of the total validator voting power. 4. Before the upgrade time (corresponding to the upgrade block height), the new major release (e.g. `v9.0.0`) including the vulnerability fix is published. 5. Upgrades happen in a similar fashion as `MsgSoftwareUpgrade`. From a node operator’s perspective, hard forks are essentially a combination of a patch version (`v8.0.1`) followed by a major version (`v9.0.0`). Please use the instructions from [Performing Upgrades](./performing-upgrades.mdx) to perform the corresponding upgrades. Starting in [`v5.0.0`](https://www.mintscan.io/dydx/proposals/59), all validating full nodes should be running the [Sidecar](/nodes/running-node/running-a-validator#slinky). Non validating full nodes do not need to run the sidecar. Upgrading the Slinky binary can be done out of band of the chain's binary. If you have a load balancer, CNAME, etc., in front of your sidecar you can simply start up the new version and switch out which version traffic is being directed to during live chain validation. If you are running the Slinky sidecar in a container you can shut down the container, pull the updated container image and relaunch your container to update. If you are running the Slinky binary via `systemd` or other management tool, you will need to stop the process and re-launch using the newly released binary. The node will still be able to participate in consensus without the sidecar, and will begin attaching prices to blocks once Slinky is available. In the worst case, an upgrade in any of these manners will cause you to miss including vote extensions for a single block which should have no negative effects on you or the network. ## Using Cosmovisor to stage dYdX Chain binary upgrade ### Prerequisite 1. Linux (Ubuntu Server 22.04.3 recommended) 2. 8-cpu (ARM or x86\_64), 64 GB RAM, 500 GB SSD NVME Storage 3. Already installed dYdXChain full node ### Preparation 1. Install Go from [https://go.dev/doc/install](https://go.dev/doc/install) (Version tested is 1.22.1) 2. Install Cosmovisor, with the following command: * `go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest` 3. Copy cosmovisor from $HOME/go/bin/ to a directory in your $PATH 4. Add two environment variables to $HOME/.profile. The data directory is typically $HOME/.dydx-mainnet-1 * `export DAEMON_NAME=dydxprotocold` * `export DAEMON_HOME=` 5. Log out and log back in. 6. Initialize Cosmovisor with the following command. The `path to executable` is the the full path to dydxprotocold * `cosmovisor init ` 7. Cosmovisor is now ready for use. ### Running dydxprotocold under Cosmovisor You have to change the way you currently run dydxprotocold to run under Cosmovisor. This is done simply by specifying “cosmovisor run” in place of the “dydxprotocold” command you used previously. Therefore, if you previously used “dydxprotocold start --p2p.seeds="ade4d8…”, you would change that to “cosmovisor run start --p2p.seeds="ade4d8…” ### Staging upgrade 1. The Cosmovisor directory structure looks like this: ![Upgrade1](/Staging_1.png) 2. To stage an upgrade, you would create a `name` directory inside the upgrades/ directory. For example, as of 4/1/2024, the current version is v3.0.0 and the next upgrade version is v4.0.0. Therefore you would create a directory called “v4.0.0” and then a bin directory inside it. ![Upgrade2](/Staging_2.png) 3. Now, download the upgraded binary and put it inside the bin directory created previously. It must be named dydxprotocold 4. Restart dydxprotocold with Cosmovisor. Now, Cosmovisor will automatically halt the current binary at the block activation height and start the upgrade binary. ### Equity Tier Limits Subaccounts have a limited number of stateful open orders at any one time determined by the net collateral of the subaccount. These limits are subject to governance. The latest limits can be queried via the `https:///dydxprotocol/clob/equity_tier` endpoint. Here is an example response: ``` "equity_tier_limit_config": { "stateful_order_equity_tiers": [ { "usd_tnc_required": "0", "limit": 0 }, { "usd_tnc_required": "20000000", "limit": 10 }, { "usd_tnc_required": "100000000", "limit": 20 }, { "usd_tnc_required": "1000000000", "limit": 40 }, { "usd_tnc_required": "10000000000", "limit": 100 }, { "usd_tnc_required": "50000000000", "limit": 200 } ] } ``` Read as: | Net Collateral | Long-term / Conditional orders | | -------------------------- | ------------------------------ | | \< $20 | 0 | | >= $20 and \< $100 | 10 | | >= $100 and \< $1,000 | 20 | | >= $1,000 and \< $10,000 | 40 | | >= $10,000 and \< $100,000 | 100 | | >= $100,000 | 200 | For example up to 20 open stateful orders across all markets for a subaccount with a net collateral of $2,000. Note: * Short term orders, including limit `Immediate-or-Cancel`, `Fill-or-Kill`, and market orders on the frontend do not have this limitation. * Only the `stateful_order_equity_tiers` field is in effect -- short term order equity limits under the `short_term_order_equity_tiers` key are no longer in effect. ## Rate Limits ### Block Rate Limits :::note All rate limits are subject to change. The latest limits can be queried via the `https:///dydxprotocol/clob/block_rate` endpoint. Note that rate limits are applied per account. That is, subaccounts under the same account share the same rate limit. ::: Here is an example response: ``` { "block_rate_limit_config": { "max_short_term_orders_per_n_blocks": [], "max_stateful_orders_per_n_blocks": [ { "num_blocks": 1, "limit": 2 }, { "num_blocks": 100, "limit": 20 } ], "max_short_term_order_cancellations_per_n_blocks": [], "max_short_term_orders_and_cancels_per_n_blocks": [ { "num_blocks": 5, "limit": 4000 } ] } } ``` #### Active Fields `max_stateful_orders_per_n_blocks`: How many stateful order **place** attempts (successful and failed) are allowed for an account per N blocks. Note that the rate limits are applied in an AND fashion such that an order placement must pass all rate limit configurations. `max_short_term_orders_and_cancels_per_n_blocks`: How many short term order **place and cancel** attempts (successful and failed) are allowed for an account per N blocks. Note that the rate limits are applied in an AND fashion such that an order placement must pass all rate limit configurations. #### Deprecated Fields These fields are not used at this time. `max_short_term_order_cancellations_per_n_blocks` `max_short_term_orders_per_n_blocks` #### Examples Examples assume the values in the provided example response. * 2 long-term orders can be placed for each of the first 10 blocks and then a new long-term order would be rate limited on the 11th block since the limit of 20 long-term orders over the past 100 blocks would apply. ### Indexer Rate Limits Indexer calls are rate limited per IP at a 100 requests / 10 sec rate. For websocket from Indexer, the following channel limits per connection are in place: ```bash V4_ACCOUNTS_CHANNEL_LIMIT: 256 V4_PARENT_ACCOUNTS_CHANNEL_LIMIT: 256 V4_CANDLES_CHANNEL_LIMIT: 32 V4_MARKETS_CHANNEL_LIMIT: 32 V4_ORDERBOOK_CHANNEL_LIMIT: 32 V4_TRADES_CHANNEL_LIMIT: 32 ``` ## Withdrawal Limits In an effort to reduce risk across the protocol, withdrawals can be rate limited and gated in specific circumstances.​ ### Withdrawal rate limits As a default setting, withdrawals of Noble USDC are rate limited to max(1% of TVL, $1mm) per hour As a default setting, withdrawals of Noble USDC are rate limited to max(10% of TVL, $10mm) per day These rate limit parameters can be updated by governance. ### Withdrawal gating All subaccount transfers and withdrawals will be gated for 50 blocks if a negative collateralized subaccount is seen in state and/or can't be liquidated or deleveraged All subaccount transfers and withdrawals will also be gated for 50 blocks if a 5+ minute chain outage occurs. ## Rewards, Fees and Parameters ### Rewards There are several reward mechanisms available with the protocol software. ![Rewards Overview](/rewards_overview.png) | | Target Users | Rewards paid in | Claim Process | Frequency | | --------------- | -------------------- | -------------------- | ------------- | ----------------------- | | Staking Rewards | Validators & Stakers | USDC & NATIVE\_TOKEN | Manual | Per Block | | Trading Rewards | Traders | NATIVE\_TOKEN | Automatic | Per Block (with trades) | ### Staking Rewards * Rewards distributed to `Validators` and `Stakers` (= Delegators) * `Staking Rewards = Trading Fees + Gas Fees - Community Tax - Validator Commission` * Distributed automatically every block * Must be claimed manually See more on [Staking Rewards](/concepts/trading/rewards/staking-rewards). ### Trading Rewards (Note that C factor has been set to 0) * Rewards distributed to `Traders` after each successful trade * Based on a specified `formula` with several inputs * Distributed automatically every block with successful trades * Claimed automatically :::note Note that traders now earn 50% trading rebates directly after each trade as trading rewards via C factor have been set to 0 via governance. ::: See more on [Trading Rewards](/concepts/trading/rewards/trading-rewards). ### Fees #### Fee tiers :::note The fee schedule is subject to adjustments by the applicable Governance Community. Refer to forum [here](https://dydx.forum/) for the latest updates on reward schedule changes per season and the dydx [rewards leaderboard](https://dydx.trade/DYDX) for estimated rewards. ::: The basic structure for fees have been developed to reflect the following characteristics: 1. Fees differ based on side (maker/taker) 2. Users are eligible for lower fees based on their 30 day trading volume across sub accounts and markets 3. Fees are uniform across all markets Refer to the dydx frontend [fees page](https://dydx.trade/portfolio/fees) for the latest schedule | Tier | 30d Trailing Volume | Taker (bps) | Maker (bps) | | ---- | ------------------- | ----------- | ----------- | | 1 | \< $1M | 5.0 | 1.0 | | 2 | ≥ $1M | 4.5 | 1.0 | | 3 | ≥ $5M | 4.0 | 0.5 | | 4 | ≥ $25M | 3.5 | 0 | | 5 | ≥ $50M | 3.0 | 0 | | 6 | ≥ $100M | 2.5 | -0.7 | | 7 | ≥ $200M | 2.5 | -1.1 | #### Staking tier * Fee discounts are based on fee tier and the amount of staked DYDX * Discounts apply only to net positive trading fees * Only staked tokens (status: bonded) count towards a given trader's staking fee discount. Unstaked DYDX in the unbonding period does not qualify. * Staking discounts do not apply to maker rebates (negative fees) * The dYdX community [voted](https://www.mintscan.io/dydx/proposals/310) to set the staking fee discounts below. Staking fee discounts can be changed at any time with a dYdX governance proposal. When staking thresholds are not defined by the dYdX community for a particular tier, the staking discount defaults to 0% | Tier | Discount 1 | | Discount 2 | | | ---- | -------------- | -------- | -------------- | -------- | | | Number of dYdX | Discount | Number of dYdX | Discount | | 1 | 3,000 | 25% | 20,000 | 50% | | 2 | 20,000 | 20% | 80,000 | 45% | | 3 | 80,000 | 20% | 200,000 | 40% | | 4 | 200,000 | 15% | 800,000 | 30% | | 5-7 | 800,000 | 5% | 5,000,000 | 10% | Refer to the dydx frontend [fees page](https://dydx.trade/portfolio/fees) for the latest schedule ### Parameters *Below is a summary of various notable parameters and what they mean for any chain utilizing the open source software. Parameters will be subject to adjustments by the applicable Governance Community and can be set to different values at Genesis by any deployer.* **Bank Parameters** This parameter establishes whether transfers for any tokens are enabled at Genesis. Transfers will be enabled. **State Parameters** The open source software will not pre-populate any bank-state on the network. Validators who participate in Genesis have the ability to determine the network’s initialized state. **Slashing Parameters** These parameters establish punishments for detrimental behavior by validators. | | Signed Blocks Window | Min Signed Per Window | Downtime Jail Duration | Slash Fraction Doublesign | Slash Fraction Downtime | | --------------- | -------------------- | --------------------- | ---------------------- | ------------------------- | ----------------------- | | Slashing Params | 8192 (-3 hrs) | 20% | 7200s | 0% | 0% | *SignedBlocksWindow*: Together with MinSignedPerWindow, specifies the number of blocks a validator must sign within a sliding window. Failure to maintain MinSignedPerWindow leads to validator being jailed (removed from active validator set). *SlashFractionDownTime*: Defines the slashing-penalty for downtime *DownTimeJailDuration*: How long before the validator can unjail themselves after being jailed for downtime. Double-signing by a validator is considered a severe violation as it can cause instability and unpredictability in the network. When a validator double-signs, they are slashed for SlashFractionDoubleSign, jailed (removed from validator set) and tombstoned (cannot rejoin validator set). **Distribution Parameters** These parameters handle the distribution of gas and trading fees generated by the network to validators. | | Community Tax | WithdrawAddrEnable | | ------------------- | ------------- | ------------------ | | Distribution Params | 0% | True | *CommunityTax*: Fraction of fees that goes to the community treasury. The software will initially reflect a 0% community tax. *WithdrawAddrEnabled*: Whether a delegator can set a different withdrawal address (other than their delegator address) for their rewards. **Staking Parameters** These parameters define how staking works on the protocol and norms around staking. \*MaxValidators and UnbondingTime are particularly subject to change based on public testnet data and feedback. | | BondDenom | MaxValidators | MinCommissionRate | Unbonding Time | | --------------- | --------------------------------- | ------------- | ----------------- | -------------- | | Slashing Params | Decided at Genesis, by validators | 60 | 5% | 30 days | *MaxValidators*: Every block, the top MaxValidators validators by stake weight are included in the active validator set. *UnbondingTime*: Specifies the duration of the unbonding process, during which tokens are in a locked state and cannot be transferred or delegated (the tokens are still “at stake”). *MinCommissionRate*: The chain-wide minimum commission rate that a validator can charge their delegators. The default commission rate will be 100%. **Governance Parameters** These parameters define how governance proposals can be submitted and executed. For more information on the governance module and its associated parameters, head to the official [Cosmos SDK docs](https://docs.cosmos.network/sdk/v0.47/build/modules/gov#parameters). | | Min Deposit | MinInitialDepositRatio | Max Deposit Period | Voting Period | Quorum | Threshold | Veto | | ---------- | ----------------------- | ---------------------- | ------------------ | ------------- | ------ | --------- | ----- | | Gov Params | 10,000 governance token | 20% | 1 Days | 4 Days | 33.4% | 50% | 33.4% | ## Staking Rewards Staking rewards are designed to reward `Validators` and `Stakers` (=Delegators). The sources of staking rewards are trading fees and gas fees collected by the protocol. The protocol uses the [CosmosSDK’s x/distribution module](https://docs.cosmos.network/sdk/v0.50/build/modules/distribution) to allocate the accrued trading and gas fees to `Validators` and `Stakers`. ![Staking Rewards](/staking_rewards.png) All trading fees (`USDC`) and gas fees (`USDC` and `NATIVE_TOKEN`) collected by the protocol are accrued and distributed within a block. Specifically — for each block, the fees generated are collected in `fee_collector` module account and then sent to the `distribution` module account in the following block. Then, the `community_tax` and `validator_commission` are subtracted from the collected pool and the resulting amount will be distributed to `Validators` and `Stakers` in accordance with their staked token amount. > 💡 Note that `Stakers` must claim the rewards manually. Unclaimed rewards will remain in the distribution module account until they are claimed. ### Details ``` Staking Rewards = fee pool * (# of delegator's staked tokens / total # of staked tokens) * (1 - community tax rate) * (1 - validator commission rate) ``` The details of how the Staking Rewards are calculated can be found in the [CosmosSDK’s x/distribution documentation](https://docs.cosmos.network/sdk/v0.50/build/modules/distribution#the-distribution-scheme). ### Parameters > 💡 The current configuration and parameters can be found by querying the network. * `x/distribution: community_tax` : specifies the proportion of fee pool that should be sent to `community_treasury` before staking rewards are distributed. This value can be configured via gov. * `x/staking: validator_commission` : specifies the proportion of the staking rewards that a given validator will take from delegator’s reward. This is configured per validator and can be updated by the validator. See [CosmosSDK doc](https://docs.cosmos.network/sdk/v0.50/build/modules/distribution#params) for details. ## Trading Rewards :::note Trading rewards are subject to adjustments by the applicable Governance Community. Note that the community has voted to set the C parameter to 0 ::: Trading rewards are designed to incentivize `Traders` to trade on the protocol. The source of trading rewards is a configured `Rewards Treasury` account. For each successful trade, `Traders` will be rewarded in NATIVE\_TOKEN dYdX based on the formula outlined in the below section. Trading rewards are distributed automatically and directly to the trader’s account per block. ### Motivation **The primary goal behind trading rewards is to incentivize trading on the protocol.** To facilitate fair trading behaviors and to preserve the protocol’s safety, trading rewards have the following secondary goals: * Self-trading should not be profitable * Any distributed rewards should be proportional to fees paid to the protocol * Trading rewards should be deterministic * Trading rewards should be settled and distributed every block * Trading rewards should limit the protocol overspending on trading activity ### Details ![Trading Rewards](/trading_rewards.png) #### Reward Treasury The amount of tokens available to be distributed to traders is tracked by the protocol’s configured Rewards Treasury account. Call the size of this Rewards Treasury `T`. Each block, new tokens are transferred into this `T` from the vesting account and rewards are then distributed from `T`. Each block, `T` can grow or shrink based on protocol activity. :::note Definitions and notes * The current configuration and parameters can be found by querying the network. * `treasury_account`: referred to as `T` in the above. specifies which account the trading rewards come from. Can be configured via gov. * `VestEntry` : specifies a vesting schedule from `vester_account` to `treasury_account` for a given `denom` token. The vesting happens linearly from `start_time` and `end_time`. * `denom`: specifies the token that the trading rewards should use. Can be configured via gov. * `fee_multiplier_ppm`: referred to as `C` in the above. Specifies the proportion (in ppm) that fees should be multiplied by to get the maximum rewards amount. Can be configured via gov. ::: #### Formula & Emission We define a trader Total rewards as: ``` Total trading rewards = trading reward from taker fee + trading reward from maker fee ``` Components of the Trading Rewards formula: 1. `Taker and Maker Volume` 2. `Fee Rate` 3. `Maker Rebates` 4. `Affiliate Fee Share` 5. `Protocol Revenue Sharing` 6. `C Constant` ##### 1. Trading Rewards for Takers For a taker, rewards in a given block are: ``` trading reward from taker fee = taker volume * (taker fee rate - max maker rebate - max possible affiliate taker fee share) * C * (1 - protocol revenue share rate) ``` where `max possible affiliate taker fee share` will be: * If the taker 30d rolling volume is \< = $50M, 50% \* taker fee rate regardless of whether the taker is referred or who they are referred by. * If the taker 30d rolling volume is > $50M, 0 since the taker doesn’t generate affiliate revenue share. `Taker Fee Revenue Share` is MegaVault revenue share + Treasury subDAO revenue share + Market Mapper revenue share. You can read more about the revenue share in the governance proposals on Megavault, Treasury subDAO and Market Mapper. ##### 2. Trading Rewards for Makers Similarly, maker rewards for a user are calculated as: ``` trading reward from maker fee = maker volume * (positive maker fees) * C * (1 - protocol revenue share rate) ``` #### Example Below is an example using the given formula to calculate the trading reward based on specific inputs for taker volume, fee rates, and other parameters. In this example, we assume the following values for a trader: * `Taker Volume`: totaling $1M in trailing volume in 30 days. * `Taker Fee Rate`: taker fee rate of 0.04% assuming they have been referred by a VIP Affiliate, starting at fee tier 3. * `Max Maker Rebate`: maximum rebate available for makers is 0.011%. * `Max Possible Affiliate Taker Fee Share`: as the trader is referred by a VIP affiliate, entitling the VIP affiliate to 50% of the trader’s taker fee rate. * `Taker Fee Revenue Share`: the taker fee revenue share is 60% or 0.6 including 50% to MegaVault and 10% to Treasury subDAO, as well as 0% to market mapper for a given market. * `C`: A constant multiplier that is currently set to 0.5 by the dYdX governance, but subject to change through dYdX governance. :::note Note that the community has voted to set the C parameter to 0 ::: ``` Trading Reward from Taker Fee = 1,000,000 * (0.0004 − 0.00011 − 0.0002) * 0.4 * (1−0.6) Trading Reward from Taker Fee = 1,000,000 * 0.00009 * 0.4 * 0.4 = 18 ``` For this example, the Trading Reward would be equivalent to $18 in DYDX for an approximate Taker Fee of $400 paid by the user. Note that however since the C factor has been reduced to 0, traders now get rebates directly instead of C constant trading rewards ### FAQ > How do trading rewards work from a user perspective? Traders are rewarded after each successful trade made on the protocol. Immediately after each fill, a user is sent a certain amount of trading rewards directly to their dYdX Chain address, based on the formulas described below. Prior to each trade, the UI also shows the maximum amount of rewards a trade of that size could receive. Users earn trading rewards up to, but not exceeding, 90% of a fill’s net-trading-fees, paid in the governance token of the network. > How do trading rewards affect potential inflation of the governance token? Trading rewards distributed by the protocol, each block, are capped at the dollar equivalent of the total net trading fees generated by the protocol that block. Thus, trading rewards distributed can fluctuate on a block by block basis. This can result in a large amount of “savings” by the protocol (via reduced inflation) by not overspending to incentivize trading activity. import { Button } from 'vocs/components' import Details from '../../../../components/Details'; #### Get Asset Positions Retrieves asset positions and respective details of a specific subaccount. ##### Method Declaration :::code-group ```python [Python] async def get_subaccount_asset_positions( self, address: str, subaccount_number: int, status: Optional[PositionStatus] = None, limit: Optional[int] = None, created_before_or_at_height: Optional[int] = None, created_before_or_at: Optional[str] = None, ) -> Any ``` ```typescript [TypeScript] async getSubaccountAssetPositions( address: string, subaccountNumber: number, status?: PositionStatus | null, limit?: number | null, createdBeforeOrAtHeight?: number | null, createdBeforeOrAt?: string | null, ): Promise ``` ```rust [Rust] pub async fn get_subaccount_asset_positions( &self, subaccount: &Subaccount, ) -> Result, Error> ``` ```url [API] /v4/assetPositions ``` :::
* Rename all methods to `get_asset_positions` - shorter is better. * Add a `Subaccount` pair to Python and JavaScript, since it's always a pair * Add options to the Rust version * Rename `created_before_or_at_time` parameter to `created_before_or_at` * Rename `PerpetualPositionStatus` to `PositionStatus`
##### Parameters | Parameter | Location | Type | Required | Description | | ------------------------- | -------- | ------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the account. | | `subaccountNumber` | query | [SubaccountNumber] | true | The identifier for the specific subaccount within the wallet address. | | `status` | query | [PerpetualPositionStatus] | false | Filter to retrieve positions with a specific status. If not provided, all positions will be returned regardless of status. | | `limit` | query | [u32] | false | Maximum number of asset positions to return in the response. | | `createdBeforeOrAtHeight` | query | [Height] | false | Restricts results to positions created at or before a specific blockchain height. | | `createdBeforeOrAt` | query | [DateTime] | false | Restricts results to positions created at or before a specific timestamp (ISO 8601 format). | ##### Response A data structure containing the requested asset positions. Typically includes details such as asset ID, size, side (buy/sell), entry price, realized PnL, and other position-specific information. | Status | Meaning | Schema | Description | | ------ | ------- | ------------------------------- | ------------------------- | | `200` | [OK] | [AssetPositionResponseObject] ⛁ | The asset positions data. | [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [OK]: /types/ok [AssetPositionResponseObject]: /types/asset_position_response_object [PerpetualPositionStatus]: /types/perpetual_position_status [u32]: /types/u32 [Height]: /types/height [DateTime]: /types/date_time import { Button } from 'vocs/components' import Details from '../../../../components/Details'; #### Get Fills Retrieves fill records for a specific subaccount on the exchange. A fill represents a trade that has been executed. ##### Method Declaration :::code-group ```python [Python] async def get_subaccount_fills( self, address: str, subaccount_number: int, ticker: Optional[str] = None, ticker_type: TickerType = TickerType.PERPETUAL, limit: Optional[int] = None, created_before_or_at_height: Optional[int] = None, created_before_or_at: Optional[str] = None, ) -> Any ``` ```typescript [TypeScript] async getSubaccountFills( address: string, subaccountNumber: number, ticker?: string | null, tickerType: TickerType = TickerType.PERPETUAL, limit?: number | null, createdBeforeOrAtHeight?: number | null, createdBeforeOrAt?: string | null, page?: number | null, ): Promise ``` ```rust [Rust] pub async fn get_subaccount_fills( &self, subaccount: &Subaccount, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/fills ``` :::
* Rename all methods to `get_fills` - shorter is better. * Add a `Subaccount` pair to Python and JavaScript, since it's always a pair * Rename `created_before_or_at_time` parameter to `created_before_or_at` * `page` optional parameter is missing in Python * `page` optional parameter is missing in Rust * In Rust `market` field of the options struct must be `ticker` * In Rust `market_type` field of the options struct must be `ticker_type`
##### Parameters | Parameter | Location | Type | Required | Description | | ------------------------- | -------- | ------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the account. | | `subaccountNumber` | query | [SubaccountNumber] | true | The identifier for the specific subaccount within the wallet address. | | `market` | query | [Ticker] | false | The market symbol to filter fills by (e.g., "BTC-USD"). If not provided, fills for all markets will be returned. (Python/TS SDK parameter: `ticker`) | | `marketType` | query | [MarketType] | false | The type of market to filter by. (Python/TS SDK parameter: `ticker_type` / `tickerType`) | | `limit` | query | [u32] | false | Maximum number of asset positions to return in the response. | | `createdBeforeOrAtHeight` | query | [Height] | false | Filters results to positions created at or before a specific blockchain height. | | `createdBeforeOrAt` | query | [DateTime] | false | Filters results to positions created at or before a specific timestamp (ISO 8601 format). | | `page` | query | [u32] | false | The page number for paginated results. | ##### Response A promise that resolves to fill data containing details such as order ID, market, side (buy/sell), size, price, execution time, and other fill-specific information. | Status | Meaning | Schema | Description | | ------ | ------- | ---------------------- | --------------- | | `200` | [OK] | [FillResponseObject] ⛁ | The fills data. | Examples: [Guide - Get Fills] [Guide - Get Fills]: ../../interaction/data/market#get-fills [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [OK]: /types/ok [Ticker]: /types/ticker [MarketType]: /types/market_type [u32]: /types/u32 [Height]: /types/height [DateTime]: /types/date_time [FillResponseObject]: /types/fill_response_object import { Button } from 'vocs/components' import Details from '../../../../components/Details'; #### Get Funding Payments Retrieves funding payment history for a specific subaccount. Funding payments are periodic settlements that occur between long and short positions based on the funding rate. ##### Method Declaration :::code-group ```python [Python] async def get_funding_payments( self, address: str, subaccount_number: int, limit: Optional[int] = None, ticker: Optional[str] = None, after_or_at: Optional[str] = None, page: Optional[int] = None, ) -> Any ``` ```typescript [TypeScript] async getFundingPayments( address: string, subaccountNumber: number, limit?: number | null, ticker?: string | null, afterOrAt?: string | null, page?: number | null, ): Promise ``` ```rust [Rust] pub async fn get_funding_payments( &self, subaccount: &Subaccount, opts: Option, ) -> Result, Error> ``` ```bash [cURL] curl -X GET "https://indexer.v4testnet.dydx.exchange/v4/fundingPayments?address=dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art&subaccountNumber=0" \ -H "Accept: application/json" ``` ```url [API] /v4/fundingPayments ``` :::
* Rename all methods to `get_funding_payments` - shorter is better. * Add a `Subaccount` pair to Python and JavaScript, since it's always a pair * `page` optional parameter is missing in Python * `page` optional parameter is missing in Rust
##### Parameters | Parameter | Location | Type | Required | Description | | ------------------ | -------- | ------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------ | | `address` | query | [Address] | true | The wallet address that owns the account. | | `subaccountNumber` | query | [SubaccountNumber] | true | The identifier for the specific subaccount within the wallet address. | | `limit` | query | [u32] | false | Maximum number of funding payments to return in the response. | | `ticker` | query | [Ticker] | false | The market symbol to filter funding payments by (e.g., "BTC-USD"). If not provided, payments for all markets will be returned. | | `afterOrAt` | query | [DateTime] | false | Filters results to funding payments created at or after a specific timestamp (ISO 8601 format). | | `page` | query | [u32] | false | The page number for paginated results. | ##### Response A promise that resolves to funding payment data containing details such as payment amount, funding rate, position size, market ticker, and timestamp information. | Status | Meaning | Schema | Description | | ------ | ------- | --------------------------------- | -------------------------- | | `200` | [OK] | [FundingPaymentsResponseObject] ⛁ | The funding payments data. | [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [OK]: /types/ok [u32]: /types/u32 [Ticker]: /types/ticker [DateTime]: /types/date_time [FundingPaymentsResponseObject]: /types/funding_payments_response_object import { Button } from 'vocs/components' import Details from '../../../../components/Details'; #### Get Funding Payments for Parent Subaccount Retrieves funding payment history for all subaccounts under a parent subaccount. This endpoint aggregates funding payments across all child subaccounts of a given parent subaccount. ##### Method Declaration :::code-group ```python [Python] async def get_funding_payments_for_parent_subaccount( self, address: str, parent_subaccount_number: int, limit: Optional[int] = None, after_or_at: Optional[str] = None, page: Optional[int] = None, ) -> Any ``` ```typescript [TypeScript] async getFundingPaymentsForParentSubaccount( address: string, parentSubaccountNumber: number, limit?: number | null, afterOrAt?: string | null, page?: number | null, ): Promise ``` ```rust [Rust] pub async fn get_funding_payments_for_parent_subaccount( &self, parent_subaccount: &ParentSubaccount, opts: Option, ) -> Result, Error> ``` ```bash [cURL] curl -X GET "https://indexer.v4testnet.dydx.exchange/v4/fundingPayments/parentSubaccount?address=dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art&parentSubaccountNumber=0" \ -H "Accept: application/json" ``` ```url [API] /v4/fundingPayments/parentSubaccount ``` :::
* Rename all methods to `get_funding_payments_for_parent_subaccount` - shorter is better. * Add a `ParentSubaccount` pair to Python and JavaScript, since it's always a pair * `page` optional parameter is missing in Python * `page` optional parameter is missing in Rust
##### Parameters | Parameter | Location | Type | Required | Description | | ------------------------ | -------- | ------------------ | -------- | ----------------------------------------------------------------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the account. | | `parentSubaccountNumber` | query | [SubaccountNumber] | true | The identifier for the parent subaccount within the wallet address. | | `limit` | query | [u32] | false | Maximum number of funding payments to return in the response. | | `afterOrAt` | query | [DateTime] | false | Filters results to funding payments created at or after a specific timestamp (ISO 8601 format). | | `page` | query | [u32] | false | The page number for paginated results. | ##### Response A promise that resolves to funding payment data containing details such as payment amount, funding rate, position size, market ticker, and timestamp information aggregated across all child subaccounts. | Status | Meaning | Schema | Description | | ------ | ------- | --------------------------------- | -------------------------- | | `200` | [OK] | [FundingPaymentsResponseObject] ⛁ | The funding payments data. | [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [OK]: /types/ok [u32]: /types/u32 [DateTime]: /types/date_time [FundingPaymentsResponseObject]: /types/funding_payments_response_object import { Button } from 'vocs/components' import Details from '../../../../components/Details'; #### Get Historical PNL Retrieves historical profit and loss (PNL) data for a specific subaccount on the exchange. These records provide insights into the trading performance over time. ##### Method Declaration :::code-group ```python [Python] async def get_subaccount_historical_pnls( self, address: str, subaccount_number: int, effective_before_or_at: Optional[str] = None, effective_at_or_after: Optional[str] = None, ) -> Any ``` ```typescript [TypeScript] async getSubaccountHistoricalPNLs( address: string, subaccountNumber: number, createdBeforeOrAtHeight?: number | null, createdBeforeOrAt?: string | null, createdOnOrAfterHeight?: number | null, createdOnOrAfter?: string | null, limit?: number | null, page?: number | null, ): Promise ``` ```rust [Rust] pub async fn get_subaccount_historical_pnls( &self, subaccount: &Subaccount, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/historical-pnl ``` :::
* Parameter `created_on_or_after_height` is missing * Parameter `created_on_or_after` is missing
##### Parameters | Parameter | Location | Type | Required | Description | | ------------------------- | -------- | ------------------ | -------- | ----------------------------------------------------------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the account. | | `subaccount_number` | query | [SubaccountNumber] | true | The identifier for the specific subaccount within the wallet address. | | `limit` | query | [u32] | false | Maximum number of asset positions to return in the response. | | `createdBeforeOrAtHeight` | query | [Height] | false | Filters results to positions created at or before a specific blockchain height. | | `createdBeforeOrAt` | query | [DateTime in UTC] | false | Filters results to positions created at or before a specific timestamp (ISO 8601 format). | | `createdOnOrAfterHeight` | query | [Height] | false | Filters results to positions created on or after a specific blockchain height. | | `createdOnOrAfter` | query | [DateTime in UTC] | false | Filters results to positions created on or after a specific timestamp (ISO 8601 format). | | `page` | query | [u32] | false | The page number for paginated results. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------------------------- | ------------------------------------- | | `200` | [OK] | [PnlTicksResponseObject] ⛁ | The historical PnLs data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [OK]: /types/ok [PnlTicksResponseObject]: /types/pnl_ticks_response_object [u32]: /types/u32 [Height]: /types/height [DateTime in UTC]: /types/date_time [Bad Request]: /types/bad-request [Not Found]: /types/not-found import { Button } from 'vocs/components' #### Get Order Retrieves detailed information about a specific order based on its unique identifier (the order ID). To get the order ID, see how to [create](/interaction/trading#identifying-the-order) it or fetch the [order history](/indexer-client/http#list-orders). ##### Method Declaration :::code-group ```python [Python] async def get_order( self, order_id: str, ) -> Any ``` ```typescript [TypeScript] async getOrder( orderId: string, ): Promise ``` ```rust [Rust] pub async fn get_order( &self, order_id: &OrderId, ) -> Result ``` ```url [API] /v4/orders/{order_id} ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | --------- | -------- | ------------- | | `orderId` | path | [OrderId] | true | The order ID. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ----------------------- | ------------------------------------- | | `200` | [OK] | [OrderResponseObject] ⛁ | The order data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The order was not found. | [OK]: /types/ok [OrderId]: /types/order_id [OrderResponseObject]: /types/order_response_object [Bad Request]: /types/bad-request [Not Found]: /types/not-found import { Button } from 'vocs/components' #### Get Parent Asset Positions Query for asset positions (size, buy/sell etc) for a parent subaccount. ##### Method Declaration :::code-group ```python [Python] async def get_parent_subaccount_asset_positions( self, address: str, subaccount_number: int, status: Optional[PositionStatus] = None, limit: Optional[int] = None, created_before_or_at_height: Optional[int] = None, created_before_or_at: Optional[str] = None, ) -> Any: ``` ```typescript [TypeScript] ``` ```rust [Rust] pub async fn get_parent_asset_positions( &self, subaccount: &ParentSubaccount, ) -> Result, Error> ``` ```url [API] /v4/assetPositions/parentSubaccountNumber ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------------------ | -------- | ------------------ | -------- | ------------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the account. | | `parentSubaccountNumber` | query | [SubaccountNumber] | true | The parent subaccount number of this wallet | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------------------------------- | ------------------------------------- | | `200` | [OK] | [AssetPositionResponseObject] ⛁ | The asset positions data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The parent subaccount was not found. | [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [AssetPositionResponseObject]: /types/asset_position_response_object [OK]: /types/ok [Bad Request]: /types/bad-request [Not Found]: /types/not-found import { Button } from 'vocs/components' #### Get Parent Fills Query for fills (i.e. filled orders data) for a parent subaccount. ##### Method Declaration :::code-group ```python [Python] async def get_parent_fills( self, address: str, subaccount_number: int, limit: Optional[int] = None, market: Optional[str] = None, market_type: Optional[TickerType] = None, created_before_or_at_height: Optional[int] = None, created_before_or_at: Optional[str] = None, ) -> Any: ``` ```typescript [TypeScript] async getParentSubaccountNumberTransfers( address: string, parentSubaccountNumber: number, limit?: number | null, createdBeforeOrAtHeight?: number | null, createdBeforeOrAt?: string | null, page?: number | null, ): Promise ``` ```rust [Rust] pub async fn get_parent_subaccount_number_fills( &self, subaccount: &ParentSubaccount, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/fills/parentSubaccountNumber ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------------------- | -------- | ------------------ | -------- | ------------------------------------------------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the parent subaccount. | | `parentSubaccountNumber` | query | [SubaccountNumber] | true | The identifier for the specific subaccount within the wallet address. | | `limit` | query | [u32] | false | Maximum number of asset positions to return in the response. | | `createdBeforeOrAtHeight` | query | [Height] | false | Filters results to positions created at or before a specific blockchain height. | | `market` | query | [Ticker] | false | Market id like USD-BTC, ETH-USD | | `marketType` | query | [MarketType] | false | Market type of filled order Data | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ---------------------- | ------------------------------------- | | `200` | [OK] | [FillResponseObject] ⛁ | The fills data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The parent subaccount was not found. | [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [u32]: /types/u32 [Height]: /types/height [Ticker]: /types/ticker [MarketType]: /types/market_type [FillResponseObject]: /types/fill_response_object [OK]: /types/ok [Bad Request]: /types/bad-request [Not Found]: /types/not-found import { Button } from 'vocs/components' #### Get Parent Historical Pnl Query for profit and loss report for the specified time/block range of a parent subaccount. ##### Method Declaration :::code-group ```python [Python] async def get_parent_historical_pnls( self, address: str, subaccount_number: int, limit: Optional[int] = None, created_before_or_at_height: Optional[int] = None, created_before_or_at: Optional[str] = None, created_on_or_after_height: Optional[int] = None, created_on_or_after: Optional[str] = None, ) -> Any: ``` ```typescript [TypeScript] async getParentSubaccountNumberHistoricalPNLs( address: string, parentSubaccountNumber: number, createdBeforeOrAtHeight?: number | null, createdBeforeOrAt?: string | null, createdOnOrAfterHeight?: number | null, createdOnOrAfter?: string | null, limit?: number | null, page?: number | null, ): Promise ``` ```rust [Rust] pub async fn get_parent_subaccount_number_historical_pnls( &self, subaccount: &ParentSubaccount, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/historical-pnl/parentSubaccountNumber ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------------------- | -------- | ------------------ | -------- | ------------------------------------------------------------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the parent subaccount. | | `parentSubaccountNumber` | query | [SubaccountNumber] | true | The identifier for the specific subaccount within the wallet address. | | `limit` | query | [u32] | false | Maximum number of asset positions to return in the response. | | `createdBeforeOrAtHeight` | query | [Height] | false | Restricts results to positions created at or before a specific blockchain height. | | `createdBeforeOrAt` | query | [DateTime in UTC] | false | Restricts results to positions created at or before a specific timestamp (ISO 8601 format). | | `createdOnOrAfterHeight` | query | [Height] | false | Restricts results to positions created on or after a specific blockchain height. | | `createdOnOrAfter` | query | [DateTime in UTC] | false | Restricts results to positions created on or after a specific timestamp (ISO 8601 format). | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------------------------- | ------------------------------------- | | `200` | [OK] | [PnlTicksResponseObject] ⛁ | The historical PnLs data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The parent subaccount was not found. | [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [u32]: /types/u32 [Height]: /types/height [DateTime in UTC]: /types/date_time [PnlTicksResponseObject]: /types/pnl_ticks_response_object [Bad Request]: /types/bad-request [Not Found]: /types/not-found [OK]: /types/ok import { Button } from 'vocs/components' #### Get Parent Subaccount Query for the parent subaccount, its child subaccounts, equity, collateral and margin. See more information on parent subaccounts [here](/concepts/trading/isolated-positions#parent-subaccounts). e.g. parent subaccount 0 has child subaccounts 128, 256,... ##### Method Declaration :::code-group ```python [Python] async def get_parent_subaccount( self, address: str, subaccount_number: int, ) -> Any ``` ```typescript [TypeScript] async getParentSubaccount(address: string, parentSubaccountNumber: number): Promise ``` ```rust [Rust] pub async fn get_parent_subaccount( &self, subaccount: &ParentSubaccount, ) -> Result ``` ```url [API] /v4/addresses/{address}/parentSubaccountNumber/{number} ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | ------------------ | -------- | --------------------------------------------------------------------- | | `address` | path | [Address] | true | The wallet address that owns the parent subaccount. | | `number` | path | [SubaccountNumber] | true | The identifier for the specific subaccount within the wallet address. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ---------------------------------- | ------------------------------------- | | `200` | [OK] | [ParentSubaccountResponseObject] ⛁ | The parent subaccount data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The parent subaccount was not found. | [Address]: /types/address [ParentSubaccountResponseObject]: /types/parent_subaccount_response_object [SubaccountNumber]: /types/subaccount_number [Bad Request]: /types/bad-request [Not Found]: /types/not-found [OK]: /types/ok #### Get Parent Subaccount Number Fills TODO ##### Method Declaration :::code-group ```python [Python] async def get_parent_fills( self, address: str, subaccount_number: int, limit: Optional[int] = None, market: Optional[str] = None, market_type: Optional[TickerType] = None, created_before_or_at_height: Optional[int] = None, created_before_or_at: Optional[str] = None, ) -> Any ``` ```typescript [TypeScript] async getParentSubaccountNumberFills( address: string, parentSubaccountNumber: number, ticker?: string | null, tickerType: TickerType = TickerType.PERPETUAL, limit?: number | null, createdBeforeOrAtHeight?: number | null, createdBeforeOrAt?: string | null, page?: number | null, ): Promise ``` ```rust [Rust] pub async fn get_parent_subaccount_number_fills( &self, subaccount: &ParentSubaccount, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/fills/parentSubaccountNumber ``` ::: ##### Parameters #### Get Parent Subaccount Number Orders TODO ##### Method Declaration :::code-group ```python [Python] async def list_parent_orders( self, address: str, subaccount_number: int, limit: Optional[int] = None, ticker: Optional[str] = None, side: Optional[OrderSide] = None, status: Optional[OrderStatus] = None, order_type: Optional[OrderType] = None, good_til_block_before_or_at: Optional[int] = None, good_til_block_time_before_or_at: Optional[str] = None, return_latest_orders: Optional[bool] = None, ) -> Any ``` ```typescript [TypeScript] async getParentSubaccountNumberOrders( address: string, parentSubaccountNumber: number, ticker?: string | null, side?: OrderSide | null, status?: OrderStatus | null, type?: OrderType | null, limit?: number | null, goodTilBlockBeforeOrAt?: number | null, goodTilBlockTimeBeforeOrAt?: string | null, returnLatestOrders?: boolean | null, ): Promise ``` ```rust [Rust] pub async fn get_parent_subaccount_number_orders( &self, subaccount: &ParentSubaccount, opts: Option, ) -> Result ``` ```url [API] /v4/orders/parentSubaccountNumber ``` ::: ##### Parameters #### Get Parent Subaccount Number Transfers TODO ##### Method Declaration :::code-group ```python [Python] async def get_parent_transfers( self, address: str, subaccount_number: int, limit: Optional[int] = None, created_before_or_at_height: Optional[int] = None, created_before_or_at: Optional[str] = None, ) -> Any ``` ```typescript [TypeScript] async getParentSubaccountNumberTransfers( address: string, parentSubaccountNumber: number, limit?: number | null, createdBeforeOrAtHeight?: number | null, createdBeforeOrAt?: string | null, page?: number | null, ): Promise ``` ```rust [Rust] pub async fn get_parent_subaccount_number_transfers( &self, subaccount: &ParentSubaccount, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/transfers/parentSubaccountNumber ``` ::: ##### Parameters import { Button } from 'vocs/components' #### Get Parent Transfers Query for transfers between subaccounts associated with a parent subaccount. ##### Method Declaration :::code-group ```python [Python] async def get_parent_transfers( self, address: str, subaccount_number: int, limit: Optional[int] = None, created_before_or_at_height: Optional[int] = None, created_before_or_at: Optional[str] = None, ) -> Any ``` ```typescript [TypeScript] async getParentSubaccountNumberTransfers( address: string, parentSubaccountNumber: number, limit?: number | null, createdBeforeOrAtHeight?: number | null, createdBeforeOrAt?: string | null, page?: number | null, ): Promise ``` ```rust [Rust] pub async fn get_parent_subaccount_number_transfers( &self, subaccount: &ParentSubaccount, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/transfers/parentSubaccountNumber ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------------------- | -------- | ------------------ | -------- | ------------------------------------------------------------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the parent subaccount. | | `parentSubaccountNumber` | query | [SubaccountNumber] | true | The identifier for the specific subaccount within the wallet address. | | `limit` | query | [u32] | false | Maximum number of asset positions to return in the response. | | `createdBeforeOrAtHeight` | query | [Height] | false | Restricts results to positions created at or before a specific blockchain height. | | `createdBeforeOrAt` | query | [DateTime in UTC] | false | Restricts results to positions created at or before a specific timestamp (ISO 8601 format). | ##### Response | Status | Meaning | Schema | | ------ | ------- | -------------------------- | | `200` | [OK] | [TransferResponseObject] ⛁ | [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [u32]: /types/u32 [Height]: /types/height [DateTime in UTC]: /types/date_time [OK]: /types/ok [TransferResponseObject]: /types/transfer_response_object import { Button } from 'vocs/components' #### Get Rewards Retrieves historical block trading rewards for the specified address. ##### Method Declaration :::code-group ```python [Python] async def get_historical_block_trading_rewards( self, address: str, limit: Optional[int] = None, ) -> Any ``` ```typescript [TypeScript] async getHistoricalBlockTradingRewards( address: string, limit?: number, startingBeforeOrAt?: string, startingBeforeOrAtHeight?: string, ): Promise ``` ```rust [Rust] pub async fn get_historical_block_trading_rewards( &self, address: &Address, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/historicalBlockTradingRewards/{address} ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | -------------------------- | -------- | ----------------- | -------- | ------------------------------------------------------------ | | `address` | path | [Address] | true | The wallet address that owns the account. | | `limit` | query | [u32] | false | Maximum number of asset positions to return in the response. | | `startingBeforeOrAtHeight` | query | [Height] | false | The timestamp filter for rewards starting before or at. | | `startingBeforeOrAt` | query | [DateTime in UTC] | false | The block height filter for rewards starting before or at. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------------------------------- | ------------------------------------------ | | `200` | [OK] | [HistoricalBlockTradingReward] ⛁ | The historical block trading rewards data. | | `400` | [Bad Request] | | The request was malformed or invalid. | [OK]: /types/ok [u32]: /types/u32 [Address]: /types/address [HistoricalBlockTradingReward]: /types/historical_block_trading_reward [Height]: /types/height [DateTime in UTC]: /types/date_time [Bad Request]: /types/bad-request import { Button } from 'vocs/components' #### Get Rewards Aggregated Retrieves aggregated historical trading rewards for the specified address. ##### Method Declaration :::code-group ```python [Python] async def get_historical_trading_rewards_aggregated( self, address: str, period: TradingRewardAggregationPeriod = TradingRewardAggregationPeriod.DAILY, limit: Optional[int] = None, starting_before_or_at: Optional[str] = None, starting_before_or_at_height: Optional[int] = None, ) -> Any ``` ```typescript [TypeScript] async getHistoricalTradingRewardsAggregations( address: string, period: TradingRewardAggregationPeriod, limit?: number, startingBeforeOrAt?: string, startingBeforeOrAtHeight?: string, ): Promise ``` ```rust [Rust] pub async fn get_historical_trading_rewards_aggregations( &self, address: &Address, period: TradingRewardAggregationPeriod, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/historicalTradingRewardAggregations/{address} ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | -------------------------- | -------- | -------------------------------- | -------- | ---------------------------------------------------------- | | `address` | path | [Address] | true | The wallet address that owns the account. | | `period` | query | [TradingRewardAggregationPeriod] | true | The aggregation period. | | `limit` | query | [u32] | false | The maximum number of aggregated rewards to retrieve. | | `startingBeforeOrAt` | query | [DateTime] | false | The timestamp filter for rewards starting before or at. | | `startingBeforeOrAtHeight` | query | [Height] | false | The block height filter for rewards starting before or at. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------------------------------------- | ----------------------------------------------- | | `200` | [OK] | [HistoricalTradingRewardAggregation] ⛁ | The aggregated historical trading rewards data. | | `400` | [Bad Request] | | The request was malformed or invalid. | [OK]: /types/ok [u32]: /types/u32 [Address]: /types/address [DateTime]: /types/date_time [Height]: /types/height [TradingRewardAggregationPeriod]: /types/trading_reward_aggregation_period [HistoricalTradingRewardAggregation]: /types/historical_trading_reward_aggregation [Bad Request]: /types/bad-request import { Button } from 'vocs/components' #### Get Subaccount Retrieves a specific subaccount associated with a given address and subaccount number. ##### Method Declaration :::code-group ```python [Python] async def get_subaccount( self, address: str, subaccount_number: int, ) -> Any ``` ```typescript [TypeScript] async getSubaccount( address: string, subaccountNumber: number, ): Promise ``` ```rust [Rust] pub async fn get_subaccount( &self, subaccount: &Subaccount, ) -> Result ``` ```url [API] /v4/addresses/{address}/subaccountNumber/{subaccount_number} ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------------ | -------- | ------------------ | -------- | ---------------------------------------------------- | | `address` | query | [Address] | true | The primary address to which the subaccount belongs. | | `subaccountNumber` | query | [SubaccountNumber] | true | The specific subaccount number to retrieve. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ---------------------------- | ------------------------------------- | | `200` | [OK] | [SubaccountResponseObject] ⛁ | The subaccount data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [OK]: /types/ok [SubaccountResponseObject]: /types/subaccount_response_object [Bad Request]: /types/bad-request [Not Found]: /types/not-found import { Button } from 'vocs/components' import Details from '../../../../components/Details'; #### Get Subaccounts Retrieves a list of subaccounts associated with a given address. Subaccounts are related addresses that fall under the authority or ownership of the primary address. ##### Method Declaration :::code-group ```python [Python] async def get_subaccounts( self, address: str, limit: Optional[int] = None, ) -> Any ``` ```typescript [TypeScript] async getSubaccounts( address: string, limit?: number, ): Promise ``` ```rust [Rust] pub async fn get_subaccounts( &self, address: &Address, ) -> Result ``` ```url [API] /v4/addresses/{address} ``` :::
* Rust implementation doesn't have optional parameters.
##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | --------- | -------- | ----------------------------------------------------------------- | | `address` | path | [Address] | true | The primary address for which to retrieve associated subaccounts. | | `limit` | query | [u32] | false | Maximum number of subaccounts in the response. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ----------------- | ------------------------------------- | | `200` | [OK] | [AddressResponse] | The subaccounts data. | | `400` | [Bad Request] | | The request was malformed or invalid. | [Address]: /types/address [OK]: /types/ok [AddressResponse]: /types/address_response [u32]: /types/u32 [Bad Request]: /types/bad-request import { Button } from 'vocs/components' #### Get Transfers Retrieves the transfer history for a specific subaccount. ##### Method Declaration :::code-group ```python [Python] async def get_subaccount_transfers( self, address: str, subaccount_number: int, limit: Optional[int] = None, created_before_or_at_height: Optional[int] = None, created_before_or_at: Optional[str] = None, ) -> Any ``` ```typescript [TypeScript] async getSubaccountTransfers( address: string, subaccountNumber: number, limit?: number | null, createdBeforeOrAtHeight?: number | null, createdBeforeOrAt?: string | null, page?: number | null, ): Promise ``` ```rust [Rust] pub async fn get_subaccount_transfers( &self, subaccount: &Subaccount, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/transfers ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------------------- | -------- | ------------------ | -------- | --------------------------------------------------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the account. | | `subaccount_number` | query | [SubaccountNumber] | true | The identifier for the specific subaccount within the wallet address. | | `limit` | query | [u32] | false | Maximum number of items in the response. | | `createdBeforeOrAtHeight` | query | [Height] | false | Restricts results to positions created at or before a specific blockchain height. | | `createdBeforeOrAt` | query | [DateTime] | false | Restricts results to positions created at or before a specific timestamp. | | `page` | query | [u32] | false | The page number for paginated results. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------------------------- | ------------------------------------- | | `200` | [OK] | [TransferResponseObject] ⛁ | The transfers data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [OK]: /types/OK [TransferResponseObject]: /types/transfer_response_object [u32]: /types/u32 [Height]: /types/height [DateTime]: /types/date_time [Bad Request]: /types/bad-request [Not Found]: /types/not-found import Details from '../../../../components/Details'; #### Get Transfers Between Fetch information regarding a transfer between two subaccounts. ##### Method Declaration :::code-group ```python [Python] async def get_transfer_between( self, source_address: str, source_subaccount_number: int, recipient_address: str, recipient_subaccount_number: int, created_before_or_at_height: Optional[int] = None, created_before_or_at: Optional[str] = None, ) -> Any ``` ```typescript [TypeScript] async getTransfersBetween( sourceAddress: string, sourceSubaccountNumber: string, recipientAddress: string, recipientSubaccountNumber: string, createdBeforeOrAtHeight?: number | null, createdBeforeOrAt?: string | null, ): Promise ``` ```rust [Rust] pub async fn get_transfers_between( &self, source_subaccount: &Subaccount, recipient_subaccount: &Subaccount, opts: Option, ) -> Result ``` ```url [API] /v4/transfers/between ``` :::
* Response object does not have defined structure in TypeScript client. Will have to work on it.
##### Parameters | Parameter | Location | Type | Required | Description | | --------------------------- | -------- | ------ | -------- | ------------------------------------------------------------------------------------------- | | `sourceAddress` | query | string | true | Sender's wallet address | | `sourceSubaccountNumber` | query | string | true | The identifier for the specific subaccount within the sender wallet address. | | `recipientAddress` | query | string | true | Receiver wallet address | | `recipientSubaccountNumber` | query | string | true | The identifier for the specific subaccount within the receiver wallet address. | | `createdBeforeOrAtHeight` | query | number | false | Restricts results to positions created at or before a specific blockchain height. | | `createdBeforeOrAt` | query | string | false | Restricts results to positions created at or before a specific timestamp (ISO 8601 format). | ##### Response :::note Response object is not strongly typed in TypeScript client. It will be fixed during unification. ::: import Accounts from './intro.mdx' import GetSubaccounts from './get_subaccounts.mdx' import GetSubaccount from './get_subaccount.mdx' import GetParentSubaccount from './get_parent_subaccount.mdx' import ListPositions from './list_positions.mdx' import ListParentPositions from './list_parent_positions.mdx' import GetAssetPositions from './get_asset_positions.mdx' import GetParentAssetPositions from './get_parent_asset_positions.mdx' import GetTransfers from './get_transfers.mdx' import GetTransfersBetween from './get_transfers_between.mdx' import GetParentTransfers from './get_parent_transfers.mdx' import ListOrders from './list_orders.mdx' import ListParentOrders from './list_parent_orders.mdx' import GetOrder from './get_order.mdx' import GetFills from './get_fills.mdx' import GetParentFills from './get_parent_fills.mdx' import GetHistoricalPnl from './get_historical_pnl.mdx' import GetParentHistoricalPnl from './get_parent_historical_pnl.mdx' import GetRewards from './get_rewards.mdx' import GetRewardsAggregated from './get_rewards_aggregated.mdx' import GetFundingPayments from './get_funding_payments.mdx' import GetFundingPaymentsForParentSubaccount from './get_funding_payments_for_parent_subaccount.mdx' ### Accounts :::code-group ```python [Python] account = indexer_client.account() ``` ```typescript [TypeScript] const account = indexerClient.account(); ``` ```rust [Rust] let accounts = indexer_client.accounts(); ``` ::: import { Button } from 'vocs/components' #### List Orders Retrieves orders for a specific subaccount, with various filtering options to narrow down the results based on order characteristics. ##### Method Declaration :::code-group ```python [Python] async def get_subaccount_orders( self, address: str, subaccount_number: int, ticker: Optional[str] = None, ticker_type: TickerType = TickerType.PERPETUAL, side: Optional[OrderSide] = None, status: Optional[OrderStatus] = None, type: Optional[OrderType] = None, limit: Optional[int] = None, good_til_block_before_or_at: Optional[int] = None, good_til_block_time_before_or_at: Optional[str] = None, return_latest_orders: Optional[bool] = None, ) -> Any ``` ```typescript [TypeScript] async getSubaccountOrders( address: string, subaccountNumber: number, ticker?: string | null, tickerType: TickerType = TickerType.PERPETUAL, side?: OrderSide | null, status?: OrderStatus | null, type?: OrderType | null, limit?: number | null, goodTilBlockBeforeOrAt?: number | null, goodTilBlockTimeBeforeOrAt?: string | null, returnLatestOrders?: boolean | null, ): Promise ``` ```rust [Rust] pub async fn get_subaccount_orders( &self, subaccount: &Subaccount, opts: Option, ) -> Result ``` ```url [API] /v4/orders ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ---------------------------- | -------- | ------------------ | -------- | --------------------------------------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the account. | | `subaccountNumber` | query | [SubaccountNumber] | true | The identifier for the specific subaccount within the wallet address. | | `limit` | query | [u32] | false | Maximum number of asset positions to return in the response. | | `ticker` | query | [Ticker] | false | The ticker filter. | | `side` | query | [OrderSide] | false | The order side filter. | | `status` | query | [OrderStatus] | false | The order status filter. | | `type` | query | [OrderType] | false | The order type filter. | | `goodTilBlockBeforeOrAt` | query | [Height] | false | The block number filter for orders good until before or at. | | `goodTilBlockTimeBeforeOrAt` | query | [DateTime in UTC] | false | The timestamp filter for orders good until before or at. | | `returnLatestOrders` | query | bool | false | Whether to return only the latest orders. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ----------------------- | ------------------------------------- | | `200` | [OK] | [OrderResponseObject] ⛁ | The orders data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | Examples: [Guide - List Orders] [Guide - List Orders]: ../../interaction/data/market#list-orders [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [u32]: /types/u32 [Ticker]: /types/ticker [OrderSide]: /types/order_side [OrderStatus]: /types/order_status [OrderType]: /types/order_type [Height]: /types/height [DateTime in UTC]: /types/date_time [OrderResponseObject]: /types/order_response_object [OK]: /types/ok [Bad Request]: /types/bad-request [Not Found]: /types/not-found import { Button } from 'vocs/components' #### List Parent Orders Query for orders filtered by order params of a parent subaccount. ##### Method Declaration :::code-group ```python [Python] async def list_parent_orders( self, address: str, subaccount_number: int, limit: Optional[int] = None, ticker: Optional[str] = None, side: Optional[OrderSide] = None, status: Optional[OrderStatus] = None, order_type: Optional[OrderType] = None, good_til_block_before_or_at: Optional[int] = None, good_til_block_time_before_or_at: Optional[str] = None, return_latest_orders: Optional[bool] = None, ) -> Any ``` ```typescript [TypeScript] async getParentSubaccountNumberOrders( address: string, parentSubaccountNumber: number, ticker?: string | null, side?: OrderSide | null, status?: OrderStatus | null, type?: OrderType | null, limit?: number | null, goodTilBlockBeforeOrAt?: number | null, goodTilBlockTimeBeforeOrAt?: string | null, returnLatestOrders?: boolean | null, ): Promise ``` ```rust [Rust] pub async fn get_parent_subaccount_number_orders( &self, subaccount: &ParentSubaccount, opts: Option, ) -> Result ``` ```url [API] /v4/orders/parentSubaccountNumber ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ---------------------------- | -------- | ------------------ | -------- | ------------------------------------------------------------ | | `address` | query | [Address] | true | The wallet address that owns the account. | | `parentSubaccountNumber` | query | [SubaccountNumber] | true | Parent subaccount number | | `limit` | query | [u32] | false | Maximum number of asset positions to return in the response. | | `ticker` | query | [Ticker] | false | The ticker filter. | | `side` | query | [OrderSide] | false | The order side filter. | | `status` | query | [OrderStatus] | false | The order status filter. | | `type` | query | [OrderType] | false | The order type filter. | | `goodTilBlockBeforeOrAt` | query | [Height] | false | The block number filter for orders good until before or at. | | `goodTilBlockTimeBeforeOrAt` | query | [DateTime in UTC] | false | The timestamp filter for orders good until before or at. | | `returnLatestOrders` | query | bool | false | Whether to return only the latest orders. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------------------- | ------------------------------------- | | `200` | [OK] | [ListOrdersResponse] | The orders data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [u32]: /types/u32 [Ticker]: /types/ticker [OrderSide]: /types/order_side [OrderStatus]: /types/order_status [OrderType]: /types/order_type [Height]: /types/height [DateTime in UTC]: /types/date_time [ListOrdersResponse]: /types/list_orders_response [OK]: /types/ok [Bad Request]: /types/bad-request [Not Found]: /types/not-found import { Button } from 'vocs/components'; #### List Parent Positions List all positions of a parent subaccount. ##### Method Declaration :::code-group ```python [Python] async def list_parent_orders( self, address: str, subaccount_number: int, limit: Optional[int] = None, ticker: Optional[str] = None, side: Optional[OrderSide] = None, status: Optional[OrderStatus] = None, order_type: Optional[OrderType] = None, good_til_block_before_or_at: Optional[int] = None, good_til_block_time_before_or_at: Optional[str] = None, return_latest_orders: Optional[bool] = None, ) -> Any ``` ```typescript [TypeScript] ``` ```rust [Rust] pub async fn get_parent_subaccount_perpetual_positions( &self, subaccount: &ParentSubaccount, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/perpetualPositions/parentSubaccountNumber ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------------------ | -------- | ------------------ | -------- | ------------------------------------------------------------ | | `address` | query | [Address] | true | The wallet address that owns the account. | | `parentSubaccountNumber` | query | [SubaccountNumber] | true | Subaccount number of the parent subaccount. | | `limit` | query | [u32] | false | Maximum number of asset positions to return in the response. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ----------------------------------- | ------------------------------------- | | `200` | [OK] | [PerpetualPositionResponseObject] ⛁ | The perpetual positions data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [OK]: /types/ok [PerpetualPositionResponseObject]: /types/perpetual_position_response_object [u32]: /types/u32 [Bad Request]: /types/bad-request [Not Found]: /types/not-found import { Button } from 'vocs/components'; #### List Positions Retrieves perpetual positions for a specific subaccount. Both open and closed/historical positions can be queried. ##### Method Declaration :::code-group ```python [Python] async def get_subaccount_perpetual_positions( self, address: str, subaccount_number: int, status: Optional[PositionStatus] = None, limit: Optional[int] = None, created_before_or_at_height: Optional[int] = None, created_before_or_at: Optional[str] = None, ) -> Any ``` ```typescript [TypeScript] async getSubaccountPerpetualPositions( address: string, subaccountNumber: number, status?: PositionStatus | null, limit?: number | null, createdBeforeOrAtHeight?: number | null, createdBeforeOrAt?: string | null, ): Promise ``` ```rust [Rust] pub async fn get_subaccount_perpetual_positions( &self, subaccount: &Subaccount, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/perpetualPositions ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------------------- | -------- | ------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the account. | | `subaccountNumber` | query | [SubaccountNumber] | true | The identifier for the specific subaccount within the wallet address. | | `status` | query | [PerpetualPositionStatus] | false | Filter to retrieve positions with a specific status. If not provided, all positions will be returned regardless of status. | | `limit` | query | [u32] | false | Maximum number of asset positions to return in the response. | | `createdBeforeOrAtHeight` | query | [Height] | false | Restricts results to positions created at or before a specific blockchain height. | | `createdBeforeOrAt` | query | [DateTime] | false | Restricts results to positions created at or before a specific timestamp (ISO 8601 format). | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ----------------------------------- | ------------------------------------- | | `200` | [OK] | [PerpetualPositionResponseObject] ⛁ | The perpetual positions data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | Examples: [Guide - List Positions] [Guide - List Positions]: ../../interaction/data/market#list-positions [Address]: /types/address [SubaccountNumber]: /types/subaccount_number [PerpetualPositionStatus]: /types/perpetual_position_status [OK]: /types/ok [PerpetualPositionResponseObject]: /types/perpetual_position_response_object [u32]: /types/u32 [Height]: /types/height [DateTime]: /types/date_time [Bad Request]: /types/bad-request [Not Found]: /types/not-found import { Button } from 'vocs/components'; #### Get Candles Retrieves candle data for a specific perpetual market. ##### Method Declaration :::code-group ```python [Python] async def get_perpetual_market_candles( self, market: str, resolution: str, from_iso: Optional[str] = None, to_iso: Optional[str] = None, limit: Optional[int] = None, ) -> dict ``` ```typescript [TypeScript] async getPerpetualMarketCandles( market: string, resolution: string, fromISO?: string | null, toISO?: string | null, limit?: number | null, ): Promise ``` ```rust [Rust] pub async fn get_perpetual_market_candles( &self, ticker: &Ticker, res: CandleResolution, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/candles/perpetualMarkets/{market} ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------ | -------- | ------------------ | -------- | ------------------------------------------------------ | | `market` | path | [Ticker] | true | The market ticker. | | `resolution` | query | [CandleResolution] | true | The candle resolution (e.g., "1DAY", "1HOUR", "1MIN"). | | `limit` | query | [u32] | false | The maximum number of candles to retrieve. | | `fromISO` | query | [DateTime] | false | The start timestamp in ISO format. | | `toISO` | query | [DateTime] | false | The end timestamp in ISO format. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ------------------------ | ------------------------------------- | | `200` | [OK] | [CandleResponseObject] ⛁ | The candle data. | | `400` | [Bad Request] | | The request was malformed or invalid. | [OK]: /types/ok [u32]: /types/u32 [Ticker]: /types/ticker [CandleResolution]: /types/candle_resolution [DateTime]: /types/date_time [CandleResponseObject]: /types/candle_response_object [Bad Request]: /types/bad-request import { Button } from 'vocs/components'; #### Get Historical Funding Retrieves historical funding rates for a specific perpetual market. ##### Method Declaration :::code-group ```python [Python] async def get_perpetual_market_historical_funding( self, market: str, effective_before_or_at: Optional[str] = None, effective_before_or_at_height: Optional[int] = None, limit: Optional[int] = None, ) -> dict ``` ```typescript [TypeScript] async getPerpetualMarketHistoricalFunding( market: string, effectiveBeforeOrAt?: string | null, effectiveBeforeOrAtHeight?: number | null, limit?: number | null, ): Promise ``` ```rust [Rust] pub async fn get_perpetual_market_historical_funding( &self, ticker: &Ticker, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/historicalFunding/{market} ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------------------------- | -------- | ---------- | -------- | ------------------------------------------------------------------ | | `market` | path | [Ticker] | true | The market ticker. | | `limit` | query | [u32] | false | The maximum number of funding rates to retrieve. | | `effectiveBeforeOrAt` | query | [DateTime] | false | The timestamp to retrieve funding rates effective before or at. | | `effectiveBeforeOrAtHeight` | query | [Height] | false | The block height to retrieve funding rates effective before or at. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ----------------------------------- | ------------------------------------- | | `200` | [OK] | [HistoricalFundingResponseObject] ⛁ | The historical funding rates data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The market was not found. | [Ticker]: /types/ticker [OK]: /types/ok [u32]: /types/u32 [Height]: /types/height [DateTime]: /types/date_time [HistoricalFundingResponseObject]: /types/historical_funding_response_object [Bad Request]: /types/bad-request [Not Found]: /types/not-found import { Button } from 'vocs/components'; #### Get Perpetual Market Orderbook Retrieves the orderbook for a specific perpetual market. ##### Method Declaration :::code-group ```python [Python] async def get_perpetual_market_orderbook( self, market: str, ) -> dict ``` ```typescript [TypeScript] async getPerpetualMarketOrderbook(market: string): Promise ``` ```rust [Rust] pub async fn get_perpetual_market_orderbook( &self, ticker: &Ticker, ) -> Result ``` ```url [API] /v4/orderbooks/perpetualMarket/{market} ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | -------- | -------- | ------------------ | | `market` | path | [Ticker] | true | The market ticker. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | --------------------------- | ------------------------------------- | | `200` | [OK] | [OrderBookResponseObject] ⛁ | The orderbook data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The market was not found. | [OK]: /types/ok [Ticker]: /types/ticker [OrderBookResponseObject]: /types/order_book_response_object [Bad Request]: /types/bad-request [Not Found]: /types/not-found import { Button } from 'vocs/components'; import Details from '../../../../components/Details'; #### Get Perpetual Markets Retrieves perpetual markets. ##### Method Declaration :::code-group ```python [Python] def get_perpetual_markets(self, market: str = None) -> Response ``` ```typescript [TypeScript] async getPerpetualMarkets(market?: string): Promise ``` ```rust [Rust] pub async fn get_perpetual_markets( &self, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/perpetualMarkets ``` :::
* Add alias for the return type in Rust: `PerpetualMarketsMap`
##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | -------- | -------- | ---------------------------------------------------------------------------------- | | `market` | query | [Ticker] | false | The specific market ticker to retrieve. If not provided, all markets are returned. | | `limit` | query | [u32] | false | Maximum number of asset positions to return in the response. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------------------- | ------------------------------------- | | `200` | [OK] | [PerpetualMarketMap] | The perpetual markets data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The market was not found. | [Ticker]: /types/ticker [PerpetualMarketMap]: /types/perpetual_market_map [OK]: /types/ok [u32]: /types/u32 [Bad Request]: /types/bad-request [Not Found]: /types/not-found import { Button } from 'vocs/components' #### Get Sparklines Retrieves sparkline data for perpetual markets. ##### Method Declaration :::code-group ```python [Python] async def get_perpetual_market_sparklines( self, period: str = TimePeriod.ONE_DAY ) -> dict ``` ```typescript [TypeScript] async getPerpetualMarketSparklines( period: string = TimePeriod.ONE_DAY, ): Promise ``` ```rust [Rust] pub async fn get_perpetual_market_sparklines( &self, period: SparklineTimePeriod, ) -> Result ``` ```url [API] /v4/sparklines ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------ | -------- | --------------------- | -------- | ------------------------------------------------------------------------- | | `timePeriod` | query | [SparklineTimePeriod] | true | The time period for the sparkline data (e.g., "ONE\_DAY", "SEVEN\_DAYS"). | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | --------------------------- | ------------------------------------- | | `200` | [OK] | [SparklineResponseObject] ⛁ | The sparkline data. | | `400` | [Bad Request] | | The request was malformed or invalid. | [OK]: /types/ok [SparklineTimePeriod]: /types/sparkline_time_period [SparklineResponseObject]: /types/sparkline_response_object [Bad Request]: /types/bad-request import { Button } from 'vocs/components'; #### Get Trades Retrieves trades for a specific perpetual market. ##### Method Declaration :::code-group ```python [Python] async def get_perpetual_market_trades( self, market: str, starting_before_or_at_height: Optional[int] = None, limit: Optional[int] = None, ) -> dict ``` ```typescript [TypeScript] async getPerpetualMarketTrades( market: string, startingBeforeOrAtHeight?: number | null, startingBeforeOrAt?: string | null, limit?: number | null, page?: number | null, ): Promise ``` ```rust [Rust] pub async fn get_perpetual_market_trades( &self, ticker: &Ticker, opts: Option, ) -> Result, Error> ``` ```url [API] /v4/trades/perpetualMarket/{market} ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | -------------------------- | -------- | -------- | -------- | ------------------------------------------------- | | `market` | path | [Ticker] | true | The market ticker. | | `limit` | query | [u32] | false | The maximum number of trades to retrieve. | | `startingBeforeOrAtHeight` | query | [Height] | false | The block height to start retrieving trades from. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ----------------------- | ------------------------------------- | | `200` | [OK] | [TradeResponseObject] ⛁ | The trades data. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The market was not found. | [Ticker]: /types/ticker [u32]: /types/u32 [Height]: /types/height [TradeResponseObject]: /types/trade_response_object [OK]: /types/ok [Bad Request]: /types/bad-request [Not Found]: /types/not-found import Markets from './intro.mdx' import GetPerpetualMarkets from './get_perpetual_markets.mdx' import GetPerpetualMarketOrderbook from './get_perpetual_market_orderbook.mdx' import GetTrades from './get_trades.mdx' import GetCandles from './get_candles.mdx' import GetHistoricalFunding from './get_historical_funding.mdx' import GetSparklines from './get_sparklines.mdx' ### Markets TODO: How to access to the `accounts` space. :::code-group ```python [Python] market = indexer_client.markets() ``` ```typescript [TypeScript] market = indexerClient.market(); ``` ```rust [Rust] let markets = indexer_client.markets(); ``` ::: import { Button } from 'vocs/components'; #### Get Compliance Screen Screen an address to see if it is restricted. ##### Method Declaration :::code-group ```python [Python] async def compliance_screen(self, address: str) -> Any ``` ```typescript [TypeScript] async complianceScreen(address: string): Promise ``` ```rust [Rust] pub async fn compliance_screen( &self, address: &Address, ) -> Result ``` ```url /v4/compliance/screen/${address} ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | ------ | -------- | ------------------- | | address | Path | string | true | evm or dydx address | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ---------------------- | ------------------------------------------- | | `200` | [OK] | [ComplianceV2Response] | whether the specified address is restricted | | `400` | [Bad Request] | | The request was malformed or invalid. | [OK]: /types/ok [ComplianceV2Response]: /types/compliance_v2_response [Bad Request]: /types/bad-request import { Button } from 'vocs/components' #### Get Height Current block height and block time (UTC) parsed by the Indexer. ##### Method Declaration :::code-group ```python [Python] async def get_height(self) -> Dict[str, str] ``` ```typescript [TypeScript] async getHeight(): Promise ``` ```rust [Rust] pub async fn get_height(&self) -> Result ``` ```url [API] /v4/height ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | ---- | -------- | ----------- | ##### Response | Status | Meaning | Schema | Description | | ------ | ------- | ------------------ | ------------------------------------------------ | | `200` | [OK] | [HeightResponse] ⛁ | Dictionary containing the block height and time. | [OK]: /types/ok [HeightResponse]: /types/height_response import { Button } from 'vocs/components'; #### Get Screen Query for screening results (compliance) of the address. ##### Method Declaration :::code-group ```python [Python] async def screen(self, address: str) -> Dict[str, bool]: ``` ```typescript [TypeScript] async screen(address: string): Promise ``` ```rust [Rust] pub async fn screen( &self, query: &Address, ) -> Result ``` ```url [API] /v4/compliance/screen/{address} ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | --------- | -------- | ----------------------------------------- | | `address` | query | [Address] | true | The wallet address that owns the account. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ---------------------- | ------------------------------------- | | `200` | [OK] | [ComplianceResponse] ⛁ | The compliance data. | | `400` | [Bad Request] | | The request was malformed or invalid. | [Address]: /types/address [OK]: /types/ok [ComplianceResponse]: /types/compliance_response [Bad Request]: /types/bad-request import { Button } from 'vocs/components' #### Get Time Get current server time of the Indexer. ##### Method Declaration :::code-group ```python [Python] async def get_time(self) -> Dict[str, str] ``` ```typescript [TypeScript] async getTime(): Promise ``` ```rust [Rust] pub async fn get_time(&self) -> Result ``` ```url [API] /v4/time ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | --------- | -------- | ---- | -------- | ----------- | ##### Response | Status | Meaning | Schema | | ------ | ------- | -------------- | | `200` | [OK] | [TimeResponse] | [OK]: /types/ok [TimeResponse]: /types/time_response import Utility from './intro.mdx' import GetTime from './get_time.mdx' import GetHeight from './get_height.mdx' import GetScreen from './get_screen.mdx' import GetComplianceScreen from './get_compliance_screen.mdx' ### Utility // TODO: Add description ##### Method Declaration import { Button } from 'vocs/components'; #### Get MegaVault Historical Pnl MegaVault historical PnL. ##### Method Declaration :::code-group ```python [Python] async def get_megavault_historical_pnl(self, resolution) ``` ```typescript [TypeScript] async getMegavaultHistoricalPnl(resolution?: PnlTickInterval | null): Promise ``` ```rust [Rust] pub async fn get_megavault_historical_pnl( &self, resolution: PnlTickInterval, ) -> Result, Error> ``` ```url [API] /v4/vault/v1/megavault/historicalPnl ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------ | -------- | ----------------- | -------- | -------------------- | | `resolution` | query | [PnlTickInterval] | true | PnL tick resolution. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------------------------- | ------------------------------------- | | `200` | [OK] | [PnlTicksResponseObject] ⛁ | The PnL ticks data. | | `400` | [Bad Request] | | The request was malformed or invalid. | [PnlTickInterval]: /types/pnl_tick_interval [OK]: /types/ok [PnlTicksResponseObject]: /types/pnl_ticks_response_object [Bad Request]: /types/bad-request import { Button } from 'vocs/components' #### Get MegaVaults Positions MegaVault positions. ##### Method Declaration :::code-group ```python [Python] async def get_megavault_positions(self) ``` ```typescript [TypeScript] async getMegavaultPositions(): Promise ``` ```rust [Rust] pub async fn get_megavault_positions(&self) -> Result, Error> ``` ```url [API] /v4/vault/v1/megavault/positions ``` ::: ##### Parameters ##### Response | Status | Meaning | Schema | | ------ | ------- | ----------------- | | `200` | [OK] | [VaultPosition] ⛁ | [OK]: /types/ok [VaultPosition]: /types/vault_position import { Button } from 'vocs/components'; #### Get Vaults Historical Pnl Vaults historical PnL. ##### Method Declaration :::code-group ```python [Python] async def get_vaults_historical_pnl(self, resolution) ``` ```typescript [TypeScript] async getVaultsHistoricalPnl(resolution?: PnlTickInterval | null): Promise ``` ```rust [Rust] pub async fn get_vaults_historical_pnl( &self, resolution: PnlTickInterval, ) -> Result, Error> ``` ```url [API] /v4/vault/v1/vaults/historicalPnl ``` ::: ##### Parameters | Parameter | Location | Type | Required | Description | | ------------ | -------- | ----------------- | -------- | -------------------- | | `resolution` | query | [PnlTickInterval] | true | PnL tick resolution. | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | ---------------------- | ------------------------------------- | | `200` | [OK] | [VaultHistoricalPnl] ⛁ | The vault historical PnL data. | | `400` | [Bad Request] | | The request was malformed or invalid. | [PnlTickInterval]: /types/pnl_tick_interval [OK]: /types/ok [VaultHistoricalPnl]: /types/vault_historical_pnl [Bad Request]: /types/bad-request import MegaVault from './intro.mdx' import GetMegaVaultHistoricalPnl from './get_megavault_historical_pnl.mdx' import GetVaultsHistoricalPnl from './get_vaults_historical_pnl.mdx' import GetMegaVaultPositions from './get_megavault_positions.mdx' ### Vaults TODO: How to access to the `vaults` space. :::code-group ```rust [Rust] let vaults = indexer_client.vaults(); ``` ```python [Python] megavault = indexer_rest_client.megavault ``` ```typescript [TypeScript] vault = indexerClient.vault(); ``` ::: #### Deposit to MegaVault Deposit USDC into MegaVault ##### Method Declaration :::code-group ```python [Python] async def deposit( self, wallet: Wallet, address: str, subaccount_number: int, amount: Decimal ) -> Any ``` ```typescript [TypeScript] async depositToMegavault( subaccount: SubaccountInfo, amountUsdc: number, broadcastMode?: BroadcastMode, ): Promise ``` ```rust [Rust] pub async fn deposit_to_megavault( &mut self, account: &mut Account, subaccount: Subaccount, amount: impl Into, ) -> Result ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Description | | ------------ | -------- | ---------------- | --------------------------- | | `account` | query | [Account] | Owner's account information | | `subaccount` | query | [SubaccountInfo] | Subaccount information | | `amount` | query | Decimal | Amount in usdc to deposit | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | The transaction hash. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | [Account]: /types/account [SubaccountInfo]: /types/subaccount_info [TxHash]: /types/tx_hash [Bad Request]: /types/bad-request [Not Found]: /types/not-found [OK]: /types/ok #### Get Owner Shares in MegaVault Query the shares associated with an [Address]. ##### Method Declaration :::code-group ```python [Python] async def get_owner_shares( self, address: str ) -> vault_query.QueryMegavaultOwnerSharesResponse ``` ```typescript [TypeScript] async getMegavaultOwnerShares( address: string, ): Promise ``` ```rust [Rust] pub async fn get_owner_shares( &mut self, address: &Address, ) -> Result ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Description | | --------- | -------- | --------- | -------------------------------------------- | | `address` | query | [Address] | The wallet address that owns the subaccount. | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | ----------------------------------- | ------------------------------------- | | `200` | [OK] | [QueryMegavaultOwnerSharesResponse] | | | `400` | [Bad Request] | | The request was malformed or invalid. | [Address]: /types/address [OK]: /types/ok [QueryMegavaultOwnerSharesResponse]: /types/megavault_owner_shares_response [Bad Request]: /types/bad-request #### Get Withdrawal Info of MegaVault Query the withdrawal information for a specified number of shares. ##### Method Declaration :::code-group ```python [Python] async def get_withdrawal_info( self, shares: int ) -> vault_query.QueryMegavaultWithdrawalInfoResponse ``` ```typescript [TypeScript] async getMegavaultWithdrawalInfo( sharesToWithdraw: bigint, ): Promise ``` ```rust [Rust] pub async fn get_withdrawal_info( &mut self, shares: &BigInt, ) -> Result ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Description | | --------- | -------- | ------ | ---------------- | | `shares` | query | BigInt | Number of shares | ##### Response | Status | Meaning | Schema | | | ------ | ------------- | -------------------------------------- | ------------------------------------- | | `200` | [OK] | [QueryMegavaultWithdrawalInfoResponse] | | | `400` | [Bad Request] | | The request was malformed or invalid. | [OK]: /types/ok [QueryMegavaultWithdrawalInfoResponse]: /types/query_megavault_withdrawal_info_response [Bad Request]: /types/bad-request import MegaVault from './intro.mdx' import Deposit from './deposit.mdx' import Withdraw from './withdraw.mdx' import GetOwnerShares from './get_owner_shares.mdx' import GetWithdrawalInfo from './get_withdrawal_info.mdx' ### MegaVault Access the vaults requests dispatcher. :::code-group ```rust [Rust] let megavault = node_client.megavault(); ``` ```python [Python] ``` ```typescript [TypeScript] ``` ::: #### Withdraw from MegaVault Withdraw funds (USDC) from the subaccount to the address. ##### Method Declaration :::code-group ```python [Python] async def withdraw( self, wallet: Wallet, address: str, subaccount_number: int, min_amount: Decimal, shares: Optional[int], ) -> Any ``` ```typescript [TypeScript] export async function withdrawFromMegavault( subaccountNumber: number, shares: number, minAmount: number ): Promise; ``` ```rust [Rust] pub async fn withdraw_from_megavault( &mut self, account: &mut Account, subaccount: Subaccount, min_amount: impl Into, shares: Option<&BigInt>, ) -> Result ``` ```url [API] ``` ::: ##### Parameters | Parameter | Location | Type | Description | | ------------ | -------- | ---------------- | --------------------------------- | | `account` | query | [Account] | Owner's account | | `subaccount` | query | [SubaccountInfo] | Subaccount information | | `min_amount` | query | Decimal | Minimum amount to withdraw i usdc | | `shares` | query | BigInt | Number of shares | ##### Response | Status | Meaning | Schema | Description | | ------ | ------------- | -------- | ------------------------------------- | | `200` | [OK] | [TxHash] | The transaction hash. | | `400` | [Bad Request] | | The request was malformed or invalid. | | `404` | [Not Found] | | The subaccount was not found. | [Account]: /types/account [SubaccountInfo]: /types/subaccount_info [TxHash]: /types/tx_hash [Bad Request]: /types/bad-request [Not Found]: /types/not-found [OK]: /types/ok