@devlab.group/agent-commerce 0.2.0-beta.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -59
- package/dist/{chunk-ASK67X56.js → chunk-64UZVJEO.js} +3 -3
- package/dist/{chunk-ASK67X56.js.map → chunk-64UZVJEO.js.map} +1 -1
- package/dist/{chunk-MQMKQGAB.js → chunk-M4I7I2UY.js} +88 -32
- package/dist/chunk-M4I7I2UY.js.map +1 -0
- package/dist/cli/index.js +279 -48
- package/dist/cli/index.js.map +1 -1
- package/dist/guardrails-Dmwi63KB.d.ts +38 -0
- package/dist/index.d.ts +11 -6
- package/dist/index.js +208 -30
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +1 -1
- package/dist/{protocol-adapter-CtB0sOnV.d.ts → protocol-adapter-DZ8qkkAa.d.ts} +22 -2
- package/dist/{runtime-Bie-9FAM.d.ts → runtime-CC6EFmLP.d.ts} +3 -3
- package/dist/x402.d.ts +20 -6
- package/dist/x402.js +79 -23
- package/dist/x402.js.map +1 -1
- package/package.json +8 -1
- package/dist/chunk-MQMKQGAB.js.map +0 -1
- package/dist/guardrails-Di_yI_DW.d.ts +0 -25
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/payments/x402/networks.ts","../src/payments/x402/local-chain/accounts.ts","../src/payments/x402/dev-key-guard.ts","../src/payments/x402/guardrails.ts"],"names":[],"mappings":"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","file":"chunk-M4I7I2UY.js","sourcesContent":["/**\n * The networks this gateway will settle on, as data.\n *\n * x402 v2 identifies a network by its CAIP-2 id, so the only thing an\n * operator has to change to move between environments is that string. What\n * the code needs on top of it — the chain id to sign against, a name a human\n * recognises, and whether real money is involved — lives here rather than\n * being derived at each call site.\n *\n * A registry rather than \"parse any `eip155:*`\" on purpose. The chain id is\n * parseable from any such string, but `mainnet` is not, and every guardrail in\n * `guardrails.ts` keys off it. An unknown id is refused at startup instead of\n * being quietly treated as a testnet.\n *\n * Base Sepolia and the local dev chain share the id `eip155:84532`: the local\n * chain deliberately reuses Base Sepolia's chain id so the unmodified x402 SDK\n * can talk to it. Nothing may infer \"public network\" from the network id alone\n * — that is what `resolveDeploymentMode` and the Anvil probe in `provider.ts`\n * are for.\n */\nimport { CommerceError } from '../../core/index.js';\n\nexport interface NetworkProfile {\n /** CAIP-2 identifier, e.g. `eip155:84532`. */\n readonly id: string;\n /** Signed into the buyer's EIP-712 domain. */\n readonly chainId: number;\n readonly displayName: string;\n readonly kind: 'testnet' | 'mainnet';\n /**\n * The USDC deployment this network is expected to settle in, with the EIP-712\n * domain the token actually reports. Enforced only on mainnet (see\n * `guardrails.ts`) — a testnet is where a mock token is a legitimate thing to\n * point at.\n *\n * `name` is not decorative and is not the symbol: it is signed into every\n * buyer's EIP-712 domain, and the two USDC deployments disagree. Base Sepolia\n * reports `\"USDC\"`; Base mainnet reports `\"USD Coin\"`. Configure the wrong\n * one and every payment is refused `invalid_exact_evm_token_name_mismatch`\n * after the buyer has signed. Read back from the contracts, not assumed.\n */\n readonly canonicalAsset?: {\n readonly symbol: string;\n readonly address: string;\n readonly name: string;\n readonly version: string;\n };\n}\n\nconst PROFILES: readonly NetworkProfile[] = [\n {\n id: 'eip155:84532',\n chainId: 84532,\n displayName: 'Base Sepolia',\n kind: 'testnet',\n canonicalAsset: {\n symbol: 'USDC',\n address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',\n name: 'USDC',\n version: '2',\n },\n },\n {\n id: 'eip155:8453',\n chainId: 8453,\n displayName: 'Base',\n kind: 'mainnet',\n canonicalAsset: {\n symbol: 'USDC',\n address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',\n name: 'USD Coin',\n version: '2',\n },\n },\n];\n\nconst BY_ID = new Map(PROFILES.map((profile) => [profile.id, profile]));\n\nexport const SUPPORTED_NETWORK_IDS: readonly string[] = PROFILES.map((p) => p.id);\n\nexport function findNetworkProfile(id: string): NetworkProfile | undefined {\n return BY_ID.get(id);\n}\n\nexport function requireNetworkProfile(id: string, path: string): NetworkProfile {\n const profile = BY_ID.get(id);\n if (!profile) {\n throw new CommerceError(\n 'CONFIG_INVALID',\n `${path}: \"${id}\" is not a supported network. Supported CAIP-2 identifiers: ${SUPPORTED_NETWORK_IDS.join(', ')}`,\n { details: { path, network: id } },\n );\n }\n return profile;\n}\n\n/**\n * What this deployment actually is, as opposed to what it is called.\n *\n * `local` is a property of the *facilitator*, not the network: settling\n * through the in-process facilitator against a dev node is local regardless of\n * the CAIP-2 id that node answers to. This distinction is the whole reason\n * diagnostics can say `eip155:84532` without claiming to be on public Base\n * Sepolia.\n */\nexport type DeploymentMode = 'local' | 'testnet' | 'mainnet';\n\nexport function resolveDeploymentMode(\n profile: NetworkProfile,\n facilitatorMode: 'local' | 'remote',\n): DeploymentMode {\n if (facilitatorMode === 'local') return 'local';\n return profile.kind;\n}\n\n/** The banner an operator must not be able to miss. Never printed for anything else. */\nexport const LIVE_MAINNET_BANNER = 'LIVE MAINNET MODE — REAL FUNDS';\n\nexport function describeDeploymentMode(mode: DeploymentMode): string {\n return mode === 'mainnet' ? LIVE_MAINNET_BANNER : mode.toUpperCase();\n}\n","/**\n * Anvil's well-known development accounts.\n *\n * LOCAL DEVELOPMENT ONLY — DO NOT FUND. These addresses and private keys are\n * derived from Foundry's public, well-documented default test mnemonic\n * (\"test test test test test test test test test test test junk\") and are\n * known to everyone who has ever run `anvil`. Anyone who sends real value to\n * one of these addresses on a real network will lose it.\n *\n * Index layout used by the local demo chain (see docs/contracts.md):\n * 0 — deployer / local facilitator signer (pays gas, broadcasts settlement)\n * 1 — merchant settlement destination (`payTo`)\n * 2 — buyer (signs EIP-3009 authorisations; never held by the gateway)\n */\nexport interface AnvilWellKnownAccount {\n readonly address: `0x${string}`;\n readonly privateKey: `0x${string}`;\n}\n\n/** Fixed-length tuple (not a bare array) so indexing by the documented layout above is fully typed. */\nexport const ANVIL_WELL_KNOWN_ACCOUNTS: readonly [\n AnvilWellKnownAccount,\n AnvilWellKnownAccount,\n AnvilWellKnownAccount,\n] = [\n {\n address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',\n privateKey: '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80',\n },\n {\n address: '0x70997970C51812dc3A010C7d01b50e0d17dc79C8',\n privateKey: '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d',\n },\n {\n address: '0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC',\n privateKey: '0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a',\n },\n];\n\nexport const LOCAL_FACILITATOR_ACCOUNT = ANVIL_WELL_KNOWN_ACCOUNTS[0];\nexport const LOCAL_MERCHANT_ACCOUNT = ANVIL_WELL_KNOWN_ACCOUNTS[1];\nexport const LOCAL_BUYER_ACCOUNT = ANVIL_WELL_KNOWN_ACCOUNTS[2];\n\n/** Safe label used everywhere a dev key appears in output, logs or manifests. */\nexport const DEV_KEY_LABEL = 'LOCAL DEVELOPMENT ONLY - DO NOT FUND';\n","/**\n * Prevents a well-known Anvil development private key — or a well-known\n * Anvil development *address* — from ever being used against anything other\n * than a loopback/private/dev RPC. Two symmetric mistakes, both covered:\n * signing settlement with a dev key (`assertDevKeyIsLocalOnly`), and routing\n * settlement revenue to a dev address (`assertPayToIsNotDevAddress`). The\n * second is the more consequential of the two — a wrong key merely fails to\n * sign, a wrong payTo silently succeeds and gives the money away, since the\n * private key behind every well-known Anvil address is public knowledge.\n *\n * Chain id alone cannot tell a real network from this project's local dev\n * chain: Base Sepolia's own chain id is 84532, the same id this project\n * deliberately reuses for the local chain so the unmodified x402 SDK can talk\n * to it. A dev key or dev payTo pointed at a real RPC by a config mistake is\n * instantly drainable — this is checked once at provider construction, not per\n * payment.\n */\nimport { CommerceError } from '../../core/index.js';\nimport { ANVIL_WELL_KNOWN_ACCOUNTS } from './local-chain/accounts.js';\n\nconst WELL_KNOWN_DEV_KEYS: ReadonlySet<string> = new Set(\n ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.privateKey.toLowerCase()),\n);\n\nconst WELL_KNOWN_DEV_ADDRESSES: ReadonlySet<string> = new Set(\n ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.address.toLowerCase()),\n);\n\n/** `0x` + 64 hex chars — the only shape `privateKeyToAccount` accepts. */\nconst PRIVATE_KEY_SHAPE = /^0x[0-9a-fA-F]{64}$/;\n\nexport function isWellKnownDevKey(privateKey: string): boolean {\n return WELL_KNOWN_DEV_KEYS.has(privateKey.toLowerCase());\n}\n\nexport function isWellKnownDevAddress(address: string): boolean {\n return WELL_KNOWN_DEV_ADDRESSES.has(address.toLowerCase());\n}\n\n/**\n * True for loopback addresses, RFC1918 private ranges, and bare (dot-free)\n * hostnames — the shape of a docker-compose/k8s service name (e.g.\n * \"anvil\"), which a real public DNS name essentially never has.\n *\n * ASSUMPTION, and the loosest edge of this guard: every dot-free, colon-free\n * hostname is treated as private, with no exceptions. A single-label *public* hostname (no dots) is\n * essentially nonexistent in practice — ICANN does not delegate bare\n * gTLDs/TLDs as public A/AAAA records — but this function cannot tell \"no\n * dots because it's a compose service name\" from \"no dots because it's an\n * unusual real host\" if one somehow existed. If that assumption ever stops\n * holding, this is the line to revisit.\n */\nexport function isLikelyLocalOrPrivateHost(hostname: string): boolean {\n const h = hostname.toLowerCase().replace(/^\\[|\\]$/g, '');\n if (h === 'localhost' || h === '127.0.0.1' || h === '::1') return true;\n if (/^127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(h)) return true;\n if (/^10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}$/.test(h)) return true;\n if (/^192\\.168\\.\\d{1,3}\\.\\d{1,3}$/.test(h)) return true;\n if (/^172\\.(1[6-9]|2\\d|3[01])\\.\\d{1,3}\\.\\d{1,3}$/.test(h)) return true;\n if (!h.includes('.') && !h.includes(':')) return true; // bare hostname\n return false;\n}\n\nfunction parseHostnameOrThrow(rpcUrl: string): string {\n try {\n return new URL(rpcUrl).hostname;\n } catch (cause) {\n // Not the URL itself: every commercial EVM RPC provider embeds an API key\n // in the path, and these messages reach `validate`, `doctor`, startup\n // output and CI logs.\n throw new CommerceError('CONFIG_INVALID', 'x402 provider: rpcUrl is not a valid URL', {\n cause,\n });\n }\n}\n\n/** Host only, for diagnostics. An RPC URL's path routinely carries a key. */\nfunction describeRpc(rpcUrl: string): string {\n try {\n return new URL(rpcUrl).origin;\n } catch {\n return '[unparseable rpcUrl]';\n }\n}\n\n/**\n * Throws CONFIG_INVALID if a well-known dev key would sign against a non-local\n * RPC — or if `signerPrivateKey` isn't shaped like a private key at all.\n *\n * The shape check runs first and unconditionally. \"Not a key I recognise\"\n * (falls through `isWellKnownDevKey`, silently treated as someone's own real\n * facilitator key) and \"not a key at all\" (e.g. a truncated 63-char string)\n * must never share a code path: a malformed key that slips past this guard\n * only fails later, inside `settle()`, after the buyer's replayKey is already\n * reserved.\n */\nexport function assertDevKeyIsLocalOnly(rpcUrl: string, signerPrivateKey: string): void {\n if (!PRIVATE_KEY_SHAPE.test(signerPrivateKey)) {\n throw new CommerceError(\n 'CONFIG_INVALID',\n 'x402 provider: facilitator.signerPrivateKey is not a well-formed private key ' +\n '(expected \"0x\" followed by exactly 64 hex characters).',\n );\n }\n\n if (!isWellKnownDevKey(signerPrivateKey)) return; // a real facilitator key: not our business\n\n const hostname = parseHostnameOrThrow(rpcUrl);\n\n if (!isLikelyLocalOrPrivateHost(hostname)) {\n throw new CommerceError(\n 'CONFIG_INVALID',\n 'x402 provider: facilitator.signerPrivateKey is a well-known Anvil development key, but rpcUrl ' +\n `${describeRpc(rpcUrl)} does not look like a local/private chain. A public dev key must never sign against ` +\n 'a public network — it is instantly drainable and can grief real settlements via nonce exhaustion. ' +\n 'Use a real facilitator key for any non-local RPC, or point rpcUrl at your local/dev chain.',\n );\n }\n}\n\n/**\n * Throws CONFIG_INVALID if `payTo` (the settlement destination) is a\n * well-known Anvil development address and `rpcUrl` doesn't look\n * local/private.\n *\n * Mirrors `assertDevKeyIsLocalOnly`, but guards the *receiving* side of the\n * payment instead of the signing side, and — unlike the key guard — applies\n * regardless of facilitator mode: which account signs the settlement\n * transaction has no bearing on whether the destination address is one whose\n * private key is public knowledge. `agent-commerce init` defaults `payTo` to\n * a well-known Anvil address as a local-demo convenience; on a real network\n * that default routes every payment to an address anyone on earth can sweep.\n */\nexport function assertPayToIsNotDevAddress(rpcUrl: string, payTo: string): void {\n if (!isWellKnownDevAddress(payTo)) return; // not a dev address: not our business\n\n const hostname = parseHostnameOrThrow(rpcUrl);\n\n if (!isLikelyLocalOrPrivateHost(hostname)) {\n throw new CommerceError(\n 'CONFIG_INVALID',\n `x402 provider: \"payTo\" (${payTo}) is a well-known Anvil development address, but rpcUrl ` +\n `${describeRpc(rpcUrl)} does not look like a local/private chain. The private key behind that address ` +\n 'is public knowledge, so any revenue settled to it is immediately spendable by anyone. Set ' +\n '\"payTo\" to your own merchant wallet for any non-local RPC, or point rpcUrl at your ' +\n 'local/dev chain.',\n );\n }\n}\n","/**\n * Everything that must be true before this gateway is allowed to settle a\n * payment on the configured network.\n *\n * One definition, two callers: the provider runs it at construction so a\n * library consumer who never touches our config loader still gets the checks,\n * and `doctor` runs it against the parsed config so an operator sees the same\n * verdict without starting the gateway. Sharing a *call* is not sharing a\n * definition — the checks live here, and nothing re-implements them.\n *\n * Every failure is a startup failure. None of these are request-time\n * decisions: a deployment that is unsafe is unsafe before the first buyer\n * arrives, and finding out during `settle()` means the buyer's authorisation\n * is already burned.\n *\n * Peer-free by construction (no viem, no `@x402/*`) so the CLI can import it.\n */\nimport { CommerceError } from '../../core/index.js';\nimport { isLikelyLocalOrPrivateHost, isWellKnownDevAddress } from './dev-key-guard.js';\nimport {\n type DeploymentMode,\n type NetworkProfile,\n requireNetworkProfile,\n resolveDeploymentMode,\n} from './networks.js';\n\n/**\n * How the gateway authenticates to a remote facilitator.\n *\n * Deliberately a list, not a vendor: x402 facilitators are not a Coinbase-only\n * category, and a scheme that only fitted one vendor's credentials would make\n * the abstraction a fiction.\n *\n * - `none` — the facilitator takes no credential (the public testnet one).\n * - `bearer` — a static token. Covers most self-hosted and third-party\n * facilitators, and needs nothing installed.\n * - `cdp` — Coinbase Developer Platform, which signs a fresh JWT per request\n * over method + host + path, so a static header cannot express it. Handled by\n * the optional peer `@coinbase/x402`, imported only when this type is\n * configured. Anyone not using CDP never installs it.\n *\n * A facilitator whose scheme is none of these is refused at config load rather\n * than sent nothing.\n */\nexport type FacilitatorAuth =\n | { readonly type: 'none' }\n | { readonly type: 'bearer'; readonly token: string }\n | { readonly type: 'cdp'; readonly apiKeyId: string; readonly apiKeySecret: string };\n\nexport type X402FacilitatorConfig =\n | { readonly mode: 'local'; readonly signerPrivateKey: string }\n | { readonly mode: 'remote'; readonly url: string; readonly auth: FacilitatorAuth };\n\nexport interface X402DeploymentInput {\n readonly network: string;\n readonly payTo: string;\n readonly asset: string;\n /** EIP-712 domain of the asset, as the token itself reports it. */\n readonly assetName?: string;\n readonly assetVersion?: string;\n readonly facilitator: X402FacilitatorConfig;\n /** Required, and required to be `true`, before anything settles on a mainnet. */\n readonly allowMainnet?: boolean;\n /**\n * Required, and required to be `true`, to settle on a mainnet through a\n * facilitator that takes no credential.\n *\n * Separate from `allowMainnet` because it names a different decision: not\n * \"I meant to use real money\" but \"I accept this particular counterparty\n * without an account, terms, or anyone to call\".\n */\n readonly allowUnauthenticatedFacilitator?: boolean;\n}\n\nexport interface X402Deployment {\n readonly profile: NetworkProfile;\n readonly mode: DeploymentMode;\n}\n\nfunction invalid(message: string, path: string): CommerceError {\n return new CommerceError('CONFIG_INVALID', message, { details: { path } });\n}\n\n/**\n * Resolves the deployment and refuses every combination that could move real\n * money by accident. Order matters only in that the network must resolve\n * first; the rest are independent.\n */\nexport function resolveX402Deployment(input: X402DeploymentInput): X402Deployment {\n const profile = requireNetworkProfile(input.network, 'payments.x402.network');\n const mode = resolveDeploymentMode(profile, input.facilitator.mode);\n\n // The in-process facilitator signs with a key this process holds. Not a\n // custody problem — that signer never holds buyer or merchant funds, it pays\n // gas and broadcasts `transferWithAuthorization`, and the money moves buyer\n // to merchant directly on-chain. What it *is*, on a mainnet, is a funded key\n // inside the resource server: compromise that process and an attacker drains\n // the gas wallet and broadcasts from it. Separate processes, separate blast\n // radius — so it is refused, not warned about.\n if (profile.kind === 'mainnet' && input.facilitator.mode === 'local') {\n throw invalid(\n `payments.x402: network \"${profile.id}\" (${profile.displayName}) is a mainnet and cannot be served by facilitator.mode \"local\". A mainnet deployment must settle through a remote facilitator.`,\n 'payments.x402.facilitator.mode',\n );\n }\n\n if (mode === 'mainnet' && input.allowMainnet !== true) {\n throw invalid(\n `payments.x402: network \"${profile.id}\" (${profile.displayName}) settles real funds. Set payments.x402.allowMainnet: true to acknowledge this explicitly — it is never the default.`,\n 'payments.x402.allowMainnet',\n );\n }\n\n if (input.facilitator.mode === 'remote') {\n assertFacilitatorUrlIsSafe(input.facilitator.url, mode);\n // Not a funds check. An EIP-3009 authorisation names its recipient, its\n // amount and its chain, so a facilitator can broadcast exactly that\n // transfer or nothing — it cannot redirect the money. What a credential\n // buys is a *relationship*: rate limits, terms, someone to call when\n // settlement stops. Running production payments through a counterparty you\n // have none of that with is a real choice, and this is where it gets made\n // explicitly rather than by omission.\n if (\n mode === 'mainnet' &&\n input.facilitator.auth.type === 'none' &&\n input.allowUnauthenticatedFacilitator !== true\n ) {\n throw invalid(\n `payments.x402: facilitator ${describeOrigin(input.facilitator.url)} takes no credential, and this is a mainnet deployment. It will see every payment authorisation you handle, with no account, terms or support behind it. Set payments.x402.allowUnauthenticatedFacilitator: true to accept that, or configure facilitator.auth.`,\n 'payments.x402.allowUnauthenticatedFacilitator',\n );\n }\n // An empty credential is refused rather than sent: a blank token or key\n // reaches the facilitator as \"unauthenticated\" and fails every payment\n // after the buyer has already signed. `${VAR:- }` resolving to whitespace\n // is the realistic way this happens.\n for (const [field, value] of credentialFields(input.facilitator.auth)) {\n if (value.trim() === '') {\n throw invalid(\n `payments.x402: facilitator.auth.type is \"${input.facilitator.auth.type}\" but ${field} is empty. An empty credential is refused rather than sent.`,\n `payments.x402.facilitator.auth.${field}`,\n );\n }\n }\n }\n\n // The private key behind every well-known Anvil address is public knowledge.\n // `dev-key-guard` already refuses one against a public *RPC*; this refuses\n // one on any non-local *deployment*, which is the case a remote facilitator\n // creates — there the RPC host says nothing about where settlement lands.\n // The zero address lived only in the config loader, so a library consumer\n // calling `createX402PaymentProvider` directly — the path this shared\n // definition exists to cover — could boot a mainnet provider whose every\n // payment burns — sharing a *call* is not sharing a definition, and this\n // check had drifted out of the shared one.\n if (/^0x0{40}$/i.test(input.payTo)) {\n throw invalid(\n 'payments.x402: \"payTo\" is the zero address. Every payment settled there is destroyed.',\n 'payments.x402.payTo',\n );\n }\n\n if (mode !== 'local' && isWellKnownDevAddress(input.payTo)) {\n throw invalid(\n `payments.x402: \"payTo\" (${input.payTo}) is a well-known Anvil development address and this is a ${mode} deployment. Anyone can spend what settles there. Set payTo to your own merchant wallet.`,\n 'payments.x402.payTo',\n );\n }\n\n // Mainnet only. A testnet is exactly where pointing at a mock token is the\n // right thing to do, so the same check there would block the normal case.\n const canonical = profile.canonicalAsset;\n if (mode === 'mainnet' && canonical) {\n if (!sameAddress(input.asset, canonical.address)) {\n throw invalid(\n `payments.x402: asset ${input.asset} is not ${canonical.symbol} on ${profile.displayName} (expected ${canonical.address}). Settling a mainnet payment in an unintended token is refused.`,\n 'payments.x402.asset',\n );\n }\n // The EIP-712 domain is signed by the buyer and checked by the scheme. Get\n // it wrong and every payment is refused `invalid_exact_evm_token_name_mismatch`\n // *after* the buyer signed — a config error charged to the buyer's patience.\n // Caught here instead, before the gateway starts.\n if (input.assetName !== undefined && input.assetName !== canonical.name) {\n throw invalid(\n `payments.x402: assetName \"${input.assetName}\" is not the EIP-712 domain name ${canonical.symbol} reports on ${profile.displayName} (expected \"${canonical.name}\"). Every payment would be refused after the buyer signed.`,\n 'payments.x402.assetName',\n );\n }\n if (input.assetVersion !== undefined && input.assetVersion !== canonical.version) {\n throw invalid(\n `payments.x402: assetVersion \"${input.assetVersion}\" is not the EIP-712 domain version ${canonical.symbol} reports on ${profile.displayName} (expected \"${canonical.version}\").`,\n 'payments.x402.assetVersion',\n );\n }\n }\n\n return { profile, mode };\n}\n\n/**\n * A facilitator sees every payment authorisation this gateway handles. Plain\n * HTTP to one on a public network puts those on the wire in the clear and\n * lets anyone in the path rewrite a settlement result, so it is allowed only\n * where the endpoint cannot leave the host — a local or private address.\n */\nfunction assertFacilitatorUrlIsSafe(url: string, mode: DeploymentMode): void {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch (cause) {\n throw new CommerceError('CONFIG_INVALID', `payments.x402: facilitator.url is not a valid URL`, {\n cause,\n details: { path: 'payments.x402.facilitator.url' },\n });\n }\n\n if (parsed.protocol === 'https:') return;\n if (parsed.protocol !== 'http:') {\n throw invalid(\n `payments.x402: facilitator ${describeOrigin(url)} must be reached over https (or http on a local/private host); got \"${parsed.protocol}//\"`,\n 'payments.x402.facilitator.url',\n );\n }\n if (mode !== 'mainnet' && isLikelyLocalOrPrivateHost(parsed.hostname)) return;\n\n // The origin, never the whole URL: the path can carry a tenant or an API\n // key, which is why `/.well-known` withholds this field entirely. A refusal\n // message goes to `validate`, `doctor`, startup output and CI logs.\n throw invalid(\n `payments.x402: facilitator ${describeOrigin(url)} is reached over plain HTTP. Payment authorisations and settlement results would travel unencrypted. Use https, or point at a local/private host on a non-mainnet deployment.`,\n 'payments.x402.facilitator.url',\n );\n}\n\n/** Host only — a facilitator URL can carry a tenant path or an API key. */\nfunction describeOrigin(url: string): string {\n try {\n return new URL(url).origin;\n } catch {\n return '[unparseable facilitator.url]';\n }\n}\n\n/** The secret-bearing fields of an auth block, for emptiness checks only. Never logged. */\nfunction credentialFields(auth: FacilitatorAuth): readonly (readonly [string, string])[] {\n switch (auth.type) {\n case 'bearer':\n return [['token', auth.token]];\n case 'cdp':\n return [\n ['apiKeyId', auth.apiKeyId],\n ['apiKeySecret', auth.apiKeySecret],\n ];\n default:\n return [];\n }\n}\n\nfunction sameAddress(a: string, b: string): boolean {\n return a.toLowerCase() === b.toLowerCase();\n}\n"]}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { ZodError, z } from 'zod';
|
|
3
|
-
import { existsSync, mkdirSync, accessSync, constants, readFileSync, chmodSync, promises } from 'fs';
|
|
3
|
+
import { existsSync, mkdirSync, accessSync, constants, closeSync, openSync, readFileSync, chmodSync, promises } from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import { resolve, dirname, join } from 'path';
|
|
6
6
|
import { stringify, parse, YAMLError } from 'yaml';
|
|
@@ -299,14 +299,24 @@ var init_networks = __esm({
|
|
|
299
299
|
chainId: 84532,
|
|
300
300
|
displayName: "Base Sepolia",
|
|
301
301
|
kind: "testnet",
|
|
302
|
-
canonicalAsset: {
|
|
302
|
+
canonicalAsset: {
|
|
303
|
+
symbol: "USDC",
|
|
304
|
+
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
305
|
+
name: "USDC",
|
|
306
|
+
version: "2"
|
|
307
|
+
}
|
|
303
308
|
},
|
|
304
309
|
{
|
|
305
310
|
id: "eip155:8453",
|
|
306
311
|
chainId: 8453,
|
|
307
312
|
displayName: "Base",
|
|
308
313
|
kind: "mainnet",
|
|
309
|
-
canonicalAsset: {
|
|
314
|
+
canonicalAsset: {
|
|
315
|
+
symbol: "USDC",
|
|
316
|
+
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
317
|
+
name: "USD Coin",
|
|
318
|
+
version: "2"
|
|
319
|
+
}
|
|
310
320
|
}
|
|
311
321
|
];
|
|
312
322
|
BY_ID = new Map(PROFILES.map((profile) => [profile.id, profile]));
|
|
@@ -333,19 +343,26 @@ function substituteEnv(value, env, path2 = "$") {
|
|
|
333
343
|
return value;
|
|
334
344
|
}
|
|
335
345
|
function substituteString(value, env, path2) {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
346
|
+
assertTemplateIsSupported(value, path2);
|
|
347
|
+
return substituteOnce(value, env, path2);
|
|
348
|
+
}
|
|
349
|
+
function assertTemplateIsSupported(value, path2) {
|
|
350
|
+
if (NESTED_PLACEHOLDER.test(value)) {
|
|
351
|
+
throw new CommerceError(
|
|
352
|
+
"CONFIG_INVALID",
|
|
353
|
+
`Configuration value at "${path2}" nests placeholders (e.g. "\${A:-\${B}}"), which is not supported \u2014 the inner one is not resolved and its closing brace ends up in the value. Use a plain default, or set the variable.`,
|
|
354
|
+
{ details: { path: path2 } }
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
BRACE_TOKEN.lastIndex = 0;
|
|
358
|
+
for (const [token] of value.matchAll(BRACE_TOKEN)) {
|
|
359
|
+
if (SUPPORTED_TOKEN.test(token)) continue;
|
|
342
360
|
throw new CommerceError(
|
|
343
361
|
"CONFIG_INVALID",
|
|
344
|
-
`Configuration value at "${path2}"
|
|
345
|
-
{ details: { path: path2,
|
|
362
|
+
`Configuration value at "${path2}" contains "${token}", which is not a supported placeholder. Only "\${VAR}" and "\${VAR:-default}" are recognised; shell forms such as "\${VAR-default}", "\${VAR:=default}" and "\${VAR:?message}" would load as literal text.`,
|
|
363
|
+
{ details: { path: path2, token } }
|
|
346
364
|
);
|
|
347
365
|
}
|
|
348
|
-
return substituted;
|
|
349
366
|
}
|
|
350
367
|
function substituteOnce(value, env, path2) {
|
|
351
368
|
return value.replace(
|
|
@@ -370,11 +387,14 @@ function substituteOnce(value, env, path2) {
|
|
|
370
387
|
function isPlainObject(value) {
|
|
371
388
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
372
389
|
}
|
|
373
|
-
var PLACEHOLDER_PATTERN;
|
|
390
|
+
var PLACEHOLDER_PATTERN, BRACE_TOKEN, SUPPORTED_TOKEN, NESTED_PLACEHOLDER;
|
|
374
391
|
var init_env = __esm({
|
|
375
392
|
"src/config/env.ts"() {
|
|
376
393
|
init_core();
|
|
377
394
|
PLACEHOLDER_PATTERN = /\$\{([A-Za-z_][A-Za-z0-9_]*)(:-([^}]*))?\}/g;
|
|
395
|
+
BRACE_TOKEN = /\$\{[^{}]*\}/g;
|
|
396
|
+
SUPPORTED_TOKEN = /^\$\{[A-Za-z_][A-Za-z0-9_]*(:-[^}]*)?\}$/;
|
|
397
|
+
NESTED_PLACEHOLDER = /\$\{[^{}]*\$\{/;
|
|
378
398
|
}
|
|
379
399
|
});
|
|
380
400
|
|
|
@@ -451,19 +471,27 @@ function resolveX402Deployment(input) {
|
|
|
451
471
|
}
|
|
452
472
|
if (input.facilitator.mode === "remote") {
|
|
453
473
|
assertFacilitatorUrlIsSafe(input.facilitator.url, mode);
|
|
454
|
-
if (mode === "mainnet" && input.facilitator.auth.type === "none") {
|
|
474
|
+
if (mode === "mainnet" && input.facilitator.auth.type === "none" && input.allowUnauthenticatedFacilitator !== true) {
|
|
455
475
|
throw invalid(
|
|
456
|
-
|
|
457
|
-
"payments.x402.
|
|
476
|
+
`payments.x402: facilitator ${describeOrigin(input.facilitator.url)} takes no credential, and this is a mainnet deployment. It will see every payment authorisation you handle, with no account, terms or support behind it. Set payments.x402.allowUnauthenticatedFacilitator: true to accept that, or configure facilitator.auth.`,
|
|
477
|
+
"payments.x402.allowUnauthenticatedFacilitator"
|
|
458
478
|
);
|
|
459
479
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
480
|
+
for (const [field, value] of credentialFields(input.facilitator.auth)) {
|
|
481
|
+
if (value.trim() === "") {
|
|
482
|
+
throw invalid(
|
|
483
|
+
`payments.x402: facilitator.auth.type is "${input.facilitator.auth.type}" but ${field} is empty. An empty credential is refused rather than sent.`,
|
|
484
|
+
`payments.x402.facilitator.auth.${field}`
|
|
485
|
+
);
|
|
486
|
+
}
|
|
465
487
|
}
|
|
466
488
|
}
|
|
489
|
+
if (/^0x0{40}$/i.test(input.payTo)) {
|
|
490
|
+
throw invalid(
|
|
491
|
+
'payments.x402: "payTo" is the zero address. Every payment settled there is destroyed.',
|
|
492
|
+
"payments.x402.payTo"
|
|
493
|
+
);
|
|
494
|
+
}
|
|
467
495
|
if (mode !== "local" && isWellKnownDevAddress(input.payTo)) {
|
|
468
496
|
throw invalid(
|
|
469
497
|
`payments.x402: "payTo" (${input.payTo}) is a well-known Anvil development address and this is a ${mode} deployment. Anyone can spend what settles there. Set payTo to your own merchant wallet.`,
|
|
@@ -471,11 +499,25 @@ function resolveX402Deployment(input) {
|
|
|
471
499
|
);
|
|
472
500
|
}
|
|
473
501
|
const canonical = profile.canonicalAsset;
|
|
474
|
-
if (mode === "mainnet" && canonical
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
502
|
+
if (mode === "mainnet" && canonical) {
|
|
503
|
+
if (!sameAddress(input.asset, canonical.address)) {
|
|
504
|
+
throw invalid(
|
|
505
|
+
`payments.x402: asset ${input.asset} is not ${canonical.symbol} on ${profile.displayName} (expected ${canonical.address}). Settling a mainnet payment in an unintended token is refused.`,
|
|
506
|
+
"payments.x402.asset"
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
if (input.assetName !== void 0 && input.assetName !== canonical.name) {
|
|
510
|
+
throw invalid(
|
|
511
|
+
`payments.x402: assetName "${input.assetName}" is not the EIP-712 domain name ${canonical.symbol} reports on ${profile.displayName} (expected "${canonical.name}"). Every payment would be refused after the buyer signed.`,
|
|
512
|
+
"payments.x402.assetName"
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
if (input.assetVersion !== void 0 && input.assetVersion !== canonical.version) {
|
|
516
|
+
throw invalid(
|
|
517
|
+
`payments.x402: assetVersion "${input.assetVersion}" is not the EIP-712 domain version ${canonical.symbol} reports on ${profile.displayName} (expected "${canonical.version}").`,
|
|
518
|
+
"payments.x402.assetVersion"
|
|
519
|
+
);
|
|
520
|
+
}
|
|
479
521
|
}
|
|
480
522
|
return { profile, mode };
|
|
481
523
|
}
|
|
@@ -484,25 +526,44 @@ function assertFacilitatorUrlIsSafe(url, mode) {
|
|
|
484
526
|
try {
|
|
485
527
|
parsed = new URL(url);
|
|
486
528
|
} catch (cause) {
|
|
487
|
-
throw new CommerceError(
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
);
|
|
529
|
+
throw new CommerceError("CONFIG_INVALID", `payments.x402: facilitator.url is not a valid URL`, {
|
|
530
|
+
cause,
|
|
531
|
+
details: { path: "payments.x402.facilitator.url" }
|
|
532
|
+
});
|
|
492
533
|
}
|
|
493
534
|
if (parsed.protocol === "https:") return;
|
|
494
535
|
if (parsed.protocol !== "http:") {
|
|
495
536
|
throw invalid(
|
|
496
|
-
`payments.x402: facilitator
|
|
537
|
+
`payments.x402: facilitator ${describeOrigin(url)} must be reached over https (or http on a local/private host); got "${parsed.protocol}//"`,
|
|
497
538
|
"payments.x402.facilitator.url"
|
|
498
539
|
);
|
|
499
540
|
}
|
|
500
541
|
if (mode !== "mainnet" && isLikelyLocalOrPrivateHost(parsed.hostname)) return;
|
|
501
542
|
throw invalid(
|
|
502
|
-
`payments.x402: facilitator
|
|
543
|
+
`payments.x402: facilitator ${describeOrigin(url)} is reached over plain HTTP. Payment authorisations and settlement results would travel unencrypted. Use https, or point at a local/private host on a non-mainnet deployment.`,
|
|
503
544
|
"payments.x402.facilitator.url"
|
|
504
545
|
);
|
|
505
546
|
}
|
|
547
|
+
function describeOrigin(url) {
|
|
548
|
+
try {
|
|
549
|
+
return new URL(url).origin;
|
|
550
|
+
} catch {
|
|
551
|
+
return "[unparseable facilitator.url]";
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
function credentialFields(auth) {
|
|
555
|
+
switch (auth.type) {
|
|
556
|
+
case "bearer":
|
|
557
|
+
return [["token", auth.token]];
|
|
558
|
+
case "cdp":
|
|
559
|
+
return [
|
|
560
|
+
["apiKeyId", auth.apiKeyId],
|
|
561
|
+
["apiKeySecret", auth.apiKeySecret]
|
|
562
|
+
];
|
|
563
|
+
default:
|
|
564
|
+
return [];
|
|
565
|
+
}
|
|
566
|
+
}
|
|
506
567
|
function sameAddress(a, b) {
|
|
507
568
|
return a.toLowerCase() === b.toLowerCase();
|
|
508
569
|
}
|
|
@@ -616,8 +677,13 @@ function normalise(raw) {
|
|
|
616
677
|
mcp: {
|
|
617
678
|
enabled: toBoolean(raw.protocols.mcp.enabled, "protocols.mcp.enabled"),
|
|
618
679
|
mountPath: raw.protocols.mcp.mountPath
|
|
680
|
+
},
|
|
681
|
+
a2a: {
|
|
682
|
+
enabled: toBoolean(raw.protocols.a2a?.enabled ?? false, "protocols.a2a.enabled"),
|
|
683
|
+
mountPath: raw.protocols.a2a?.mountPath ?? DEFAULT_A2A_MOUNT_PATH
|
|
619
684
|
}
|
|
620
685
|
};
|
|
686
|
+
validateMountPaths(protocols);
|
|
621
687
|
const x402Raw = raw.payments.x402;
|
|
622
688
|
const facilitator = x402Raw === void 0 ? void 0 : x402Raw.facilitator.mode === "local" ? { mode: "local", signerPrivateKey: x402Raw.facilitator.signerPrivateKey } : {
|
|
623
689
|
mode: "remote",
|
|
@@ -644,7 +710,13 @@ function normalise(raw) {
|
|
|
644
710
|
}
|
|
645
711
|
),
|
|
646
712
|
facilitator,
|
|
647
|
-
...x402Raw.allowMainnet !== void 0 ? { allowMainnet: toBoolean(x402Raw.allowMainnet, "payments.x402.allowMainnet") } : {}
|
|
713
|
+
...x402Raw.allowMainnet !== void 0 ? { allowMainnet: toBoolean(x402Raw.allowMainnet, "payments.x402.allowMainnet") } : {},
|
|
714
|
+
...x402Raw.allowUnauthenticatedFacilitator !== void 0 ? {
|
|
715
|
+
allowUnauthenticatedFacilitator: toBoolean(
|
|
716
|
+
x402Raw.allowUnauthenticatedFacilitator,
|
|
717
|
+
"payments.x402.allowUnauthenticatedFacilitator"
|
|
718
|
+
)
|
|
719
|
+
} : {}
|
|
648
720
|
} : void 0;
|
|
649
721
|
if (x402) {
|
|
650
722
|
validateAddress("payments.x402.payTo", x402.payTo);
|
|
@@ -653,8 +725,11 @@ function normalise(raw) {
|
|
|
653
725
|
network: x402.network,
|
|
654
726
|
payTo: x402.payTo,
|
|
655
727
|
asset: x402.asset,
|
|
728
|
+
assetName: x402.assetName,
|
|
729
|
+
assetVersion: x402.assetVersion,
|
|
656
730
|
facilitator: x402.facilitator,
|
|
657
|
-
...x402.allowMainnet !== void 0 ? { allowMainnet: x402.allowMainnet } : {}
|
|
731
|
+
...x402.allowMainnet !== void 0 ? { allowMainnet: x402.allowMainnet } : {},
|
|
732
|
+
...x402.allowUnauthenticatedFacilitator !== void 0 ? { allowUnauthenticatedFacilitator: x402.allowUnauthenticatedFacilitator } : {}
|
|
658
733
|
});
|
|
659
734
|
}
|
|
660
735
|
const resources = Object.entries(raw.resources).map(
|
|
@@ -683,6 +758,25 @@ function normalise(raw) {
|
|
|
683
758
|
}
|
|
684
759
|
};
|
|
685
760
|
}
|
|
761
|
+
function validateMountPaths(protocols) {
|
|
762
|
+
const mounts = [
|
|
763
|
+
["mcp", protocols.mcp],
|
|
764
|
+
["a2a", protocols.a2a]
|
|
765
|
+
].filter(([, p]) => p.enabled);
|
|
766
|
+
for (const [index, [nameA, a]] of mounts.entries()) {
|
|
767
|
+
for (const [nameB, b] of mounts.slice(index + 1)) {
|
|
768
|
+
const baseA = a.mountPath.replace(/\/+$/, "");
|
|
769
|
+
const baseB = b.mountPath.replace(/\/+$/, "");
|
|
770
|
+
if (baseA === baseB || baseA.startsWith(`${baseB}/`) || baseB.startsWith(`${baseA}/`)) {
|
|
771
|
+
throw new CommerceError(
|
|
772
|
+
"CONFIG_INVALID",
|
|
773
|
+
`protocols.${nameA}.mountPath ("${a.mountPath}") collides with protocols.${nameB}.mountPath ("${b.mountPath}") \u2014 each mount registers a wildcard, so overlapping prefixes cannot both be served`,
|
|
774
|
+
{ details: { path: `protocols.${nameA}.mountPath` } }
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
686
780
|
function normaliseResource(id, entry, protocols, x402) {
|
|
687
781
|
if (entry.input !== void 0) validateResourceSchemaKeywords(id, "input", entry.input);
|
|
688
782
|
const inputProperties = entry.input?.["properties"];
|
|
@@ -703,7 +797,7 @@ function normaliseResource(id, entry, protocols, x402) {
|
|
|
703
797
|
const hint = protocol === "ucp" ? " (UCP is planned, not supported in this release)" : "";
|
|
704
798
|
throw new CommerceError(
|
|
705
799
|
"CONFIG_INVALID",
|
|
706
|
-
`Resource "${id}" exposes unsupported protocol "${protocol}"${hint}. Supported: http, mcp.`,
|
|
800
|
+
`Resource "${id}" exposes unsupported protocol "${protocol}"${hint}. Supported: http, mcp, a2a.`,
|
|
707
801
|
{ details: { path: `resources.${id}.expose`, resourceId: id, protocol } }
|
|
708
802
|
);
|
|
709
803
|
}
|
|
@@ -724,6 +818,13 @@ function normaliseResource(id, entry, protocols, x402) {
|
|
|
724
818
|
);
|
|
725
819
|
}
|
|
726
820
|
}
|
|
821
|
+
if (entry.expose.includes("a2a") && !protocols.a2a.enabled) {
|
|
822
|
+
throw new CommerceError(
|
|
823
|
+
"CONFIG_INVALID",
|
|
824
|
+
`Resource "${id}" is exposed via "a2a" but protocols.a2a.enabled is false`,
|
|
825
|
+
{ details: { path: `resources.${id}.expose`, resourceId: id } }
|
|
826
|
+
);
|
|
827
|
+
}
|
|
727
828
|
if (entry.expose.includes("http") && !protocols.http.enabled) {
|
|
728
829
|
throw new CommerceError(
|
|
729
830
|
"CONFIG_INVALID",
|
|
@@ -813,6 +914,10 @@ function defaultClosedObjectSchema(schema) {
|
|
|
813
914
|
if (isPlainObject2(items)) {
|
|
814
915
|
result["items"] = defaultClosedObjectSchema(items);
|
|
815
916
|
}
|
|
917
|
+
const additional = schema["additionalProperties"];
|
|
918
|
+
if (isPlainObject2(additional)) {
|
|
919
|
+
result["additionalProperties"] = defaultClosedObjectSchema(additional);
|
|
920
|
+
}
|
|
816
921
|
return result;
|
|
817
922
|
}
|
|
818
923
|
function isPlainObject2(value) {
|
|
@@ -860,6 +965,10 @@ function validateResourceSchemaKeywords(id, path2, schema) {
|
|
|
860
965
|
if (isPlainObject2(sub)) validateResourceSchemaKeywords(id, `${path2}.properties.${key}`, sub);
|
|
861
966
|
}
|
|
862
967
|
}
|
|
968
|
+
const additional = schema["additionalProperties"];
|
|
969
|
+
if (isPlainObject2(additional)) {
|
|
970
|
+
validateResourceSchemaKeywords(id, `${path2}.additionalProperties`, additional);
|
|
971
|
+
}
|
|
863
972
|
const items = schema["items"];
|
|
864
973
|
if (isPlainObject2(items)) {
|
|
865
974
|
validateResourceSchemaKeywords(id, `${path2}.items`, items);
|
|
@@ -907,6 +1016,23 @@ function validatePathParametersDeclared(id, url, input) {
|
|
|
907
1016
|
}
|
|
908
1017
|
const params = extractPathParameterNames(url);
|
|
909
1018
|
if (params.length === 0) return;
|
|
1019
|
+
const firstBrace = url.indexOf("{");
|
|
1020
|
+
if (firstBrace !== -1) {
|
|
1021
|
+
const prefix = url.slice(0, firstBrace);
|
|
1022
|
+
let origin;
|
|
1023
|
+
try {
|
|
1024
|
+
const parsedPrefix = new URL(prefix);
|
|
1025
|
+
origin = parsedPrefix.hostname === "" ? void 0 : parsedPrefix.origin;
|
|
1026
|
+
} catch {
|
|
1027
|
+
}
|
|
1028
|
+
if (origin === void 0 || !prefix.startsWith(`${origin}/`)) {
|
|
1029
|
+
throw new CommerceError(
|
|
1030
|
+
"CONFIG_INVALID",
|
|
1031
|
+
`Resource "${id}" has a backend.url parameter before the end of the host ("${url}"). A caller-supplied value would choose which host the gateway calls, which is request forgery with the gateway's own network position. Parameters are supported inside the path and query only`,
|
|
1032
|
+
{ details: { path: `resources.${id}.backend.url`, resourceId: id } }
|
|
1033
|
+
);
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
910
1036
|
const propertiesRaw = input?.["properties"];
|
|
911
1037
|
const properties = isPlainObject2(propertiesRaw) ? propertiesRaw : {};
|
|
912
1038
|
const requiredRaw = input?.["required"];
|
|
@@ -968,7 +1094,7 @@ function validateAddress(path2, value) {
|
|
|
968
1094
|
);
|
|
969
1095
|
}
|
|
970
1096
|
}
|
|
971
|
-
var SUPPORTED_CONFIG_VERSION, ADDRESS_PATTERN, ZERO_ADDRESS_PATTERN, MCP_TOOL_NAME_PATTERN, NumberOrString, BooleanOrString, MerchantSchema, ServerSchema, StorageSchema, ProtocolsSchema, BackendMethodSchema, BackendHandlerSchema, PricingSchema, JsonSchemaValueSchema, ResourceEntrySchema, ResourcesMapSchema, FacilitatorAuthSchema, FacilitatorSchema, X402Schema, PaymentsSchema, RawConfigSchema, SUPPORTED_PROTOCOLS, SUPPORTED_PAYMENT_METHODS, EMPTY_CLOSED_OBJECT_SCHEMA, UNSUPPORTED_SCHEMA_KEYWORDS, PRICING_AMOUNT_PATTERN, ZERO_AMOUNT_PATTERN;
|
|
1097
|
+
var SUPPORTED_CONFIG_VERSION, ADDRESS_PATTERN, ZERO_ADDRESS_PATTERN, MCP_TOOL_NAME_PATTERN, NumberOrString, BooleanOrString, MerchantSchema, ServerSchema, StorageSchema, RESERVED_GATEWAY_PATHS, MountPathSchema, ProtocolsSchema, DEFAULT_A2A_MOUNT_PATH, BackendMethodSchema, BackendHandlerSchema, PricingSchema, JsonSchemaValueSchema, ResourceEntrySchema, ResourcesMapSchema, FacilitatorAuthSchema, FacilitatorSchema, X402Schema, PaymentsSchema, RawConfigSchema, SUPPORTED_PROTOCOLS, SUPPORTED_PAYMENT_METHODS, EMPTY_CLOSED_OBJECT_SCHEMA, UNSUPPORTED_SCHEMA_KEYWORDS, PRICING_AMOUNT_PATTERN, ZERO_AMOUNT_PATTERN;
|
|
972
1098
|
var init_schema = __esm({
|
|
973
1099
|
"src/config/schema.ts"() {
|
|
974
1100
|
init_execution();
|
|
@@ -1011,13 +1137,48 @@ var init_schema = __esm({
|
|
|
1011
1137
|
path: z.string().min(1)
|
|
1012
1138
|
}).strict()
|
|
1013
1139
|
}).strict();
|
|
1140
|
+
RESERVED_GATEWAY_PATHS = [
|
|
1141
|
+
"/health",
|
|
1142
|
+
"/ready",
|
|
1143
|
+
"/.well-known/agent-commerce",
|
|
1144
|
+
// Fixed by the A2A specification, so it is the adapter's to serve and never
|
|
1145
|
+
// a configurable mount's to claim.
|
|
1146
|
+
"/.well-known/agent-card.json",
|
|
1147
|
+
"/api/resources",
|
|
1148
|
+
"/api/resources/:id/invoke",
|
|
1149
|
+
"/api/receipts",
|
|
1150
|
+
"/api/events",
|
|
1151
|
+
"/api/events/stream"
|
|
1152
|
+
];
|
|
1153
|
+
MountPathSchema = z.string().min(1).refine((value) => value.startsWith("/"), {
|
|
1154
|
+
message: 'must start with "/" \u2014 it is an absolute gateway path, e.g. "/mcp".'
|
|
1155
|
+
}).refine((value) => !/[:*?\s]/.test(value), {
|
|
1156
|
+
message: 'must not contain ":", "*", "?" or whitespace \u2014 the mount is a literal path prefix, not a Fastify route pattern, and registers its own wildcard.'
|
|
1157
|
+
}).refine(
|
|
1158
|
+
(value) => {
|
|
1159
|
+
const base = value.replace(/\/+$/, "");
|
|
1160
|
+
return !RESERVED_GATEWAY_PATHS.some(
|
|
1161
|
+
(reserved) => reserved === base || reserved.startsWith(`${base}/`)
|
|
1162
|
+
);
|
|
1163
|
+
},
|
|
1164
|
+
{
|
|
1165
|
+
message: `must not collide with a route the gateway already serves (${RESERVED_GATEWAY_PATHS.join(", ")}); pick a dedicated prefix such as "/mcp".`
|
|
1166
|
+
}
|
|
1167
|
+
);
|
|
1014
1168
|
ProtocolsSchema = z.object({
|
|
1015
1169
|
http: z.object({ enabled: BooleanOrString }).strict(),
|
|
1016
1170
|
mcp: z.object({
|
|
1017
1171
|
enabled: BooleanOrString,
|
|
1018
|
-
mountPath:
|
|
1019
|
-
}).strict()
|
|
1172
|
+
mountPath: MountPathSchema
|
|
1173
|
+
}).strict(),
|
|
1174
|
+
// Optional block: A2A is off unless an operator asks for it, and an
|
|
1175
|
+
// existing config predating the adapter stays valid.
|
|
1176
|
+
a2a: z.object({
|
|
1177
|
+
enabled: BooleanOrString,
|
|
1178
|
+
mountPath: MountPathSchema.optional()
|
|
1179
|
+
}).strict().optional()
|
|
1020
1180
|
}).strict();
|
|
1181
|
+
DEFAULT_A2A_MOUNT_PATH = "/a2a";
|
|
1021
1182
|
BackendMethodSchema = z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
1022
1183
|
BackendHandlerSchema = z.object({
|
|
1023
1184
|
type: z.literal("http"),
|
|
@@ -1052,7 +1213,14 @@ var init_schema = __esm({
|
|
|
1052
1213
|
ResourcesMapSchema = z.record(z.string().min(1), ResourceEntrySchema);
|
|
1053
1214
|
FacilitatorAuthSchema = z.discriminatedUnion("type", [
|
|
1054
1215
|
z.object({ type: z.literal("none") }).strict(),
|
|
1055
|
-
z.object({ type: z.literal("bearer"), token: z.string().min(1) }).strict()
|
|
1216
|
+
z.object({ type: z.literal("bearer"), token: z.string().min(1) }).strict(),
|
|
1217
|
+
// Coinbase Developer Platform. Needs the optional peer `@coinbase/x402`,
|
|
1218
|
+
// which signs a fresh JWT per request; a static header cannot express it.
|
|
1219
|
+
z.object({
|
|
1220
|
+
type: z.literal("cdp"),
|
|
1221
|
+
apiKeyId: z.string().min(1),
|
|
1222
|
+
apiKeySecret: z.string().min(1)
|
|
1223
|
+
}).strict()
|
|
1056
1224
|
]);
|
|
1057
1225
|
FacilitatorSchema = z.discriminatedUnion("mode", [
|
|
1058
1226
|
z.object({
|
|
@@ -1081,7 +1249,9 @@ var init_schema = __esm({
|
|
|
1081
1249
|
maxTimeoutSeconds: NumberOrString,
|
|
1082
1250
|
facilitator: FacilitatorSchema,
|
|
1083
1251
|
/** Real funds. Never defaulted — see src/payments/x402/guardrails.ts. */
|
|
1084
|
-
allowMainnet: BooleanOrString.optional()
|
|
1252
|
+
allowMainnet: BooleanOrString.optional(),
|
|
1253
|
+
/** Accepts a mainnet facilitator that takes no credential. Never defaulted. */
|
|
1254
|
+
allowUnauthenticatedFacilitator: BooleanOrString.optional()
|
|
1085
1255
|
}).strict();
|
|
1086
1256
|
PaymentsSchema = z.object({
|
|
1087
1257
|
x402: X402Schema.optional()
|
|
@@ -1095,7 +1265,7 @@ var init_schema = __esm({
|
|
|
1095
1265
|
resources: ResourcesMapSchema,
|
|
1096
1266
|
payments: PaymentsSchema
|
|
1097
1267
|
}).strict();
|
|
1098
|
-
SUPPORTED_PROTOCOLS = /* @__PURE__ */ new Set(["http", "mcp"]);
|
|
1268
|
+
SUPPORTED_PROTOCOLS = /* @__PURE__ */ new Set(["http", "mcp", "a2a"]);
|
|
1099
1269
|
SUPPORTED_PAYMENT_METHODS = /* @__PURE__ */ new Set(["x402"]);
|
|
1100
1270
|
EMPTY_CLOSED_OBJECT_SCHEMA = {
|
|
1101
1271
|
type: "object",
|
|
@@ -1284,9 +1454,46 @@ init_execution();
|
|
|
1284
1454
|
init_core();
|
|
1285
1455
|
init_networks();
|
|
1286
1456
|
|
|
1457
|
+
// src/protocols/a2a/constants.ts
|
|
1458
|
+
var A2A_SPEC_VERSION = "1.0.0";
|
|
1459
|
+
var A2A_PROTOCOL_VERSION = "1.0";
|
|
1460
|
+
var A2A_PROTOCOL_BINDING = "JSONRPC";
|
|
1461
|
+
var A2A_AGENT_CARD_PATH = "/.well-known/agent-card.json";
|
|
1462
|
+
var A2A_UNSUPPORTED_METHODS = [
|
|
1463
|
+
"SendStreamingMessage",
|
|
1464
|
+
"GetTask",
|
|
1465
|
+
"ListTasks",
|
|
1466
|
+
"CancelTask",
|
|
1467
|
+
"SubscribeToTask",
|
|
1468
|
+
"CreateTaskPushNotificationConfig",
|
|
1469
|
+
"GetTaskPushNotificationConfig",
|
|
1470
|
+
"ListTaskPushNotificationConfigs",
|
|
1471
|
+
"DeleteTaskPushNotificationConfig",
|
|
1472
|
+
"GetExtendedAgentCard"
|
|
1473
|
+
];
|
|
1474
|
+
|
|
1475
|
+
// src/protocols/a2a/descriptor.ts
|
|
1476
|
+
var A2A_UNSUPPORTED = [
|
|
1477
|
+
// Methods, named as the protocol names them. Same list the transport
|
|
1478
|
+
// rejects by name, so the descriptor cannot promise less than it refuses.
|
|
1479
|
+
...A2A_UNSUPPORTED_METHODS,
|
|
1480
|
+
// Transports other than the one binding served.
|
|
1481
|
+
"HTTP+JSON/REST binding",
|
|
1482
|
+
"gRPC binding",
|
|
1483
|
+
// Behaviours.
|
|
1484
|
+
"SSE",
|
|
1485
|
+
"long-running task persistence",
|
|
1486
|
+
"task resumption",
|
|
1487
|
+
"push notifications",
|
|
1488
|
+
"multi-turn conversational continuation",
|
|
1489
|
+
"authenticated extended agent cards",
|
|
1490
|
+
"A2A authentication schemes",
|
|
1491
|
+
"artifact types beyond Agent Commerce outcome data"
|
|
1492
|
+
];
|
|
1493
|
+
|
|
1287
1494
|
// src/storage/receipts/store.ts
|
|
1288
1495
|
init_core();
|
|
1289
|
-
var PACKAGE_VERSION = "
|
|
1496
|
+
var PACKAGE_VERSION = "1.1.0" ;
|
|
1290
1497
|
|
|
1291
1498
|
// src/storage/receipts/redact.ts
|
|
1292
1499
|
var SECRET_KEY_PATTERN = /private[-_ ]?key|secret|password|mnemonic|seed[-_ ]?phrase|api[-_ ]?key|authoriz(e|ation)|^auth$|auth[-_]?header|payment[-_ ]?proof|^proof$|x-payment|signature|signed[-_ ]?message|token|bearer|credential|cookie|session|jwt/i;
|
|
@@ -1511,6 +1718,12 @@ function createSqliteReceiptStore(options) {
|
|
|
1511
1718
|
);
|
|
1512
1719
|
}
|
|
1513
1720
|
}
|
|
1721
|
+
if (isFileBacked) {
|
|
1722
|
+
try {
|
|
1723
|
+
closeSync(openSync(path2, "a", 384));
|
|
1724
|
+
} catch {
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1514
1727
|
const db = new Database(path2);
|
|
1515
1728
|
db.pragma("journal_mode = WAL");
|
|
1516
1729
|
db.pragma("foreign_keys = ON");
|
|
@@ -2033,7 +2246,7 @@ function maskMiddle(value, keep = 6) {
|
|
|
2033
2246
|
}
|
|
2034
2247
|
createRequire(import.meta.url);
|
|
2035
2248
|
function readVersionReport() {
|
|
2036
|
-
const injected = '{"cliVersion":"
|
|
2249
|
+
const injected = '{"cliVersion":"1.1.0","pinned":[{"name":"@coinbase/x402","version":"2.1.0","via":"@devlab.group/agent-commerce"},{"name":"@modelcontextprotocol/sdk","version":"1.30.0","via":"@devlab.group/agent-commerce"},{"name":"@x402/core","version":"2.23.0","via":"@devlab.group/agent-commerce"},{"name":"@x402/evm","version":"2.23.0","via":"@devlab.group/agent-commerce"},{"name":"viem","version":"2.55.18","via":"@devlab.group/agent-commerce"},{"name":"better-sqlite3","version":"13.0.3","via":"@devlab.group/agent-commerce"},{"name":"fastify","version":"5.12.0","via":"@devlab.group/agent-commerce"},{"name":"pino","version":"10.3.1","via":"@devlab.group/agent-commerce"},{"name":"zod","version":"3.25.76","via":"@devlab.group/agent-commerce"}]}' ;
|
|
2037
2250
|
{
|
|
2038
2251
|
return JSON.parse(injected);
|
|
2039
2252
|
}
|
|
@@ -2172,10 +2385,27 @@ async function runDoctor(options, deps = {}) {
|
|
|
2172
2385
|
checks.push({ name: "Protocols", status: "FAIL", detail: "well-known document unreachable" });
|
|
2173
2386
|
} else {
|
|
2174
2387
|
const mcpMountPath = config.protocols.mcp.enabled ? config.protocols.mcp.mountPath : void 0;
|
|
2388
|
+
const a2aMountPath = config.protocols.a2a.enabled ? config.protocols.a2a.mountPath : void 0;
|
|
2175
2389
|
checks.push({
|
|
2176
2390
|
name: "Protocols",
|
|
2177
2391
|
status: "PASS",
|
|
2178
|
-
detail: `http=${config.protocols.http.enabled ? "on" : "off"} mcp=${config.protocols.mcp.enabled ? `on (${mcpMountPath})` : "off"}`
|
|
2392
|
+
detail: `http=${config.protocols.http.enabled ? "on" : "off"} mcp=${config.protocols.mcp.enabled ? `on (${mcpMountPath})` : "off"} a2a=${config.protocols.a2a.enabled ? `on (${a2aMountPath})` : "off"}`
|
|
2393
|
+
});
|
|
2394
|
+
}
|
|
2395
|
+
if (config === void 0) {
|
|
2396
|
+
checks.push({ name: "A2A", status: "WARN", detail: "skipped \u2014 config invalid" });
|
|
2397
|
+
} else if (!config.protocols.a2a.enabled) {
|
|
2398
|
+
checks.push({ name: "A2A", status: "INFO", detail: "disabled" });
|
|
2399
|
+
} else {
|
|
2400
|
+
checks.push({
|
|
2401
|
+
name: "A2A",
|
|
2402
|
+
status: "PASS",
|
|
2403
|
+
detail: `experimental \xB7 spec ${A2A_SPEC_VERSION} \xB7 protocol ${A2A_PROTOCOL_VERSION} \xB7 binding ${A2A_PROTOCOL_BINDING} \xB7 mount ${config.protocols.a2a.mountPath} \xB7 card ${A2A_AGENT_CARD_PATH}`
|
|
2404
|
+
});
|
|
2405
|
+
checks.push({
|
|
2406
|
+
name: "A2A unsupported",
|
|
2407
|
+
status: "INFO",
|
|
2408
|
+
detail: A2A_UNSUPPORTED.join(", ")
|
|
2179
2409
|
});
|
|
2180
2410
|
}
|
|
2181
2411
|
const x402 = config?.payments.x402;
|
|
@@ -2213,10 +2443,11 @@ async function runDoctor(options, deps = {}) {
|
|
|
2213
2443
|
);
|
|
2214
2444
|
}
|
|
2215
2445
|
if (mode === "mainnet") {
|
|
2446
|
+
const unauthenticated = x402.facilitator.mode === "remote" && x402.facilitator.auth.type === "none";
|
|
2216
2447
|
checks.push({
|
|
2217
2448
|
name: "Mainnet safety",
|
|
2218
|
-
status: "INFO",
|
|
2219
|
-
detail: `${describeDeploymentMode("mainnet")} \u2014 enforced at config load: explicit allowMainnet opt-in, remote facilitator over HTTPS with a credential, non-development payTo, and the canonical asset for this network. Payments are fail-closed.`
|
|
2449
|
+
status: unauthenticated ? "WARN" : "INFO",
|
|
2450
|
+
detail: unauthenticated ? `${describeDeploymentMode("mainnet")} \u2014 settling through a facilitator that takes no credential, accepted via allowUnauthenticatedFacilitator. It sees every payment authorisation you handle, with no account or terms behind it. Everything else is enforced at config load: explicit allowMainnet opt-in, HTTPS, non-development payTo, canonical asset. Payments are fail-closed.` : `${describeDeploymentMode("mainnet")} \u2014 enforced at config load: explicit allowMainnet opt-in, remote facilitator over HTTPS with a credential, non-development payTo, and the canonical asset for this network. Payments are fail-closed.`
|
|
2220
2451
|
});
|
|
2221
2452
|
}
|
|
2222
2453
|
}
|
|
@@ -2369,7 +2600,7 @@ async function runValidate(options, io, deps = {}) {
|
|
|
2369
2600
|
io.stdout(` merchant: ${config.merchant.name} (${config.merchant.id})`);
|
|
2370
2601
|
io.stdout(` resources: ${config.resources.length}`);
|
|
2371
2602
|
io.stdout(
|
|
2372
|
-
` protocols: http=${config.protocols.http.enabled ? "on" : "off"} mcp=${config.protocols.mcp.enabled ? "on" : "off"}`
|
|
2603
|
+
` protocols: http=${config.protocols.http.enabled ? "on" : "off"} mcp=${config.protocols.mcp.enabled ? "on" : "off"} a2a=${config.protocols.a2a.enabled ? "on" : "off"}`
|
|
2373
2604
|
);
|
|
2374
2605
|
io.stdout(` payments: x402=${config.payments.x402?.enabled === true ? "on" : "off"}`);
|
|
2375
2606
|
return 0;
|