For the complete documentation index, see llms.txt. This page is also available as Markdown.

Account Multisig

Configure an account-level multisig quorum over your account's owner-signed actions, and submit quorum-signed requests.

An account multisig puts a signer quorum in front of your account's owner-signed actions. Once it is active, moving value or changing who can trade for you takes threshold-of-signers approvals instead of one owner signature.

It does not cover trading. Trading actions continue to be signed by a single API wallet key.

What a quorum can sign

Covered — needs the quorum

transfer · withdraw · settle · repay · approveAgent · revokeAgent · activateFor · setAccountMultisig

Not covered — API wallet, single signature

order · cancel · cancelAll · modify · batch

Trading actions can never be quorum-signed. A request that carries auth_account together with a trading action is rejected with AccountAuthNotAllowedForAction.

Setup order

Enabling a multisig locks the owner key out of trading, so the sequence matters:

  1. Approve an API wallet with an owner-signed approveAgent, and confirm it can place and cancel an order. Do this before step 3 — afterwards the owner key can no longer sign trading actions, and approveAgent itself will need the quorum.

  2. Fund the account with the creation fee — 1,000 USDC or 1,000 USDT — unless a sponsor is paying it.

  3. Submit setAccountMultisig, owner-signed, with creation_fee.

  4. Read accountMultisig and record policy_epoch. Every quorum-signed request from here on carries it, and it advances on each configuration change, so re-read it after any setAccountMultisig.

Calling the API

setAccountMultisig is submitted to POST /trade like any other write, and the configuration is read from POST /info. Examples below use mainnet, API_URL=https://api.native.org; for testnet, substitute https://api-test.native.org and sign with the testnet chain id given on Transaction Signing.

POST /trade is synchronous and answers with the standard envelope — submission_status of accepted, rejected, or timeout, plus tx_hash and an error.code. The full contract, including what timeout means for whether a transaction can still land, is on POST /trade. Every code in Errors below arrives as that error.code.

nonce is a Unix millisecond timestamp and is validated per authority; see Nonces & API Wallets. expires_after_ms is optional on every request here, except a sponsored creation, which requires it.

Reading your configuration

found reports whether the account exists, not whether it has a multisig row — read enabled to decide that. role is non-null only for protocol-operated accounts; for your own account it is always null. policy_epoch is the value every quorum-signed request must carry; it advances on each configuration change.

setAccountMultisig

Creates the account's first configuration, or replaces the current one. The action is the same, but the envelope is not: the first creation is owner-signed with signature, as below; once a configuration is Active, a replacement is a covered action like any other and must be quorum-signed — an owner-signed replacement is rejected with AccountMultisigRequired. A first-ever creation additionally carries creation_fee.

Field
Type
Notes

threshold

number

Approvals required. 1 … signers.length. 0 is rejected.

signers

string[]

1–32 addresses. Must be unique, non-zero, and sorted ascending — an unsorted array is rejected even if the set is correct.

creation_fee

object

Only on a first-ever creation. Omit it when replacing an existing configuration.

cloid

string

Required. 16-byte hex. Used only for txStatusByCloid; it is not an idempotency key.

Field names are snake_case and the payload is strict — an unrecognised field fails the request rather than being ignored. asset_id and amount are decimal strings, not JSON numbers, and amount is in raw atoms with no display decimals.

Creation fee

Charged once, on the first configuration only.

asset_id

Asset

amount (atoms)

Value

"1"

USDC

"100000000000"

1,000 USDC

"2"

USDT

"100000000000"

1,000 USDT

The amount must match one of the rows above exactly — a different value is rejected with InvalidAccountMultisigCreationFee, not rounded or topped up.

selfPaid — the account pays its own fee from its available balance:

sponsored — another account pays it. The sponsor signs a separate EIP-712 AccountMultisigSponsor message; its 65-byte signature rides inside the action:

The sponsor signs its own EIP-712 message, under the quorum-signed domain (version: "2" — see EIP-712 typed data):

targetAccount is the account being configured. expiresAfterMs is the value the request's expires_after_ms field carries — which is why that field is required on a sponsored creation; the sponsor's authorization expires with it. The signature is verified by recovering the signer and requiring it to equal payer. It is not part of the outer SetAccountMultisig digest — signing the outer message does not cover it, and it must be produced separately by the sponsor.

Signer-set restriction

A configuration you submit must not list your own account's key among signers. This applies to both a first-ever creation and any replacement set. Violations are rejected with InvalidAccountMultisigConfig before the nonce is consumed.

Submitting a quorum-signed request

Once a configuration is Active, the covered actions are submitted with an account-auth request: drop signature, and send the collected approvals in signatures along with the two account-auth fields.

Field
Notes

auth_account

The account the quorum acts for. Must be sent with policy_epoch.

policy_epoch

From the accountMultisig read. A stale value is rejected with AccountMultisigEpochMismatch.

signatures

Array of 65-byte hex signatures, at least threshold of them, each from a distinct configured signer. Order matters — see below.

agent_epoch

Must be absent. Account-auth and agent-auth are mutually exclusive.

