Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

API Reference

This chapter documents the complete API for the Dango perpetual futures exchange. All interactions with the chain go through a single GraphQL endpoint that supports queries, mutations, and WebSocket subscriptions.

1. Transport

1.1 HTTP

All queries and mutations use a standard GraphQL POST request.

Endpoint: See §11. Constants.

Headers:

HeaderValue
Content-Typeapplication/json

Request body:

{
  "query": "query { ... }",
  "variables": { ... }
}

Example — query chain status:

curl -X POST https://<host>/graphql \
  -H 'Content-Type: application/json' \
  -d '{"query": "{ queryStatus { block { blockHeight timestamp } chainId } }"}'

Response:

{
  "data": {
    "queryStatus": {
      "block": {
        "blockHeight": 123456,
        "timestamp": "2026-01-15T12:00:00"
      },
      "chainId": "dango-1"
    }
  }
}

1.2 WebSocket

Subscriptions (real-time data) use WebSocket with the graphql-ws protocol.

Endpoint: See §11. Constants.

Connection handshake:

{
  "type": "connection_init",
  "payload": {}
}

Subscribe:

{
  "id": "1",
  "type": "subscribe",
  "payload": {
    "query": "subscription { perpsTrades(pairId: \"perp/btcusd\") { fillPrice fillSize } }"
  }
}

Messages arrive as:

{
  "id": "1",
  "type": "next",
  "payload": {
    "data": {
      "perpsTrades": { ... }
    }
  }
}

1.3 Pagination

List queries use cursor-based pagination (Relay Connection specification).

ParameterTypeDescription
firstIntReturn the first N items
afterStringCursor — return items after this
lastIntReturn the last N items
beforeStringCursor — return items before this
sortByEnumBLOCK_HEIGHT_ASC or BLOCK_HEIGHT_DESC

Response shape:

{
  "pageInfo": {
    "hasNextPage": true,
    "hasPreviousPage": false,
    "startCursor": "abc...",
    "endCursor": "xyz..."
  },
  "nodes": [ ... ]
}

Use first + after for forward pagination, last + before for backward.

1.4 Multi-query

When you need to fetch multiple pieces of state (e.g. oracle prices and user positions), use the multi-query to execute them atomically within a single block. This is the preferred method over issuing separate GraphQL requests, which may be evaluated at different block heights and return an inconsistent snapshot.

Wrap the individual queries in a multi array:

query {
  queryApp(request: {
    multi: [
      {
        wasm_smart: {
          contract: "ORACLE_CONTRACT",
          msg: { prices: {} }
        }
      },
      {
        wasm_smart: {
          contract: "PERPS_CONTRACT",
          msg: {
            user_state: { user: "0x1234...abcd" }
          }
        }
      }
    ]
  })
}

Response:

{
  "multi": [
    { "Ok": { "wasm_smart": { /* oracle prices */ } } },
    { "Ok": { "wasm_smart": { /* user state */ } } }
  ]
}

Each element in the response array corresponds to the query at the same index in the request. Individual queries that fail return {"Err": "..."} without aborting the others.

2. Authentication and transactions

2.1 Transaction structure

Every write operation is wrapped in a signed transaction (Tx):

{
  "sender": "0x1234...abcd",
  "gas_limit": 1500000,
  "msgs": [
    {
      "execute": {
        "contract": "PERPS_CONTRACT",
        "msg": { ... },
        "funds": {}
      }
    }
  ],
  "data": { ... },
  "credential": { ... }
}
FieldTypeDescription
senderAddrAccount address sending the transaction
gas_limitu64Maximum gas units for execution
msgs[Message]Non-empty list of messages to execute atomically
dataMetadataAuthentication metadata (see §2.2)
credentialCredentialCryptographic proof of sender authorization

Messages execute atomically — either all succeed or all fail.

2.2 Metadata

The data field contains authentication metadata:

{
  "user_index": 0,
  "chain_id": "dango-1",
  "nonce": 42,
  "expiry": null
}
FieldTypeDescription
user_indexu32The user index that owns the sender account
chain_idStringChain identifier (prevents cross-chain replay)
nonceu32Replay protection nonce
expiryTimestamp | nullOptional expiration; null = no expiry

Nonce semantics: Dango uses unordered nonces with a sliding window of 20, similar to the approach used by Hyperliquid. The account tracks the 20 most recently seen nonces. A transaction is accepted if its nonce is newer than the oldest seen nonce, has not been used before, and not greater than newest seen nonce + 100. This means transactions may arrive out of order without being rejected. SDK implementations should track the next available nonce client-side by querying the account’s seen nonces and choosing the next integer above the maximum.

2.3 Message format

The primary message type for interacting with contracts is execute:

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "trade": {
        "submit_order": {
          "pair_id": "perp/btcusd",
          "size": "0.100000",
          "kind": {
            "market": {
              "max_slippage": "0.010000"
            }
          },
          "reduce_only": false
        }
      }
    },
    "funds": {}
  }
}
FieldTypeDescription
contractAddrTarget contract address
msgJSONContract-specific execute message (snake_case keys)
fundsCoinsTokens to send with the message: {"<denom>": "<amount>"} or {} if none

The funds field is a map of denomination to amount string. For example, depositing 1000 USDC:

{
  "funds": {
    "bridge/usdc": "1000000000"
  }
}

USDC uses 6 decimal places in its base unit (1 USDC = 1000000 base units). All bridged tokens use the bridge/ prefix.

2.4 Signing methods

The credential field wraps a StandardCredential or SessionCredential. A StandardCredential identifies the signing key and contains the signature:

Passkey (Secp256r1 / WebAuthn):

{
  "standard": {
    "key_hash": "A1B2C3D4...64HEX",
    "signature": {
      "passkey": {
        "authenticator_data": "<base64>",
        "client_data": "<base64>",
        "sig": "<base64>"
      }
    }
  }
}
  • sig: 64-byte Secp256r1 signature (base64-encoded)
  • client_data: base64-encoded WebAuthn client data JSON (challenge = base64url of SHA-256 of SignDoc)
  • authenticator_data: base64-encoded WebAuthn authenticator data

Secp256k1:

{
  "standard": {
    "key_hash": "A1B2C3D4...64HEX",
    "signature": {
      "secp256k1": "<base64>"
    }
  }
}
  • 64-byte Secp256k1 signature (base64-encoded)

EIP-712 (Ethereum wallets):

{
  "standard": {
    "key_hash": "A1B2C3D4...64HEX",
    "signature": {
      "eip712": {
        "typed_data": "<base64>",
        "sig": "<base64>"
      }
    }
  }
}
  • sig: 65-byte signature (64-byte Secp256k1 + 1-byte recovery ID; base64-encoded)
  • typed_data: base64-encoded JSON of the EIP-712 typed data object

2.5 Session credentials

Session keys allow delegated signing without requiring the master key for every transaction.

{
  "session": {
    "session_info": {
      "session_key": "<base64>",
      "expire_at": "1700000000"
    },
    "session_signature": "<base64>",
    "authorization": {
      "key_hash": "A1B2C3D4...64HEX",
      "signature": { ... }
    }
  }
}
FieldTypeDescription
session_infoSessionInfoSession key public key + expiration
session_signatureByteArray<64>SignDoc signed by the session key (base64-encoded)
authorizationStandardCredentialSessionInfo signed by the user’s master key

2.6 SignDoc

The SignDoc is the data structure that gets signed. It mirrors the transaction but replaces the credential with the structured Metadata:

{
  "data": {
    "chain_id": "dango-1",
    "expiry": null,
    "nonce": 42,
    "user_index": 0
  },
  "gas_limit": 1500000,
  "messages": [ ... ],
  "sender": "0x1234...abcd"
}

