Skip to main content
Every settlement is co-signed with a BLS key, and each chain’s key registry must hold that key for your account before a co-signature counts. These endpoints file your key on every registry, change or end it later, and record which makers may name your account as their settlement signer. The relayer files every leg and pays the gas. See Settlement keys for how keys are derived and used.

Endpoints

One owner signature, every registry

Each key operation is one wallet signature, the owner signature, over one message that names every registry it is for. You never sign once per chain.
  • The account is your settlement identity: an EVM address (0x + 40 hex) or a Stellar ed25519 key (0x + 64 hex, the G… key decoded). An EVM address padded to 32 bytes is accepted and read as the 20-byte form.
  • The scheme follows the account, not the chain. An EVM account signs EIP-712 typed data (scheme: "secp256k1"). A Stellar account signs a fixed SEP-53 text (scheme: "sep53"). Both registries accept both schemes, so the same signature works on Stellar and on EVM.
  • Each registry is a leg { chainId, registry, nonce }: the chain id, the registry’s 32-byte id, and that registry’s current nonce for your account. Legs are always ordered EVM first, then by chain id. A message names at most 8 legs.
  • The EIP-712 domain is { name: "ProofBridge Keys", version: "2", salt }, where the salt binds the environment (local, testnet or mainnet). It has no chain id; the legs bind the chains.
  • Each leg lands on its own. If one chain lands and another fails, posting the same body again sends only the missing legs. A registry that already holds your key counts as landed, so signing a fresh message after a partial landing is safe too.
Registration also needs a proof of possession per registry: a signature by the BLS key itself (not your wallet) over that leg’s popMsg. It proves you hold the key’s secret. Each chain encodes the key differently, so each leg has its own popMsg and its own proof.
Always rebuild the message locally from the prepare answer and compare it with signPayload before you sign. Check that env matches your own environment. The @proofbridge/bls-encodings package has keyMessageTypedData and keyMessageText for this.
Who may post. The write routes file only for an account that is one of your signed-in wallets. Anything else is 403 caller does not control this account. This is only the relayer’s gas rule: the registries take the same calls from anyone, so you can always submit them on chain yourself. Common refusals on every prepare and write route:

Get the half-encryption key

GET /v1/keys/half-encryption Before you send your half of a trade secret (POST /v1/trades/:id/half, or the maker’s lock), fetch the key to encrypt it to. The key is signed by a long-term announcement key whose public half is pinned in the frontend and the agent runtime. Verify signature against your pin and refuse to seal anything if it fails. Send keyId back with every envelope you seal. See Trade secrets.
verifyKeyAnnouncement(pinnedKey, { keyId, spki, signature }) in @proofbridge/bls-encodings checks it and returns the key for sealHalf. The key rotates; always use the latest. Answers 503 if the key provider cannot answer.

Register a key

Prepare

POST /v1/keys/register/prepare reads each registry’s nonce and returns one leg per registry, each with the bytes your BLS key signs for its proof of possession, plus the one owner message. The owner message does not contain the proofs, so you can sign them in either order. It writes nothing.
For a Stellar account, scheme is sep53 and signPayload is { "kind": "sep53", "text": "…" }. The text reads ProofBridge: register a settlement key, then the network, account, key fingerprint, expiry and one line per leg. Sign it with SEP-53 signMessage.

File

POST /v1/keys/register checks everything the registries will check (the deadline, the owner signature over every leg, each leg’s nonce and each proof of possession) before spending any gas. It then queues one job per leg and answers 202. Each registry is filed in its own transaction from the relayer’s gas keys. You pay nothing and sign no transaction.
The response is the key status (see Key status), with one REGISTER publication per queued leg in PENDING. Poll GET /v1/keys/:account until every leg is CONFIRMED.

Rotate, retire and cancel

Rotation has no route of its own. You register a new key with POST /v1/keys/register while the old one stays. Each registry keeps up to 5 live keys per account, and a new key is usable as soon as it lands. A key that was ever registered can never be registered again for that account, so each rotation uses a fresh key. A co-signature with the new key is accepted once it is live on both chains of the trade; new trades switch to it as soon as both legs land. Retirement ends one key at a time you choose. The relayer stops accepting co-signatures under a key 15 minutes before its retirement time, so move open trades to the new key first, or pick a time after the open orders signed under the old key have closed. A retired key still counts toward the 5 until the registry prunes it. A pre-signed retirement is an emergency off switch you file in advance. You sign a retirement that takes effect at once, and the retirement service stores it. It is sent only in an incident. A stored retirement can only end that key; it can never move funds. Each time you rotate, file one for the new key too. Revocation removes every key on the account. Cancel stops a registration you signed but no longer want, before it lands everywhere.

