@ftptech/canton-agent-wallet
v0.1.15
Published
Self-custody Canton wallet + x402 autopay for AI agents. One CLI: create, pay, balance, withdraw, export, import. Backs the canton-x402-agent skill.
Readme
@ftptech/canton-agent-wallet
A self-custody Canton wallet + x402 autopay for AI agents. The agent holds its own Ed25519 key and pays for x402-gated resources on the Canton Network on its own — the hard parts of Canton (a party must be hosted on a validator, and its API is authed) are hidden behind a facilitator relay the agent talks to over plain HTTP. The relay can prepare and submit on the agent's behalf but never signs, so it has no custody.
Backs the canton-x402-agent skill.
Install
npm install @ftptech/canton-agent-wallet
# or run the CLI without installing:
npx @ftptech/canton-agent-wallet <command>Bin: canton-agent-wallet.
CLI
canton-agent-wallet create --relay-url <url> # generate + onboard a self-custody wallet (idempotent)
canton-agent-wallet address # print the party id (fund this)
canton-agent-wallet balance # print CC balance
canton-agent-wallet claim # accept incoming transfers (e.g. the initial funding)
canton-agent-wallet pay --relay-url <url> <url> # fetch a URL, auto-paying any x402 402 challenge
canton-agent-wallet withdraw --to <party> [--amount <cc>] # send CC back out (default: full balance)
canton-agent-wallet export # print the private key (backup; guard it)create and pay require a relay (facilitator) URL — pass --relay-url
<url> or set CANTON_AGENT_RELAY_URL. There is no default: a stale built-in
default would silently send payments to a dead host, so the CLI fails fast when
none is given. address, balance, claim, withdraw and export reuse the
relay stored in the wallet at create time. The current FTP facilitator is
http://46.225.91.251:4022 (plain HTTP by IP; a TLS domain is not yet live).
On Canton, incoming CC arrives as a pending transfer the agent must accept,
so the first-run flow is:
create --relay-url http://46.225.91.251:4022 → tell your human to send CC to
the printed party id → claim → balance. Funding once is the only human step,
exactly like funding an EVM agent.
Programmatic API
makePayingFetch() returns a fetch that transparently pays x402 challenges
from the agent's wallet (lazily creating the wallet on first use):
import { makePayingFetch } from "@ftptech/canton-agent-wallet";
const fetch = await makePayingFetch({
relayUrl: process.env.CANTON_AGENT_RELAY_URL,
network: "canton:testnet",
});
const res = await fetch("https://paid.api/resource"); // 402 -> pay -> retry, transparentlyOn a 402 it resolves the transfer factory via the relay, builds the CIP-56 transfer, signs the prepared-transaction hash locally, has the relay submit it, then retries the request with the on-ledger proof.
Config (env)
CANTON_AGENT_RELAY_URL— facilitator relay base URL. Required for the commands that talk to a relay (create,pay); there is intentionally no built-in default (a stale default would silently send payments to a dead host). Supply it here or via--relay-url <url>. The current FTP facilitator ishttps://facilitator.ftptech.xyz.CANTON_AGENT_NETWORK—canton:testnet(default) orcanton:mainnet; also settable via--network <net>.CANTON_AGENT_API_KEY— only if the relay requires one (sent as theX-Agent-Keyheader).HTTPS_PROXY/HTTP_PROXY(orALL_PROXY) — if set, the CLI routes all relay calls through that proxy. Node'sfetchdoes NOT honor these on its own, so behind a corporate/regional proxy you must set one of them or every call fails withfetch failed. Credentials are supported (http://user:pass@host:port) and never logged.CANTON_AGENT_HOME— override the wallet directory (default~/.canton-agent; used by tests and power users).
Your wallet is YOURS (self-custody)
The wallet lives at ~/.canton-agent/wallet.json (mode 0600). This file IS
your money — back it up. Losing it loses the funds. The agent reuses the same
wallet forever and never silently creates a second one. The validator only
hosts your party; it can never spend your CC — only your signature authorizes a
transfer. canton-agent-wallet export prints the private key for backup or
import.
How it works
Self-custody external party per CIP-0103: you generate and hold the Ed25519 key; the facilitator relay bridges onboarding and submission to the Canton participant using the validator's auth, so you need no Canton account.
- Onboard: the relay runs
generate-topology; the agent signs the returned multi-hash locally; the relay allocates the party. - Pay / withdraw: the relay prepares the transaction (and proxies the public Scan registry resolves the agent can't reach); the agent verifies the prepared transaction matches its intent and binds the hash to those exact bytes before signing; the relay only forwards the signed submission.
Every state-changing operation is authorized by the agent's signature.
Verify-before-sign and hash binding (fail-closed)
The relay is treated as untrusted. Before signing any transfer that moves funds, the agent structurally decodes the relay-prepared transaction and checks the sender / receiver / amount / instrument against its own intent (rejecting ambiguous encodings that a spec-conformant parser would read differently). It then binds the hash it signs to those validated bytes — because the Canton Ledger API is explicit that "clients MUST recompute the hash from the raw transaction if the preparing participant is not trusted". A compromised relay that returns honest bytes paired with the hash of a different (tampered) transaction is therefore rejected.
Binding requires one of:
recomputeHash(programmatic): passhashBinding: { recomputeHash }tomakePayingFetch/withdraw/transfer, whererecomputeHashreproduces Canton'sHASHING_SCHEME_VERSION_V2for the prepared bytes. This is the real cryptographic binding.CANTON_AGENT_TRUST_RELAY_HASH=1(operator opt-in): accept the relay's hash WITHOUT recomputation. This re-opens the blind-signing risk and is only acceptable when a human reviews each transfer or the relay is fully trusted.
With neither configured, a value-moving transfer refuses to sign (fail-closed)
rather than blind-sign a relay-chosen hash. Accepting incoming transfers
(claim) needs no binding — no funds leave.
License
Apache-2.0.