Signing process:

  1. Serialize the SignDoc to canonical JSON (fields sorted alphabetically).
  2. Hash the serialized bytes with SHA-256.
  3. Sign the hash with the appropriate key.

For Passkey (WebAuthn), the SHA-256 hash becomes the challenge in the WebAuthn request. For EIP-712, the SignDoc is mapped to an EIP-712 typed data structure and signed via eth_signTypedData_v4.

2.7 Signing flow

The full transaction lifecycle:

  1. Compose messages — build the contract execute message(s).
  2. Fetch metadata — query chain ID, account’s user_index, and next available nonce.
  3. Simulate — send an UnsignedTx to estimate gas (see §2.8).
  4. Set gas limit — use the simulation result, adding ~770,000 for signature verification overhead.
  5. Build SignDoc — assemble {sender, gas_limit, messages, data}.
  6. Sign — sign the SignDoc with the chosen method.
  7. Broadcast — submit the signed Tx via broadcastTxSync (see §2.9).

2.8 Gas estimation

Use the simulate query to dry-run a transaction:

query Simulate($tx: UnsignedTx!) {
  simulate(tx: $tx)
}

Variables:

{
  "tx": {
    "sender": "0x1234...abcd",
    "msgs": [
      {
        "execute": {
          "contract": "PERPS_CONTRACT",
          "msg": {
            "trade": {
              "deposit": {}
            }
          },
          "funds": {
            "bridge/usdc": "1000000000"
          }
        }
      }
    ],
    "data": {
      "user_index": 0,
      "chain_id": "dango-1",
      "nonce": 42,
      "expiry": null
    }
  }
}

Response:

{
  "data": {
    "simulate": {
      "gas_limit": null,
      "gas_used": 750000,
      "result": {
        "ok": [ ... ]
      }
    }
  }
}

Simulation skips signature verification. Add 770,000 gas (Secp256k1 verification cost) to gas_used when setting gas_limit in the final transaction.

2.9 Broadcasting

Submit a signed transaction:

mutation BroadcastTx($tx: Tx!) {
  broadcastTxSync(tx: $tx)
}

Variables:

{
  "tx": {
    "sender": "0x1234...abcd",
    "gas_limit": 1500000,
    "msgs": [ ... ],
    "data": {
      "user_index": 0,
      "chain_id": "dango-1",
      "nonce": 42,
      "expiry": null
    },
    "credential": {
      "standard": {
        "key_hash": "...",
        "signature": { ... }
      }
    }
  }
}

The mutation returns the transaction outcome as JSON.

3. Account management

Dango uses smart accounts instead of externally-owned accounts (EOAs). A user profile is identified by a UserIndex and may own 1 master account and 0-4 subaccounts. Keys are associated with the user profile, not individual accounts.

3.1 Register user

Creating a new user profile is a two-step process:

Step 1 — Register. Call register_user on the account factory: use the the account factory address itself as sender, and null for the data and credential fields.

{
  "sender": "ACCOUNT_FACTORY_CONTRACT",
  "gas_limit": 1500000,
  "msgs": [
    {
      "execute": {
        "contract": "ACCOUNT_FACTORY_CONTRACT",
        "msg": {
          "register_user": {
            "key": {
              "secp256r1": "<base64>"
            },
            "key_hash": "A1B2C3D4...64HEX",
            "seed": 12345,
            "signature": {
              "passkey": {
                "authenticator_data": "<base64>",
                "client_data": "<base64>",
                "sig": "<base64>"
              }
            }
          }
        },
        "funds": {}
      }
    }
  ],
  "data": null,
  "credential": null
}
FieldTypeDescription
keyKeyThe user’s initial public key (see §10.3)
key_hashHash256Client-chosen hash identifying this key
seedu32Arbitrary number for address variety
signatureSignatureSignature over {"chain_id": "dango-1"} proving key ownership

A master account is created in the inactive state (for the purpose of spam prevention). The new account address is returned in the transaction events.

Step 2 — Activate. Send at least the minimum_deposit (10 USDC = 10000000 bridge/usdc on mainnet) to the new master account address. The transfer can either come from an existing Dango account, or from another chain via Hyperlane bridging. Upon receipt, the account activates itself and becomes ready to use.

3.2 Register subaccount

Create an additional account for an existing user (maximum 5 accounts per user):

{
  "execute": {
    "contract": "ACCOUNT_FACTORY_CONTRACT",
    "msg": {
      "register_account": {}
    },
    "funds": {}
  }
}

Must be sent from an existing account owned by the user.

3.3 Account address derivation

3.3.1 Master account

The first account of a new user (§3.1) is derived as:

address := ripemd160(sha256(deployer || code_hash || seed || key_hash || key_tag || key))

where || denotes byte concatenation.

The preimage layout (122 bytes total):

Byte rangeSizeFieldDescription
[0..20)20deployerThe ACCOUNT_FACTORY_CONTRACT address (see §11)
[20..52)32code_hashThe code hash of the Dango single-signature account contract (see §11)
[52..56)4seedUser-chosen u32, big-endian — arbitrary value for frontrunning protection
[56..88)32key_hashClient-chosen 32-byte identifier for the key (see §3.8 for hashing rules)
[88..89)1key_tagKey type: 0 = Secp256r1, 1 = Secp256k1, 2 = Ethereum
[89..122)33keySecp256r1 / Secp256k1: 33-byte compressed public key. Ethereum: 13 zero bytes followed by the 20-byte address

3.3.2 Subaccount

address := ripemd160(sha256(deployer || code_hash || account_index))

The preimage layout (56 bytes total):

Byte rangeSizeFieldDescription
[0..20)20deployerThe ACCOUNT_FACTORY_CONTRACT address (see §11)
[20..52)32code_hashThe code hash of the Dango single-signature account contract (see §11)
[52..56)4account_indexGlobal account index, u32, big-endian

The global account index is a chain-wide monotonic counter maintained by the account factory; it is incremented for every account created across all users, so every account has a unique index.

3.4 Update key

Associate or disassociate a key with the user profile.

Add a key:

{
  "execute": {
    "contract": "ACCOUNT_FACTORY_CONTRACT",
    "msg": {
      "update_key": {
        "key_hash": "A1B2C3D4...64HEX",
        "key": {
          "insert": {
            "secp256k1": "<base64>"
          }
        }
      }
    },
    "funds": {}
  }
}

Remove a key:

{
  "execute": {
    "contract": "ACCOUNT_FACTORY_CONTRACT",
    "msg": {
      "update_key": {
        "key_hash": "A1B2C3D4...64HEX",
        "key": "delete"
      }
    },
    "funds": {}
  }
}

3.5 Update username

Set the user’s human-readable username (one-time operation):

{
  "execute": {
    "contract": "ACCOUNT_FACTORY_CONTRACT",
    "msg": {
      "update_username": "alice"
    },
    "funds": {}
  }
}

Username rules: 1–15 characters, lowercase a-z, digits 0-9, and underscore _ only.

The username is cosmetic only — used for human-readable display on the frontend. It is not used in any business logic of the exchange.

3.6 Query user

query {
  user(userIndex: 0) {
    userIndex
    createdBlockHeight
    createdAt
    publicKeys {
      keyHash
      publicKey
      keyType
      createdBlockHeight
      createdAt
    }
    accounts {
      accountIndex
      address
      createdBlockHeight
      createdAt
    }
  }
}

The keyType enum values are: SECP256R1, SECP256K1, ETHEREUM.

3.7 Query user by username

Look up a user by their username via a smart contract query against the account factory:

query {
  queryApp(request: {
    wasm_smart: {
      contract: "ACCOUNT_FACTORY_CONTRACT",
      msg: {
        user: {
          name: "alice"
        }
      }
    }
  })
}