Retire prepare

POST /v1/keys/retire/prepare builds the RetireKey(account, keyCommitment, validUntil) message. It names the key, not a registry, and has no nonce and no deadline. Because a retirement can only shorten a key’s life, one signature never goes stale and can do nothing but end that key. Send validUntil: "1" to prepare a pre-signed emergency retirement.

Store a pre-signed retirement

POST /v1/keys/retire/presign files your signature over RetireKey(account, keyCommitment, 1) with the retirement service. Nothing is sent on chain. One entry is stored per registry where the key is live, each ON_FILE. Filing again for the same key replaces the stored signature.
Answers 201.

Retire now

POST /v1/keys/retire sends a signed retirement to every registry where the key has a slot. A registry whose key already expires at or before validUntil is skipped. You need an earlier registration through the relayer.
Answers 202 with the key status, one SET_VALID_UNTIL publication per chain.

Revoke prepare and revoke

POST /v1/keys/revoke/prepare builds the RevokeKeys(account, legs) message, naming every registry. A revocation removes every key on the account, a hosted pod’s included; pods lists the pods it would stop, so you can say so before the owner signs.
POST /v1/keys/revoke files it, one REVOKE publication per registry, and sets the record to REVOKED.

Cancel prepare and cancel

POST /v1/keys/cancel/prepare builds CancelPending(account, legs) for a registration that has not landed everywhere. A cancel uses up the registry’s nonce without touching any key, so the pending registration signature can never land there. Its legs name only the registries where the key has not landed. Use it to stop a signed registration you no longer want before its deadline.
The response has the same shape as revoke prepare, plus keyCommitment, and without pods. POST /v1/keys/cancel takes { account, keyCommitment, ownerAuth, revocations }, where revocations are the legs prepare returned.

Key status

GET /v1/keys/:account Your key record, every leg the relayer filed (publications, oldest first), and each registry’s live state read now (onChain). Anyone signed in may read any account. It works with no record too, which is how you read the live nonces before signing. It writes nothing.
  • action is REGISTER, REVOKE, CANCEL or SET_VALID_UNTIL (a retirement).
  • validUntil on a slot is unix seconds; "0" means no expiry.
  • unread: true means that registry could not be read this time. Keep your last known state for that chain rather than reading the empty fields as “no key”.
Reading it. Your key is live on a chain when that chain’s onChain[].matchesRecord is true. Settlement needs it live on both chains of the trade. A leg is pending while its publication is PENDING or SUBMITTED; show error as the reason when set. If a leg fails with this key was registered before — derive a fresh key and retry, derive the next key index and register that.

Maker approvals

When an ad names a settlement signer other than the maker’s own wallet, that signer must approve the maker in the ad chain’s key registry first. The escrow refuses the ad, and every lock on it, until it has. These routes let the signer file approvals, or withdraw them, through the relayer, which pays the gas. An ad that names its own maker needs no approval. See Agents.

Prepare

POST /v1/keys/maker-approvals/prepare answers each pair’s live state and the nonce its next ApproveMaker must carry. Sign one ApproveMaker per row, with one leg { chainId, registry: registryId, nonce }, under the account’s scheme. It writes nothing.
Answers 404 when the chain has no key registry.

File

POST /v1/keys/maker-approvals checks every row before recording any, then records one filing per accepted row and answers 202. Nothing is sent inside the request; the relayer sends each filing afterwards from its gas key.
  • Already as asked. A row already in the requested state on chain is answered under skipped and costs nothing. To retry a partly filed batch, post the same body again.
  • Known makers only. The relayer pays to approve a maker it has seen: the creator of an ad naming your account, an account linked on that chain, or a wallet that has signed in. Withdrawing is always allowed.
  • Budgets. The gas the relayer pays is capped per rolling window (24 h by default): 20 filings per user, 100 per caller address across every user behind it, and 500 for the relayer as a whole. The caps count filings, not attempts. After five failed attempts for one pair the relayer stops re-sending it, and you file on the registry directly.

Filings

GET /v1/keys/:account/maker-approvals/filings lists your account’s relayer-paid filings, newest first (at most 50). Poll it until each accepted id is CONFIRMED or FAILED. The account must be one of your signed-in wallets.

List

GET /v1/keys/:account/maker-approvals lists the makers whose ads name your account as settlement signer, one row per ad chain and maker, with the pair’s live state. The account must be one of your signed-in wallets. To withdraw, sign ApproveMaker with approved: false at the row’s nonce and file it.

Pods

A hosted pod registers its own agent key through these same routes: the owner signs the registration the pod prepares and posts it to POST /v1/keys/register. See Pods.