Every signature covers the same quorum-signed digest, so the signers can sign independently and in any sequence — but the array you submit must be ordered so that the recovered addresses ascend. Collect the approvals in any order, then sort them by recovered signer address before submitting. An out-of-order array is rejected with DecodeMultisigProofSignaturesNotSorted even when every signature is individually valid, and the same signer appearing twice is rejected with DecodeMultisigProofDuplicateRecoveredSigner rather than counting as two approvals.

EIP-712 typed data

Which scheme you sign follows the envelope, not the action.

Owner-signed (the first creation) — the standard v4 scheme: the domain, the six common fields, and the encoding rules are exactly as specified on Transaction Signing, with authKind: 1 and authScope: 0.

Quorum-signed (every account-auth request) — a v5 variant of that scheme. Every approval in a signatures array must be computed this way, and it differs from the v4 scheme in exactly three places:

  • The domain version is "2". Everything else about the domain — the name, the zero verifyingContract, the absence of chainId — is unchanged.

  • The six common fields become seven: authScope is replaced by address authAccount, uint256 policyEpoch, signed with the same values the envelope's auth_account and policy_epoch fields carry:

  • authKind is 2 — including an array carrying a single approval.

The action tail after the common fields is identical under both schemes — for this action and for every other covered action. Written out in full, the v4 primary type for setAccountMultisig is below; for the v5 form, replace the six common fields at its head with the seven above and leave everything from uint256 threshold onward unchanged.

Within that tail the fee is flattened, not nested. feeMode is the discriminator — 0 no fee (a replacement), 1 selfPaid, 2 sponsored — and every unused fee field is zero-valued: address zero for feePayer, 0 for the three numerics.

Errors

Every code below is a fixed CamelCase string returned verbatim as error.code. Match on the exact string; the codes are grouped here by how far the request got, because that determines what you do next.

Request-level — the request is malformed and never reaches execution. submission_status is rejected and no tx_hash is returned:

Error
Meaning

AuthAccountRequiresPolicyEpoch

auth_account sent without policy_epoch.

PolicyEpochRequiresAuthAccount

policy_epoch sent without auth_account.

AgentEpochNotAllowedWithAuthAccount

Both agent-auth and account-auth fields present.

SignaturesNotAllowedForAction

signatures sent without auth_account. This is the usual shape of a forgotten auth_account.

AccountAuthRequiresEip712

auth_account sent without auth_scheme: "eip712".

AccountAuthNotAllowedForAction

Account-auth used with an action a quorum cannot sign — a trading action, most often.

AccountAuthNotSupported

Account multisig is not available on the network you addressed.

InvalidSigner / InvalidPayer / InvalidSponsorSignature

Malformed hex, or a wrong length, in signers, payer, or the sponsor signature.

DecodeMultisigProofSignaturesNotSorted

signatures is not ordered by ascending recovered signer address.

DecodeMultisigProofDuplicateRecoveredSigner

The same signer appears twice in signatures.

InvalidSignaturesLen

signatures is empty, or holds more than 32 entries.

DecodeMultisigProofRecoveryFailed

A signature in the array does not recover to any address.

LegacySignatureNotAccepted

Submitted without auth_scheme:"eip712" — this action accepts no other scheme.

InvalidAuthAccount

auth_account is not a well-formed address.

AmbiguousAuthFields

Both signature and signatures were sent, or neither — a quorum request carries only signatures.

Execution-level — the request was accepted and then rejected on chain. These split at the nonce boundary, and the side decides whether you can retry with the same nonce.

Before the nonce is consumed — the nonce is untouched, so correct the request and resubmit it unchanged:

Error
Meaning

AccountMultisigAgentRequired

The owner key submitted a trading action while a configuration is Active. Use an API wallet.

AccountMultisigEpochMismatch

policy_epoch is stale. Re-read accountMultisig and re-sign.

AccountMultisigNotConfigured

The account has no configuration.

AccountMultisigNotActive

A configuration exists but is not Active.

AccountMultisigRequired

The account's lifecycle requires a quorum, but a single owner signature was sent.

UnauthorizedMultisig

Fewer than threshold valid signatures, or a signature from an address outside the set.

InvalidAccountMultisigConfig

Structural problem — empty, unsorted, duplicated, zero address, threshold out of range, or the set includes the account's own key.

FeatureDisabled

The network is below the account-multisig activation height, so account-auth frames are not yet accepted.

After the nonce is consumed — the nonce is spent. Fix the cause and resubmit with a fresh nonce:

Error
Meaning

AccountFrozen

The account is frozen and cannot reconfigure until an admin unfreezes it. A frozen sponsor is rejected the same way.

AccountMultisigCreationFeeRequired

First-ever creation submitted without creation_fee.

UnexpectedAccountMultisigCreationFee

creation_fee sent on a replacement. Only the first-ever creation carries one.

InvalidAccountMultisigCreationFee

asset_id or amount does not exactly match a creation-fee row.

InvalidAccountMultisigFeePayer

The sponsor is ineligible, frozen, or payer_policy_epoch is stale.

AccountNotFound

The account does not exist. Deposit to create it first.

AssetNotFound

The asset_id in creation_fee is not a known asset.

AccountMultisigActiveLimitExceeded

The network's cap of 10,000 active configurations is reached.

AccountMultisigLifecycleLimitExceeded

The network's cap of 100,000 lifecycle entries is reached.

AccountMultisigEpochOverflow

The global policy epoch counter overflowed. Not expected in normal operation.

Last updated