Response:

{
  "index": 42,
  "name": "alice",
  "accounts": {
    "100": "0xabcd...1234"
  },
  "keys": {
    "A1B2C3...": {
      "ethereum": "0x1234...abcd"
    }
  }
}

You can also look up by index: { "user": { "index": 42 } }.

3.8 Query users by key

Search for users by public key or key hash. Useful when you know a user’s key but not their index or username.

query {
  users(publicKeyHash: "A1B2C3D4...64HEX", first: 10) {
    nodes {
      userIndex
      createdBlockHeight
      createdAt
      publicKeys {
        keyHash
        publicKey
        keyType
        createdBlockHeight
        createdAt
      }
      accounts {
        accountIndex
        address
        createdBlockHeight
        createdTxHash
        createdAt
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Filter by publicKeyHash or by publicKey (the raw key value). The key hash is computed differently depending on key type:

Key typeInput to SHA-256
ETHEREUMUTF-8 bytes of the lowercase hex address (with 0x prefix)
SECP256K1Compressed public key bytes (33 bytes)
SECP256R1WebAuthn credential ID bytes

The resulting hash is hex-encoded in uppercase.

3.9 Query accounts

query {
  accounts(userIndex: 0, first: 10) {
    nodes {
      accountIndex
      address
      createdBlockHeight
      createdTxHash
      createdAt
      users {
        userIndex
      }
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}

Filter by userIndex to get all accounts for a specific user, or by address for a specific account.

4. Market data

4.1 Global parameters

query {
  queryApp(request: {
    wasm_smart: {
      contract: "PERPS_CONTRACT",
      msg: {
        param: {}
      }
    }
  })
}

Response:

{
  "max_unlocks": 5,
  "max_open_orders": 50,
  "max_action_batch_size": 5,
  "maker_fee_rates": {
    "base": "0.000000",
    "tiers": {}
  },
  "taker_fee_rates": {
    "base": "0.001000",
    "tiers": {}
  },
  "protocol_fee_rate": "0.100000",
  "liquidation_fee_rate": "0.010000",
  "liquidation_buffer_ratio": "0.000000",
  "funding_period": "3600",
  "vault_total_weight": "10.000000",
  "vault_cooldown_period": "604800",
  "referral_active": true,
  "min_referrer_volume": "0.000000",
  "referrer_commission_rates": {
    "base": "0.000000",
    "tiers": {}
  }
}
FieldTypeDescription
max_unlocksusizeMax concurrent vault unlock requests per user
max_open_ordersusizeMax resting limit orders per user (all pairs)
max_action_batch_sizeusizeMax actions in a single batch_update_orders message
maker_fee_ratesRateScheduleVolume-tiered maker fee rates
taker_fee_ratesRateScheduleVolume-tiered taker fee rates
protocol_fee_rateDimensionlessFraction of trading fees routed to treasury
liquidation_fee_rateDimensionlessInsurance fund fee on liquidations
liquidation_buffer_ratioDimensionlessPost-liquidation equity buffer above maintenance margin
funding_periodDurationInterval between funding collections
vault_total_weightDimensionlessSum of all pairs’ vault liquidity weights
vault_cooldown_periodDurationWaiting time before vault withdrawal release
referral_activeboolWhether the referral commission system is active
min_referrer_volumeUsdValueMinimum lifetime volume to become a referrer
referrer_commission_ratesRateScheduleVolume-tiered referrer commission rates

A RateSchedule has two fields: base (the default rate) and tiers (a map of volume threshold to rate; highest qualifying tier wins).

For fee mechanics, see Order matching §8.

4.2 Global state

query {
  queryApp(request: {
    wasm_smart: {
      contract: "PERPS_CONTRACT",
      msg: {
        state: {}
      }
    }
  })
}

Response:

{
  "last_funding_time": "1700000000.123456789",
  "vault_share_supply": "500000000",
  "insurance_fund": "25000.000000",
  "treasury": "12000.000000"
}
FieldTypeDescription
last_funding_timeTimestampLast funding collection time
vault_share_supplyUint128Total vault share tokens
insurance_fundUsdValueInsurance fund balance
treasuryUsdValueAccumulated protocol fees

4.3 Pair parameters

All pairs:

query {
  queryApp(request: {
    wasm_smart: {
      contract: "PERPS_CONTRACT",
      msg: {
        pair_params: {
          start_after: null,
          limit: 30
        }
      }
    }
  })
}

Single pair:

query {
  queryApp(request: {
    wasm_smart: {
      contract: "PERPS_CONTRACT",
      msg: {
        pair_param: {
          pair_id: "perp/btcusd"
        }
      }
    }
  })
}

Response (single pair):

{
  "tick_size": "1.000000",
  "min_order_size": "10.000000",
  "max_abs_oi": "1000000.000000",
  "max_abs_funding_rate": "0.000500",
  "initial_margin_ratio": "0.050000",
  "maintenance_margin_ratio": "0.025000",
  "impact_size": "10000.000000",
  "vault_liquidity_weight": "1.000000",
  "vault_half_spread": "0.001000",
  "vault_max_quote_size": "50000.000000",
  "max_limit_price_deviation": "0.100000",
  "max_market_slippage": "0.100000",
  "bucket_sizes": ["1.000000", "5.000000", "10.000000"]
}
FieldTypeDescription
tick_sizeUsdPriceMinimum price increment for limit orders
min_order_sizeUsdValueMinimum notional value (reduce-only exempt)
max_abs_oiQuantityMaximum open interest per side
max_abs_funding_rateFundingRateDaily funding rate cap
initial_margin_ratioDimensionlessMargin to open (e.g. 0.05 = 20x max leverage)
maintenance_margin_ratioDimensionlessMargin to stay open (liquidation threshold)
impact_sizeUsdValueNotional for impact price calculation
vault_liquidity_weightDimensionlessVault allocation weight for this pair
vault_half_spreadDimensionlessHalf the vault’s bid-ask spread
vault_max_quote_sizeQuantityMaximum vault resting size per side
max_limit_price_deviationDimensionlessMax symmetric deviation of a limit price from oracle at submission
max_market_slippageDimensionlessMax max_slippage a user may set on a market or TP/SL child order
bucket_sizes[UsdPrice]Price bucket granularities for depth queries

For the relationship between margin ratios and leverage, see Risk §2.

4.4 Pair state

All pairs:

query {
  queryApp(request: {
    wasm_smart: {
      contract: "PERPS_CONTRACT",
      msg: {
        pair_states: {
          start_after: null,
          limit: 30
        }
      }
    }
  })
}

Single pair:

query {
  queryApp(request: {
    wasm_smart: {
      contract: "PERPS_CONTRACT",
      msg: {
        pair_state: {
          pair_id: "perp/btcusd"
        }
      }
    }
  })
}

Response:

{
  "long_oi": "12500.000000",
  "short_oi": "10300.000000",
  "funding_per_unit": "0.000123",
  "funding_rate": "0.000050"
}
FieldTypeDescription
long_oiQuantityTotal long open interest
short_oiQuantityTotal short open interest
funding_per_unitFundingPerUnitCumulative funding accumulator
funding_rateFundingRateCurrent per-day funding rate (positive = longs pay)

For funding mechanics, see Funding.

4.5 Order book depth

Query aggregated order book depth at a given price bucket granularity:

query {
  queryApp(request: {
    wasm_smart: {
      contract: "PERPS_CONTRACT",
      msg: {
        liquidity_depth: {
          pair_id: "perp/btcusd",
          bucket_size: "10.000000",
          limit: 20
        }
      }
    }
  })
}
ParameterTypeDescription
pair_idPairIdTrading pair
bucket_sizeUsdPricePrice aggregation granularity (must be in bucket_sizes)
limitu32?Max number of price levels per side

Response:

{
  "bids": {
    "64990.000000": {
      "size": "12.500000",
      "notional": "812375.000000"
    },
    "64980.000000": {
      "size": "8.200000",
      "notional": "532836.000000"
    }
  },
  "asks": {
    "65010.000000": {
      "size": "10.000000",
      "notional": "650100.000000"
    },
    "65020.000000": {
      "size": "5.500000",
      "notional": "357610.000000"
    }
  }
}

Each level contains:

FieldTypeDescription
sizeQuantityAbsolute order size in the bucket
notionalUsdValueUSD notional (size × price)

4.6 Pair statistics

All pairs:

query {
  allPerpsPairStats {
    pairId
    currentPrice
    price24HAgo
    volume24H
    priceChange24H
  }
}

Single pair:

query {
  perpsPairStats(pairId: "perp/btcusd") {
    pairId
    currentPrice
    price24HAgo
    volume24H
    priceChange24H
  }
}
FieldTypeDescription
pairIdString!Pair identifier
currentPriceBigDecimalCurrent price (nullable)
price24HAgoBigDecimalPrice 24 hours ago (nullable)
volume24HBigDecimal!24h trading volume in USD
priceChange24HBigDecimal24h price change percentage (e.g. 5.25 = +5.25%)

4.7 Historical candles

query {
  perpsCandles(
    pairId: "perp/btcusd",
    interval: ONE_HOUR,
    laterThan: "2026-01-01T00:00:00Z",
    earlierThan: "2026-01-02T00:00:00Z",
    first: 24
  ) {
    nodes {
      pairId
      interval
      open
      high
      low
      close
      volume
      volumeUsd
      timeStart
      timeStartUnix
      timeEnd
      timeEndUnix
      minBlockHeight
      maxBlockHeight
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
ParameterTypeDescription
pairIdString!Trading pair (e.g. "perp/btcusd")
intervalCandleInterval!Candle interval
laterThanDateTimeCandles after this time (inclusive)
earlierThanDateTimeCandles before this time (exclusive)

CandleInterval values: ONE_SECOND, ONE_MINUTE, FIVE_MINUTES, FIFTEEN_MINUTES, ONE_HOUR, FOUR_HOURS, ONE_DAY, ONE_WEEK.

PerpsCandle fields:

FieldTypeDescription
openBigDecimalOpening price
highBigDecimalHighest price
lowBigDecimalLowest price
closeBigDecimalClosing price
volumeBigDecimalVolume in base units
volumeUsdBigDecimalVolume in USD
timeStartStringPeriod start (ISO 8601)
timeStartUnixIntPeriod start (Unix timestamp)
timeEndStringPeriod end (ISO 8601)
timeEndUnixIntPeriod end (Unix timestamp)
minBlockHeightIntFirst block in this candle
maxBlockHeightIntLast block in this candle

4.8 Fees and revenue

query {
  perpsFeesAndRevenue(
    from: "2026-01-01T00:00:00Z",
    to: "2026-02-01T00:00:00Z"
  ) {
    from
    to
    feeEventsCount
    protocolFee
    vaultFee
    refereeRebate
    referrerPayout
    volumeUsd
  }
}
ParameterTypeDescription
fromDateTime!Window lower bound (inclusive)
toDateTime!Window upper bound (inclusive)

from must be less than or equal to to.

Resolution. Windows shorter than 3 days are served from per-block rows with microsecond-precise bounds. Windows of 3 days or more are served from the perps_fees_hourly materialized view; bounds are snapped to the enclosing hours, so a request that overlaps partial hours at either end includes those hours’ full aggregates.

PerpsFeesAndRevenue fields:

FieldTypeDescription
fromString!Lower bound echoed back as ISO 8601
toString!Upper bound echoed back as ISO 8601
feeEventsCountInt!Number of FeeDistributed events aggregated in the window
protocolFeeBigDecimal!Protocol fee accrued (USD)
vaultFeeBigDecimal!Vault fee accrued (USD), already net of referral commissions
refereeRebateBigDecimal!Referral commissions paid back to referees (USD)
referrerPayoutBigDecimal!Referral commissions paid out to referrers (USD)
volumeUsdBigDecimal!USD notional volume from OrderFilled and Deleveraged events

Total protocol revenue over the window is protocolFee + vaultFee. The total fee paid by users is protocolFee + vaultFee + refereeRebate + referrerPayout; refereeRebate and referrerPayout are informational totals of referral commissions distributed.

This query backs the Dango entry on DefiLlama: https://defillama.com/protocol/dango.

5. User state and orders

5.1 User state

query {
  queryApp(request: {
    wasm_smart: {
      contract: "PERPS_CONTRACT",
      msg: {
        user_state: {
          user: "0x1234...abcd"
        }
      }
    }
  })
}

Response:

{
  "margin": "10000.000000",
  "vault_shares": "0",
  "positions": {
    "perp/btcusd": {
      "size": "0.500000",
      "entry_price": "64500.000000",
      "entry_funding_per_unit": "0.000100",
      "conditional_order_above": {
        "order_id": "55",
        "size": "-0.500000",
        "trigger_price": "70000.000000",
        "max_slippage": "0.020000"
      },
      "conditional_order_below": null
    }
  },
  "unlocks": [],
  "reserved_margin": "500.000000",
  "open_order_count": 2
}
FieldTypeDescription
marginUsdValueDeposited margin (USD)
vault_sharesUint128Vault liquidity shares owned
positionsMap<PairId, Position>Open positions by pair
unlocks[Unlock]Pending vault withdrawals
reserved_marginUsdValueMargin reserved for resting limit orders
open_order_countusizeNumber of resting limit orders

Position:

FieldTypeDescription
sizeQuantityPosition size (positive = long, negative = short)
entry_priceUsdPriceAverage entry price
entry_funding_per_unitFundingPerUnitFunding accumulator at last modification
conditional_order_aboveConditionalOrder|nullTP/SL that triggers when oracle >= trigger_price
conditional_order_belowConditionalOrder|nullTP/SL that triggers when oracle <= trigger_price

ConditionalOrder (embedded in Position):

FieldTypeDescription
order_idOrderIdInternal ID for price-time priority
sizeQuantity|nullSize to close (sign opposes position); null closes entire position
trigger_priceUsdPriceOracle price that activates this order
max_slippageDimensionlessSlippage tolerance for the market order at trigger

Unlock:

FieldTypeDescription
end_timeTimestampWhen cooldown completes
amount_to_releaseUsdValueUSD value to release

Returns null if the user has no state.

Enumerate all user states (paginated):

query {
  queryApp(request: {
    wasm_smart: {
      contract: "PERPS_CONTRACT",
      msg: {
        user_states: {
          start_after: null,
          limit: 10
        }
      }
    }
  })
}

Returns: { "<address>": <UserState>, ... }

5.2 Open orders

Query all resting limit orders for a user:

query {
  queryApp(request: {
    wasm_smart: {
      contract: "PERPS_CONTRACT",
      msg: {
        orders_by_user: {
          user: "0x1234...abcd"
        }
      }
    }
  })
}

Response:

{
  "42": {
    "pair_id": "perp/btcusd",
    "size": "0.500000",
    "limit_price": "63000.000000",
    "reduce_only": false,
    "reserved_margin": "1575.000000",
    "created_at": "1700000000"
  }
}

The response is a map of OrderId → order details. This query returns only resting limit orders. Conditional (TP/SL) orders are stored on the position itself and can be queried via user_state (see §5.1, conditional_order_above / conditional_order_below fields).

FieldTypeDescription
pair_idPairIdTrading pair
sizeQuantityOrder size (positive = buy, negative = sell)
limit_priceUsdPriceLimit price
reduce_onlyboolWhether the order only reduces an existing position
reserved_marginUsdValueMargin reserved for this order
created_atTimestampCreation time

5.3 Single order

query {
  queryApp(request: {
    wasm_smart: {
      contract: "PERPS_CONTRACT",
      msg: {
        order: {
          order_id: "42"
        }
      }
    }
  })
}

Response:

{
  "user": "0x1234...abcd",
  "pair_id": "perp/btcusd",
  "size": "0.500000",
  "limit_price": "63000.000000",
  "reduce_only": false,
  "reserved_margin": "1575.000000",
  "created_at": "1700000000"
}
FieldTypeDescription
userAddrOrder owner
pair_idPairIdTrading pair
sizeQuantityOrder size (positive = buy, negative = sell)
limit_priceUsdPriceLimit price
reduce_onlyboolWhether the order only reduces an existing position
reserved_marginUsdValueMargin reserved for this order
created_atTimestampCreation time

Returns null if the order does not exist.

5.4 Trading volume

query {
  queryApp(request: {
    wasm_smart: {
      contract: "PERPS_CONTRACT",
      msg: {
        volume: {
          user: "0x1234...abcd",
          since: null
        }
      }
    }
  })
}
ParameterTypeDescription
userAddrAccount address
sinceTimestamp?Start time; null for lifetime volume

Returns a UsdValue string (e.g. "1250000.000000").

5.5 Trade history

Query historical perps events such as fills, liquidations, and order lifecycle:

query {
  perpsEvents(
    userAddr: "0x1234...abcd",
    eventType: "order_filled",
    pairId: "perp/btcusd",
    first: 50,
    sortBy: BLOCK_HEIGHT_DESC
  ) {
    nodes {
      idx
      blockHeight
      txHash
      eventType
      userAddr
      pairId
      data
      createdAt
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
ParameterTypeDescription
userAddrStringFilter by user address
eventTypeStringFilter by event type (see §9)
pairIdStringFilter by trading pair
blockHeightIntFilter by block height

The data field contains the event-specific payload as JSON. For example, an order_filled event:

{
  "order_id": "42",
  "pair_id": "perp/btcusd",
  "user": "0x1234...abcd",
  "fill_price": "65000.000000",
  "fill_size": "0.100000",
  "closing_size": "0.000000",
  "opening_size": "0.100000",
  "realized_pnl": "0.000000",
  "realized_funding": "0.000000",
  "fee": "6.500000",
  "client_order_id": "42",
  "fill_id": "17",
  "is_maker": false
}

5.6 Extended user state

Query user state with additional computed fields (equity, available margin, maintenance margin, and per-position unrealized PnL/funding):

query {
  queryApp(request: {
    wasm_smart: {
      contract: "PERPS_CONTRACT",
      msg: {
        user_state_extended: {
          user: "0x1234...abcd",
          include_equity: true,
          include_available_margin: true,
          include_maintenance_margin: true,
          include_unrealized_pnl: true,
          include_unrealized_funding: true,
          include_liquidation_price: true
        }
      }
    }
  })
}
ParameterTypeDescription
userAddrAccount address
include_equityboolCompute and return the user’s equity
include_available_marginboolCompute and return the user’s free margin
include_maintenance_marginboolCompute and return the user’s maintenance margin
include_unrealized_pnlboolCompute and return per-position unrealized PnL
include_unrealized_fundingboolCompute and return per-position unrealized funding costs
include_liquidation_priceboolCompute and return per-position liquidation price

Response:

{
  "margin": "10000.000000",
  "vault_shares": "0",
  "unlocks": [],
  "reserved_margin": "500.000000",
  "open_order_count": 2,
  "equity": "10250.000000",
  "available_margin": "8625.000000",
  "maintenance_margin": "1875.000000",
  "positions": {
    "perp/ethusd": {
      "size": "5.000000",
      "entry_price": "2000.000000",
      "entry_funding_per_unit": "0.000000",
      "conditional_order_above": null,
      "conditional_order_below": null,
      "unrealized_pnl": "250.000000",
      "unrealized_funding": "0.000000",
      "liquidation_price": "1052.631578"
    }
  }
}
FieldTypeDescription
marginUsdValueThe user’s deposited margin
vault_sharesUint128Vault shares owned by this user
unlocks[Unlock]Pending vault withdrawal cooldowns
reserved_marginUsdValueMargin reserved for resting limit orders
open_order_countusizeNumber of resting limit orders
equityUsdValue|nullmargin + unrealized PnL − unrealized funding; null if not requested
available_marginUsdValue|nullmargin − initial margin requirements − reserved margin; null if not requested
maintenance_marginUsdValue|nullsum of |size| * oracle_price * maintenance_margin_ratio across all positions; null if not requested
positionsMap<PairId, PositionExtended>Open positions with optional computed data (see below)

PositionExtended:

FieldTypeDescription
sizeQuantityPositive = long, negative = short
entry_priceUsdPriceAverage entry price
entry_funding_per_unitFundingPerUnitFunding accumulator at last update
conditional_order_aboveConditionalOrder|nullTP/SL that triggers when oracle >= trigger_price
conditional_order_belowConditionalOrder|nullTP/SL that triggers when oracle <= trigger_price
unrealized_pnlUsdValue|nullsize * (oracle_price - entry_price); positive = profit; null if not requested
unrealized_fundingUsdValue|nullsize * (current_funding_per_unit - entry_funding_per_unit); positive = cost; null if not requested
liquidation_priceUsdPrice|nullOracle price that triggers account liquidation (other prices held constant); null if not requested or no valid price exists

equity reflects the total account value including unrealized positions. available_margin is the amount the user can withdraw or use for new orders. maintenance_margin is the minimum equity required to keep positions open — if equity falls below this threshold the account becomes liquidatable.

6. Trading operations

Each message is wrapped in a Tx as described in §2 and broadcast via broadcastTxSync.

6.1 Deposit margin

Deposit USDC into the trading margin account:

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "trade": {
        "deposit": {}
      }
    },
    "funds": {
      "bridge/usdc": "1000000000"
    }
  }
}

The deposited USDC is converted to USD at a fixed rate of $1 per USDC and credited to user_state.margin. In this example, 1000000000 base units = 1,000 USDC = $1,000.

6.2 Withdraw margin

Withdraw USD from the trading margin account:

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "trade": {
        "withdraw": {
          "amount": "500.000000"
        }
      }
    },
    "funds": {}
  }
}
FieldTypeDescription
amountUsdValueUSD amount to withdraw

The USD amount is converted to USDC at the fixed rate of $1 per USDC (floor-rounded) and transferred to the sender.

6.3 Submit market order

Buy or sell at the best available prices with a slippage tolerance:

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "trade": {
        "submit_order": {
          "pair_id": "perp/btcusd",
          "size": "0.100000",
          "kind": {
            "market": {
              "max_slippage": "0.010000"
            }
          },
          "reduce_only": false
        }
      }
    },
    "funds": {}
  }
}
FieldTypeDescription
pair_idPairIdTrading pair (e.g. "perp/btcusd")
sizeQuantityContract size — positive = buy, negative = sell
max_slippageDimensionlessMaximum slippage as a fraction of oracle price (0.01 = 1%)
reduce_onlyboolIf true, only the position-closing portion executes
tpChildOrder?Optional take-profit child order (see below)
slChildOrder?Optional stop-loss child order (see below)

Market orders execute immediately (IOC behavior). Any unfilled remainder is discarded. If nothing fills, the transaction reverts.

Child orders (TP/SL): When tp or sl is provided, a conditional order is automatically attached to the resulting position after fill. See ChildOrder in the types reference.

{
  "tp": {
    "trigger_price": "70000.000000",
    "max_slippage": "0.020000",
    "size": null
  },
  "sl": {
    "trigger_price": "60000.000000",
    "max_slippage": "0.020000",
    "size": null
  }
}
FieldTypeDescription
trigger_priceUsdPriceOracle price that activates this order
max_slippageDimensionlessSlippage tolerance for the market order at trigger
sizeQuantity|nullSize to close (sign opposes position); null closes entire position

For order matching mechanics, see Order matching.

6.4 Submit limit order

Place a resting order on the book:

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "trade": {
        "submit_order": {
          "pair_id": "perp/btcusd",
          "size": "-0.500000",
          "kind": {
            "limit": {
              "limit_price": "65000.000000",
              "time_in_force": "GTC",
              "client_order_id": "42"
            }
          },
          "reduce_only": false
        }
      }
    },
    "funds": {}
  }
}
FieldTypeDescription
limit_priceUsdPriceLimit price — must be aligned to tick_size
time_in_forceTimeInForce"GTC" (default), "IOC", or "POST" — see below
client_order_idClientOrderId?Optional caller-assigned id for in-flight cancel — see below
reduce_onlyboolIf true, only position-closing portion is kept
tpChildOrder?Optional take-profit child order (see §6.3)
slChildOrder?Optional stop-loss child order (see §6.3)

