The co-signer holds the second key. It never runs in the browser, so the key is never exposed. On each spend request it validates the request against the account’s on-chain policy and the poisoning firewall, then returns a signature or a veto. Fail-closed: if the firewall’s data source is unreachable, it withholds the signature.

Server endpoint

Expose two routes on the same origin as your app:
  • GET /api/cosign returns the co-signer’s public address, which the browser locks into each new account.
  • POST /api/cosign authorizes a spend and returns a signature or a veto.
cosign reconstructs the request, runs a LocalCoSigner backed by the SDK poisoning check(), and signs the exact struct hash the contract will verify.

The policy the co-signer enforces

Even though the contract re-checks these, the co-signer refuses to put its name on a request that violates them:
The spend target must equal the account’s on-chain lockedTarget. A script that swaps the destination cannot get a signature.
The amount must be within the remaining cumulative cap, and the account must not have expired.
Runs check(owner, target). Vetoes on a block verdict (drainer, lookalike, zero-value bait, or unavailable data). Set vetoOn: 'warning' to also stop new/fresh recipients.

In-process co-signer (tests and local)

For tests or a single-process setup, use LocalCoSigner directly with a pluggable risk source:
The co-signer is a trust point. If it is unreachable, funds are never stranded: after expiry anyone can sweepExpired the account home to the vault with the owner’s key alone. Design your key management around that escape hatch.