
Countersign
An agent's Hedera treasury cannot move until an independent guard, paid per decision, countersigns.
Demo
About
Autonomous agents are being handed treasuries. The usual protection is a policy check inside the agent's own code — which a compromised agent simply skips. Countersign removes that assumption. The treasury account holds a native Hedera key tree: 1-of[ownerKey, 2-of[agentKey, guardKey]]. The owner can always recover funds alone. The agent alone satisfies neither branch — when it signs a transfer by itself, the Hedera network rejects it with INVALID_SIGNATURE. Nothing inside the agent's process can talk its way past that. So the agent proposes instead. It publishes the transfer it wants as a Hedera Scheduled Transaction — public, and unexecuted, carrying only its own signature. It then pays an independent guard to review it. That guard is a live service at https://countersign.gudman.xyz. It answers HTTP 402 Payment Required, quotes its price in HBAR, and settlement goes through the Blocky402 x402 facilitator on Hedera testnet. Only after payment does the guard do any work. It resolves the ScheduleID from consensus — it never trusts a summary the caller sent it — and checks 48 decoded fields against a mandate the treasury owner signed: recipient allowlist, amount cap, asset, fee, expiry, who created the schedule, who pays for it, and that its own key is not already on it. If every check passes it adds its signature and Hedera executes the transfer. If one fails it refuses, and the schedule simply never executes. The caller pays either way. A refusal is a delivered service, not a failed request. Refusal has no state of its own on Hedera, so it is proved by absence: the schedule exists, its executed_timestamp is null, only the agent's key prefix appears in signatures[], and the treasury balance is unchanged. Every verdict is also written to a Hedera Consensus Service topic. All of it is readable from the public mirror node without running any of my code. You can pay the guard yourself. `curl https://countersign.gudman.xyz/guard` returns its public key and HCS-14 identifier for free, and `npm run hosted-review` in the repository sends a real paid review request to it from your own machine. The approved and refused runs linked in the README were made exactly that way, from a separate machine, against that endpoint. Honest limits, also stated in the README: these are my own operator runs, not external users, and testnet payments are paid protocol trials, not revenue. One guard process authorizes exactly one treasury, so this is a deployed service and not a marketplace — a second treasury needs a second guard. HTS token approval is deliberately disabled, and every token proposal is refused.
How it's made
TypeScript on Node 24, and no Solidity anywhere — Hedera native services only, through @hiero-ledger/sdk. The treasury is a nested KeyList, 1-of[ownerKey, 2-of[agentKey, guardKey]]. Hedera evaluates it at consensus, which is what makes the guarantee structural instead of procedural. Scheduled Transactions are the product mechanism rather than decoration: they are how a second signature arrives asynchronously. The agent's ScheduleCreate publishes the exact transfer; the guard's ScheduleSign completes the threshold. Payment is x402 through @x402/core and @x402/hedera, settled by the hosted Blocky402 facilitator on hedera:testnet. Hedera's x402 is exact-scheme only, so the price is one known check-unit quoted up front in the 402 challenge — not post-usage metering, and the README says so. The fee is paid from a separately keyed operational account, so no treasury authorization key ever enters a payment payload; startup asserts that separation and refuses to run otherwise. The validator is a pure function, and the subtle part is what it is allowed to trust. ScheduleInfo returns an already-decoded body, and a protobuf decoder silently drops fields it does not know. So the guard pins the exact HAPI protobuf and services versions, reads them from the same consensus node both before and after resolving the schedule, and refuses on any mismatch — a schema change makes it fail closed rather than approve something it cannot see. It also rejects any body carrying a field outside the audited schema. Mandates are RFC 8785 JCS-canonicalized, domain-separated and Ed25519-signed by the owner, so the agent cannot rewrite its own policy. They are single-use: the nonce is atomically reserved in SQLite before approval, and only the request that wins the reservation may submit ScheduleSign. Participant identity is HCS-14 for both agent and guard, and verdicts go to immutable, submit-key-protected HCS topics with custom fees rejected at startup. 360 tests across 15 files, all offline. One defect the offline suite could not have caught surfaced on the first live run: the mirror node returns signatures[].public_key_prefix as base64 and three code paths expected hex. The fixtures used "a".repeat(16), which is itself valid base64, so they passed while asserting nothing about the real encoding. The regression tests now run against verbatim mirror-node responses captured from that run. The build briefs each task was run from, and a full AI usage disclosure, are published in the repository.