Time-in-force options:

  • GTC (Good-Til-Canceled, default): the matching portion fills immediately; any unfilled remainder is stored on the book. Margin is reserved for the unfilled portion.
  • IOC (Immediate-Or-Cancel): fills as much as possible against the book, then discards any unfilled remainder. Errors if nothing fills at all.
  • POST (Post-Only): the entire order is placed on the book without matching. Rejected if the limit price would cross the best offer on the opposite side.

Client order id:

client_order_id is a caller-assigned Uint64 that lets an algo trader cancel an order in the same block it was submitted, without round-tripping through the server response to learn the system-assigned OrderId. Cancel via CancelOrderRequest::OneByClientOrderId.

  • Uniqueness scope: per-sender, across the sender’s active (resting) limit orders only. The contract does not remember client order ids of orders that have been canceled or filled, so they can be reused freely.
  • Submitting a second order with a client_order_id that the sender already has on the book fails with duplicate_data.
  • Not allowed with time_in_force: "IOC" — IOC never enters the book, so the alias would be unreachable. Submission is rejected with a clear error.

6.5 Cancel order

Cancel a single order by system-assigned OrderId:

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "trade": {
        "cancel_order": {
          "one": "42"
        }
      }
    },
    "funds": {}
  }
}

Cancel a single order by caller-assigned ClientOrderId:

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "trade": {
        "cancel_order": {
          "one_by_client_order_id": "42"
        }
      }
    },
    "funds": {}
  }
}

