APIWalletsRegister a wallet
Register a wallet
Register a Cardano guarded wallet and its mandate.
- Auth Node Token
- Rate-limited Yes
- Guide Create and register a wallet
Register a Cardano guarded wallet and its mandate. The mandate is fixed at registration: there is no route that changes it, so a different mandate needs a new wallet. Registration is idempotent by content: the same body from the same node returns 200 with the same walletId; a different body for the same wallet returns 409 wallet_exists. network must equal the Node Token’s network.
nodeTokenAuthorizationBearer <token>Node Token from the Exchain console. Works for wallets this node registered; the node’s network (preprod or mainnet) decides the network of the request. Use a preprod token in the playground.
application/json- body
One governed asset (governedAsset with constitution) or several (governedAssets, one mandate per asset, the first listed first everywhere a single asset is reported).
walletAddress?Bech32Address^addr(_test)?1[02-9ac-hj-np-z]+$stateToken?string^[0-9a-f]{56}\.([0-9a-f]{2}){1,32}$ownerKeyHash?KeyHash^[0-9a-f]{56}$agentKeyHashes?array<>1 <= itemsquorumKeyHashes?array<>1 <= itemsquorumThreshold?integer1 <= valueescrowAddresses?array<>1 <= itemsgovernedAsset*constitution*||governedAssets?array<>Several governed assets, each judged only against its own mandate. Every asset must be enabled on the network and listed once. When lovelace is one of them, the lovelace riding with a token payment also counts against the lovelace mandate.
1 <= items <= 16network?string"preprod""mainnet"orgId?string1 <= lengthnodeId?string1 <= lengthpriceGate?booleanpriceTolerancePct?integer0 <= value <= 100sellerAllowList?array<>sellerDenyList?array<>hydraParties?array<>The owner’s own Hydra verification keys (raw 32-byte Ed25519, hex). An in-head spend is co-signed when the head’s layer-1 party set contains one of them, or when every party is on Exchain’s list for the node.
items <= 16registryGate?booleantrueThis node registered the same body before; the same walletId is returned.
application/json- response
walletId*WalletId^wal_[0-9A-HJKMNP-TV-Z]{26}$constitutionId*Hash32^[0-9a-f]{64}$mandateEnglish*stringmandateRules*array<>1 <= itemsgovernedAssets?array<>Every governed asset with its own mandate; the top-level fields describe the first.
asOf*string^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\.[0-9]{3}Z$date-timeschemaVersion*stringWire schema version, "1.1" today.
"1.0""1.1"chainTip*|nullcurl -X POST 'https://cosign.exchain.network/v1/wallets' \ -H 'Content-Type: application/json' \ -d '{ "walletAddress": "addr_test1wz8nv2s4u5yqjm3pv3k8x5c7tdgfm8wq0r6cjyh4ldm9a2gqv7x5e", "stateToken": "15e9acb98b2868a013203378a6b514c9dcf97a5a905e3932c0ef9cef.6578636861696e2d7374617465", "ownerKeyHash": "241c5e9eed0f49da2cddd68cfcf3e7dad9201cf8def2c06e4c9f5dac", "agentKeyHashes": [ "903105119401d2618ca8b5193adfc74592a1362c07a375c3490fe90e" ], "quorumKeyHashes": [ "b02e5e1f6167ef12fe823d259b1034f56818e92ef2b4c73722ee3412", "32c72c8379803dd1957e426ca9c11ffeb47c8dd1a644028214ef39ef", "347d7c288cd19c106e46e990bd0d38be27fe73fe7aa04b6cbdb9d4ad" ], "quorumThreshold": 3, "escrowAddresses": [ "addr_test1wqag3rt979nep9g2wtdwu8mr4gz6m4kjdpp5zp705km8wys6t2kla" ], "governedAsset": { "id": "lovelace", "decimals": 6 }, "constitution": { "params": { "perTxCap": "200000000", "daily": "8000000000", "perSeller": "500000000", "perAgent": "4000000000", "envelope": "200000000000", "burstPerMinute": 10 } }, "network": "preprod", "orgId": "org_acme", "nodeId": "acme-node-1"}'{ "walletId": "wal_01J9XK3M4N5P6Q7R8S9T0V1W2Z", "constitutionId": "394b588411cfdb72c9377adba91bb86fad77b65e6be25bdb2a00558ec853e0f2", "mandateEnglish": "No single payment above 200 tADA. No more than 10 payments in any 60 seconds. No more than 8,000 tADA in any rolling 24 hours. No more than 500 tADA to any one counterparty in any rolling 24 hours. No more than 4,000 tADA by any one agent in any rolling 24 hours. Total lifetime envelope: 200,000 tADA. Only registry-verified agents can be paid. No payment above the seller's published price.", "mandateRules": [ { "name": "per_tx_cap", "metric": "max(payments.amount)", "window": 60, "scope": null, "bound": "200000000", "level": "member" }, { "name": "velocity_burst", "metric": "count(payments)", "window": 60, "scope": null, "bound": "10", "level": "member" }, { "name": "daily_outflow", "metric": "sum(payments.amount)", "window": 86400, "scope": null, "bound": "8000000000", "level": "member" }, { "name": "per_seller_cap", "metric": "sum(payments.amount)", "window": 86400, "scope": "counterparty", "bound": "500000000", "level": "member" }, { "name": "per_agent_cap", "metric": "sum(payments.amount)", "window": 86400, "scope": "agent", "bound": "4000000000", "level": "member" }, { "name": "envelope", "metric": "sum(payments.amount)", "window": 315360000, "scope": null, "bound": "200000000000", "level": "member" }, { "name": "registry_gate", "metric": "counterparty.registered", "window": null, "scope": "counterparty", "bound": null, "level": "member" }, { "name": "price_gate", "metric": "payments.amount <= counterparty.published_price", "window": null, "scope": "counterparty", "bound": null, "level": "member" } ], "governedAssets": [ { "id": "lovelace", "decimals": 6, "symbol": "tADA", "constitutionId": "394b588411cfdb72c9377adba91bb86fad77b65e6be25bdb2a00558ec853e0f2", "mandateEnglish": "No single payment above 200 tADA. No more than 10 payments in any 60 seconds. No more than 8,000 tADA in any rolling 24 hours. No more than 500 tADA to any one counterparty in any rolling 24 hours. No more than 4,000 tADA by any one agent in any rolling 24 hours. Total lifetime envelope: 200,000 tADA. Only registry-verified agents can be paid. No payment above the seller's published price.", "mandateRules": [ { "name": "per_tx_cap", "metric": "max(payments.amount)", "window": 60, "scope": null, "bound": "200000000", "level": "member" }, { "name": "velocity_burst", "metric": "count(payments)", "window": 60, "scope": null, "bound": "10", "level": "member" }, { "name": "daily_outflow", "metric": "sum(payments.amount)", "window": 86400, "scope": null, "bound": "8000000000", "level": "member" }, { "name": "per_seller_cap", "metric": "sum(payments.amount)", "window": 86400, "scope": "counterparty", "bound": "500000000", "level": "member" }, { "name": "per_agent_cap", "metric": "sum(payments.amount)", "window": 86400, "scope": "agent", "bound": "4000000000", "level": "member" }, { "name": "envelope", "metric": "sum(payments.amount)", "window": 315360000, "scope": null, "bound": "200000000000", "level": "member" }, { "name": "registry_gate", "metric": "counterparty.registered", "window": null, "scope": "counterparty", "bound": null, "level": "member" }, { "name": "price_gate", "metric": "payments.amount <= counterparty.published_price", "window": null, "scope": "counterparty", "bound": null, "level": "member" } ] } ], "asOf": "2026-11-05T14:22:07.318Z", "schemaVersion": "1.1", "chainTip": { "slot": 104118120, "blockHash": "9a301ac2e5747e7ebeeecececd2f114f55695614277514e085211c5417d04c26" }}Errors
| Status | Code | Meaning |
|---|---|---|
400 | bad_request | The body is not JSON or fails the schema; detail names the field. |
400 | network_mismatch | network or an address is not on the Node Token’s network. |
400 | node_mismatch | nodeId differs from the token’s node. |
400 | asset_not_allowed | The asset is an ERC-20 (use POST /v1/x402/wallets) or not enabled on this network. |
400 | duplicate_asset | An asset is listed twice in governedAssets. |
400 | asset_mismatch | The mandate’s base asset differs from the governed asset. |
400 | invalid_constitution | The mandate does not compile, or a bound is not a constant. |
400 | unknown_invariant | A custom mandate names a rule with no refusal code. |
400 | quorum_mismatch | quorumKeyHashes or quorumThreshold do not match Exchain’s quorum; retry once every member answers. |
401 | unauthorized | The bearer credential is missing, unknown or revoked. |
409 | wallet_exists | This wallet is registered with a different body or by another node. A new mandate needs a new wallet. |
413 | payload_too_large | The request body is larger than 64 KiB. |
429 | rate_limited | Over the limit; retry after Retry-After seconds (also in retryAfterSec). |
Every code and what to do about it: Errors and codes.