payOnce reserved MAX_AMOUNT before awaiting confirmation but only released
it when confirmWithUser returned false. A prompt that threw (timeout, closed
window) or a challenge the payment policy rejected before signing kept the
reservation, so five such failures exhausted SESSION_CAP with zero payments.
Track per call whether a request carrying PAYMENT-SIGNATURE / X-PAYMENT was
actually sent, and release the reservation in a finally block unless it was.
A sent signed request may settle, so it stays counted even if the response
fails. The reservation still happens before any await, so concurrent calls
cannot overrun the cap. Mirrored in ja-JP and zh-CN.
Two overlapping first payOnce calls could both pass the session-cap check
while sessionSpent was still 0, then both increment after the confirm await,
exceeding SESSION_CAP. Reserve MAX_AMOUNT before any await and release it
only when the user declines. Mirrored in ja-JP and zh-CN.
Two findings from the bot review.
The reusable payment client had no authorization boundary. The policy
validates one challenge at a time, so it cannot see a session total and
cannot ask a human anything — a long-lived wrapped client could pay
repeatedly, to any recipient, past any intended budget. This also
contradicted the skill's own Spending Controls section, which promises
per-session budgets and allowlisted recipients.
Adds an allowlist of recipients to the policy, and routes every paid call
through a boundary that asks for confirmation once and holds a session
ledger. Settlement responses carry no amount, so the ledger reserves the
per-call cap — a deliberate over-estimate that can stop early, never late.
The payment-enabled client stays private behind it.
The Solana recipient check was too loose. Scanning an owner's token
accounts proves only that some account exists for the mint; the exact-SVM
client targets the canonical ATA specifically, so an auxiliary account
would pass while settlement still fails. Now derives the ATA with
findAssociatedTokenPda and checks that exact address. Also corrects the
provisioning advice: a bare transfer does not create the account, only one
that includes the create instruction does.
Mirrored across en, ja-JP, and zh-CN.
Addresses the security review on #2575.
Enforce policy against the actual challenge. The previous example compared
a budget against a number the caller passed in, so a server quoting any
amount could still be paid. Replaces it with a PaymentPolicy registered on
the client, which filters the server-provided requirements before a
signature exists and validates all three fields — amount, asset, network.
Filtering everything out makes createPaymentPayload throw rather than fall
back to paying.
Testing this adversarially surfaced a bug in the guard itself: EVM
addresses are case-insensitive, so a server quoting lowercase USDC — the
same token — was dropped. Assets are now normalised before comparison,
lowercasing only 0x addresses, since Solana base58 is case-sensitive and
must not be folded. Also rejects negative amounts.
Verified against 12 cases: over-budget, wrong mint, wrong network,
case-flipped Solana mint, non-numeric and negative amounts all refuse;
checksummed, lowercase and uppercase EVM forms and the cap boundary all
pass.
Refresh the supported contract. Pins move 2.19.0 -> 2.22.0 (upstream
shipped 2.20, 2.21 and 2.22 since this branch opened). The documented
server and client APIs are unchanged across that range — re-ran the
seller middleware on 2.22.0 and it still returns a multi-network 402.
Keep facilitator guidance vendor-neutral. Self-facilitation and running
your own facilitator are now listed first, ahead of the testnet default
and hosted options, and the hosted paragraph points at the upstream
maintained list rather than naming a default. Adds an explicit disclosure
of the author's affiliation with one listed facilitator.
Mirrored across en, ja-JP, and zh-CN.
* fix(skills): move version into metadata and normalize to semver
29 skills declared `version` at the top level of their frontmatter. The
schema reads it from `metadata`, so tooling that follows the schema either
misses it or has to special-case the top level.
Three motion skills also declared `version: 1.0`, which is not a valid
semantic version; normalized to `1.0.0`.
No behavioral change — frontmatter metadata only.
* fix(skills): state activation triggers in skill descriptions
148 skills described what they cover but never named the situation that
should trigger them. Since the description is what Claude matches against
to decide whether to load a skill, a description without a trigger makes
activation guesswork — the skill is either missed or loaded at the wrong
time.
Added a "Use when ..." clause to each, derived from the skill's own body
(most already stated the trigger under "## When to Use" or in the opening
line; that intent is now reflected in the frontmatter where it is actually
read from).
Descriptions were only appended to; no existing wording was removed.
* fix(skills): sync activation triggers into the Codex skill mirror
10 of the skills whose descriptions changed are also mirrored under
`.agents/skills/`, where the description was previously a verbatim copy.
Left alone, the two surfaces would disagree about when the skill applies.
Only the description line is synced; the Codex copies keep their reduced
frontmatter, since that validator accepts only name, description,
metadata, license, and allowed-tools.
* fix(skills): correct three activation clauses from review
- autonomous-loops: the clause pulled new loop work into a skill that its
own body marks as a compatibility shim retained for one release. It now
points at the canonical continuous-agent-loop instead.
- continuous-learning: the description carried the v1 routing directive
twice; collapsed to one.
- homelab-pihole-dns: the clause fired on any broken home DNS. Narrowed to
tasks that actually involve Pi-hole.
* chore: retain current main lockfile
---------
Co-authored-by: Çağrı Solakoğlu <cagri.solakoglu@vtcenerji.com>
Co-authored-by: haelyra <49814733+haelyra@users.noreply.github.com>
The exact-SVM client transfers to the associated token account it derives
for payTo and does not create it. When that account is missing, settlement
fails at simulation and the 402 returns transaction_simulation_failed,
which reads like a client bug rather than a missing recipient account.
Adds a pre-flight check plus the three ways to create the account, and
notes that freshly provisioned and custodial wallets are the common case.
Found while running the documented flow end to end on Solana devnet;
confirmed against the upstream client scheme, which only derives the
destination ATA. Not currently covered in the upstream docs.
Mirrored across en, ja-JP, and zh-CN.
Adds a third integration path to the agent-payment-x402 skill — Solana
plus multi-network EVM via the upstream x402 packages — alongside the
existing Base (agentwallet-sdk) and X Layer (OKX) options. The skill had
no Solana coverage and no pointer to the canonical implementation.
Option C centers on the actively maintained x402-foundation/x402 monorepo
and stays facilitator-neutral:
- Buyer: wrap the agent's HTTP client with @x402/fetch or @x402/axios and
register both eip155:* and solana:* schemes, mirroring the upstream
client examples — one client pays whichever chain the 402 offers
- Seller: upstream middleware (@x402/express|hono|next|fastify pinned at
2.19.0, Python x402, github.com/x402-foundation/x402/go/v2); one route
can advertise Base and Solana simultaneously
- Facilitators: x402.org for testnets (the packages' default, testnet
only), production via the upstream facilitator list — PayAI suggested
(multi-network incl. Solana mainnet, no API keys), CDP noted (KYT/OFAC)
- Discovery: bazaar /discovery/resources on CDP and PayAI, plus pay.sh
for Solana-payable services
- Safety rails: explicit user confirmation before the first paid request
(mirroring Option B), a defined fail-closed budget gate invoked before
every paid call, USDC mint validation with concrete mainnet/devnet
addresses, and no unpinned npx commands
Also resyncs the zh-CN translation, which predated the X Layer / Options
structure, and mirrors the additions into ja-JP.
Package names, versions, facilitator scope, USDC mints, and discovery
URLs verified against x402-foundation/x402 main.
Security model:
- Remove set_policy from agent-callable tools table; document as
orchestrator-only to prevent self-privilege escalation
- Pin agentwallet-sdk@6.0.0 in MCP config with pre-install guidance
(npx without -y hangs in non-interactive MCP startup)
- Whitelist only required env vars (PATH, NODE_ENV, WALLET_PRIVATE_KEY)
instead of forwarding entire process.env to subprocess
Code example (complete rewrite):
- Add StdioClientTransport import and client.connect() for runnable code
- Wrap in async main() for CJS/ESM compatibility (top-level await)
- Verify set_policy result via isError before delegating
- Five distinct fail-closed error paths in preToolCheck:
1. Invalid apiCost input (NaN/Infinity bypass prevention)
2. Transport/connectivity failure
3. Tool-level error (isError: true, e.g., auth failure)
4. Unexpected response format (missing/non-finite remaining)
5. Budget exceeded (clear amounts in message)
- Use Number.isFinite() for both apiCost and remaining validation
Documentation:
- Rename headings per CONTRIBUTING.md format
- Replace broken mcp-server-patterns cross-ref with security-review
- Add 'Pin your dependencies' to Best Practices
- Add security note about supply-chain risk