This resolves to the active order owned by the sender that carries the given client_order_id (see §6.4). It bails if no such order exists. The lookup is per-sender, so two traders can independently use the same client_order_id value without colliding.

Pattern: an algo trader can submit and cancel in the same block by reusing the client_order_id they assigned at submission, without waiting for the server response.

Cancel all orders:

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "trade": {
        "cancel_order": "all"
      }
    },
    "funds": {}
  }
}

Cancellation releases reserved margin and decrements open_order_count.

6.6 Batch update orders

Apply a sequence of submit and cancel actions atomically. Actions execute in order; later actions observe the state written by earlier ones. If any action fails, the whole message reverts and no partial state is persisted.

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "trade": {
        "batch_update_orders": [
          { "cancel": "all" },
          {
            "submit": {
              "pair_id": "perp/btcusd",
              "size": "0.100000",
              "kind": {
                "limit": {
                  "limit_price": "64000.000000",
                  "time_in_force": "POST",
                  "client_order_id": "1"
                }
              },
              "reduce_only": false
            }
          },
          {
            "submit": {
              "pair_id": "perp/btcusd",
              "size": "-0.100000",
              "kind": {
                "limit": {
                  "limit_price": "66000.000000",
                  "time_in_force": "POST",
                  "client_order_id": "2"
                }
              },
              "reduce_only": false
            }
          }
        ]
      }
    },
    "funds": {}
  }
}

