goatx402-quickpay
v0.1.0
Published
GoatX402 QuickPay public payer/agent library and CLI
Maintainers
Readme
goatx402-quickpay
Public payer/agent library and CLI for GoatX402 QuickPay. It is generic, stateless, and manifest-driven: it does not know any specific merchant — the merchant identity comes entirely from the link a merchant shares.
# inspect a merchant's payment capabilities (machine-readable JSON)
npx goatx402-quickpay inspect https://pay.goat.network/quickpay/acme/agent.md --json
# pay a custom amount via x402
npx goatx402-quickpay pay-x402 https://pay.goat.network/quickpay/acme/agent.md \
--amount 12.50 --token-contract 0xToken --chain 4217 --wallet <privateKey>
# pay a fixed MPP route
npx goatx402-quickpay pay-mpp https://pay.goat.network/quickpay/acme/agent.md \
--route GET:api:data --wallet <privateKey>Library usage:
import { QuickPayClient } from 'goatx402-quickpay'
const client = new QuickPayClient('https://pay.goat.network/quickpay/acme/agent.md')
const manifest = await client.loadManifest()
const summary = await client.inspect()Security model — the host is the trust anchor
The input link's origin (scheme://host) is the single trust anchor:
- The
merchant_idis taken from the trusted URL path, and the fetchedmanifest.jsonmust self-identify as the same merchant or the command fails closed. - Every endpoint the CLI calls (session create/status, MPP challenge/verify) is derived from the origin. Absolute URLs embedded in the manifest are never trusted, so a tampered manifest cannot redirect a payment to another host.
- The origin must be
https://(plaintexthttp://is rejected except for loopback hosts in local dev), because an on-path attacker on plaintext could swap the session'spayToand redirect funds.
Share only links on a host you trust (e.g. pay.goat.network).
Retry safety (avoid double-paying)
pay-x402 and pay-mpp are designed so a retry never silently pays twice:
- The broadcast
tx_hashis always returned (even if confirmation polling fails), so you can resume by polling rather than re-sending. - A reused session (same payment intent) is not auto-paid — the CLI
resumes/polls it. Only pass
--forcetopay-x402to broadcast on a reused session, and only when you are certain no payment was sent (e.g. the wallet rejected the first attempt). - If
pay-mppfails after broadcasting, the JSON output preserves thetx_hashandchallengeso you can resume verification instead of paying again.
Configuration
- Wallet key:
--wallet <privateKey>orQUICKPAY_PRIVATE_KEY. - Token: prefer
--token-contract <address>from the manifest.--token <SYM>is accepted only when the symbol is unique on that chain. - RPC URL (for the on-chain transfer):
--rpc <url>, orQUICKPAY_RPC_<chainId>, orQUICKPAY_RPC.
Architecture
client.ts— library-firstQuickPayClientfacade.manifest.ts— link resolution, schema validation, trust-anchor enforcement.inspect.ts/pay.ts— capability discovery and the x402 / MPP orchestration. The on-chain step is behind an injectable backend so the orchestration is unit tested without a chain.backend-ethers.ts— real ERC20 transfer forpay-x402(ethers v6).backend-mpp-sdk.ts—pay-mppdelegates togoatx402-sdk'sMPPClient(an optional dependency loaded at runtime).
The live on-chain flows (the actual ERC20 transfer / MPP settlement) require a chain + wallet and are not exercised by the unit tests; the manifest handling, trust-anchor enforcement, and request orchestration are.
Develop
pnpm install
pnpm typecheck # tsc --noEmit
pnpm test:run # vitest
pnpm build # emit dist/