Skip to main content
Numo has no API keys. Every order is authenticated by an EIP-712 signature over an Action struct, verified onchain by the Matching contract when the trade settles. markets-service stores the signature and forwards it — it never signs on your behalf. An order request therefore carries three related things:
  1. Flat order fields (side, limit_price, desired_amount, …) that markets-service uses to place you on the book
  2. action_json — the Action struct you signed
  3. signature — your EIP-712 signature over that struct
The service rejects the order if the flat fields and action_json disagree, so the two must be built from the same values.

The Action struct

Action is defined in IActionVerifier and hashed with this type string:
uint256
required
The subaccount placing the order.
uint256
required
Unique per (owner, nonce). The trade module tracks fills against it, so reusing a nonce reuses its fill state.
address
required
The trade module address for your network — see Contract addresses.
bytes
required
ABI-encoded TradeData. See Encoding data.
uint256
required
Unix seconds after which the action can no longer be matched.
address
required
The account that owns the subaccount.
address
required
The address whose key produced the signature. Equal to owner unless you are using a session key.

EIP-712 domain

The domain is fixed by the Matching contract, which is the verifying contract:
Use the chainId and Matching address for the network you are trading on.

Encoding data

Action.data is the ABI encoding of TradeData, defined in ITradeModule: Every field is static, so the encoding is exactly seven 32-byte words — 448 hex characters after the 0x. markets-service rejects any other length with expected encoded TradeData tuple.
limitPrice and desiredAmount are signed int256, but both must be positive. The service rejects zero or negative values.

Price and amount use two different scales

This is the easiest thing to get wrong, and a mismatch produces an order that is accepted but never matches.
  • action_json.data carries the raw onchain values in wei — the numbers the contracts settle on.
  • The order body carries human decimal values. markets-service normalizes them itself: the price is divided by the instrument’s tick size, and the amount by its minimum size.
So a 0.1-contract bid at 1605 is limit_price: "1605" and desired_amount: "0.1" in the body, while data holds 1605000000000000000000 and 100000000000000000.
Sending wei in the body is the classic failure: it parses fine and the order rests on the book at a tick level nothing else will ever cross. Compute the wei values first, then derive the body values with formatUnits(value, 18).

Sign and submit

Then post the same values, with action_json mirroring the struct you signed:

What the service checks

markets-service validates consistency before the order reaches the book. It does not verify your signature — that happens onchain at match time, so a well-formed order with a bad signature is accepted here and fails later. Metadata must agree with action_json:
  • action_json.subaccount_id matches subaccount_id
  • action_json.nonce matches nonce
  • action_json.owner matches owner_address
  • action_json.signer matches signer_address
And the decoded action_json.data must agree with the flat fields:
  • data.asset matches asset_address
  • data.subId matches sub_id
  • data.isBid matches sidetrue for buy, false for sell
  • data.limitPrice is a positive whole multiple of the tick-normalized limit_price
  • data.desiredAmount is a positive whole multiple of the size-normalized desired_amount
These last two are what tie the wei values in data to the human values in the body. Derive one from the other as shown above and they hold automatically.

Session keys

Set signer to a different address than owner to trade from a hot key without exposing the owner key. The session key must be registered onchain first:
Matching then accepts that signer for the owner’s subaccounts until expiry. Deregistering is subject to a 10-minute cooldown (DEREGISTER_KEY_COOLDOWN). Contract signers are supported too — if signer is a contract, verification falls back to ERC-1271.

Contract addresses

Quickstart

See the signed order in the context of the full integration flow.