@devlab.group/agent-commerce 1.6.1 → 2.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/LICENSE +187 -2
- package/README.md +105 -54
- package/dist/ap2.d.ts +4 -4
- package/dist/ap2.js +95 -28
- package/dist/ap2.js.map +1 -1
- package/dist/{authorization-BpbWGGrf.d.ts → authorization-CMkIuAkO.d.ts} +1 -1
- package/dist/chunk-2CJNA7MS.js +8 -0
- package/dist/chunk-2CJNA7MS.js.map +1 -0
- package/dist/chunk-5ZYXBCDX.js +113 -0
- package/dist/chunk-5ZYXBCDX.js.map +1 -0
- package/dist/{chunk-V2V2E7HX.js → chunk-6Z2H53CG.js} +7 -6
- package/dist/chunk-6Z2H53CG.js.map +1 -0
- package/dist/chunk-ES3V6ECS.js +125 -0
- package/dist/chunk-ES3V6ECS.js.map +1 -0
- package/dist/chunk-IFGRCXME.js +581 -0
- package/dist/chunk-IFGRCXME.js.map +1 -0
- package/dist/chunk-KA2AX5FH.js +44 -0
- package/dist/chunk-KA2AX5FH.js.map +1 -0
- package/dist/{chunk-VKDXMXHL.js → chunk-LZS5BL3W.js} +3 -3
- package/dist/{chunk-VKDXMXHL.js.map → chunk-LZS5BL3W.js.map} +1 -1
- package/dist/{chunk-N5KRK7YA.js → chunk-SY6GUYSQ.js} +6 -10
- package/dist/chunk-SY6GUYSQ.js.map +1 -0
- package/dist/chunk-UHMYRV3J.js +1396 -0
- package/dist/chunk-UHMYRV3J.js.map +1 -0
- package/dist/cli/index.js +2980 -1964
- package/dist/cli/index.js.map +1 -1
- package/dist/{constants-Bkq04FRZ.d.ts → constants-nackLItD.d.ts} +4 -3
- package/dist/{guardrails-71XwtXlT.d.ts → guardrails-CX_jYXvZ.d.ts} +9 -1
- package/dist/index.d.ts +88 -49
- package/dist/index.js +3432 -2161
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +3 -3
- package/dist/mcp.js +252 -94
- package/dist/mcp.js.map +1 -1
- package/dist/mpp.d.ts +15 -4
- package/dist/mpp.js +115 -74
- package/dist/mpp.js.map +1 -1
- package/dist/near-D6C42VPE.js +58 -0
- package/dist/near-D6C42VPE.js.map +1 -0
- package/dist/networks-olfflCI_.d.ts +38 -0
- package/dist/{protocol-adapter-DTkrXTev.d.ts → protocol-adapter-CfxU8Z2T.d.ts} +4 -2
- package/dist/{runtime-CiYbgGpi.d.ts → runtime-7vLNYvmU.d.ts} +25 -3
- package/dist/svm-QEUAUNPQ.js +33 -0
- package/dist/svm-QEUAUNPQ.js.map +1 -0
- package/dist/{types-O6lg3Q5V.d.ts → types-CVw6Sf46.d.ts} +11 -0
- package/dist/x402.d.ts +78 -63
- package/dist/x402.js +13 -4
- package/dist/x402.js.map +1 -1
- package/package.json +36 -13
- package/dist/chunk-AFI2CEZS.js +0 -10
- package/dist/chunk-AFI2CEZS.js.map +0 -1
- package/dist/chunk-CBJIT7QP.js +0 -258
- package/dist/chunk-CBJIT7QP.js.map +0 -1
- package/dist/chunk-JKEKKXRJ.js +0 -680
- package/dist/chunk-JKEKKXRJ.js.map +0 -1
- package/dist/chunk-N5KRK7YA.js.map +0 -1
- package/dist/chunk-V2V2E7HX.js.map +0 -1
package/dist/x402.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/payments/x402/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/payments/x402/client.ts"],"names":[],"mappings":"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","file":"x402.js","sourcesContent":["/**\n * Client-side payment helper: builds and EIP-712-signs an x402 v2 `exact`/EVM\n * authorization on the buyer's side. The demo buyer agent and the test suites\n * use it, and the `/x402` entry exports it. The gateway never calls it and\n * never holds a buyer key.\n *\n * It signs directly rather than through the SDK's `x402Client` because of\n * `overrides`: negative tests need deliberately wrong authorizations that a\n * conforming client will not produce. The x402 settlement E2E suite also\n * settles a payment built by `x402Client` itself to cover interoperability.\n */\n\nimport { PaymentRequirementsV2Schema } from '@x402/core/schemas';\nimport type { PaymentPayload, PaymentRequirements } from '@x402/core/types';\nimport { getAddress, toHex } from 'viem';\nimport { privateKeyToAccount } from 'viem/accounts';\nimport { CommerceError } from '../../core';\nimport { chainIdFromCaip2 } from './chain';\n\nexport interface CreatePaymentProofOptions {\n /** The buyer's private key. It signs locally and is never sent anywhere */\n readonly buyerPrivateKey: `0x${string}`;\n /** One entry from PaymentRequiredEnvelope.payment.accepts, verbatim */\n readonly accepts: Readonly<Record<string, unknown>>;\n /** For negative tests only: a wrong amount, recipient, nonce or validity window */\n readonly overrides?: {\n readonly value?: string;\n readonly payTo?: string;\n readonly nonce?: `0x${string}`;\n readonly validBefore?: number;\n readonly validAfter?: number;\n };\n}\n\nconst X402_VERSION = 2;\n\nconst TRANSFER_WITH_AUTHORIZATION_TYPES = {\n TransferWithAuthorization: [\n { name: 'from', type: 'address' },\n { name: 'to', type: 'address' },\n { name: 'value', type: 'uint256' },\n { name: 'validAfter', type: 'uint256' },\n { name: 'validBefore', type: 'uint256' },\n { name: 'nonce', type: 'bytes32' },\n ],\n} as const;\n\n/** Returns the base64 `PAYMENT-SIGNATURE` value to send back to the gateway */\nexport async function createPaymentProof(options: CreatePaymentProofOptions): Promise<string> {\n const requirements = PaymentRequirementsV2Schema.parse(options.accepts);\n const method = requirements.extra?.['assetTransferMethod'];\n if (method !== undefined && method !== 'eip3009') {\n throw new CommerceError(\n 'INPUT_INVALID',\n `createPaymentProof: signs EIP-3009 only, and this offer uses \"${String(method)}\"; sign it with the SDK's x402Client`,\n );\n }\n if (\n !requirements.extra ||\n typeof requirements.extra['name'] !== 'string' ||\n typeof requirements.extra['version'] !== 'string'\n ) {\n throw new CommerceError(\n 'INPUT_INVALID',\n 'createPaymentProof: payment requirement is missing extra.name/extra.version, so the EIP-712 domain cannot be built',\n );\n }\n\n const chainId = chainIdFromCaip2(requirements.network);\n if (chainId === undefined) {\n throw new CommerceError(\n 'INPUT_INVALID',\n `createPaymentProof: network \"${requirements.network}\" is not a CAIP-2 eip155 identifier, so there is no chain id to sign against`,\n );\n }\n\n const account = privateKeyToAccount(options.buyerPrivateKey);\n\n const nonce = options.overrides?.nonce ?? randomNonce();\n const nowSeconds = Math.floor(Date.now() / 1000);\n // 0, as in the SDK's own v2 client: nothing here wants the authorization\n // delayed\n const validAfter = options.overrides?.validAfter ?? 0;\n const validBefore = options.overrides?.validBefore ?? nowSeconds + requirements.maxTimeoutSeconds;\n const to = getAddress((options.overrides?.payTo ?? requirements.payTo) as `0x${string}`);\n const value = BigInt(options.overrides?.value ?? requirements.amount);\n\n const signature = await account.signTypedData({\n domain: {\n name: requirements.extra['name'],\n version: requirements.extra['version'],\n chainId,\n verifyingContract: getAddress(requirements.asset as `0x${string}`),\n },\n types: TRANSFER_WITH_AUTHORIZATION_TYPES,\n primaryType: 'TransferWithAuthorization',\n message: {\n from: account.address,\n to,\n value,\n validAfter: BigInt(validAfter),\n validBefore: BigInt(validBefore),\n nonce,\n },\n });\n\n // v2 echoes the chosen requirement back as `accepted` so a stateless\n // facilitator knows which offer was taken. The gateway verifies against its\n // own copy.\n const payload: PaymentPayload = {\n x402Version: X402_VERSION,\n accepted: requirements as PaymentRequirements,\n payload: {\n signature,\n authorization: {\n from: account.address,\n to,\n value: value.toString(),\n validAfter: validAfter.toString(),\n validBefore: validBefore.toString(),\n nonce,\n },\n },\n };\n\n return Buffer.from(JSON.stringify(payload), 'utf8').toString('base64');\n}\n\nfunction randomNonce(): `0x${string}` {\n const bytes = new Uint8Array(32);\n crypto.getRandomValues(bytes);\n return toHex(bytes);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devlab.group/agent-commerce",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Open-source, self-hosted, non-custodial commerce and payment orchestration gateway for AI agents.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -118,31 +118,43 @@
|
|
|
118
118
|
},
|
|
119
119
|
"peerDependencies": {
|
|
120
120
|
"@coinbase/x402": "2.1.0",
|
|
121
|
-
"@modelcontextprotocol/
|
|
121
|
+
"@modelcontextprotocol/server": "2.3.0",
|
|
122
122
|
"@sd-jwt/core": "0.20.1",
|
|
123
|
-
"@
|
|
124
|
-
"@x402/
|
|
123
|
+
"@solana/kit": "5.5.1",
|
|
124
|
+
"@x402/core": "2.25.0",
|
|
125
|
+
"@x402/evm": "2.25.0",
|
|
126
|
+
"@x402/near": "2.25.0",
|
|
127
|
+
"@x402/svm": "2.25.0",
|
|
125
128
|
"canonicalize": "5.0.0",
|
|
126
129
|
"jose": "6.2.12",
|
|
127
|
-
"mppx": "0.
|
|
128
|
-
"viem": "2.
|
|
130
|
+
"mppx": "0.13.1",
|
|
131
|
+
"viem": "2.57.1"
|
|
129
132
|
},
|
|
130
133
|
"peerDependenciesMeta": {
|
|
131
134
|
"@coinbase/x402": {
|
|
132
135
|
"optional": true
|
|
133
136
|
},
|
|
134
|
-
"@modelcontextprotocol/
|
|
137
|
+
"@modelcontextprotocol/server": {
|
|
135
138
|
"optional": true
|
|
136
139
|
},
|
|
137
140
|
"@sd-jwt/core": {
|
|
138
141
|
"optional": true
|
|
139
142
|
},
|
|
143
|
+
"@solana/kit": {
|
|
144
|
+
"optional": true
|
|
145
|
+
},
|
|
140
146
|
"@x402/core": {
|
|
141
147
|
"optional": true
|
|
142
148
|
},
|
|
143
149
|
"@x402/evm": {
|
|
144
150
|
"optional": true
|
|
145
151
|
},
|
|
152
|
+
"@x402/near": {
|
|
153
|
+
"optional": true
|
|
154
|
+
},
|
|
155
|
+
"@x402/svm": {
|
|
156
|
+
"optional": true
|
|
157
|
+
},
|
|
146
158
|
"canonicalize": {
|
|
147
159
|
"optional": true
|
|
148
160
|
},
|
|
@@ -157,29 +169,40 @@
|
|
|
157
169
|
}
|
|
158
170
|
},
|
|
159
171
|
"devDependencies": {
|
|
160
|
-
"@a2a-js/sdk": "1.
|
|
172
|
+
"@a2a-js/sdk": "1.3.0",
|
|
161
173
|
"@biomejs/biome": "2.5.9",
|
|
162
174
|
"@coinbase/x402": "2.1.0",
|
|
163
|
-
"@modelcontextprotocol/
|
|
175
|
+
"@modelcontextprotocol/client": "2.3.0",
|
|
176
|
+
"@modelcontextprotocol/sdk": "1.32.0",
|
|
177
|
+
"@modelcontextprotocol/server": "2.3.0",
|
|
178
|
+
"@near-js/accounts": "2.5.1",
|
|
179
|
+
"@near-js/crypto": "2.5.1",
|
|
180
|
+
"@near-js/providers": "2.5.1",
|
|
181
|
+
"@near-js/signers": "2.5.1",
|
|
182
|
+
"@near-js/transactions": "2.5.1",
|
|
164
183
|
"@sd-jwt/core": "0.20.1",
|
|
184
|
+
"@solana/kit": "5.5.1",
|
|
165
185
|
"@types/better-sqlite3": "9.6.0",
|
|
166
186
|
"@types/node": "24.13.3",
|
|
167
187
|
"@types/react": "19.2.18",
|
|
168
188
|
"@types/react-dom": "19.2.4",
|
|
169
189
|
"@vitejs/plugin-react": "6.0.5",
|
|
170
190
|
"@vitest/coverage-v8": "4.1.11",
|
|
171
|
-
"@x402/core": "2.
|
|
172
|
-
"@x402/evm": "2.
|
|
191
|
+
"@x402/core": "2.25.0",
|
|
192
|
+
"@x402/evm": "2.25.0",
|
|
193
|
+
"@x402/mcp": "2.25.0",
|
|
194
|
+
"@x402/near": "2.25.0",
|
|
195
|
+
"@x402/svm": "2.25.0",
|
|
173
196
|
"canonicalize": "5.0.0",
|
|
174
197
|
"jose": "6.2.12",
|
|
175
|
-
"mppx": "0.
|
|
198
|
+
"mppx": "0.13.1",
|
|
176
199
|
"pino-pretty": "13.1.2",
|
|
177
200
|
"react": "19.0.8",
|
|
178
201
|
"react-dom": "19.0.8",
|
|
179
202
|
"tsup": "8.5.1",
|
|
180
203
|
"tsx": "4.23.12",
|
|
181
204
|
"typescript": "5.9.3",
|
|
182
|
-
"viem": "2.
|
|
205
|
+
"viem": "2.57.1",
|
|
183
206
|
"vite": "8.2.1",
|
|
184
207
|
"vitest": "4.1.11"
|
|
185
208
|
},
|
package/dist/chunk-AFI2CEZS.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// src/protocols/mcp/constants.ts
|
|
2
|
-
var MCP_TOOL_NAME_CHARS = "A-Za-z0-9._-";
|
|
3
|
-
var MCP_TOOL_NAME_MAX_LENGTH = 128;
|
|
4
|
-
var MCP_TOOL_NAME_PATTERN = new RegExp(
|
|
5
|
-
`^[${MCP_TOOL_NAME_CHARS}]{1,${MCP_TOOL_NAME_MAX_LENGTH}}$`
|
|
6
|
-
);
|
|
7
|
-
|
|
8
|
-
export { MCP_TOOL_NAME_PATTERN };
|
|
9
|
-
//# sourceMappingURL=chunk-AFI2CEZS.js.map
|
|
10
|
-
//# sourceMappingURL=chunk-AFI2CEZS.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocols/mcp/constants.ts"],"names":[],"mappings":";AAUO,IAAM,mBAAA,GAAsB,cAAA;AAG5B,IAAM,wBAAA,GAA2B,GAAA;AAQjC,IAAM,wBAAwB,IAAI,MAAA;AAAA,EACvC,CAAA,EAAA,EAAK,mBAAmB,CAAA,IAAA,EAAO,wBAAwB,CAAA,EAAA;AACzD","file":"chunk-AFI2CEZS.js","sourcesContent":["/**\n * MCP protocol constants shared by the config layer, the adapter and the\n * OpenAPI importer.\n *\n * Kept dependency-free: `src/config` validates tool names at load time, so\n * anything imported here would become a config-load dependency too, and the\n * MCP SDK is an optional peer the main entry and CLI must never load.\n */\n\n/** Characters SEP-986 allows in an MCP tool name, as a regex character class body */\nexport const MCP_TOOL_NAME_CHARS = 'A-Za-z0-9._-';\n\n/** Longest MCP tool name SEP-986 allows */\nexport const MCP_TOOL_NAME_MAX_LENGTH = 128;\n\n/**\n * MCP tool names per SEP-986: 1-128 chars of [A-Za-z0-9._-], the SDK's own\n * `TOOL_NAME_REGEX`, which it does not export. Config refuses an mcp-exposed\n * resource whose id fails it; the low-level `Server` checks no names, so the\n * adapter also skips such an id with a warning.\n */\nexport const MCP_TOOL_NAME_PATTERN = new RegExp(\n `^[${MCP_TOOL_NAME_CHARS}]{1,${MCP_TOOL_NAME_MAX_LENGTH}}$`,\n);\n"]}
|
package/dist/chunk-CBJIT7QP.js
DELETED
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import { CommerceError } from './chunk-N5KRK7YA.js';
|
|
2
|
-
|
|
3
|
-
// src/payments/x402/networks.ts
|
|
4
|
-
var LOCAL_CHAIN_ID = 84532;
|
|
5
|
-
var PROFILES = [
|
|
6
|
-
{
|
|
7
|
-
id: "eip155:84532",
|
|
8
|
-
chainId: 84532,
|
|
9
|
-
displayName: "Base Sepolia",
|
|
10
|
-
kind: "testnet",
|
|
11
|
-
canonicalAsset: {
|
|
12
|
-
symbol: "USDC",
|
|
13
|
-
address: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
14
|
-
name: "USDC",
|
|
15
|
-
version: "2"
|
|
16
|
-
}
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
id: "eip155:8453",
|
|
20
|
-
chainId: 8453,
|
|
21
|
-
displayName: "Base",
|
|
22
|
-
kind: "mainnet",
|
|
23
|
-
canonicalAsset: {
|
|
24
|
-
symbol: "USDC",
|
|
25
|
-
address: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
26
|
-
name: "USD Coin",
|
|
27
|
-
version: "2"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
];
|
|
31
|
-
var BY_ID = new Map(PROFILES.map((profile) => [profile.id, profile]));
|
|
32
|
-
var SUPPORTED_NETWORK_IDS = PROFILES.map((p) => p.id);
|
|
33
|
-
function requireNetworkProfile(id, path) {
|
|
34
|
-
const profile = BY_ID.get(id);
|
|
35
|
-
if (!profile) {
|
|
36
|
-
throw new CommerceError(
|
|
37
|
-
"CONFIG_INVALID",
|
|
38
|
-
`${path}: "${id}" is not a supported network. Supported CAIP-2 identifiers: ${SUPPORTED_NETWORK_IDS.join(", ")}`,
|
|
39
|
-
{ details: { path, network: id } }
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
return profile;
|
|
43
|
-
}
|
|
44
|
-
function resolveDeploymentMode(profile, facilitatorMode) {
|
|
45
|
-
if (facilitatorMode === "local") return "local";
|
|
46
|
-
return profile.kind;
|
|
47
|
-
}
|
|
48
|
-
var LIVE_MAINNET_BANNER = "LIVE MAINNET MODE - REAL FUNDS";
|
|
49
|
-
function describeDeploymentMode(mode) {
|
|
50
|
-
return mode === "mainnet" ? LIVE_MAINNET_BANNER : mode.toUpperCase();
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// src/payments/x402/local-chain/accounts.ts
|
|
54
|
-
var ANVIL_WELL_KNOWN_ACCOUNTS = [
|
|
55
|
-
{
|
|
56
|
-
address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
|
|
57
|
-
privateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
|
|
61
|
-
privateKey: "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
address: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
|
|
65
|
-
privateKey: "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
|
|
66
|
-
}
|
|
67
|
-
];
|
|
68
|
-
|
|
69
|
-
// src/payments/x402/dev-key-guard.ts
|
|
70
|
-
var WELL_KNOWN_DEV_KEYS = new Set(
|
|
71
|
-
ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.privateKey.toLowerCase())
|
|
72
|
-
);
|
|
73
|
-
var WELL_KNOWN_DEV_ADDRESSES = new Set(
|
|
74
|
-
ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.address.toLowerCase())
|
|
75
|
-
);
|
|
76
|
-
var PRIVATE_KEY_SHAPE = /^0x[0-9a-fA-F]{64}$/;
|
|
77
|
-
function isWellKnownDevKey(privateKey) {
|
|
78
|
-
return WELL_KNOWN_DEV_KEYS.has(privateKey.toLowerCase());
|
|
79
|
-
}
|
|
80
|
-
function isWellKnownDevAddress(address) {
|
|
81
|
-
return WELL_KNOWN_DEV_ADDRESSES.has(address.toLowerCase());
|
|
82
|
-
}
|
|
83
|
-
function isLikelyLocalOrPrivateHost(hostname) {
|
|
84
|
-
const h = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
85
|
-
if (h === "localhost" || h === "::1") return true;
|
|
86
|
-
if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
87
|
-
if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
88
|
-
if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
89
|
-
if (/^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
90
|
-
if (!h.includes(".") && !h.includes(":")) return true;
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
function parseHostnameOrThrow(rpcUrl) {
|
|
94
|
-
try {
|
|
95
|
-
return new URL(rpcUrl).hostname;
|
|
96
|
-
} catch (cause) {
|
|
97
|
-
throw new CommerceError("CONFIG_INVALID", "x402 provider: rpcUrl is not a valid URL", {
|
|
98
|
-
cause
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
function describeRpc(rpcUrl) {
|
|
103
|
-
try {
|
|
104
|
-
return new URL(rpcUrl).origin;
|
|
105
|
-
} catch {
|
|
106
|
-
return "[unparseable rpcUrl]";
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
function assertDevKeyIsLocalOnly(rpcUrl, signerPrivateKey) {
|
|
110
|
-
if (!PRIVATE_KEY_SHAPE.test(signerPrivateKey)) {
|
|
111
|
-
throw new CommerceError(
|
|
112
|
-
"CONFIG_INVALID",
|
|
113
|
-
'x402 provider: facilitator.signerPrivateKey is not a well-formed private key (expected "0x" followed by exactly 64 hex characters).'
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
if (!isWellKnownDevKey(signerPrivateKey)) return;
|
|
117
|
-
const hostname = parseHostnameOrThrow(rpcUrl);
|
|
118
|
-
if (!isLikelyLocalOrPrivateHost(hostname)) {
|
|
119
|
-
throw new CommerceError(
|
|
120
|
-
"CONFIG_INVALID",
|
|
121
|
-
`x402 provider: facilitator.signerPrivateKey is a well-known Anvil development key, but rpcUrl ${describeRpc(rpcUrl)} does not look like a local/private chain. A public dev key must never sign against a public network: it is instantly drainable and can grief real settlements via nonce exhaustion. Use a real facilitator key for any non-local RPC, or point rpcUrl at your local/dev chain.`
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
function assertPayToIsNotDevAddress(rpcUrl, payTo) {
|
|
126
|
-
if (!isWellKnownDevAddress(payTo)) return;
|
|
127
|
-
const hostname = parseHostnameOrThrow(rpcUrl);
|
|
128
|
-
if (!isLikelyLocalOrPrivateHost(hostname)) {
|
|
129
|
-
throw new CommerceError(
|
|
130
|
-
"CONFIG_INVALID",
|
|
131
|
-
`x402 provider: "payTo" (${payTo}) is a well-known Anvil development address, but rpcUrl ${describeRpc(rpcUrl)} does not look like a local/private chain. The private key behind that address is public knowledge, so any revenue settled to it is immediately spendable by anyone. Set "payTo" to your own merchant wallet for any non-local RPC, or point rpcUrl at your local/dev chain.`
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// src/payments/x402/guardrails.ts
|
|
137
|
-
function invalid(message, path) {
|
|
138
|
-
return new CommerceError("CONFIG_INVALID", message, { details: { path } });
|
|
139
|
-
}
|
|
140
|
-
function resolveX402Deployment(input) {
|
|
141
|
-
const at = input.configPath ?? "payments.x402";
|
|
142
|
-
const payTo = input.payToField ?? "payTo";
|
|
143
|
-
const profile = requireNetworkProfile(input.network, `${at}.network`);
|
|
144
|
-
const mode = resolveDeploymentMode(profile, input.facilitator.mode);
|
|
145
|
-
if (profile.kind === "mainnet" && input.facilitator.mode === "local") {
|
|
146
|
-
throw invalid(
|
|
147
|
-
`${at}: 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.`,
|
|
148
|
-
`${at}.facilitator.mode`
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
if (mode === "mainnet" && input.allowMainnet !== true) {
|
|
152
|
-
throw invalid(
|
|
153
|
-
`${at}: network "${profile.id}" (${profile.displayName}) settles real funds. Set ${at}.allowMainnet: true to acknowledge this explicitly - it is never the default.`,
|
|
154
|
-
`${at}.allowMainnet`
|
|
155
|
-
);
|
|
156
|
-
}
|
|
157
|
-
if (input.facilitator.mode === "remote") {
|
|
158
|
-
assertFacilitatorUrlIsSafe(input.facilitator.url, mode, at);
|
|
159
|
-
if (mode === "mainnet" && input.facilitator.auth.type === "none" && input.allowUnauthenticatedFacilitator !== true) {
|
|
160
|
-
throw invalid(
|
|
161
|
-
`${at}: no credential is configured for facilitator ${describeOrigin(input.facilitator.url)} on mainnet. Set ${at}.allowUnauthenticatedFacilitator: true to accept anonymous access and the facilitator's limits, or configure facilitator.auth.`,
|
|
162
|
-
`${at}.allowUnauthenticatedFacilitator`
|
|
163
|
-
);
|
|
164
|
-
}
|
|
165
|
-
for (const [field, value] of credentialFields(input.facilitator.auth)) {
|
|
166
|
-
if (value.trim() === "") {
|
|
167
|
-
throw invalid(
|
|
168
|
-
`${at}: facilitator.auth.type is "${input.facilitator.auth.type}" but ${field} is empty. An empty credential is refused rather than sent.`,
|
|
169
|
-
`${at}.facilitator.auth.${field}`
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
if (/^0x0{40}$/i.test(input.payTo)) {
|
|
175
|
-
throw invalid(
|
|
176
|
-
`${at}: "${payTo}" is the zero address. Every payment settled there is destroyed.`,
|
|
177
|
-
`${at}.${payTo}`
|
|
178
|
-
);
|
|
179
|
-
}
|
|
180
|
-
if (mode !== "local" && isWellKnownDevAddress(input.payTo)) {
|
|
181
|
-
throw invalid(
|
|
182
|
-
`${at}: "${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.`,
|
|
183
|
-
`${at}.${payTo}`
|
|
184
|
-
);
|
|
185
|
-
}
|
|
186
|
-
const canonical = profile.canonicalAsset;
|
|
187
|
-
if (mode === "mainnet" && canonical) {
|
|
188
|
-
if (!sameAddress(input.asset, canonical.address)) {
|
|
189
|
-
throw invalid(
|
|
190
|
-
`${at}: asset ${input.asset} is not ${canonical.symbol} on ${profile.displayName} (expected ${canonical.address}). Settling a mainnet payment in an unintended token is refused.`,
|
|
191
|
-
`${at}.asset`
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
if (input.assetName !== void 0 && input.assetName !== canonical.name) {
|
|
195
|
-
throw invalid(
|
|
196
|
-
`${at}: 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.`,
|
|
197
|
-
`${at}.assetName`
|
|
198
|
-
);
|
|
199
|
-
}
|
|
200
|
-
if (input.assetVersion !== void 0 && input.assetVersion !== canonical.version) {
|
|
201
|
-
throw invalid(
|
|
202
|
-
`${at}: assetVersion "${input.assetVersion}" is not the EIP-712 domain version ${canonical.symbol} reports on ${profile.displayName} (expected "${canonical.version}").`,
|
|
203
|
-
`${at}.assetVersion`
|
|
204
|
-
);
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
return { profile, mode };
|
|
208
|
-
}
|
|
209
|
-
function assertFacilitatorUrlIsSafe(url, mode, at) {
|
|
210
|
-
let parsed;
|
|
211
|
-
try {
|
|
212
|
-
parsed = new URL(url);
|
|
213
|
-
} catch (cause) {
|
|
214
|
-
throw new CommerceError("CONFIG_INVALID", `${at}: facilitator.url is not a valid URL`, {
|
|
215
|
-
cause,
|
|
216
|
-
details: { path: `${at}.facilitator.url` }
|
|
217
|
-
});
|
|
218
|
-
}
|
|
219
|
-
if (parsed.protocol === "https:") return;
|
|
220
|
-
if (parsed.protocol !== "http:") {
|
|
221
|
-
throw invalid(
|
|
222
|
-
`${at}: facilitator ${describeOrigin(url)} must be reached over https (or http on a local/private host); got "${parsed.protocol}//"`,
|
|
223
|
-
`${at}.facilitator.url`
|
|
224
|
-
);
|
|
225
|
-
}
|
|
226
|
-
if (mode !== "mainnet" && isLikelyLocalOrPrivateHost(parsed.hostname)) return;
|
|
227
|
-
throw invalid(
|
|
228
|
-
`${at}: facilitator ${describeOrigin(url)} is reached over plain HTTP. Payment authorizations and settlement results would travel unencrypted. Use https, or point at a local/private host on a non-mainnet deployment.`,
|
|
229
|
-
`${at}.facilitator.url`
|
|
230
|
-
);
|
|
231
|
-
}
|
|
232
|
-
function describeOrigin(url) {
|
|
233
|
-
try {
|
|
234
|
-
return new URL(url).origin;
|
|
235
|
-
} catch {
|
|
236
|
-
return "[unparseable facilitator.url]";
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
function credentialFields(auth) {
|
|
240
|
-
switch (auth.type) {
|
|
241
|
-
case "bearer":
|
|
242
|
-
return [["token", auth.token]];
|
|
243
|
-
case "cdp":
|
|
244
|
-
return [
|
|
245
|
-
["apiKeyId", auth.apiKeyId],
|
|
246
|
-
["apiKeySecret", auth.apiKeySecret]
|
|
247
|
-
];
|
|
248
|
-
default:
|
|
249
|
-
return [];
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
function sameAddress(a, b) {
|
|
253
|
-
return a.toLowerCase() === b.toLowerCase();
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export { LOCAL_CHAIN_ID, SUPPORTED_NETWORK_IDS, assertDevKeyIsLocalOnly, assertPayToIsNotDevAddress, describeDeploymentMode, describeRpc, requireNetworkProfile, resolveDeploymentMode, resolveX402Deployment };
|
|
257
|
-
//# sourceMappingURL=chunk-CBJIT7QP.js.map
|
|
258
|
-
//# sourceMappingURL=chunk-CBJIT7QP.js.map
|
|
@@ -1 +0,0 @@
|
|
|
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-CBJIT7QP.js","sourcesContent":["/**\n * The networks this gateway settles on, as data: the chain id to sign against,\n * a display name, and whether real money is involved.\n *\n * A registry rather than \"parse any `eip155:*`\": the chain id parses from any\n * such string, but whether it is a mainnet does not, and the guardrails key off\n * that. An unknown id is refused at startup instead of passing as a testnet.\n *\n * The local dev chain runs under Base Sepolia's `eip155:84532`, so nothing may\n * infer \"public network\" from the id alone. `resolveDeploymentMode` derives\n * the mode from the facilitator too, and the provider's health check confirms\n * that a local facilitator's node is Anvil.\n */\nimport { CommerceError } from '../../core';\n\n/** Chain id of the local dev chain, which Base Sepolia uses too */\nexport const LOCAL_CHAIN_ID = 84532;\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 settles in, with the EIP-712 domain the\n * token reports. Enforced on mainnet only, since a testnet may use a mock\n * token.\n *\n * `name` is the signed EIP-712 domain name, not the symbol, and the two\n * deployments differ: `\"USDC\"` on Base Sepolia, `\"USD Coin\"` on Base. The\n * values were read back from the contracts.\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 a deployment is, as opposed to the network it names. `local` comes from\n * the facilitator: the in-process facilitator makes a deployment local whatever\n * CAIP-2 id it is configured with, which lets diagnostics show `eip155:84532`\n * without claiming public Base 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 miss. Printed for mainnet only\nconst 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. The keys come from Foundry's public\n * default mnemonic (\"test test ... junk\"), so value sent to these addresses on\n * a real network can be taken by anyone.\n *\n * Index layout on the local demo chain:\n * 0 - deployer and local facilitator signer (pays gas, broadcasts settlement)\n * 1 - merchant settlement destination (`payTo`)\n * 2 - buyer (signs EIP-3009 authorizations; never held by the gateway)\n */\nexport interface AnvilWellKnownAccount {\n readonly address: `0x${string}`;\n readonly privateKey: `0x${string}`;\n}\n\n/** A fixed-length tuple, so indexing by the layout above is 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/** Label printed wherever a dev key appears in output, logs or manifests */\nexport const DEV_KEY_LABEL = 'LOCAL DEVELOPMENT ONLY - DO NOT FUND';\n","/**\n * Rejects well-known Anvil keys and payment destinations when the RPC host\n * does not look local or private. Their private keys are published, so anyone\n * can spend what such an address receives on a public chain.\n *\n * The check uses the RPC hostname because the local chain shares Base\n * Sepolia's chain id. Both guards run at provider construction.\n */\nimport { CommerceError } from '../../core';\nimport { ANVIL_WELL_KNOWN_ACCOUNTS } from './local-chain/accounts';\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` plus 64 hex characters, 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, RFC 1918 private ranges, and bare (dot-free)\n * hostnames, such as the compose service name \"anvil\".\n *\n * Bare hostnames are treated as local without resolving them. Operators must\n * point them at a private chain.\n */\nexport function isLikelyLocalOrPrivateHost(hostname: string): boolean {\n const h = hostname.toLowerCase().replace(/^\\[|\\]$/g, '');\n if (h === 'localhost' || 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 // Never the URL itself: RPC providers commonly put an API key in the path,\n // and this message reaches startup 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/** Origin only, for diagnostics: RPC URLs often carry an API key in the path or query */\nexport function 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 `signerPrivateKey` is not shaped like a private key,\n * or if it is a well-known dev key and the RPC is not local.\n *\n * The shape check runs first, so a truncated key is reported as malformed\n * instead of passing as \"not a dev key\".\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; // the operator's own key\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` is a well-known Anvil address and `rpcUrl`\n * does not look local or private.\n *\n * Guards the receiving side, in every facilitator mode. `agent-commerce init`\n * defaults `payTo` to a well-known address for the local demo, and on a real\n * network that default sends every payment where anyone can sweep it.\n */\nexport function assertPayToIsNotDevAddress(rpcUrl: string, payTo: string): void {\n if (!isWellKnownDevAddress(payTo)) return;\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 * What must hold before this gateway may settle a payment on the configured\n * network. Two callers share these checks: the provider runs them at\n * construction, so a library consumer who skips our config loader still gets\n * them, and the config loader runs them, so `validate` and `doctor` report\n * them too.\n *\n * Both run before any request, because a failure found in `settle()` would\n * come after the pipeline reserved the buyer's replay key.\n *\n * Imports no peer (no viem, no `@x402/*`), so the CLI can load it.\n */\nimport { CommerceError } from '../../core';\nimport { isLikelyLocalOrPrivateHost, isWellKnownDevAddress } from './dev-key-guard';\nimport {\n type DeploymentMode,\n type NetworkProfile,\n requireNetworkProfile,\n resolveDeploymentMode,\n} from './networks';\n\n/**\n * How the gateway authenticates to a remote facilitator.\n *\n * - `none`: send no credential; use it when the facilitator accepts\n * anonymous requests.\n * - `bearer`: send a static token; no extra peer package is needed.\n * - `cdp`: Coinbase Developer Platform. The optional peer `@coinbase/x402`,\n * imported only for this type, signs a fresh JWT per request.\n *\n * Any other auth type is refused at config load.\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 /** Must be `true` before anything settles on a mainnet */\n readonly allowMainnet?: boolean;\n /**\n * Must be `true` on mainnet when facilitator auth is `none`. A separate\n * decision from `allowMainnet`: it accepts settling without an account at\n * the facilitator, under its anonymous-access limits.\n */\n readonly allowUnauthenticatedFacilitator?: boolean;\n /** Error-path prefix, `payments.x402` by default */\n readonly configPath?: string;\n /** Settlement-recipient field named in errors, `payTo` by default */\n readonly payToField?: string;\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 the combinations that could move real\n * money by accident. The network must resolve first; the other checks are\n * independent.\n */\nexport function resolveX402Deployment(input: X402DeploymentInput): X402Deployment {\n const at = input.configPath ?? 'payments.x402';\n const payTo = input.payToField ?? 'payTo';\n const profile = requireNetworkProfile(input.network, `${at}.network`);\n const mode = resolveDeploymentMode(profile, input.facilitator.mode);\n\n // Local mode keeps a gas-paying key in the gateway process. A compromise\n // could expose that key, so mainnet requires a remote facilitator.\n if (profile.kind === 'mainnet' && input.facilitator.mode === 'local') {\n throw invalid(\n `${at}: 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 `${at}.facilitator.mode`,\n );\n }\n\n if (mode === 'mainnet' && input.allowMainnet !== true) {\n throw invalid(\n `${at}: network \"${profile.id}\" (${profile.displayName}) settles real funds. Set ${at}.allowMainnet: true to acknowledge this explicitly - it is never the default.`,\n `${at}.allowMainnet`,\n );\n }\n\n if (input.facilitator.mode === 'remote') {\n assertFacilitatorUrlIsSafe(input.facilitator.url, mode, at);\n // The authorization fixes recipient, amount and chain, so a facilitator\n // cannot redirect funds. A credential adds an account: rate limits, terms\n // and support. On mainnet, going without one is an explicit choice.\n if (\n mode === 'mainnet' &&\n input.facilitator.auth.type === 'none' &&\n input.allowUnauthenticatedFacilitator !== true\n ) {\n throw invalid(\n `${at}: no credential is configured for facilitator ${describeOrigin(input.facilitator.url)} on mainnet. Set ${at}.allowUnauthenticatedFacilitator: true to accept anonymous access and the facilitator's limits, or configure facilitator.auth.`,\n `${at}.allowUnauthenticatedFacilitator`,\n );\n }\n // A blank credential reaches the facilitator as \"unauthenticated\" and fails\n // every payment after the buyer signed. `${VAR:- }` resolving to whitespace\n // is the usual cause.\n for (const [field, value] of credentialFields(input.facilitator.auth)) {\n if (value.trim() === '') {\n throw invalid(\n `${at}: facilitator.auth.type is \"${input.facilitator.auth.type}\" but ${field} is empty. An empty credential is refused rather than sent.`,\n `${at}.facilitator.auth.${field}`,\n );\n }\n }\n }\n\n // The config loader's address check refuses this too; repeated here for a\n // library consumer who calls `createX402PaymentProvider` without it\n if (/^0x0{40}$/i.test(input.payTo)) {\n throw invalid(\n `${at}: \"${payTo}\" is the zero address. Every payment settled there is destroyed.`,\n `${at}.${payTo}`,\n );\n }\n\n // Anyone can spend from a well-known Anvil address. `dev-key-guard` refuses\n // one behind a public RPC; this refuses one on any non-local deployment,\n // whose RPC host says nothing about where a remote facilitator settles.\n if (mode !== 'local' && isWellKnownDevAddress(input.payTo)) {\n throw invalid(\n `${at}: \"${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 `${at}.${payTo}`,\n );\n }\n\n // Mainnet only: a testnet is where pointing at a mock token is normal\n const canonical = profile.canonicalAsset;\n if (mode === 'mainnet' && canonical) {\n if (!sameAddress(input.asset, canonical.address)) {\n throw invalid(\n `${at}: asset ${input.asset} is not ${canonical.symbol} on ${profile.displayName} (expected ${canonical.address}). Settling a mainnet payment in an unintended token is refused.`,\n `${at}.asset`,\n );\n }\n // The buyer signs the EIP-712 domain and the scheme checks it. A wrong one\n // refuses every payment with `invalid_exact_evm_token_name_mismatch` after\n // the buyer signed, so it is caught here, before the gateway starts.\n if (input.assetName !== undefined && input.assetName !== canonical.name) {\n throw invalid(\n `${at}: 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 `${at}.assetName`,\n );\n }\n if (input.assetVersion !== undefined && input.assetVersion !== canonical.version) {\n throw invalid(\n `${at}: assetVersion \"${input.assetVersion}\" is not the EIP-712 domain version ${canonical.symbol} reports on ${profile.displayName} (expected \"${canonical.version}\").`,\n `${at}.assetVersion`,\n );\n }\n }\n\n return { profile, mode };\n}\n\n/**\n * A facilitator sees every payment authorization this gateway handles. Plain\n * HTTP puts those on the wire in the clear and lets anyone in the path rewrite\n * a settlement result, so it is allowed only to a local or private host, and\n * never on a mainnet.\n */\nfunction assertFacilitatorUrlIsSafe(url: string, mode: DeploymentMode, at: string): void {\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch (cause) {\n throw new CommerceError('CONFIG_INVALID', `${at}: facilitator.url is not a valid URL`, {\n cause,\n details: { path: `${at}.facilitator.url` },\n });\n }\n\n if (parsed.protocol === 'https:') return;\n if (parsed.protocol !== 'http:') {\n throw invalid(\n `${at}: facilitator ${describeOrigin(url)} must be reached over https (or http on a local/private host); got \"${parsed.protocol}//\"`,\n `${at}.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, and this message reaches `validate`, `doctor`, startup output and CI logs\n throw invalid(\n `${at}: facilitator ${describeOrigin(url)} is reached over plain HTTP. Payment authorizations and settlement results would travel unencrypted. Use https, or point at a local/private host on a non-mainnet deployment.`,\n `${at}.facilitator.url`,\n );\n}\n\n// Origin 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"]}
|