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.

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 (nanoseconds since epoch); 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": "0102...40hex"
      }
    }
  }
}
  • sig: 64-byte Secp256r1 signature (hex-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": "0102...40hex"
    }
  }
}
  • 64-byte Secp256k1 signature (hex-encoded)

EIP-712 (Ethereum wallets):

{
  "standard": {
    "key_hash": "a1b2c3d4...64hex",
    "signature": {
      "eip712": {
        "typed_data": "<base64>",
        "sig": "0102...41hex"
      }
    }
  }
}
  • sig: 65-byte signature (64-byte Secp256k1 + 1-byte recovery ID; hex-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": "02abc...33bytes",
      "expire_at": "1700000000000000000"
    },
    "session_signature": "0102...40hex",
    "authorization": {
      "key_hash": "a1b2c3d4...64hex",
      "signature": { ... }
    }
  }
}
FieldTypeDescription
session_infoSessionInfoSession key public key + expiration
session_signatureByteArray<64>SignDoc signed by the session key (hex-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": "02abc123...33bytes_hex"
            },
            "key_hash": "a1b2c3d4...64hex",
            "seed": 12345,
            "signature": {
              "passkey": {
                "authenticator_data": "<base64>",
                "client_data": "<base64>",
                "sig": "0102...40hex"
              }
            }
          }
        },
        "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 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": "03def456...33bytes_hex"
          }
        }
      }
    },
    "funds": {}
  }
}

Remove a key:

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

3.4 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.5 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.6 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: {
    wasmSmart: {
      contract: "PERPS_CONTRACT",
      msg: {
        param: {}
      }
    }
  })
}

Response:

{
  "max_unlocks": 5,
  "max_open_orders": 50,
  "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": "3600000000000",
  "vault_total_weight": "10.000000",
  "vault_cooldown_period": "604800000000000",
  "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)
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 (nanoseconds)
vault_total_weightDimensionlessSum of all pairs’ vault liquidity weights
vault_cooldown_periodDurationWaiting time before vault withdrawal release (nanoseconds)
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: {
    wasmSmart: {
      contract: "PERPS_CONTRACT",
      msg: {
        state: {}
      }
    }
  })
}

Response:

{
  "last_funding_time": "1700000000000000000",
  "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: {
    wasmSmart: {
      contract: "PERPS_CONTRACT",
      msg: {
        pair_params: {
          start_after: null,
          limit: 30
        }
      }
    }
  })
}

Single pair:

query {
  queryApp(request: {
    wasmSmart: {
      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",
  "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
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: {
    wasmSmart: {
      contract: "PERPS_CONTRACT",
      msg: {
        pair_states: {
          start_after: null,
          limit: 30
        }
      }
    }
  })
}

Single pair:

query {
  queryApp(request: {
    wasmSmart: {
      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: {
    wasmSmart: {
      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

5. User state and orders

5.1 User state

query {
  queryApp(request: {
    wasmSmart: {
      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: {
    wasmSmart: {
      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: {
    wasmSmart: {
      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": "1700000000000000000"
  }
}

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: {
    wasmSmart: {
      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": "1700000000000000000"
}
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: {
    wasmSmart: {
      contract: "PERPS_CONTRACT",
      msg: {
        volume: {
          user: "0x1234...abcd",
          since: null
        }
      }
    }
  })
}
ParameterTypeDescription
userAddrAccount address
sinceTimestamp?Start time (nanoseconds); 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",
  "fee": "6.500000"
}

5.6 Extended user state

Query user state with additional computed fields (equity and available margin):

query {
  queryApp(request: {
    wasmSmart: {
      contract: "PERPS_CONTRACT",
      msg: {
        user_state_extended: {
          user: "0x1234...abcd",
          include_equity: true,
          include_available_margin: true
        }
      }
    }
  })
}
ParameterTypeDescription
userAddrAccount address
include_equityboolCompute and return the user’s equity
include_available_marginboolCompute and return the user’s free margin

Response:

{
  "raw": {
    "margin": "10000.000000",
    "vault_shares": "0",
    "positions": { ... },
    "unlocks": [],
    "reserved_margin": "500.000000",
    "open_order_count": 2
  },
  "equity": "10250.000000",
  "available_margin": "8625.000000"
}
FieldTypeDescription
rawUserStateThe raw user state (same as §5.1)
equityUsdValue|nullmargin + unrealized PnL − unrealized funding; null if not requested
available_marginUsdValue|nullmargin − initial margin requirements − reserved margin; null if not requested

equity reflects the total account value including unrealized positions. available_margin is the amount the user can withdraw or use for new orders.

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",
              "post_only": false
            }
          },
          "reduce_only": false
        }
      }
    },
    "funds": {}
  }
}
FieldTypeDescription
limit_priceUsdPriceLimit price — must be aligned to tick_size
post_onlyboolIf true, rejected if it would match immediately (maker-only)
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)

Limit orders are GTC (good-till-cancelled). The matching portion fills immediately; any unfilled remainder is stored on the book. Margin is reserved for the unfilled portion.

6.5 Cancel order

Cancel a single order:

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

Cancel all orders:

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

Cancellation releases reserved margin and decrements open_order_count.

6.6 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.7 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.8 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
  }
}

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 from the closing portion
feeStringTrading fee charged
createdAtStringTimestamp (ISO 8601)
blockHeightIntBlock in which the trade occurred
tradeIdxIntIndex within the block

8.3 Contract query polling

Poll any contract query at a regular block interval:

subscription {
  queryApp(
    request: {
      wasmSmart: {
        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, feeOrder partially or fully filled
order_persistedorder_id, pair_id, user, limit_price, sizeLimit order placed on book
order_removedorder_id, pair_id, user, reasonOrder removed from book

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_pnlPosition liquidated in a pair
deleverageduser, pair_id, closing_size, fill_price, realized_pnlCounter-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

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"
AddrHex address"0x1234...abcd"
Hash25664-char hex"a1b2c3d4e5f6..."
UserIndexu320
AccountIndexu321
Username1–15 chars, [a-z0-9_]"alice"
TimestampNanoseconds since epoch (u64)"1700000000000000000"
DurationNanoseconds (u64)"3600000000000" (1 hour)

10.3 Enums

OrderKind:

{
  "market": {
    "max_slippage": "0.010000"
  }
}
{
  "limit": {
    "limit_price": "65000.000000",
    "post_only": false
  }
}

TriggerDirection:

"above"
"below"

CancelOrderRequest:

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

CancelConditionalOrderRequest:

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

Key:

{
  "secp256r1": "02abc123...33bytes_hex"
}
{
  "secp256k1": "03def456...33bytes_hex"
}
{
  "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

These addresses are the same on both mainnet and testnet.

NameAddress
ACCOUNT_FACTORY_CONTRACT0x18d28bafcdf9d4574f920ea004dea2d13ec16f6b
PERPS_CONTRACT0xd04b99adca5d3d31a1e7bc72fd606202f1e2fc69
ORACLE_CONTRACT0xcedc5f73cbb963a48471b849c3650e6e34cd3b6d