August 19, 2026 · Iris, Growth @ minia2a.uk
An agent registers a self-custody wallet, gets its free credits, appends ?wallet=0x… to an
endpoint the way every quickstart shows — and gets HTTP 402. Nothing is down. The call simply
isn't signed, and an unsigned call never reaches the wallet's own trial bucket.
This post is the missing page: the exact message to sign, the one detail inside it that will cost you an afternoon if nobody tells you, and four recipes that work. Everything below was measured against the live gateway on August 19, 2026 — the pass/fail marks are real responses, not documentation.
There are two independent pools of free calls:
They really are independent. The measurements in this post were taken from a host whose anonymous
bucket was already at 0/15: unsigned calls returned 402, and signed calls from the same host,
same second, returned 200. If your agents run behind shared cloud egress — Lambda, CI runners, a Workers
deploy where thousands of tenants share an IP — this is the difference between "free tier exhausted before
I started" and "my wallet carries its own allowance wherever it runs."
Sign this string with EIP-191 (personal_sign), send the signature and the timestamp as
headers, and put the wallet in the query string:
minia2a trial:<wallet>:<serviceId>:<unixSeconds>
wallet — byte-for-byte the same string you pass in ?wallet=. Checksummed or
lowercase both work; mixing the two forms does not.serviceId — see the next section. This is the one that bites.unixSeconds — integer seconds, accepted within ±5 minutes, repeated in
X-Trial-Timestamp.You are standing at the URL https://minia2a.uk/x402/time. The obvious reading of
serviceId is time. It isn't. It's the catalog id —
x402-time — the value in the id field of
/api/services, also echoed in every 402 body at
extensions.bazaar.routeTemplate.
Four candidates, same wallet, same endpoint, same minute:
| Signed as | Result |
|---|---|
x402-time | 200 — x-trial-mode: wallet, x-trial-remaining: 14 |
time | 402 |
/x402/time | 402 |
https://minia2a.uk/x402/time | 402 |
Repeated on a second endpoint for confirmation: gas → 402, x402-gas → 200.
Here is why this is worse than an ordinary gotcha. A wrong service ID and a forged signature
produce byte-identical responses. Both are a 402 with trial.mode:"none". An agent
debugging this has no signal telling it whether its crypto is wrong, its clock is wrong, its registration
failed, or it merely used the wrong noun — so it tends to go rewrite the signing code, which was fine all
along. If you take one thing from this post: check the ID first.
A 402 body that names a variable — "signwallet:service:ts" — is not machine-readable. The client has to guess whatserviceexpands to, and every wrong guess looks exactly like an auth failure. Emit the literal string, or name the field it comes from. The cost of the ambiguity lands entirely on the caller, which in the agent economy means it lands on software that cannot file a bug report.
W=0xYOUR_WALLET
TS=$(date +%s)
SIG=$(cast wallet sign --private-key $PK "minia2a trial:$W:x402-time:$TS")
curl "https://minia2a.uk/x402/time?wallet=$W" \
-H "X-Wallet-Signature: $SIG" \
-H "X-Trial-Timestamp: $TS"
# 200 · x-trial-mode: wallet · x-trial-remaining: 14
import time, requests
from eth_account import Account
from eth_account.messages import encode_defunct
acct, sid = Account.from_key(PRIVATE_KEY), "x402-time"
ts = str(int(time.time()))
sig = acct.sign_message(
encode_defunct(text=f"minia2a trial:{acct.address}:{sid}:{ts}")
).signature.hex()
r = requests.get(f"https://minia2a.uk/x402/time?wallet={acct.address}",
headers={"X-Wallet-Signature": sig, "X-Trial-Timestamp": ts})
print(r.status_code, r.headers.get("x-trial-mode"), r.headers.get("x-trial-remaining"))
The CLI resolves the catalog ID itself, so the ID trap can't reach you. Run it without a signature and it hands you the exact line to sign:
npx -y minia2a-skill call x402-time --wallet 0xYOUR_WALLET
# → sign this exact message (EIP-191 personal_sign):
# minia2a trial:0xYOUR_WALLET:x402-time:1787113324
npx -y minia2a-skill call x402-time --wallet 0xYOUR_WALLET \
--timestamp 1787113324 --signature 0xYOUR_SIGNATURE
# → trial: mode=wallet remaining=12
Inside Claude Code, Cursor, or any MCP client, pass a key once (argument or
MINIA2A_PRIVATE_KEY) and the server signs each call locally — only the signature crosses the
wire, and the service ID comes from the catalog automatically:
npx -y minia2a-mcp # v1.1.21+
# minia2a_call_service { serviceId: "x402-time", privateKey: "0x..." }
# → { "trialMode": "wallet", "trialRemaining": "13", ... }
Don't infer which bucket paid — the gateway tells you, on every successful call:
x-trial-mode: wallet — the signature was accepted; you're spending the wallet's bucket.x-trial-mode: ip — you're on the anonymous bucket. If you meant to use the wallet's,
something in the signed message was wrong and the call quietly fell back.x-trial-remaining — what's left in whichever bucket answered.That ip case is the quiet one. A 200 doesn't prove your signing works; it may just prove
your IP still had calls left. Test from an exhausted IP, or watch the header — those are the only two
honest checks.
You get a 402 carrying a standard x402 v2 accepts[] array: amount in
micro-units, asset, network as CAIP-2, payTo, scheme.
Those are real addresses on real chains — settle one and retry with a payment header. The free buckets are
there so an agent can find out whether an endpoint is worth paying for before it spends anything,
which is the whole point of a trial: not a discount, but a way for autonomous software to evaluate a
supplier without a purchase order.
Full machine-readable spec: AGENTS.md · llms.txt · service catalog: /api/services