The payload is a JSON array of SubmitOrCancelOrderRequest values. Each entry is one of:

  • { "submit": { … } } — same shape as submit_order (every field of SubmitOrderRequest).
  • { "cancel": <CancelOrderRequest> } — any variant of CancelOrderRequest: { "one": "..." }, { "one_by_client_order_id": "..." }, or the string "all".

Constraints:

Atomicity: every storage write the earlier actions made — including realized fills that mutated counterparties’ state — is discarded if a later action fails. Events are emitted only for the successful-batch case; a reverting batch surfaces just the top-level transaction failure.

Example use case — atomic book replacement:

An algo trader refreshing quotes at a new reference price can send a single batch_update_orders carrying { "cancel": "all" } followed by the new submit entries. The old orders are canceled (freeing reserved margin) before the new submits run their margin checks, and if any new submit would fail (margin check, price band, OI cap, …) the old orders are restored along with the rest of the batch.

Reusing a client_order_id within one batch: a { "cancel": { "one_by_client_order_id": "X" } } entry releases the id before a later { "submit": { … "client_order_id": "X" } } runs, so the same id can be rebound to a new order within a single message.

6.7 Submit conditional order (TP/SL)

Place a take-profit or stop-loss order that triggers when the oracle price crosses a threshold:

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "trade": {
        "submit_conditional_order": {
          "pair_id": "perp/btcusd",
          "size": "-0.100000",
          "trigger_price": "70000.000000",
          "trigger_direction": "above",
          "max_slippage": "0.020000"
        }
      }
    },
    "funds": {}
  }
}
FieldTypeDescription
pair_idPairIdTrading pair
sizeQuantitySize to close — sign must oppose the position
trigger_priceUsdPriceOracle price that activates this order
trigger_directionTriggerDirection"above" or "below" (see below)
max_slippageDimensionlessSlippage tolerance for the market order at trigger

Trigger direction:

DirectionTriggers whenUse case
aboveoracle_price >= trigger_priceTake-profit for longs, stop-loss for shorts
beloworacle_price <= trigger_priceStop-loss for longs, take-profit for shorts

Conditional orders are always reduce-only with zero reserved margin. When triggered, they execute as market orders.

6.8 Cancel conditional order

Conditional orders are identified by (pair_id, trigger_direction), not by order ID.

Cancel a single conditional order:

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "trade": {
        "cancel_conditional_order": {
          "one": {
            "pair_id": "perp/btcusd",
            "trigger_direction": "above"
          }
        }
      }
    },
    "funds": {}
  }
}

Cancel all conditional orders for a specific pair:

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "trade": {
        "cancel_conditional_order": {
          "all_for_pair": {
            "pair_id": "perp/btcusd"
          }
        }
      }
    },
    "funds": {}
  }
}

Cancel all conditional orders:

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "trade": {
        "cancel_conditional_order": "all"
      }
    },
    "funds": {}
  }
}

6.9 Liquidate (permissionless)

Force-close all positions of an undercollateralized user. This message can be sent by anyone (liquidation bots):

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "maintain": {
        "liquidate": {
          "user": "0x5678...ef01"
        }
      }
    },
    "funds": {}
  }
}

The transaction reverts if the user is not below the maintenance margin. Unfilled positions are ADL’d against counter-parties at the bankruptcy price. For mechanics, see Liquidation & ADL.

7. Vault operations

The counterparty vault provides liquidity for the exchange. Users can deposit margin into the vault to earn trading fees, and withdraw with a cooldown period.

7.1 Add liquidity

