@devlab.group/agent-commerce 0.1.0-alpha.0 → 1.0.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 +144 -46
- package/dist/{chunk-CZ6ZDAYD.js → chunk-BQNE3A5H.js} +9 -7
- package/dist/chunk-BQNE3A5H.js.map +1 -0
- package/dist/chunk-MEPETSP6.js +255 -0
- package/dist/chunk-MEPETSP6.js.map +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +401 -61
- package/dist/cli/index.js.map +1 -1
- package/dist/guardrails-Dmwi63KB.d.ts +38 -0
- package/dist/index.d.ts +38 -19
- package/dist/index.js +177 -48
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +2 -2
- package/dist/mcp.js.map +1 -1
- package/dist/{protocol-adapter-C0goMgLK.d.ts → protocol-adapter-CZPOb9uG.d.ts} +2 -3
- package/dist/{runtime-LCk2TzGN.d.ts → runtime-B-9IjMbO.d.ts} +17 -5
- package/dist/x402.d.ts +83 -17
- package/dist/x402.js +321 -216
- package/dist/x402.js.map +1 -1
- package/package.json +19 -7
- package/dist/chunk-CZ6ZDAYD.js.map +0 -1
package/dist/x402.js
CHANGED
|
@@ -1,11 +1,49 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assertPayToIsNotDevAddress, resolveX402Deployment, assertDevKeyIsLocalOnly, describeDeploymentMode } from './chunk-MEPETSP6.js';
|
|
2
|
+
export { SUPPORTED_NETWORK_IDS } from './chunk-MEPETSP6.js';
|
|
3
|
+
import { CommerceError, systemClock, NOOP_LOGGER, PACKAGE_VERSION } from './chunk-BQNE3A5H.js';
|
|
4
|
+
import { PaymentRequirementsV2Schema, PaymentPayloadV2Schema } from '@x402/core/schemas';
|
|
2
5
|
import { getAddress, toHex, isAddress, createWalletClient, http, publicActions, createPublicClient, HttpRequestError, TimeoutError, WaitForTransactionReceiptTimeoutError, encodeAbiParameters, keccak256, ContractFunctionRevertedError, ContractFunctionExecutionError } from 'viem';
|
|
3
6
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { x402Facilitator } from '@x402/core/facilitator';
|
|
8
|
+
import { HTTPFacilitatorClient } from '@x402/core/http';
|
|
9
|
+
import { isEIP3009Payload, toFacilitatorEvmSigner } from '@x402/evm';
|
|
10
|
+
import { registerExactEvmScheme } from '@x402/evm/exact/facilitator';
|
|
8
11
|
|
|
12
|
+
var LOCAL_CHAIN_ID = 84532;
|
|
13
|
+
function chainIdFromCaip2(network) {
|
|
14
|
+
const match = /^eip155:(\d+)$/.exec(network);
|
|
15
|
+
if (!match?.[1]) return void 0;
|
|
16
|
+
const chainId = Number(match[1]);
|
|
17
|
+
return Number.isSafeInteger(chainId) && chainId > 0 ? chainId : void 0;
|
|
18
|
+
}
|
|
19
|
+
function buildLocalChain(rpcUrl, chainId = LOCAL_CHAIN_ID) {
|
|
20
|
+
return {
|
|
21
|
+
id: chainId,
|
|
22
|
+
name: "agent-commerce-local",
|
|
23
|
+
nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
|
|
24
|
+
rpcUrls: {
|
|
25
|
+
default: { http: [rpcUrl] }
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function createLocalPublicClient(rpcUrl, timeoutMs, chainId) {
|
|
30
|
+
return createPublicClient({
|
|
31
|
+
chain: buildLocalChain(rpcUrl, chainId),
|
|
32
|
+
transport: http(rpcUrl, { timeout: timeoutMs } )
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function createLocalFacilitatorClient(rpcUrl, signerPrivateKey) {
|
|
36
|
+
const chain = buildLocalChain(rpcUrl);
|
|
37
|
+
const account = privateKeyToAccount(signerPrivateKey);
|
|
38
|
+
return createWalletClient({
|
|
39
|
+
account,
|
|
40
|
+
chain,
|
|
41
|
+
transport: http(rpcUrl)
|
|
42
|
+
}).extend(publicActions);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/payments/x402/client.ts
|
|
46
|
+
var X402_VERSION = 2;
|
|
9
47
|
var TRANSFER_WITH_AUTHORIZATION_TYPES = {
|
|
10
48
|
TransferWithAuthorization: [
|
|
11
49
|
{ name: "from", type: "address" },
|
|
@@ -16,23 +54,27 @@ var TRANSFER_WITH_AUTHORIZATION_TYPES = {
|
|
|
16
54
|
{ name: "nonce", type: "bytes32" }
|
|
17
55
|
]
|
|
18
56
|
};
|
|
19
|
-
var DEFAULT_VALID_AFTER_SKEW_SECONDS = 600;
|
|
20
57
|
async function createPaymentProof(options) {
|
|
21
|
-
const requirements =
|
|
58
|
+
const requirements = PaymentRequirementsV2Schema.parse(options.accepts);
|
|
22
59
|
if (!requirements.extra || typeof requirements.extra["name"] !== "string" || typeof requirements.extra["version"] !== "string") {
|
|
23
60
|
throw new Error(
|
|
24
61
|
"createPaymentProof: payment requirement is missing extra.name/extra.version \u2014 cannot build the EIP-712 domain"
|
|
25
62
|
);
|
|
26
63
|
}
|
|
64
|
+
const chainId = chainIdFromCaip2(requirements.network);
|
|
65
|
+
if (chainId === void 0) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`createPaymentProof: network "${requirements.network}" is not a CAIP-2 eip155 identifier \u2014 cannot determine the chain id to sign against`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
27
70
|
const account = privateKeyToAccount(options.buyerPrivateKey);
|
|
28
71
|
void options.rpcUrl;
|
|
29
|
-
const chainId = getNetworkId(requirements.network);
|
|
30
72
|
const nonce = options.overrides?.nonce ?? randomNonce();
|
|
31
73
|
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
32
|
-
const validAfter = options.overrides?.validAfter ??
|
|
74
|
+
const validAfter = options.overrides?.validAfter ?? 0;
|
|
33
75
|
const validBefore = options.overrides?.validBefore ?? nowSeconds + requirements.maxTimeoutSeconds;
|
|
34
76
|
const to = getAddress(options.overrides?.payTo ?? requirements.payTo);
|
|
35
|
-
const value = BigInt(options.overrides?.value ?? requirements.
|
|
77
|
+
const value = BigInt(options.overrides?.value ?? requirements.amount);
|
|
36
78
|
const signature = await account.signTypedData({
|
|
37
79
|
domain: {
|
|
38
80
|
name: requirements.extra["name"],
|
|
@@ -52,9 +94,8 @@ async function createPaymentProof(options) {
|
|
|
52
94
|
}
|
|
53
95
|
});
|
|
54
96
|
const payload = {
|
|
55
|
-
x402Version:
|
|
56
|
-
|
|
57
|
-
network: requirements.network,
|
|
97
|
+
x402Version: X402_VERSION,
|
|
98
|
+
accepted: requirements,
|
|
58
99
|
payload: {
|
|
59
100
|
signature,
|
|
60
101
|
authorization: {
|
|
@@ -67,7 +108,7 @@ async function createPaymentProof(options) {
|
|
|
67
108
|
}
|
|
68
109
|
}
|
|
69
110
|
};
|
|
70
|
-
return
|
|
111
|
+
return Buffer.from(JSON.stringify(payload), "utf8").toString("base64");
|
|
71
112
|
}
|
|
72
113
|
function randomNonce() {
|
|
73
114
|
const bytes = new Uint8Array(32);
|
|
@@ -114,109 +155,111 @@ function formatCanonicalAmount(baseUnits, decimals) {
|
|
|
114
155
|
const sign = negative ? "-" : "";
|
|
115
156
|
return fractionPart.length > 0 ? `${sign}${integerPart}.${fractionPart}` : `${sign}${integerPart}`;
|
|
116
157
|
}
|
|
117
|
-
var
|
|
118
|
-
function
|
|
158
|
+
var SETTLEMENT_CONFIRMATION_TIMEOUT_MS = 6e4;
|
|
159
|
+
function createLocalFacilitatorBinding(client, network, isTransportError) {
|
|
160
|
+
const build = () => {
|
|
161
|
+
let failed = false;
|
|
162
|
+
const watch = (promise) => promise.catch((err) => {
|
|
163
|
+
if (isTransportError(err)) failed = true;
|
|
164
|
+
throw err;
|
|
165
|
+
});
|
|
166
|
+
const signer = {
|
|
167
|
+
address: client.account.address,
|
|
168
|
+
readContract: (args) => watch(client.readContract(args)),
|
|
169
|
+
verifyTypedData: (args) => watch(client.verifyTypedData(args)),
|
|
170
|
+
writeContract: (args) => watch(client.writeContract(args)),
|
|
171
|
+
sendTransaction: (args) => watch(client.sendTransaction(args)),
|
|
172
|
+
waitForTransactionReceipt: (args) => watch(client.waitForTransactionReceipt(args)),
|
|
173
|
+
getCode: (args) => watch(client.getCode(args))
|
|
174
|
+
};
|
|
175
|
+
const facilitator = registerExactEvmScheme(new x402Facilitator(), {
|
|
176
|
+
signer: toFacilitatorEvmSigner(signer, {
|
|
177
|
+
confirmationTimeoutMs: SETTLEMENT_CONFIRMATION_TIMEOUT_MS
|
|
178
|
+
}),
|
|
179
|
+
networks: network
|
|
180
|
+
});
|
|
181
|
+
return { facilitator, failed: () => failed };
|
|
182
|
+
};
|
|
119
183
|
return {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
184
|
+
kind: "local",
|
|
185
|
+
describe: "local (in-process)",
|
|
186
|
+
open() {
|
|
187
|
+
const { facilitator, failed } = build();
|
|
188
|
+
return {
|
|
189
|
+
verify: (payload, requirements) => facilitator.verify(payload, requirements),
|
|
190
|
+
settle: (payload, requirements) => facilitator.settle(payload, requirements),
|
|
191
|
+
transportFailed: failed
|
|
192
|
+
};
|
|
193
|
+
},
|
|
194
|
+
async supported() {
|
|
195
|
+
return build().facilitator.getSupported().kinds;
|
|
125
196
|
}
|
|
126
197
|
};
|
|
127
198
|
}
|
|
128
|
-
function
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
199
|
+
function cdpAuthHeaders(apiKeyId, apiKeySecret) {
|
|
200
|
+
const loading = import('@coinbase/x402').then(
|
|
201
|
+
(mod) => mod.createCdpAuthHeaders(apiKeyId, apiKeySecret),
|
|
202
|
+
(cause) => {
|
|
203
|
+
throw new CommerceError(
|
|
204
|
+
"CONFIG_INVALID",
|
|
205
|
+
'x402 provider: facilitator.auth.type is "cdp", which needs the optional peer "@coinbase/x402". Install it (npm install @coinbase/x402), or use a facilitator that accepts a static token with auth.type "bearer".',
|
|
206
|
+
{ cause }
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
loading.catch(() => {
|
|
132
211
|
});
|
|
212
|
+
return async () => {
|
|
213
|
+
const create = await loading;
|
|
214
|
+
if (!create) {
|
|
215
|
+
throw new CommerceError(
|
|
216
|
+
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
217
|
+
"x402 provider: @coinbase/x402 returned no auth-header factory"
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
return await create();
|
|
221
|
+
};
|
|
133
222
|
}
|
|
134
|
-
function
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
// src/payments/x402/local-chain/accounts.ts
|
|
145
|
-
var ANVIL_WELL_KNOWN_ACCOUNTS = [
|
|
146
|
-
{
|
|
147
|
-
address: "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
|
|
148
|
-
privateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
address: "0x70997970C51812dc3A010C7d01b50e0d17dc79C8",
|
|
152
|
-
privateKey: "0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
address: "0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC",
|
|
156
|
-
privateKey: "0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a"
|
|
157
|
-
}
|
|
158
|
-
];
|
|
159
|
-
|
|
160
|
-
// src/payments/x402/dev-key-guard.ts
|
|
161
|
-
var WELL_KNOWN_DEV_KEYS = new Set(
|
|
162
|
-
ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.privateKey.toLowerCase())
|
|
163
|
-
);
|
|
164
|
-
var WELL_KNOWN_DEV_ADDRESSES = new Set(
|
|
165
|
-
ANVIL_WELL_KNOWN_ACCOUNTS.map((a) => a.address.toLowerCase())
|
|
166
|
-
);
|
|
167
|
-
var PRIVATE_KEY_SHAPE = /^0x[0-9a-fA-F]{64}$/;
|
|
168
|
-
function isWellKnownDevKey(privateKey) {
|
|
169
|
-
return WELL_KNOWN_DEV_KEYS.has(privateKey.toLowerCase());
|
|
170
|
-
}
|
|
171
|
-
function isWellKnownDevAddress(address) {
|
|
172
|
-
return WELL_KNOWN_DEV_ADDRESSES.has(address.toLowerCase());
|
|
173
|
-
}
|
|
174
|
-
function isLikelyLocalOrPrivateHost(hostname) {
|
|
175
|
-
const h = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
176
|
-
if (h === "localhost" || h === "127.0.0.1" || h === "::1") return true;
|
|
177
|
-
if (/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
178
|
-
if (/^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
179
|
-
if (/^192\.168\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
180
|
-
if (/^172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}$/.test(h)) return true;
|
|
181
|
-
if (!h.includes(".") && !h.includes(":")) return true;
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
function parseHostnameOrThrow(rpcUrl) {
|
|
185
|
-
try {
|
|
186
|
-
return new URL(rpcUrl).hostname;
|
|
187
|
-
} catch (cause) {
|
|
188
|
-
throw new CommerceError(
|
|
189
|
-
"CONFIG_INVALID",
|
|
190
|
-
`x402 provider: rpcUrl "${rpcUrl}" is not a valid URL`,
|
|
191
|
-
{ cause }
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
function assertDevKeyIsLocalOnly(rpcUrl, signerPrivateKey) {
|
|
196
|
-
if (!PRIVATE_KEY_SHAPE.test(signerPrivateKey)) {
|
|
197
|
-
throw new CommerceError(
|
|
198
|
-
"CONFIG_INVALID",
|
|
199
|
-
'x402 provider: facilitator.signerPrivateKey is not a well-formed private key (expected "0x" followed by exactly 64 hex characters).'
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
if (!isWellKnownDevKey(signerPrivateKey)) return;
|
|
203
|
-
const hostname = parseHostnameOrThrow(rpcUrl);
|
|
204
|
-
if (!isLikelyLocalOrPrivateHost(hostname)) {
|
|
205
|
-
throw new CommerceError(
|
|
206
|
-
"CONFIG_INVALID",
|
|
207
|
-
`x402 provider: facilitator.signerPrivateKey is a well-known Anvil development key, but rpcUrl "${rpcUrl}" does not look like a local/private chain. A public dev key must never sign against a public network \u2014 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.`
|
|
208
|
-
);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
function assertPayToIsNotDevAddress(rpcUrl, payTo) {
|
|
212
|
-
if (!isWellKnownDevAddress(payTo)) return;
|
|
213
|
-
const hostname = parseHostnameOrThrow(rpcUrl);
|
|
214
|
-
if (!isLikelyLocalOrPrivateHost(hostname)) {
|
|
215
|
-
throw new CommerceError(
|
|
216
|
-
"CONFIG_INVALID",
|
|
217
|
-
`x402 provider: "payTo" (${payTo}) is a well-known Anvil development address, but rpcUrl "${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.`
|
|
218
|
-
);
|
|
223
|
+
function createRemoteFacilitatorBinding(options) {
|
|
224
|
+
const auth = options.auth;
|
|
225
|
+
let createAuthHeaders;
|
|
226
|
+
if (auth.type === "bearer") {
|
|
227
|
+
createAuthHeaders = async () => {
|
|
228
|
+
const headers = { Authorization: `Bearer ${auth.token}` };
|
|
229
|
+
return { verify: headers, settle: headers, supported: headers };
|
|
230
|
+
};
|
|
231
|
+
} else if (auth.type === "cdp") {
|
|
232
|
+
createAuthHeaders = cdpAuthHeaders(auth.apiKeyId, auth.apiKeySecret);
|
|
219
233
|
}
|
|
234
|
+
const client = new HTTPFacilitatorClient({
|
|
235
|
+
url: options.url,
|
|
236
|
+
timeoutMs: options.timeoutMs ?? SETTLEMENT_CONFIRMATION_TIMEOUT_MS,
|
|
237
|
+
...createAuthHeaders ? { createAuthHeaders } : {}
|
|
238
|
+
});
|
|
239
|
+
return {
|
|
240
|
+
kind: "remote",
|
|
241
|
+
describe: `remote ${new URL(options.url).origin} (auth=${auth.type})`,
|
|
242
|
+
open() {
|
|
243
|
+
let failed = false;
|
|
244
|
+
const watch = async (call) => {
|
|
245
|
+
try {
|
|
246
|
+
return await call();
|
|
247
|
+
} catch (err) {
|
|
248
|
+
failed = true;
|
|
249
|
+
throw err;
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
return {
|
|
253
|
+
verify: (payload, requirements) => watch(() => client.verify(payload, requirements)),
|
|
254
|
+
settle: (payload, requirements) => watch(() => client.settle(payload, requirements)),
|
|
255
|
+
transportFailed: () => failed
|
|
256
|
+
};
|
|
257
|
+
},
|
|
258
|
+
async supported() {
|
|
259
|
+
const response = await client.getSupported();
|
|
260
|
+
return response.kinds;
|
|
261
|
+
}
|
|
262
|
+
};
|
|
220
263
|
}
|
|
221
264
|
function decodePaymentSubmission(payloadBase64) {
|
|
222
265
|
let json;
|
|
@@ -226,11 +269,11 @@ function decodePaymentSubmission(payloadBase64) {
|
|
|
226
269
|
} catch {
|
|
227
270
|
return void 0;
|
|
228
271
|
}
|
|
229
|
-
const result =
|
|
272
|
+
const result = PaymentPayloadV2Schema.safeParse(json);
|
|
230
273
|
return result.success ? result.data : void 0;
|
|
231
274
|
}
|
|
232
275
|
function isExactEvmPayload(payload) {
|
|
233
|
-
return payload.scheme === "exact" && typeof payload.payload === "object" && payload.payload !== null &&
|
|
276
|
+
return payload.accepted.scheme === "exact" && typeof payload.payload === "object" && payload.payload !== null && isEIP3009Payload(payload.payload) && typeof payload.payload.signature === "string";
|
|
234
277
|
}
|
|
235
278
|
function computeReplayKey(input) {
|
|
236
279
|
const encoded = encodeAbiParameters(
|
|
@@ -241,10 +284,20 @@ function computeReplayKey(input) {
|
|
|
241
284
|
}
|
|
242
285
|
|
|
243
286
|
// src/payments/x402/provider.ts
|
|
244
|
-
var
|
|
287
|
+
var X402_VERSION2 = 2;
|
|
288
|
+
var X402_PROTOCOL_VERSION = String(X402_VERSION2);
|
|
245
289
|
var DEFAULT_MAX_TIMEOUT_SECONDS = 60;
|
|
246
290
|
var DEFAULT_MIME_TYPE = "application/json";
|
|
247
291
|
var HEALTH_TIMEOUT_MS = 4e3;
|
|
292
|
+
var SETTLEMENT_PENDING_REASON = "settlement_pending";
|
|
293
|
+
var MAX_REASON_LENGTH = 64;
|
|
294
|
+
var REASON_SHAPE = /^[a-z0-9_.-]+$/i;
|
|
295
|
+
function sanitiseReason(reason, fallback) {
|
|
296
|
+
if (reason === void 0) return fallback;
|
|
297
|
+
const trimmed = reason.trim();
|
|
298
|
+
if (trimmed.length === 0 || trimmed.length > MAX_REASON_LENGTH) return fallback;
|
|
299
|
+
return REASON_SHAPE.test(trimmed) ? trimmed : fallback;
|
|
300
|
+
}
|
|
248
301
|
var DEFAULT_IDS = {
|
|
249
302
|
next: (prefix) => `${prefix ? `${prefix}_` : ""}${crypto.randomUUID()}`
|
|
250
303
|
};
|
|
@@ -268,11 +321,23 @@ function createX402PaymentProvider(options) {
|
|
|
268
321
|
`x402 provider: "assetDecimals" must be a non-negative integer`
|
|
269
322
|
);
|
|
270
323
|
}
|
|
271
|
-
|
|
324
|
+
const { profile, mode } = resolveX402Deployment({
|
|
325
|
+
network: options.network,
|
|
326
|
+
payTo: options.payTo,
|
|
327
|
+
asset: options.asset,
|
|
328
|
+
assetName: options.assetName,
|
|
329
|
+
assetVersion: options.assetVersion,
|
|
330
|
+
facilitator: options.facilitator,
|
|
331
|
+
...options.allowMainnet !== void 0 ? { allowMainnet: options.allowMainnet } : {},
|
|
332
|
+
...options.allowUnauthenticatedFacilitator !== void 0 ? { allowUnauthenticatedFacilitator: options.allowUnauthenticatedFacilitator } : {}
|
|
333
|
+
});
|
|
334
|
+
const network = options.network;
|
|
335
|
+
let binding;
|
|
272
336
|
if (options.facilitator.mode === "local") {
|
|
273
337
|
assertDevKeyIsLocalOnly(options.rpcUrl, options.facilitator.signerPrivateKey);
|
|
338
|
+
let client;
|
|
274
339
|
try {
|
|
275
|
-
|
|
340
|
+
client = createLocalFacilitatorClient(
|
|
276
341
|
options.rpcUrl,
|
|
277
342
|
options.facilitator.signerPrivateKey
|
|
278
343
|
);
|
|
@@ -283,9 +348,18 @@ function createX402PaymentProvider(options) {
|
|
|
283
348
|
{ cause }
|
|
284
349
|
);
|
|
285
350
|
}
|
|
351
|
+
binding = createLocalFacilitatorBinding(client, network, isProviderUnavailableError);
|
|
352
|
+
} else {
|
|
353
|
+
binding = createRemoteFacilitatorBinding({
|
|
354
|
+
url: options.facilitator.url,
|
|
355
|
+
auth: options.facilitator.auth
|
|
356
|
+
});
|
|
286
357
|
}
|
|
287
|
-
const
|
|
288
|
-
|
|
358
|
+
const healthPublicClient = createLocalPublicClient(
|
|
359
|
+
options.rpcUrl,
|
|
360
|
+
HEALTH_TIMEOUT_MS,
|
|
361
|
+
profile.chainId
|
|
362
|
+
);
|
|
289
363
|
const clock = options.clock ?? systemClock;
|
|
290
364
|
const ids = options.ids ?? DEFAULT_IDS;
|
|
291
365
|
const logger = options.logger ?? NOOP_LOGGER;
|
|
@@ -294,43 +368,54 @@ function createX402PaymentProvider(options) {
|
|
|
294
368
|
name: "x402",
|
|
295
369
|
kind: "payment",
|
|
296
370
|
implementationVersion: PACKAGE_VERSION,
|
|
297
|
-
supportedSpec:
|
|
298
|
-
capabilities: [
|
|
371
|
+
supportedSpec: `x402/v${X402_VERSION2} scheme=exact family=eip155`,
|
|
372
|
+
capabilities: [
|
|
373
|
+
"exact-evm",
|
|
374
|
+
`${binding.kind}-facilitator`,
|
|
375
|
+
"eip-3009",
|
|
376
|
+
"caip-2",
|
|
377
|
+
`mode=${mode}`
|
|
378
|
+
],
|
|
299
379
|
status: "stable",
|
|
300
|
-
unsupported: [
|
|
380
|
+
unsupported: [
|
|
381
|
+
"svm",
|
|
382
|
+
"permit2",
|
|
383
|
+
"upto scheme",
|
|
384
|
+
"per-request signed facilitator credentials (e.g. CDP JWT)"
|
|
385
|
+
]
|
|
301
386
|
};
|
|
302
387
|
async function createRequirement(context) {
|
|
303
|
-
const
|
|
304
|
-
context.amount,
|
|
305
|
-
options.assetDecimals
|
|
306
|
-
).toString();
|
|
388
|
+
const amount = parseCanonicalAmount(context.amount, options.assetDecimals).toString();
|
|
307
389
|
const requirements = {
|
|
308
390
|
scheme: "exact",
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
network: options.network,
|
|
313
|
-
maxAmountRequired,
|
|
314
|
-
// Descriptive metadata only: `resource` is not part of the EIP-3009
|
|
315
|
-
// authorisation the buyer signs (`from`/`to`/`value`/`validAfter`/
|
|
316
|
-
// `validBefore`/`nonce`), nothing verifies against it, and the replay
|
|
317
|
-
// key is keyed on the authorisation rather than on this string. It is
|
|
318
|
-
// still what a facilitator, wallet or buyer agent logs and shows, so it
|
|
319
|
-
// carries `network` and `payTo` — both already public in this same
|
|
320
|
-
// challenge — to stay unambiguous across the wider x402 ecosystem,
|
|
321
|
-
// where the `agent-commerce` authority alone is not reserved to us.
|
|
322
|
-
resource: `resource://agent-commerce/${encodeURIComponent(options.network)}/${options.payTo}/resources/${encodeURIComponent(context.resource.id)}`,
|
|
323
|
-
description: context.resource.description ?? context.resource.name,
|
|
324
|
-
mimeType: DEFAULT_MIME_TYPE,
|
|
391
|
+
network,
|
|
392
|
+
asset: options.asset,
|
|
393
|
+
amount,
|
|
325
394
|
payTo: options.payTo,
|
|
326
395
|
maxTimeoutSeconds,
|
|
327
|
-
|
|
328
|
-
//
|
|
329
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
|
|
396
|
+
// `name`/`version` are what let a buyer and the facilitator build the
|
|
397
|
+
// same EIP-712 domain without an on-chain `version()` call, so
|
|
398
|
+
// verification stays deterministic against a token that has none.
|
|
399
|
+
//
|
|
400
|
+
// `assetTransferMethod` is stated rather than left to default: x402 v2's
|
|
401
|
+
// `exact`/EVM scheme also has a Permit2 path, and a client that picked it
|
|
402
|
+
// would produce a payload this provider cannot settle. Declaring the one
|
|
403
|
+
// supported method turns that into a client-side non-choice instead of a
|
|
404
|
+
// server-side rejection.
|
|
405
|
+
extra: {
|
|
406
|
+
name: options.assetName,
|
|
407
|
+
version: options.assetVersion,
|
|
408
|
+
assetTransferMethod: "eip3009"
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
const envelope = {
|
|
412
|
+
x402Version: X402_VERSION2,
|
|
413
|
+
resource: {
|
|
414
|
+
url: `resource://agent-commerce/${encodeURIComponent(options.network)}/${options.payTo}/resources/${encodeURIComponent(context.resource.id)}`,
|
|
415
|
+
description: context.resource.description ?? context.resource.name,
|
|
416
|
+
mimeType: DEFAULT_MIME_TYPE
|
|
417
|
+
},
|
|
418
|
+
accepts: [requirements]
|
|
334
419
|
};
|
|
335
420
|
const expiresAt = new Date(clock.now().getTime() + maxTimeoutSeconds * 1e3).toISOString();
|
|
336
421
|
return {
|
|
@@ -347,11 +432,12 @@ function createX402PaymentProvider(options) {
|
|
|
347
432
|
challenge: {
|
|
348
433
|
provider: "x402",
|
|
349
434
|
version: X402_PROTOCOL_VERSION,
|
|
350
|
-
accepts: [requirements]
|
|
435
|
+
accepts: [requirements],
|
|
436
|
+
envelope
|
|
351
437
|
}
|
|
352
438
|
};
|
|
353
439
|
}
|
|
354
|
-
async function verify
|
|
440
|
+
async function verify(context) {
|
|
355
441
|
const { requirement, submission } = context;
|
|
356
442
|
const rejected = (reason) => ({
|
|
357
443
|
status: "rejected",
|
|
@@ -379,6 +465,9 @@ function createX402PaymentProvider(options) {
|
|
|
379
465
|
if (!payload) {
|
|
380
466
|
return rejected("malformed_payment_payload");
|
|
381
467
|
}
|
|
468
|
+
if (payload.x402Version !== X402_VERSION2) {
|
|
469
|
+
return rejected("unsupported_x402_version");
|
|
470
|
+
}
|
|
382
471
|
if (!isExactEvmPayload(payload)) {
|
|
383
472
|
return rejected("unsupported_scheme");
|
|
384
473
|
}
|
|
@@ -386,35 +475,32 @@ function createX402PaymentProvider(options) {
|
|
|
386
475
|
if (!isAddress(authorization.from) || !isAddress(authorization.to)) {
|
|
387
476
|
return rejected("malformed_payment_payload");
|
|
388
477
|
}
|
|
389
|
-
if (payload.network !== options.network) {
|
|
478
|
+
if (payload.accepted.network !== options.network) {
|
|
390
479
|
return rejected("wrong_network");
|
|
391
480
|
}
|
|
392
481
|
if (getAddress(authorization.to) !== getAddress(options.payTo)) {
|
|
393
482
|
return rejected("wrong_recipient");
|
|
394
483
|
}
|
|
395
484
|
let authorizedValue;
|
|
396
|
-
let
|
|
485
|
+
let required;
|
|
397
486
|
try {
|
|
398
487
|
authorizedValue = BigInt(authorization.value);
|
|
399
|
-
|
|
488
|
+
required = BigInt(requirements.amount);
|
|
400
489
|
} catch {
|
|
401
490
|
return rejected("malformed_payment_payload");
|
|
402
491
|
}
|
|
403
|
-
if (authorizedValue
|
|
492
|
+
if (authorizedValue !== required) {
|
|
404
493
|
return rejected("wrong_amount");
|
|
405
494
|
}
|
|
495
|
+
const scope = binding.open();
|
|
406
496
|
let sdkResult;
|
|
407
497
|
try {
|
|
408
|
-
sdkResult = await verify(
|
|
409
|
-
publicClient,
|
|
410
|
-
payload,
|
|
411
|
-
requirements
|
|
412
|
-
);
|
|
498
|
+
sdkResult = await scope.verify(payload, requirements);
|
|
413
499
|
} catch (err) {
|
|
414
|
-
if (isProviderUnavailableError(err)) {
|
|
500
|
+
if (isProviderUnavailableError(err) || scope.transportFailed()) {
|
|
415
501
|
throw new CommerceError(
|
|
416
502
|
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
417
|
-
|
|
503
|
+
`x402 provider: ${binding.kind} facilitator unreachable during verify()`,
|
|
418
504
|
{
|
|
419
505
|
cause: err
|
|
420
506
|
}
|
|
@@ -424,10 +510,27 @@ function createX402PaymentProvider(options) {
|
|
|
424
510
|
return rejected("unexpected_verify_error");
|
|
425
511
|
}
|
|
426
512
|
if (!sdkResult.isValid) {
|
|
427
|
-
|
|
513
|
+
if (scope.transportFailed()) {
|
|
514
|
+
throw new CommerceError(
|
|
515
|
+
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
516
|
+
`x402 provider: ${binding.kind} facilitator unreachable during verify()`,
|
|
517
|
+
{ details: { reportedReason: sdkResult.invalidReason ?? "invalid_payment" } }
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
if (sdkResult.invalidReason !== void 0) {
|
|
521
|
+
logger.debug(
|
|
522
|
+
{ reportedReason: sdkResult.invalidReason },
|
|
523
|
+
"x402 verify(): facilitator rejection reason"
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
return rejected(sanitiseReason(sdkResult.invalidReason, "invalid_payment"));
|
|
527
|
+
}
|
|
528
|
+
const payloadChainId = chainIdFromCaip2(payload.accepted.network);
|
|
529
|
+
if (payloadChainId === void 0) {
|
|
530
|
+
return rejected("wrong_network");
|
|
428
531
|
}
|
|
429
532
|
const replayKey = computeReplayKey({
|
|
430
|
-
chainId:
|
|
533
|
+
chainId: payloadChainId,
|
|
431
534
|
asset: getAddress(requirements.asset),
|
|
432
535
|
from: getAddress(authorization.from),
|
|
433
536
|
nonce: authorization.nonce
|
|
@@ -439,12 +542,12 @@ function createX402PaymentProvider(options) {
|
|
|
439
542
|
payee: getAddress(authorization.to),
|
|
440
543
|
amount: formatCanonicalAmount(authorizedValue, options.assetDecimals),
|
|
441
544
|
currency: requirement.currency,
|
|
442
|
-
network: payload.network,
|
|
545
|
+
network: payload.accepted.network,
|
|
443
546
|
asset: getAddress(requirements.asset),
|
|
444
547
|
replayKey
|
|
445
548
|
};
|
|
446
549
|
}
|
|
447
|
-
async function settle
|
|
550
|
+
async function settle(context) {
|
|
448
551
|
const { requirement, submission, verification } = context;
|
|
449
552
|
if (verification.status !== "verified") {
|
|
450
553
|
throw new CommerceError(
|
|
@@ -452,12 +555,6 @@ function createX402PaymentProvider(options) {
|
|
|
452
555
|
"x402 provider: settle() called without a successful verify()"
|
|
453
556
|
);
|
|
454
557
|
}
|
|
455
|
-
if (options.facilitator.mode !== "local" || facilitatorClient === void 0) {
|
|
456
|
-
throw new CommerceError(
|
|
457
|
-
"PROTOCOL_UNSUPPORTED",
|
|
458
|
-
'x402 provider: remote facilitator settlement is not implemented in v0.1.0-alpha; only { mode: "local" } is supported'
|
|
459
|
-
);
|
|
460
|
-
}
|
|
461
558
|
const requirements = requirement.challenge.accepts[0];
|
|
462
559
|
if (!requirements) {
|
|
463
560
|
throw new CommerceError(
|
|
@@ -472,30 +569,26 @@ function createX402PaymentProvider(options) {
|
|
|
472
569
|
"x402 provider: settle() received an undecodable payment payload"
|
|
473
570
|
);
|
|
474
571
|
}
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
}
|
|
482
|
-
|
|
572
|
+
const rejectedSettlement = (rejectionReason) => ({
|
|
573
|
+
status: "rejected",
|
|
574
|
+
provider: "x402",
|
|
575
|
+
amount: verification.amount,
|
|
576
|
+
currency: requirement.currency,
|
|
577
|
+
...verification.network !== void 0 ? { network: verification.network } : {},
|
|
578
|
+
...verification.asset !== void 0 ? { asset: verification.asset } : {},
|
|
579
|
+
...verification.replayKey !== void 0 ? { replayKey: verification.replayKey } : {},
|
|
580
|
+
rejectionReason
|
|
581
|
+
});
|
|
582
|
+
const scope = binding.open();
|
|
483
583
|
let sdkResult;
|
|
484
584
|
try {
|
|
485
|
-
sdkResult = await settle(
|
|
486
|
-
settlementClient,
|
|
487
|
-
payload,
|
|
488
|
-
requirements
|
|
489
|
-
);
|
|
585
|
+
sdkResult = await scope.settle(payload, requirements);
|
|
490
586
|
} catch (err) {
|
|
491
|
-
if (isProviderUnavailableError(err)) {
|
|
587
|
+
if (isProviderUnavailableError(err) || scope.transportFailed()) {
|
|
492
588
|
throw new CommerceError(
|
|
493
589
|
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
494
|
-
|
|
495
|
-
{
|
|
496
|
-
cause: err,
|
|
497
|
-
...broadcastTxHash !== void 0 ? { details: { transactionHash: broadcastTxHash } } : {}
|
|
498
|
-
}
|
|
590
|
+
`x402 provider: ${binding.kind} facilitator unreachable during settle()`,
|
|
591
|
+
{ cause: err }
|
|
499
592
|
);
|
|
500
593
|
}
|
|
501
594
|
const rejectionReason = isOnChainRevertError(err) ? "transaction_reverted" : "unexpected_settle_error";
|
|
@@ -503,28 +596,28 @@ function createX402PaymentProvider(options) {
|
|
|
503
596
|
{ err: describeError(err), rejectionReason },
|
|
504
597
|
"x402 settle(): settlement transaction failed"
|
|
505
598
|
);
|
|
506
|
-
return
|
|
507
|
-
status: "rejected",
|
|
508
|
-
provider: "x402",
|
|
509
|
-
amount: verification.amount,
|
|
510
|
-
currency: requirement.currency,
|
|
511
|
-
...verification.network !== void 0 ? { network: verification.network } : {},
|
|
512
|
-
...verification.asset !== void 0 ? { asset: verification.asset } : {},
|
|
513
|
-
...verification.replayKey !== void 0 ? { replayKey: verification.replayKey } : {},
|
|
514
|
-
rejectionReason
|
|
515
|
-
};
|
|
599
|
+
return rejectedSettlement(rejectionReason);
|
|
516
600
|
}
|
|
517
601
|
if (!sdkResult.success) {
|
|
602
|
+
if (sdkResult.errorReason === SETTLEMENT_PENDING_REASON || scope.transportFailed()) {
|
|
603
|
+
throw new CommerceError(
|
|
604
|
+
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
605
|
+
"x402 provider: settlement was broadcast but could not be confirmed",
|
|
606
|
+
{
|
|
607
|
+
...sdkResult.transaction ? { details: { transactionHash: sdkResult.transaction } } : {}
|
|
608
|
+
}
|
|
609
|
+
);
|
|
610
|
+
}
|
|
611
|
+
if (sdkResult.errorReason !== void 0) {
|
|
612
|
+
logger.debug(
|
|
613
|
+
{ reportedReason: sdkResult.errorReason },
|
|
614
|
+
"x402 settle(): facilitator failure reason"
|
|
615
|
+
);
|
|
616
|
+
}
|
|
518
617
|
return {
|
|
519
|
-
|
|
520
|
-
provider: "x402",
|
|
521
|
-
amount: verification.amount,
|
|
522
|
-
currency: requirement.currency,
|
|
618
|
+
...rejectedSettlement(sanitiseReason(sdkResult.errorReason, "settlement_failed")),
|
|
523
619
|
network: sdkResult.network,
|
|
524
|
-
...
|
|
525
|
-
...sdkResult.payer !== void 0 ? { payer: sdkResult.payer } : {},
|
|
526
|
-
...verification.replayKey !== void 0 ? { replayKey: verification.replayKey } : {},
|
|
527
|
-
rejectionReason: sdkResult.errorReason ?? "settlement_failed"
|
|
620
|
+
...sdkResult.payer !== void 0 ? { payer: sdkResult.payer } : {}
|
|
528
621
|
};
|
|
529
622
|
}
|
|
530
623
|
return {
|
|
@@ -546,10 +639,10 @@ function createX402PaymentProvider(options) {
|
|
|
546
639
|
const checkedAt = clock.nowIso();
|
|
547
640
|
try {
|
|
548
641
|
const chainId = await healthPublicClient.getChainId();
|
|
549
|
-
if (chainId !==
|
|
642
|
+
if (chainId !== profile.chainId) {
|
|
550
643
|
return {
|
|
551
644
|
status: "fail",
|
|
552
|
-
detail: `RPC at ${options.rpcUrl} reports chain id ${chainId}, expected ${
|
|
645
|
+
detail: `RPC at ${options.rpcUrl} reports chain id ${chainId}, expected ${profile.chainId} (${profile.displayName})`,
|
|
553
646
|
checkedAt,
|
|
554
647
|
durationMs: clock.monotonicMs() - startedAt
|
|
555
648
|
};
|
|
@@ -563,10 +656,22 @@ function createX402PaymentProvider(options) {
|
|
|
563
656
|
durationMs: clock.monotonicMs() - startedAt
|
|
564
657
|
};
|
|
565
658
|
}
|
|
566
|
-
if (
|
|
659
|
+
if (binding.kind === "remote") {
|
|
660
|
+
const kinds = await binding.supported();
|
|
661
|
+
const supportsUs = kinds.some(
|
|
662
|
+
(kind) => kind.x402Version === X402_VERSION2 && kind.scheme === "exact" && kind.network === options.network
|
|
663
|
+
);
|
|
664
|
+
if (!supportsUs) {
|
|
665
|
+
return {
|
|
666
|
+
status: "fail",
|
|
667
|
+
detail: `Facilitator ${binding.describe} does not advertise x402 v${X402_VERSION2} scheme=exact on ${options.network} (${profile.displayName}); no payment on this deployment can settle`,
|
|
668
|
+
checkedAt,
|
|
669
|
+
durationMs: clock.monotonicMs() - startedAt
|
|
670
|
+
};
|
|
671
|
+
}
|
|
567
672
|
return {
|
|
568
|
-
status: "
|
|
569
|
-
detail:
|
|
673
|
+
status: "pass",
|
|
674
|
+
detail: `${describeDeploymentMode(mode)} \u2014 RPC ${options.rpcUrl} reachable, chain id ${profile.chainId} (${profile.displayName}), asset ${options.asset} has code, facilitator ${binding.describe} supports exact/${options.network}`,
|
|
570
675
|
checkedAt,
|
|
571
676
|
durationMs: clock.monotonicMs() - startedAt
|
|
572
677
|
};
|
|
@@ -575,14 +680,14 @@ function createX402PaymentProvider(options) {
|
|
|
575
680
|
if (!isAnvil) {
|
|
576
681
|
return {
|
|
577
682
|
status: "fail",
|
|
578
|
-
detail: `RPC at ${options.rpcUrl} reports chain id ${
|
|
683
|
+
detail: `RPC at ${options.rpcUrl} reports chain id ${profile.chainId} but does not answer "anvil_nodeInfo" \u2014 it does not look like a local Anvil dev node. Refusing to treat it as safe for a local-facilitator dev key.`,
|
|
579
684
|
checkedAt,
|
|
580
685
|
durationMs: clock.monotonicMs() - startedAt
|
|
581
686
|
};
|
|
582
687
|
}
|
|
583
688
|
return {
|
|
584
689
|
status: "pass",
|
|
585
|
-
detail:
|
|
690
|
+
detail: `${describeDeploymentMode(mode)} \u2014 RPC ${options.rpcUrl} reachable, chain id ${profile.chainId}, asset ${options.asset} has code, confirmed Anvil dev node`,
|
|
586
691
|
checkedAt,
|
|
587
692
|
durationMs: clock.monotonicMs() - startedAt
|
|
588
693
|
};
|
|
@@ -616,8 +721,8 @@ function createX402PaymentProvider(options) {
|
|
|
616
721
|
name: "x402",
|
|
617
722
|
descriptor,
|
|
618
723
|
createRequirement,
|
|
619
|
-
verify
|
|
620
|
-
settle
|
|
724
|
+
verify,
|
|
725
|
+
settle,
|
|
621
726
|
health
|
|
622
727
|
};
|
|
623
728
|
}
|