Transaction Signing
This doc describes the underlying methodology and process of handling transaction signing.
Official SDK coming soon for various common programming languages.
signature is not a signature over the JSON text. The write path reconstructs the canonical unsigned transaction payload from action, nonce, agent_epoch, and expires_after_ms, then verifies the recoverable secp256k1 signature over that exact binary payload. For order and modify actions, public JSON price and quantity are display decimals; the signed binary action contains the raw atoms obtained from market price_decimals and base_quantity_decimals. The JSON action and signed binary action must describe the same action.
Canonical unsigned payload:
string("NATIVE_CORE_TX_SIGNING_V1")
u32(1) // tx codec version
u32(696969) // Native Core chain id
u64(nonce)
option<u64>(agent_epoch)
option<u64>(expires_after_ms)
action_bytesEncoding rules:
Integers are unsigned big-endian.
stringand byte vectors encode asu32 byte_lengthfollowed by raw bytes.option<T>encodes asu8(0)fornull/omitted, oru8(1)followed byT.Hex values are raw bytes after removing the
0xprefix.The signing digest is
keccak256(unsigned_payload_bytes).signatureis0x+ 65 bytes encoded asr || s || v;vmay be0/1or27/28. High-s signatures are rejected.
Public action tags:
order
0
Top-level order.
cancel with oid
2
If both oid and cloid are present, oid wins.
cancel with only cloid
4
Canonical cancel-by-cloid.
modify with oid
6
If both oid and cloid are present, oid wins.
modify with only cloid
8
Canonical modify-by-cloid.
cancelAll
26
Cancel every open order for the effective owner in one market. No cloid.
batch
18
Batch item tags are order=0, cancel by oid=1, modify by oid=2, modify by cloid=3, cancel by cloid=4, cancelAll=5.
Order action bytes:
Cancel action bytes:
Modify action bytes:
replacement_order uses the same fields as order after market_id:
CancelAll action bytes:
cancelAll carries no oid and no cloid. The signed payload is a single u32 market id; the JSON body must match ({ "type": "cancelAll", "market_id": "<id>" }).
Batch action bytes:
Each batch item starts with a u8 item tag followed by the item payload without the top-level u16 action tag:
TypeScript signing helper example for Node.js with ethers. The example uses Node's global Buffer; import it from node:buffer only if your TypeScript setup requires explicit Buffer types.
Accepted response:
Rejected response:
Rejected request-local validation responses usually have no tx_hash because canonical bytes were not assembled. Rejections after canonical byte assembly include tx_hash. Rate-limit responses also include error.retry_after_ms. If node admission cannot be reached, the response is submission_status: "timeout" with an error.code beginning with node_unreachable: .
Malformed JSON, unknown fields, invalid action type tags, invalid field types, negative numeric strings, and malformed decimal strings can fail in Axum's JSON extractor before handle_trade runs. Those pre-handler responses are not the TradeResponse shape above and do not guarantee error.code.
/trade error codes
/trade error codesRequest-shaping errors:
must provide action + nonce + signature
One or more required envelope fields were omitted.
market_metadata_unavailable
The write path needed market decimal metadata from node query state, but the metadata query failed or returned an invalid shape.
asset_metadata_unavailable
The write path needed asset decimal metadata from node query state, but the metadata query failed or returned an invalid shape.
unknown_market
The request referenced a market absent from refreshed market metadata.
unknown_asset
The request referenced an asset absent from refreshed asset metadata.
invalid_market_id
A market id was a valid u64 JSON value but exceeded the protocol u32 range.
invalid_asset_id
An asset id was a valid u64 JSON value but exceeded the protocol u32 range.
invalid_base_asset_id
openMarket.base_asset_id exceeded the protocol u32 range.
invalid_quote_asset_id
openMarket.quote_asset_id exceeded the protocol u32 range.
invalid_account_address
An admin credit/freeze account was not a 20-byte hex owner address.
invalid_cloid
A cloid was not a 16-byte hex value.
invalid_side
side was not bid, ask, buy, or sell.
invalid_order_type
order_type was not limit or market.
invalid_tif
tif was not gtc, ioc, fok, or alo.
missing_oid_or_cloid
A cancel or modify target omitted both oid and cloid. Does not apply to cancelAll, which carries no oid/cloid.
invalid_price
price was numerically too large to parse as decimal conversion input. Malformed decimal strings are rejected before this response shape.
invalid_price_precision
price had more fractional digits than the market's price_decimals.
invalid_price_overflow
Decimal-to-atom conversion for price overflowed u64.
invalid_quantity
quantity was numerically too large to parse as decimal conversion input. Malformed decimal strings are rejected before this response shape.
invalid_quantity_precision
quantity had more fractional digits than the market's base_quantity_decimals.
invalid_quantity_overflow
Decimal-to-atom conversion for quantity overflowed u64.
invalid_min_quantity
setQuoteAssetMinQuantity.min_quantity converted to zero or could not be parsed for the conversion path.
invalid_min_quantity_precision
min_quantity had more fractional digits than the target asset's balance_decimals.
invalid_min_quantity_overflow
Decimal-to-atom conversion for min_quantity overflowed u64.
invalid_credit_usd
creditUsd was numerically too large to parse as USD decimal conversion input. Malformed decimal strings are rejected before this response shape.
invalid_credit_usd_precision
creditUsd had more than 8 fractional digits.
invalid_credit_usd_overflow
Decimal-to-atom conversion for creditUsd overflowed u64.
invalid_signature_hex
signature was not hex or did not decode to exactly 65 bytes.
encode_error: <TxCodecError>
The write path could not assemble canonical signed tx bytes, for example because a batch length exceeded codec limits.
empty_tx_bytes
Defensive guard: canonical byte assembly produced an empty byte vector. This should not occur for normal JSON requests.
decode_error: <TxDecodeError>
The write path assembled bytes but could not decode/recover the signer from them. For public JSON this is the usual shape for a malformed or unrecoverable 65-byte signature.
UnauthorizedAdmin
An admin/operator action was submitted with an agent signature or by a signer not configured as an admin.
RateLimited
The signer exceeded the per-signer request rate. The response includes retry_after_ms.
node_unreachable: <tonic error>
The submit path could not complete node admission. The HTTP status is 504 and submission_status is "timeout".
Node admission pass-through errors:
QueryLagBackpressure
The node's query view is missing or more than four blocks behind execution; retry the same signed request after projection catches up.
DuplicateTxHash
The same transaction hash is already pending in ingress.
DuplicateSignerNonce
The same signer/nonce pair is already pending in ingress.
MalformedTx
The node could not decode canonical transaction bytes. Public JSON normally fails earlier if bytes cannot be built.
BadSignature
The node could not recover a signer from the canonical transaction signature. Public JSON normally fails earlier during signer recovery.
SignerHintMismatch
The node recovered a different signer than the submit-path signer hint. This indicates the hint did not match the canonical transaction signer.
WrongChainId
The signed payload's chain id did not match the node's configured chain id.
TooManyPending
Global pending capacity or per-owner pending capacity was reached.
InvalidIngressConfig
The node ingress configuration was invalid.
DirectSignerIsActiveAgent
A signer currently registered as an active agent attempted direct-owner mode.
OwnerDoesNotExist
Direct-owner admission resolved to an owner account that does not exist.
UnknownAgent
Agent-mode submission used a signer that is not an active agent.
AgentEpochMismatch
Agent-mode submission used an epoch that does not match the active agent slot epoch.
AgentActionNotAllowed
Agent-mode submission attempted an action kind not allowed for agent signatures.
OracleUnavailable
A non-cancel SpotCreditAccount action was submitted while the oracle status was unavailable.
SpotCreditAccountFrozen
A non-cancel action was submitted for a frozen SpotCreditAccount.
AccountNotFunded
Balance-mode precheck found no balance row for the asset required by the order reserve.
InsufficientSpotBalance
Balance-mode precheck found clearly insufficient available balance for the order reserve.
MinTradeSpotNtl
An order, modify replacement, or batch order/replacement was below the current quote asset minimum notional. Market orders use their submitted protection price for this precheck.
InsufficientSpotCredit
Spot-credit precheck showed the single-order risk leg would take available credit below zero.
DuplicateCloid
The submitted order or modify replacement cloid is already open for the same owner and market, or the tx contains duplicate (market_id, cloid) intents.
MarketNotFound
The latest acceptable QueryView has no referenced market.
OracleMarkPriceMissing
A SpotCreditAccount order path needs a fresh base or quote mark price that is absent or stale.
AssetNotFound
An admin metadata action referenced an asset absent from the latest acceptable QueryView.
DuplicateAsset
addAsset referenced an asset id that already exists.
DuplicateSymbol
addAsset referenced a symbol that already exists case-insensitively.
InvalidAssetConfig
addAsset carried invalid asset metadata, such as an invalid symbol or unsupported balance decimals.
InvalidIssuer
addAssetWithIssuer was rejected: the issuer is the zero address or is currently an active agent signer.
InvalidMarketPair
openMarket carried an invalid pair or precision configuration.
InvalidQuoteAsset
The requested quote asset is not allowlisted for quote usage or has invalid quote-min metadata.
DuplicateMarket
openMarket would duplicate an existing market pair or id.
MarketLimitExceeded
Market registry capacity was reached. This is a node admission code but is not currently emitted by the QueryView precheck path.
Overflow
Admission precheck arithmetic or id allocation overflowed.
Execution remains authoritative, so an accepted response means the tx entered the node pipeline; it may still later execute as a failed transaction.
Supported top-level action types:
ordercancelcancelAllmodifybatch
Last updated