Proof transactions are self-contained binary envelopes: a MessagePack array carrying the action type, a timestamp nonce, a MessagePack-encoded payload, the signer’s Ed25519 public key, and a 64-byte signature. TheDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/Proof-labs/trading-sdk/llms.txt
Use this file to discover all available pages before exploring further.
ExchangeClient assembles and submits these automatically, but the SDK also exports the raw codec layer so you can build envelopes by hand, inspect bytes in flight, bridge to an air-gapped signing device, or verify byte-for-byte compatibility with the Rust reference implementation.
Codec exports
The following functions are exported directly from@proof/trading-sdk:
| Function | Source module | Signature | Purpose |
|---|---|---|---|
signAndEncode | codec | (chainId, action, seq, privateKey) => Uint8Array | Build a complete signed wire envelope |
encodeSignedTx | codec | (action, seq, pubkey, signature) => Uint8Array | Assemble a wire envelope from a pre-computed pubkey and signature |
encodePayloadBytes | codec | (action: Action) => Uint8Array | Encode only the action payload, with no signature wrapper |
signEnvelopeFromPayload | codec | (chainId, actionType, seq, payloadBytes, privateKey) => Uint8Array | Sign and wrap pre-encoded payload bytes — useful for cross-language conformance |
decodeTx | codec | (bytes: Uint8Array) => { version, action, seq, pubkey, signature } | Decode and verify the structure of a wire envelope |
peekActionType | codec | (bytes: Uint8Array) => ActionTypeValue | null | Read the action type byte without a full decode |
chainIdFromString | crypto | (chainId: string) => Uint8Array | Hash a chain ID string to 32 bytes without a network call |
fetchChainId | client | (rpcUrl: string) => Promise<Uint8Array> | Resolve a 32-byte chain ID from CometBFT /status over the network |
Complete offline signing flow
txBytes is ready to POST directly to the gateway’s /exchange endpoint or to broadcast_tx_sync on CometBFT. ExchangeClient.submitTx() does exactly this under the hood.
Wire envelope format
The V2 envelope is a MessagePack positional array with six fields:2 (the envelope version). The payload bytes at index 3 are themselves a MessagePack array whose element order matches the Rust struct field declaration order in exchange-core. Field ordering is the wire contract — inserting or reordering a field is a breaking change.
V3 signing message layout
The signature covers a deterministic byte string assembled bysigningMessage() in src/crypto.ts:
| Segment | Size | Value |
|---|---|---|
DOMAIN_PREFIX | 16 bytes | ASCII string "ProofExchange-v3" |
chain_id | 32 bytes | keccak256(cometbft_chain_id_string) |
action_type | 1 byte | Wire action type constant (e.g. 0x01) |
seq_be | 8 bytes | Timestamp nonce as big-endian uint64 |
payload | variable | MessagePack-encoded action payload |
v2 to v3 on 2026-04-23 when the 32-byte chain_id binding was introduced. A signature produced under the old prefix fails verification against a v3 engine, preventing cross-version replays.
Chain ID binding
chainIdFromString computes keccak256(UTF-8(chainIdString)) — identical to the Rust chain_id_from_string in crypto.rs. You can derive the chain ID entirely offline as long as you know the CometBFT chain ID string:
fetchChainId is a convenience wrapper that calls /status on the CometBFT RPC endpoint, parses the node_info.network field, and passes it through chainIdFromString. Use it when you want to avoid hardcoding the chain ID, but the two paths are byte-for-byte equivalent for a known chain.
UNBOUND_CHAIN_ID
The SDK exports a 32-byte all-zeros constant calledUNBOUND_CHAIN_ID:
Nonce (seq) semantics
Theseq field is a millisecond Unix timestamp. Use BigInt(Date.now()) and ensure nonces are strictly increasing across calls:
seq against a per-account sliding window. Duplicate or stale nonces are rejected with error code 21 (InvalidNonce). The SDK’s ExchangeClient manages this allocation automatically; you only need to track it yourself when calling signAndEncode directly.
Relaying pre-signed bytes
Once you hold atxBytes buffer you can relay it to any compatible gateway without possessing the private key: