POST /order require Ed25519 signatures for authentication.
JSON vs Binary Formats
The API accepts transactions as JSON with base58-encoded cryptographic fields:| Field | JSON Format | Example |
|---|---|---|
account | base58 string | "9J8TUdEWrrcADK913r1Cs7DdqX63VdVU88imfDzT1ypt" |
signer | base58 string | "9J8TUdEWrrcADK913r1Cs7DdqX63VdVU88imfDzT1ypt" |
signature | base58 string | "5j7sVt3k2YxPqH4w..." |
Order IDs (oid) | base58 string | "Fpa3oVuL3UzjNANAMZZdmrn6D1Zhk83GmBuJpuAWG51F" |
Pubkeys (u, a, target) | base58 string | "8DmyR3yJhpQHBqgSGua4c69PZ9ZMeaJddTumUdmTx7a" |
What Gets Signed
The signature is computed over:signeris required in the JSON payload but is not part of the signed message bytes.- If
signer != account, the signer must be an authorized agent for that account. account_pubkey_bytesmeans raw 32 bytes (bs58.decode(account)).- The
signaturefield itself is not included in what gets signed.
Nonce: Use a unique value for replay protection (e.g. timestamp in nanoseconds:
BigInt(Date.now()) * 1_000_000n or an incrementing counter).Transaction Structure
All signed requests toPOST /order use this unified envelope:
| Field | Description |
|---|---|
actions | Array of actions to execute atomically (see Place Order and related pages). |
nonce | Unique integer for replay protection (use timestamp in nanoseconds or incrementing counter). |
account | Account public key (base58) — the account performing the action. |
signer | Signer public key (base58) — who is signing (usually same as account, or authorized agent). |
signature | Ed25519 signature (base58). |
Action Encoding
bincode_serialize(actions) must match the canonical BULK protocol encoding. On the wire, wincode and bincode are the same (bincode-compatible; see wincode for the Rust crate). For production use, use the official signing library so the signed bytes match the server.
For order actions (l / m), px and sz use fixed-point binary encoding:
Official Signing Library (bulk-keychain)
The recommended way to sign transactions is the official bulk-keychain library. One Rust core with bindings for:| Environment | Package | Install |
|---|---|---|
| Node.js | bulk-keychain | npm install bulk-keychain |
| Browser (Web) | bulk-keychain-wasm | npm install bulk-keychain-wasm |
| Python | bulk-keychain | pip install bulk-keychain |
| Rust | bulk-keychain | cargo add bulk-keychain |
Reference: Serialization Format (wincode)
The following describes a logical binary format used in some implementations. The authoritative signing message isbincode_serialize(actions) + nonce_le_u64 + account_pubkey_bytes (no signer); the BULK protocol and bulk-keychain use the canonical encoding.
| Type | Encoding |
|---|---|
| Enum variant | u32 discriminant (0, 1, 2…) |
| Pubkey/Hash | Raw 32 bytes (decoded from base58) |
| Signature | Raw 64 bytes |
| String | u64 length prefix + UTF-8 bytes |
| Option<T> | 1 byte (0=None, 1=Some) + T if Some |
| Vec<T> | u64 count + elements |
| bool | 1 byte (0 or 1) |
| u64/f64 | 8 bytes little-endian |
| u32 | 4 bytes little-endian |
Enum Discriminant Mappings
Signing Example (canonical message)
The signed message must be exactly:Working Example (reference implementation)
Install Dependencies
Implementation
Binary Layout Reference
The prescribed signing message isbincode_serialize(actions) + nonce_le_u64 + account_pubkey_bytes (no signer). The layouts below describe a logical equivalent; the BULK protocol defines the canonical bincode for actions.
Order Action Binary Layout (logical equivalent)
Faucet Action Binary Layout
Agent Wallet Action Binary Layout
Update User Settings Binary Layout
Account vs Signer
The transaction includes two separate public key fields:account: The account being traded (whose positions/orders are affected)signer: Who’s signing the transaction (usually same as account, or authorized agent)
Same Account and Signer
Most common case: you’re trading your own account.Agent Wallet (Different Signer)
Agent wallet trading on behalf of user:Implementation Notes
Common Issues
Invalid signature
Invalid signature
The signed bytes must be exactly
bincode_serialize(actions) + nonce_le_u64 + account_pubkey_bytes. Do not include signer or signature in the signed message.Unauthorized signer
Unauthorized signer
Account not found
Account not found
Account must be funded first via a faucet action (e.g. Request Faucet on testnet).
Replay attack / duplicate nonce
Replay attack / duplicate nonce
Each nonce can only be used once. Use nanosecond timestamps or incrementing counters.