Transfer margin from the trading account to the vault:

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "vault": {
        "add_liquidity": {
          "amount": "1000.000000",
          "min_shares_to_mint": "900000"
        }
      }
    },
    "funds": {}
  }
}
FieldTypeDescription
amountUsdValueUSD margin amount to transfer to the vault
min_shares_to_mintUint128?Revert if fewer shares are minted (slippage guard)

Shares are minted proportionally to the vault’s current NAV. For vault mechanics, see Vault.

7.2 Remove liquidity

Request a withdrawal from the vault (initiates cooldown):

{
  "execute": {
    "contract": "PERPS_CONTRACT",
    "msg": {
      "vault": {
        "remove_liquidity": {
          "shares_to_burn": "500000"
        }
      }
    },
    "funds": {}
  }
}
FieldTypeDescription
shares_to_burnUint128Number of shares to burn

Shares are burned immediately. The corresponding USD value enters a cooldown queue. After vault_cooldown_period elapses, funds are automatically credited back to the user’s trading margin.

8. Real-time subscriptions

All subscriptions use the WebSocket transport described in §1.2.

8.1 Perps candles

Stream OHLCV candlestick data for a perpetual pair:

subscription {
  perpsCandles(pairId: "perp/btcusd", interval: ONE_MINUTE) {
    pairId
    interval
    open
    high
    low
    close
    volume
    volumeUsd
    timeStart
    timeStartUnix
    timeEnd
    timeEndUnix
    minBlockHeight
    maxBlockHeight
  }
}

Pushes updated candle data as new trades occur. Fields match the PerpsCandle type in §4.7.

8.2 Perps trades

Stream real-time trade fills for a pair:

subscription {
  perpsTrades(pairId: "perp/btcusd") {
    orderId
    pairId
    user
    fillPrice
    fillSize
    closingSize
    openingSize
    realizedPnl
    fee
    createdAt
    blockHeight
    tradeIdx
    isMaker
  }
}

Behavior: On connection, cached recent trades are replayed first, then new trades stream in real-time.

FieldTypeDescription
orderIdStringOrder ID that produced this fill
pairIdStringTrading pair
userStringAccount address
fillPriceStringExecution price
fillSizeStringFilled size (positive = buy, negative = sell)
closingSizeStringPortion that closed existing position
openingSizeStringPortion that opened new position
realizedPnlStringPnL realized on this fill, including funding settled on the pre-existing position; excludes trading fees
feeStringTrading fee charged
createdAtStringTimestamp (ISO 8601)
blockHeightIntBlock in which the trade occurred
tradeIdxIntIndex within the block
isMakerBoolean?True for the maker side of a match, false for the taker side; null for trades executed before v0.16.0

8.3 Contract query polling

Poll any contract query at a regular block interval:

subscription {
  queryApp(
    request: {
      wasm_smart: {
        contract: "PERPS_CONTRACT",
        msg: {
          user_state: {
            user: "0x1234...abcd"
          }
        }
      }
    },
    blockInterval: 5
  ) {
    response
    blockHeight
  }
}
ParameterTypeDefaultDescription
requestGrugQueryInputAny valid queryApp request
blockIntervalInt10Push updates every N blocks

Common use cases:

  • User state — monitor margin, positions, and order counts.
  • Order book depth — track bid/ask levels.
  • Pair states — monitor open interest and funding.

8.4 Block stream

Subscribe to new blocks as they are finalized:

subscription {
  block {
    blockHeight
    hash
    appHash
    createdAt
  }
}

8.5 Event stream

Subscribe to events with optional filtering:

subscription {
  events(
    sinceBlockHeight: 100000,
    filter: [
      {
        type: "order_filled",
        data: [
          {
            path: ["user"],
            checkMode: EQUAL,
            value: ["0x1234...abcd"]
          }
        ]
      }
    ]
  ) {
    type
    method
    eventStatus
    data
    blockHeight
    createdAt
  }
}
Filter fieldTypeDescription
typeStringEvent type name
data[FilterData]Conditions on the event’s JSON data
path[String]JSON path to the field
checkModeCheckValueEQUAL (exact match) or CONTAINS (substring)
value[JSON]Values to match against

9. Events reference

The perps contract emits the following events. These can be queried via perpsEvents (§5.5) or streamed via the events subscription (§8.5).

Margin events

EventFieldsDescription
depositeduser, amountMargin deposited
withdrewuser, amountMargin withdrawn

Vault events

EventFieldsDescription
liquidity_addeduser, amount, shares_mintedLP deposited to vault
liquidity_unlockinguser, amount, shares_burned, end_timeLP withdrawal initiated (cooldown)
liquidity_releaseduser, amountCooldown completed, funds released

Order events

EventFieldsDescription
order_filledorder_id, pair_id, user, fill_price, fill_size, closing_size, opening_size, realized_pnl, realized_funding?, fee, client_order_id?, fill_id?, is_maker?Order partially or fully filled
order_persistedorder_id, pair_id, user, limit_price, size, client_order_id?Limit order placed on book
order_removedorder_id, pair_id, user, reason, client_order_id?Order removed from book

client_order_id is null if the order was submitted without one. Off-chain consumers can use it to correlate fills, persistence, and removal with the originally-submitted client id.

fill_id groups the two sides of a single order-book match. When a taker crosses a resting maker, two order_filled events are emitted — one for each side — and both carry the same fill_id. Successive matches use consecutive ids (strictly increasing), so a taker that crosses two makers in the same transaction produces four events with two distinct fill_id values. fill_id is null for trades executed before v0.15.0 — fill IDs were not assigned prior to that release. Not emitted for ADL fills, which use the deleveraged and liquidated events instead.

is_maker is true for the maker side of a match and false for the taker side. Within a single match’s pair of order_filled events (sharing one fill_id), exactly one carries is_maker = true and one carries is_maker = false. is_maker is null for trades executed before v0.16.0 — the maker/taker flag was not recorded prior to that release.

realized_pnl on order_filled and deleveraged (and adl_realized_pnl on liquidated) reports the closing PnL on the fill — price movement on the closed portion. The funding settled on the user’s pre-existing position immediately before the fill is reported separately as realized_funding (or adl_realized_funding on liquidated).

realized_funding is null for events emitted before v0.17.0 — funding was bundled into realized_pnl (and adl_realized_pnl) prior to that release. From v0.17.0 onward the field is always present and realized_pnl + realized_funding equals the pre-v0.17.0 lump sum.

Trading fees are reported separately in the fee field on order_filled; ADL and deleverage fills incur no trading fees.

Conditional order events

EventFieldsDescription
conditional_order_placedpair_id, user, trigger_price, trigger_direction, size, max_slippageTP/SL order created
conditional_order_triggeredpair_id, user, trigger_price, trigger_direction, oracle_priceTP/SL triggered by price move
conditional_order_removedpair_id, user, trigger_direction, reasonTP/SL removed

Liquidation events

EventFieldsDescription
liquidateduser, pair_id, adl_size, adl_price, adl_realized_pnl, adl_realized_funding?Position liquidated in a pair
deleverageduser, pair_id, closing_size, fill_price, realized_pnl, realized_funding?Counter-party hit by ADL
bad_debt_coveredliquidated_user, amount, insurance_fund_remainingInsurance fund absorbed bad debt

ReasonForOrderRemoval

ValueDescription
filledOrder fully filled
canceledUser voluntarily canceled
position_closedPosition was closed (conditional orders only)
self_trade_preventionOrder crossed user’s own order on the opposite side
liquidatedUser was liquidated
deleveragedUser was hit by auto-deleveraging
slippage_exceededConditional order triggered but could not fill within slippage
price_band_violationResting price drifted outside the per-pair band before match
slippage_cap_tightenedConditional order’s stored max_slippage now exceeds the pair’s tightened cap

