| name | x402-pay |
| description | Use when an agent must autonomously buy an x402-protected resource (Hedera testnet) — e.g. a product from the workshop shop. Sets up a delegated signer whose private key never enters the agent context, then runs the 402 → sign → 200 buy flow. Supports an optional spend cap. |
x402-pay
Overview
Lets an agent buy an x402-protected resource on Hedera testnet by driving the HTTP flow
itself while a separate signer process holds the key. The agent pipes the 402
challenge through the signer and gets back a payment header — the private key never
reaches the agent/LLM context.
Core boundary: the agent does HTTP + reasoning; the local x402-sign.ts process does the
signing from .env. This is the security invariant of the whole workshop — do not break it.
When to Use
- Buying from the workshop shop's x402 gateway (
GET /catalog, then buy a product) or any
402 Payment Required endpoint that returns a payment-required header.
- Network
hedera:testnet, scheme exact, asset HBAR (or an HTS token like USDC).
Not for: x402 on EVM chains, or production custody (swap the signer for an HSM/KMS or the
Hiero CLI behind the same ExactHederaScheme interface — the flow is identical).
Flow
digraph x402_pay {
"Signer present?" [shape=diamond];
"Scaffold + install" [shape=box];
"Pause: user fills .env" [shape=box];
"Discover + choose" [shape=box];
"Budget check" [shape=diamond];
"Buy (402→sign→200)" [shape=box];
"Signer present?" -> "Discover + choose" [label="yes"];
"Signer present?" -> "Scaffold + install" [label="no"];
"Scaffold + install" -> "Pause: user fills .env" -> "Discover + choose";
"Discover + choose" -> "Budget check";
"Budget check" -> "Buy (402→sign→200)" [label="within cap"];
"Budget check" -> "stop + report" [label="over cap"];
}
1. Resolve the signer working directory
- Path given →
ls it. Contains x402-sign.ts → signer ready, skip to step 3.
- Empty / none → ask the user for a dir (default
~/x402-signer), mkdir -p it, go to step 2.
2. Scaffold the signer
Copy the scaffold (including dotfiles, so pnpm-lock.yaml comes along) from the repo's
top-level signer/ directory into the working dir, then install. This skill lives in that
repo (<skill-dir>/../../.. = the repo root). node 22.18+ runs the .ts directly — no
tsx needed.
--frozen-lockfile is not optional: it installs the exact @x402/* tree the workshop was
tested against. A plain pnpm install would resolve a newer @x402 release than the gateway
is pinned to.
cp -a "<repo-root>/signer/." "<workdir>/"
cd "<workdir>" && pnpm install --frozen-lockfile && cp .env.example .env
Then STOP. Tell the user to fill <workdir>/.env with a funded ECDSA testnet
account (HEDERA_CLIENT_ID, HEDERA_CLIENT_KEY) from the Hedera Portal. Wait for their
confirmation. Never read, cat, echo, or ask for the key value — it stays in .env,
read only by the signer process.
3. Discover products & choose (with budget)
Need the shop's gateway base URL (skill argument). If missing, ask.
BASE="<base-url>"
curl -s "$BASE/catalog"
Present the catalog. If the user gave a spend cap (total HBAR budget), track spend
yourself and stop before exceeding it — do the arithmetic before each buy, and refuse
a purchase that would cross the cap. Report remaining budget after each buy.
This cap is advisory: the signer signs whatever it is handed, so nothing but your own
arithmetic stops an over-budget purchase here. Part 3 of the workshop moves the budget into
code — a SpendCapPolicy that blocks the tool call before any payment happens.
4. Buy flow (per product)
URL="$BASE/buy/<product>"
PR=$(curl -s -D - -o /dev/null "$URL" \
| grep -i '^payment-required:' | cut -d: -f2- | tr -d ' \r')
SIG=$(printf '%s' "$PR" | node "<workdir>/x402-sign.ts")
curl -s -i "$URL" -H "payment-signature: $SIG"
Decode the payment-response header (base64 JSON) and report: success, payer,
transaction (the Hedera tx id — link it on HashScan), network. Then report the goods
returned in the 200 body (order id / unlocked item).
Variant: secure signing via the Hiero CLI (KMS-backed)
Instead of the local .env signer, the official hiero-cli can sign — the key lives in the
CLI's key manager rather than a file you hand-manage, and never appears on the command line
after setup. Same challenge-in / signature-out contract, so the agent never notices.
The default key manager (local) writes the raw key in PLAINTEXT to
~/.hiero-cli/state/kms-secrets-local-storage.json. Verified. Choose local_encrypted
(AES-256-GCM, what hiero-cli's own README recommends) before configuring the operator, or
this variant has weaker custody than the .env signer it replaces.
The human runs the setup, not the agent. set-operator takes the private key as an
argument. If an agent executes it, the key enters the agent's context and its shell history
— the exact boundary this workshop exists to teach. Set the operator yourself, out of band;
the agent only ever runs x402 sign.
One-time setup (⚠️ the x402 plugin is on main, not yet in the npm release — build from source):
git clone https://github.com/hiero-ledger/hiero-cli.git && cd hiero-cli
npm install && npm run build
alias hcli="node $PWD/dist/hiero-cli.js"
hcli config set --default_key_manager local_encrypted
hcli network set-operator -o "<accountId>:<privateKey>" -N testnet
Then step 4's sign becomes (everything else identical):
SIG=$(hcli x402 sign --challenge "$PR" -N testnet -F json \
| python3 -c "import json,sys; print(json.load(sys.stdin)['paymentSignatureHeader'])")
The JSON also returns structured metadata (payer, payTo, amount, asset, network,
feePayer, transactionId) — report it. Same challenge-in/signature-out contract, upgraded
custody: file → CLI key manager → (production) HSM/KMS, and the agent never notices.
Verified on hiero-cli 1.2.0 (main): builds from source, x402 sign signs a live challenge
and the payment settles on testnet. Note this path enforces no spend cap — the cap lives in
x402-sign.ts, not in hiero-cli, so a KMS-backed signer will sign any amount it is handed.
Quick Reference
| Step | Command |
|---|
| Scaffold | cp -a <repo-root>/signer/. <workdir>/ |
| Install | pnpm install && cp .env.example .env |
| Catalog | curl -s "$BASE/catalog" |
| Sign | printf '%s' "$PR" | node <workdir>/x402-sign.ts |
| Pay | curl -s -i "$URL" -H "payment-signature: $SIG" |
Common Mistakes
- Signing too early. The signed payload expires after
maxTimeoutSeconds (~180s). Sign
right before the retry, not before discovery.
- ED25519 account. The signer defaults to ECDSA (
fromStringECDSA). An ED25519 key
needs fromStringED25519 in x402-sign.ts.
- Retry returns 402
"fetch failed". The gateway can't reach the facilitator — a
server-side network issue, not your signature.
- Ignoring the budget. If a spend cap was set, refuse any buy that would exceed it.
- Touching the key. Never
cat .env or print the key to "verify". Verify only that the
file is filled, never the value.