For liquidation and ADL mechanics, see Liquidation & ADL.

10. Types reference

10.1 Numeric types

All numeric types are signed fixed-point decimals with 6 decimal places, built on dango_types::Number. They are serialized as strings:

Type aliasDimensionExample usageExample value
Dimensionless(pure scalar)Fee rates, margin ratios, slippage"0.050000"
QuantityquantityPosition size, order size, OI"-0.500000"
UsdValueusdMargin, PnL, notional, fees"10000.000000"
UsdPriceusd / quantityOracle price, limit price, entry price"65000.000000"
FundingPerUnitusd / quantityCumulative funding accumulator"0.000123"
FundingRateper dayFunding rate cap"0.000500"

Additional integer types:

TypeEncodingDescription
Uint128StringLarge integer (e.g. vault shares)
u64Number or StringGas limit, timestamps
u32NumberUser index, account index, nonce

10.2 Identifiers

TypeFormatExample
PairIdperp/<base><quote>"perp/btcusd", "perp/ethusd"
OrderIdUint64 (string)"42"
ConditionalOrderIdUint64 (shared counter)"43"
ClientOrderIdUint64 (caller-assigned)"42"
FillIdUint64 (per-match identifier)"17"
AddrHex address"0x1234...abcd"
Hash25664-char uppercase hex"A1B2C3D4E5F6..."
UserIndexu320
AccountIndexu321
Username1–15 chars, [a-z0-9_]"alice"
TimestampSeconds since epoch (decimal)"1700000000.123456789"
DurationSeconds (decimal)"3600" (1 hour)

Timestamp and Duration are encoded as fixed-point decimal strings with up to 9 fractional digits (nanosecond precision); trailing zeros are elided. So "1700000000", "1700000000.5", and "1700000000.123456789" are all valid Timestamp values.

10.3 Enums

OrderKind:

{
  "market": {
    "max_slippage": "0.010000"
  }
}
{
  "limit": {
    "limit_price": "65000.000000",
    "time_in_force": "GTC",
    "client_order_id": "42"
  }
}

client_order_id is optional. Defaults to null when omitted; not allowed with time_in_force: "IOC".

TimeInForce: "GTC" | "IOC" | "POST" (defaults to "GTC" if omitted)

TriggerDirection:

"above"
"below"

CancelOrderRequest:

{
  "one": "42"
}
{
  "one_by_client_order_id": "42"
}
"all"

SubmitOrderRequest:

{
  "pair_id": "perp/btcusd",
  "size": "-0.500000",
  "kind": {
    "limit": {
      "limit_price": "65000.000000",
      "time_in_force": "GTC",
      "client_order_id": "42"
    }
  },
  "reduce_only": false,
  "tp": null,
  "sl": null
}

Same shape used by submit_order and each submit entry in batch_update_orders.

SubmitOrCancelOrderRequest:

{ "submit": { /* SubmitOrderRequest */ } }
{ "cancel": { "one": "42" } }
{ "cancel": "all" }

One action inside a batch_update_orders list. Conditional (TP/SL) orders are not supported.

CancelConditionalOrderRequest:

{
  "one": {
    "pair_id": "perp/btcusd",
    "trigger_direction": "above"
  }
}
{
  "all_for_pair": {
    "pair_id": "perp/btcusd"
  }
}
"all"

Key:

{
  "secp256r1": "<base64>"
}
{
  "secp256k1": "<base64>"
}
{
  "ethereum": "0x1234...abcd"
}

Credential:

{
  "standard": {
    "key_hash": "...",
    "signature": { ... }
  }
}
{
  "session": {
    "session_info": { ... },
    "session_signature": "...",
    "authorization": { ... }
  }
}

CandleInterval (GraphQL enum):

ONE_SECOND | ONE_MINUTE | FIVE_MINUTES | FIFTEEN_MINUTES | ONE_HOUR | FOUR_HOURS | ONE_DAY | ONE_WEEK

10.4 Response types

Param (global parameters) — see §4.1 for all fields.

PairParam (per-pair parameters) — see §4.3 for all fields.

PairState:

FieldTypeDescription
long_oiQuantityTotal long open interest
short_oiQuantityTotal short open interest
funding_per_unitFundingPerUnitCumulative funding accumulator
funding_rateFundingRateCurrent per-day funding rate (positive = longs pay)

State (global state) — see §4.2 for all fields.

UserState — see §5.1 for all fields.

UserStateExtended — see §5.6 for all fields.

Position:

FieldTypeDescription
sizeQuantityPositive = long, negative = short
entry_priceUsdPriceAverage entry price
entry_funding_per_unitFundingPerUnitFunding accumulator at last update
conditional_order_aboveConditionalOrder|nullTP/SL that triggers when oracle >= trigger_price
conditional_order_belowConditionalOrder|nullTP/SL that triggers when oracle <= trigger_price

ConditionalOrder (embedded in Position):

FieldTypeDescription
order_idOrderIdInternal ID for price-time priority
sizeQuantity|nullSize to close (sign opposes position); null closes entire position
trigger_priceUsdPriceOracle price that activates this order
max_slippageDimensionlessSlippage tolerance for the market order at trigger

ChildOrder (TP/SL attached to a parent order):

FieldTypeDescription
trigger_priceUsdPriceOracle price that activates this order
max_slippageDimensionlessSlippage tolerance for the market order at trigger
sizeQuantity|nullSize to close (sign opposes position); null closes entire position

Unlock:

FieldTypeDescription
end_timeTimestampWhen cooldown completes
amount_to_releaseUsdValueUSD value to release

QueryOrderResponse:

FieldTypeDescription
userAddrOrder owner
pair_idPairIdTrading pair
sizeQuantityOrder size
limit_priceUsdPriceLimit price
reduce_onlyboolWhether the order only reduces an existing position
reserved_marginUsdValueMargin reserved for this order
created_atTimestampCreation time

LiquidityDepthResponse:

FieldTypeDescription
bidsMap<UsdPrice, LiquidityDepth>Bid-side depth by price
asksMap<UsdPrice, LiquidityDepth>Ask-side depth by price

LiquidityDepth:

FieldTypeDescription
sizeQuantityAbsolute order size in bucket
notionalUsdValueUSD notional (size × price)

User (account factory):

FieldTypeDescription
indexUserIndexUser’s numerical index
nameUsernameUser’s username
accountsMap<AccountIndex, Addr>Accounts owned (index → address)
keysMap<Hash256, Key>Associated keys (hash → key)

Account:

FieldTypeDescription
indexAccountIndexAccount’s unique index
ownerUserIndexOwning user’s index

11. Constants

Endpoints

NetworkHTTPWebSocket
Mainnethttps://api-mainnet.dango.zone/graphqlwss://api-mainnet.dango.zone/graphql
Testnethttps://api-testnet.dango.zone/graphqlwss://api-testnet.dango.zone/graphql

Chain IDs

NetworkChain ID
Mainnetdango-1
Testnetdango-testnet-1

Contract addresses

NameMainnetTestnet
ACCOUNT_FACTORY_CONTRACT0x18d28bafcdf9d4574f920ea004dea2d13ec16f6b0x18d28bafcdf9d4574f920ea004dea2d13ec16f6b
ORACLE_CONTRACT0xcedc5f73cbb963a48471b849c3650e6e34cd3b6d0xcedc5f73cbb963a48471b849c3650e6e34cd3b6d
PERPS_CONTRACT0x90bc84df68d1aa59a857e04ed529e9a26edbea4f0xf6344c5e2792e8f9202c58a2d88fbbde4cd3142f

Code hashes

NameValue
Single-signature accountd86e8112f3c4c4442126f8e9f44f16867da487f29052bf91b810457db34209a4

The code hash is the same on mainnet and testnet.