@devlab.group/agent-commerce 1.4.1 → 1.6.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 +168 -183
- package/dist/ap2.d.ts +21 -24
- package/dist/ap2.js +68 -82
- package/dist/ap2.js.map +1 -1
- package/dist/authorization-BpbWGGrf.d.ts +115 -0
- package/dist/{chunk-NWIQLJNU.js → chunk-74BSEXB5.js} +37 -34
- package/dist/chunk-74BSEXB5.js.map +1 -0
- package/dist/chunk-AFI2CEZS.js +10 -0
- package/dist/chunk-AFI2CEZS.js.map +1 -0
- package/dist/chunk-ATTWSFQP.js +680 -0
- package/dist/chunk-ATTWSFQP.js.map +1 -0
- package/dist/{chunk-ZEGFLRGV.js → chunk-IVJC5YLE.js} +63 -26
- package/dist/chunk-IVJC5YLE.js.map +1 -0
- package/dist/{chunk-GF6KHHKL.js → chunk-RAJGPGJG.js} +5 -8
- package/dist/chunk-RAJGPGJG.js.map +1 -0
- package/dist/chunk-V2V2E7HX.js +27 -0
- package/dist/chunk-V2V2E7HX.js.map +1 -0
- package/dist/cli/index.js +812 -573
- package/dist/cli/index.js.map +1 -1
- package/dist/constants-Bkq04FRZ.d.ts +48 -0
- package/dist/guardrails-71XwtXlT.d.ts +31 -0
- package/dist/index.d.ts +245 -265
- package/dist/index.js +1222 -626
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +6 -59
- package/dist/mcp.js +64 -53
- package/dist/mcp.js.map +1 -1
- package/dist/mpp.d.ts +50 -0
- package/dist/mpp.js +353 -0
- package/dist/mpp.js.map +1 -0
- package/dist/protocol-adapter-DTkrXTev.d.ts +238 -0
- package/dist/runtime-CiYbgGpi.d.ts +303 -0
- package/dist/{types-CJtwLu1c.d.ts → types-O6lg3Q5V.d.ts} +17 -23
- package/dist/x402.d.ts +43 -75
- package/dist/x402.js +13 -693
- package/dist/x402.js.map +1 -1
- package/package.json +13 -2
- package/dist/authorization-CPDcpJVa.d.ts +0 -123
- package/dist/chunk-GF6KHHKL.js.map +0 -1
- package/dist/chunk-NWIQLJNU.js.map +0 -1
- package/dist/chunk-ZEGFLRGV.js.map +0 -1
- package/dist/guardrails-Dmwi63KB.d.ts +0 -38
- package/dist/protocol-adapter-Bebmggf_.d.ts +0 -273
- package/dist/runtime-CKq3d_3K.d.ts +0 -340
package/dist/x402.js
CHANGED
|
@@ -1,48 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
1
|
+
import { chainIdFromCaip2 } from './chunk-ATTWSFQP.js';
|
|
2
|
+
export { createX402PaymentProvider, createX402PaymentProvider as x402 } from './chunk-ATTWSFQP.js';
|
|
3
|
+
export { SUPPORTED_NETWORK_IDS } from './chunk-74BSEXB5.js';
|
|
4
|
+
import { CommerceError } from './chunk-IVJC5YLE.js';
|
|
5
|
+
import { PaymentRequirementsV2Schema } from '@x402/core/schemas';
|
|
6
|
+
import { getAddress, toHex } from 'viem';
|
|
6
7
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
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';
|
|
11
8
|
|
|
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
9
|
var X402_VERSION = 2;
|
|
47
10
|
var TRANSFER_WITH_AUTHORIZATION_TYPES = {
|
|
48
11
|
TransferWithAuthorization: [
|
|
@@ -57,18 +20,19 @@ var TRANSFER_WITH_AUTHORIZATION_TYPES = {
|
|
|
57
20
|
async function createPaymentProof(options) {
|
|
58
21
|
const requirements = PaymentRequirementsV2Schema.parse(options.accepts);
|
|
59
22
|
if (!requirements.extra || typeof requirements.extra["name"] !== "string" || typeof requirements.extra["version"] !== "string") {
|
|
60
|
-
throw new
|
|
61
|
-
"
|
|
23
|
+
throw new CommerceError(
|
|
24
|
+
"INPUT_INVALID",
|
|
25
|
+
"createPaymentProof: payment requirement is missing extra.name/extra.version, so the EIP-712 domain cannot be built"
|
|
62
26
|
);
|
|
63
27
|
}
|
|
64
28
|
const chainId = chainIdFromCaip2(requirements.network);
|
|
65
29
|
if (chainId === void 0) {
|
|
66
|
-
throw new
|
|
67
|
-
|
|
30
|
+
throw new CommerceError(
|
|
31
|
+
"INPUT_INVALID",
|
|
32
|
+
`createPaymentProof: network "${requirements.network}" is not a CAIP-2 eip155 identifier, so there is no chain id to sign against`
|
|
68
33
|
);
|
|
69
34
|
}
|
|
70
35
|
const account = privateKeyToAccount(options.buyerPrivateKey);
|
|
71
|
-
void options.rpcUrl;
|
|
72
36
|
const nonce = options.overrides?.nonce ?? randomNonce();
|
|
73
37
|
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
74
38
|
const validAfter = options.overrides?.validAfter ?? 0;
|
|
@@ -116,650 +80,6 @@ function randomNonce() {
|
|
|
116
80
|
return toHex(bytes);
|
|
117
81
|
}
|
|
118
82
|
|
|
119
|
-
|
|
120
|
-
var DECIMAL_STRING_PATTERN = /^\d+(?:\.\d+)?$/;
|
|
121
|
-
function parseCanonicalAmount(amount, decimals) {
|
|
122
|
-
if (!Number.isInteger(decimals) || decimals < 0) {
|
|
123
|
-
throw new CommerceError("INTERNAL_ERROR", `Invalid asset decimals: ${decimals}`);
|
|
124
|
-
}
|
|
125
|
-
if (!DECIMAL_STRING_PATTERN.test(amount)) {
|
|
126
|
-
throw new CommerceError(
|
|
127
|
-
"PAYMENT_INVALID",
|
|
128
|
-
`Amount "${amount}" is not a valid non-negative decimal string`,
|
|
129
|
-
{
|
|
130
|
-
details: { amount }
|
|
131
|
-
}
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
const [integerPart = "0", fractionPart = ""] = amount.split(".");
|
|
135
|
-
if (fractionPart.length > decimals) {
|
|
136
|
-
throw new CommerceError(
|
|
137
|
-
"PAYMENT_INVALID",
|
|
138
|
-
`Amount "${amount}" has more precision than the asset supports (${decimals} decimals)`,
|
|
139
|
-
{ details: { amount, assetDecimals: decimals } }
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
const paddedFraction = fractionPart.padEnd(decimals, "0");
|
|
143
|
-
const combined = `${integerPart}${paddedFraction}`;
|
|
144
|
-
return BigInt(combined);
|
|
145
|
-
}
|
|
146
|
-
function formatCanonicalAmount(baseUnits, decimals) {
|
|
147
|
-
if (!Number.isInteger(decimals) || decimals < 0) {
|
|
148
|
-
throw new CommerceError("INTERNAL_ERROR", `Invalid asset decimals: ${decimals}`);
|
|
149
|
-
}
|
|
150
|
-
const negative = baseUnits < 0n;
|
|
151
|
-
const abs = negative ? -baseUnits : baseUnits;
|
|
152
|
-
const digits = abs.toString().padStart(decimals + 1, "0");
|
|
153
|
-
const integerPart = digits.slice(0, digits.length - decimals) || "0";
|
|
154
|
-
const fractionPart = digits.slice(digits.length - decimals).replace(/0+$/, "");
|
|
155
|
-
const sign = negative ? "-" : "";
|
|
156
|
-
return fractionPart.length > 0 ? `${sign}${integerPart}.${fractionPart}` : `${sign}${integerPart}`;
|
|
157
|
-
}
|
|
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
|
-
};
|
|
183
|
-
return {
|
|
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;
|
|
196
|
-
}
|
|
197
|
-
};
|
|
198
|
-
}
|
|
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(() => {
|
|
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
|
-
};
|
|
222
|
-
}
|
|
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);
|
|
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
|
-
};
|
|
263
|
-
}
|
|
264
|
-
function decodePaymentSubmission(payloadBase64) {
|
|
265
|
-
let json;
|
|
266
|
-
try {
|
|
267
|
-
const decoded = Buffer.from(payloadBase64, "base64").toString("utf8");
|
|
268
|
-
json = JSON.parse(decoded);
|
|
269
|
-
} catch {
|
|
270
|
-
return void 0;
|
|
271
|
-
}
|
|
272
|
-
const result = PaymentPayloadV2Schema.safeParse(json);
|
|
273
|
-
return result.success ? result.data : void 0;
|
|
274
|
-
}
|
|
275
|
-
function isExactEvmPayload(payload) {
|
|
276
|
-
return payload.accepted.scheme === "exact" && typeof payload.payload === "object" && payload.payload !== null && isEIP3009Payload(payload.payload) && typeof payload.payload.signature === "string";
|
|
277
|
-
}
|
|
278
|
-
function computeReplayKey(input) {
|
|
279
|
-
const encoded = encodeAbiParameters(
|
|
280
|
-
[{ type: "uint256" }, { type: "address" }, { type: "address" }, { type: "bytes32" }],
|
|
281
|
-
[BigInt(input.chainId), input.asset, input.from, input.nonce]
|
|
282
|
-
);
|
|
283
|
-
return keccak256(encoded);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// src/payments/x402/provider.ts
|
|
287
|
-
var X402_VERSION2 = 2;
|
|
288
|
-
var X402_PROTOCOL_VERSION = String(X402_VERSION2);
|
|
289
|
-
var DEFAULT_MAX_TIMEOUT_SECONDS = 60;
|
|
290
|
-
var DEFAULT_MIME_TYPE = "application/json";
|
|
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
|
-
}
|
|
301
|
-
var DEFAULT_IDS = {
|
|
302
|
-
next: (prefix) => `${prefix ? `${prefix}_` : ""}${crypto.randomUUID()}`
|
|
303
|
-
};
|
|
304
|
-
function createX402PaymentProvider(options) {
|
|
305
|
-
if (!isAddress(options.asset)) {
|
|
306
|
-
throw new CommerceError(
|
|
307
|
-
"CONFIG_INVALID",
|
|
308
|
-
`x402 provider: "asset" is not a valid EVM address: ${options.asset}`
|
|
309
|
-
);
|
|
310
|
-
}
|
|
311
|
-
if (!isAddress(options.payTo)) {
|
|
312
|
-
throw new CommerceError(
|
|
313
|
-
"CONFIG_INVALID",
|
|
314
|
-
`x402 provider: "payTo" is not a valid EVM address: ${options.payTo}`
|
|
315
|
-
);
|
|
316
|
-
}
|
|
317
|
-
assertPayToIsNotDevAddress(options.rpcUrl, options.payTo);
|
|
318
|
-
if (!Number.isInteger(options.assetDecimals) || options.assetDecimals < 0) {
|
|
319
|
-
throw new CommerceError(
|
|
320
|
-
"CONFIG_INVALID",
|
|
321
|
-
`x402 provider: "assetDecimals" must be a non-negative integer`
|
|
322
|
-
);
|
|
323
|
-
}
|
|
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;
|
|
336
|
-
if (options.facilitator.mode === "local") {
|
|
337
|
-
assertDevKeyIsLocalOnly(options.rpcUrl, options.facilitator.signerPrivateKey);
|
|
338
|
-
let client;
|
|
339
|
-
try {
|
|
340
|
-
client = createLocalFacilitatorClient(
|
|
341
|
-
options.rpcUrl,
|
|
342
|
-
options.facilitator.signerPrivateKey
|
|
343
|
-
);
|
|
344
|
-
} catch (cause) {
|
|
345
|
-
throw new CommerceError(
|
|
346
|
-
"CONFIG_INVALID",
|
|
347
|
-
"x402 provider: could not construct a local facilitator signer from facilitator.signerPrivateKey \u2014 it must be a valid 32-byte hex private key",
|
|
348
|
-
{ cause }
|
|
349
|
-
);
|
|
350
|
-
}
|
|
351
|
-
binding = createLocalFacilitatorBinding(client, network, isProviderUnavailableError);
|
|
352
|
-
} else {
|
|
353
|
-
binding = createRemoteFacilitatorBinding({
|
|
354
|
-
url: options.facilitator.url,
|
|
355
|
-
auth: options.facilitator.auth
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
const healthPublicClient = createLocalPublicClient(
|
|
359
|
-
options.rpcUrl,
|
|
360
|
-
HEALTH_TIMEOUT_MS,
|
|
361
|
-
profile.chainId
|
|
362
|
-
);
|
|
363
|
-
const clock = options.clock ?? systemClock;
|
|
364
|
-
const ids = options.ids ?? DEFAULT_IDS;
|
|
365
|
-
const logger = options.logger ?? NOOP_LOGGER;
|
|
366
|
-
const maxTimeoutSeconds = options.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;
|
|
367
|
-
const descriptor = {
|
|
368
|
-
name: "x402",
|
|
369
|
-
kind: "payment",
|
|
370
|
-
implementationVersion: PACKAGE_VERSION,
|
|
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
|
-
],
|
|
379
|
-
status: "stable",
|
|
380
|
-
unsupported: [
|
|
381
|
-
"svm",
|
|
382
|
-
"permit2",
|
|
383
|
-
"upto scheme",
|
|
384
|
-
"per-request signed facilitator credentials (e.g. CDP JWT)"
|
|
385
|
-
]
|
|
386
|
-
};
|
|
387
|
-
async function createRequirement(context) {
|
|
388
|
-
const amount = parseCanonicalAmount(context.amount, options.assetDecimals).toString();
|
|
389
|
-
const requirements = {
|
|
390
|
-
scheme: "exact",
|
|
391
|
-
network,
|
|
392
|
-
asset: options.asset,
|
|
393
|
-
amount,
|
|
394
|
-
payTo: options.payTo,
|
|
395
|
-
maxTimeoutSeconds,
|
|
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]
|
|
419
|
-
};
|
|
420
|
-
const expiresAt = new Date(clock.now().getTime() + maxTimeoutSeconds * 1e3).toISOString();
|
|
421
|
-
return {
|
|
422
|
-
id: ids.next("payreq"),
|
|
423
|
-
requestId: context.requestId,
|
|
424
|
-
resourceId: context.resource.id,
|
|
425
|
-
provider: "x402",
|
|
426
|
-
amount: context.amount,
|
|
427
|
-
currency: context.currency,
|
|
428
|
-
destination: options.payTo,
|
|
429
|
-
network: options.network,
|
|
430
|
-
asset: options.asset,
|
|
431
|
-
expiresAt,
|
|
432
|
-
challenge: {
|
|
433
|
-
provider: "x402",
|
|
434
|
-
version: X402_PROTOCOL_VERSION,
|
|
435
|
-
accepts: [requirements],
|
|
436
|
-
envelope
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
async function verify(context) {
|
|
441
|
-
const { requirement, submission } = context;
|
|
442
|
-
const rejected = (reason) => ({
|
|
443
|
-
status: "rejected",
|
|
444
|
-
provider: "x402",
|
|
445
|
-
amount: requirement.amount,
|
|
446
|
-
currency: requirement.currency,
|
|
447
|
-
...requirement.network !== void 0 ? { network: requirement.network } : {},
|
|
448
|
-
...requirement.asset !== void 0 ? { asset: requirement.asset } : {},
|
|
449
|
-
rejectionReason: reason
|
|
450
|
-
});
|
|
451
|
-
if (requirement.provider !== "x402" || requirement.challenge.provider !== "x402") {
|
|
452
|
-
return rejected("wrong_provider");
|
|
453
|
-
}
|
|
454
|
-
const requirements = requirement.challenge.accepts[0];
|
|
455
|
-
if (!requirements) {
|
|
456
|
-
return rejected("missing_payment_requirements");
|
|
457
|
-
}
|
|
458
|
-
if (requirement.expiresAt !== void 0 && Date.parse(requirement.expiresAt) < clock.now().getTime()) {
|
|
459
|
-
return rejected("requirement_expired");
|
|
460
|
-
}
|
|
461
|
-
if (!isAddress(requirements.asset) || getAddress(requirements.asset) !== getAddress(options.asset)) {
|
|
462
|
-
return rejected("wrong_asset");
|
|
463
|
-
}
|
|
464
|
-
const payload = decodePaymentSubmission(submission.payload);
|
|
465
|
-
if (!payload) {
|
|
466
|
-
return rejected("malformed_payment_payload");
|
|
467
|
-
}
|
|
468
|
-
if (payload.x402Version !== X402_VERSION2) {
|
|
469
|
-
return rejected("unsupported_x402_version");
|
|
470
|
-
}
|
|
471
|
-
if (!isExactEvmPayload(payload)) {
|
|
472
|
-
return rejected("unsupported_scheme");
|
|
473
|
-
}
|
|
474
|
-
const { authorization } = payload.payload;
|
|
475
|
-
if (!isAddress(authorization.from) || !isAddress(authorization.to)) {
|
|
476
|
-
return rejected("malformed_payment_payload");
|
|
477
|
-
}
|
|
478
|
-
if (payload.accepted.network !== options.network) {
|
|
479
|
-
return rejected("wrong_network");
|
|
480
|
-
}
|
|
481
|
-
if (getAddress(authorization.to) !== getAddress(options.payTo)) {
|
|
482
|
-
return rejected("wrong_recipient");
|
|
483
|
-
}
|
|
484
|
-
let authorizedValue;
|
|
485
|
-
let required;
|
|
486
|
-
try {
|
|
487
|
-
authorizedValue = BigInt(authorization.value);
|
|
488
|
-
required = BigInt(requirements.amount);
|
|
489
|
-
} catch {
|
|
490
|
-
return rejected("malformed_payment_payload");
|
|
491
|
-
}
|
|
492
|
-
if (authorizedValue !== required) {
|
|
493
|
-
return rejected("wrong_amount");
|
|
494
|
-
}
|
|
495
|
-
const scope = binding.open();
|
|
496
|
-
let sdkResult;
|
|
497
|
-
try {
|
|
498
|
-
sdkResult = await scope.verify(payload, requirements);
|
|
499
|
-
} catch (err) {
|
|
500
|
-
if (isProviderUnavailableError(err) || scope.transportFailed()) {
|
|
501
|
-
throw new CommerceError(
|
|
502
|
-
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
503
|
-
`x402 provider: ${binding.kind} facilitator unreachable during verify()`,
|
|
504
|
-
{
|
|
505
|
-
cause: err
|
|
506
|
-
}
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
logger.warn({ err: describeError(err) }, "x402 verify(): unexpected SDK error");
|
|
510
|
-
return rejected("unexpected_verify_error");
|
|
511
|
-
}
|
|
512
|
-
if (!sdkResult.isValid) {
|
|
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");
|
|
531
|
-
}
|
|
532
|
-
const replayKey = computeReplayKey({
|
|
533
|
-
chainId: payloadChainId,
|
|
534
|
-
asset: getAddress(requirements.asset),
|
|
535
|
-
from: getAddress(authorization.from),
|
|
536
|
-
nonce: authorization.nonce
|
|
537
|
-
});
|
|
538
|
-
return {
|
|
539
|
-
status: "verified",
|
|
540
|
-
provider: "x402",
|
|
541
|
-
payer: getAddress(authorization.from),
|
|
542
|
-
payee: getAddress(authorization.to),
|
|
543
|
-
amount: formatCanonicalAmount(authorizedValue, options.assetDecimals),
|
|
544
|
-
currency: requirement.currency,
|
|
545
|
-
network: payload.accepted.network,
|
|
546
|
-
asset: getAddress(requirements.asset),
|
|
547
|
-
replayKey
|
|
548
|
-
};
|
|
549
|
-
}
|
|
550
|
-
async function settle(context) {
|
|
551
|
-
const { requirement, submission, verification } = context;
|
|
552
|
-
if (verification.status !== "verified") {
|
|
553
|
-
throw new CommerceError(
|
|
554
|
-
"PAYMENT_INVALID",
|
|
555
|
-
"x402 provider: settle() called without a successful verify()"
|
|
556
|
-
);
|
|
557
|
-
}
|
|
558
|
-
const requirements = requirement.challenge.accepts[0];
|
|
559
|
-
if (!requirements) {
|
|
560
|
-
throw new CommerceError(
|
|
561
|
-
"PAYMENT_INVALID",
|
|
562
|
-
"x402 provider: settle() found no payment requirements to settle against"
|
|
563
|
-
);
|
|
564
|
-
}
|
|
565
|
-
const payload = decodePaymentSubmission(submission.payload);
|
|
566
|
-
if (!payload || !isExactEvmPayload(payload)) {
|
|
567
|
-
throw new CommerceError(
|
|
568
|
-
"PAYMENT_INVALID",
|
|
569
|
-
"x402 provider: settle() received an undecodable payment payload"
|
|
570
|
-
);
|
|
571
|
-
}
|
|
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();
|
|
583
|
-
let sdkResult;
|
|
584
|
-
try {
|
|
585
|
-
sdkResult = await scope.settle(payload, requirements);
|
|
586
|
-
} catch (err) {
|
|
587
|
-
if (isProviderUnavailableError(err) || scope.transportFailed()) {
|
|
588
|
-
throw new CommerceError(
|
|
589
|
-
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
590
|
-
`x402 provider: ${binding.kind} facilitator unreachable during settle()`,
|
|
591
|
-
{ cause: err }
|
|
592
|
-
);
|
|
593
|
-
}
|
|
594
|
-
if (!isOnChainRevertError(err)) {
|
|
595
|
-
logger.warn(
|
|
596
|
-
{ err: describeError(err) },
|
|
597
|
-
"x402 settle(): settlement failed with an unclassified error; outcome unknown"
|
|
598
|
-
);
|
|
599
|
-
throw new CommerceError(
|
|
600
|
-
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
601
|
-
"x402 provider: settlement failed with an unclassified error; outcome unknown",
|
|
602
|
-
{ cause: err }
|
|
603
|
-
);
|
|
604
|
-
}
|
|
605
|
-
logger.warn(
|
|
606
|
-
{ err: describeError(err), rejectionReason: "transaction_reverted" },
|
|
607
|
-
"x402 settle(): settlement transaction reverted on chain"
|
|
608
|
-
);
|
|
609
|
-
return rejectedSettlement("transaction_reverted");
|
|
610
|
-
}
|
|
611
|
-
if (!sdkResult.success) {
|
|
612
|
-
if (sdkResult.errorReason === SETTLEMENT_PENDING_REASON || scope.transportFailed()) {
|
|
613
|
-
throw new CommerceError(
|
|
614
|
-
"PAYMENT_PROVIDER_UNAVAILABLE",
|
|
615
|
-
"x402 provider: settlement was broadcast but could not be confirmed",
|
|
616
|
-
{
|
|
617
|
-
...sdkResult.transaction ? { details: { transactionHash: sdkResult.transaction } } : {}
|
|
618
|
-
}
|
|
619
|
-
);
|
|
620
|
-
}
|
|
621
|
-
if (sdkResult.errorReason !== void 0) {
|
|
622
|
-
logger.debug(
|
|
623
|
-
{ reportedReason: sdkResult.errorReason },
|
|
624
|
-
"x402 settle(): facilitator failure reason"
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
return {
|
|
628
|
-
...rejectedSettlement(sanitiseReason(sdkResult.errorReason, "settlement_failed")),
|
|
629
|
-
network: sdkResult.network,
|
|
630
|
-
...sdkResult.payer !== void 0 ? { payer: sdkResult.payer } : {}
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
return {
|
|
634
|
-
status: "settled",
|
|
635
|
-
provider: "x402",
|
|
636
|
-
externalReference: sdkResult.transaction,
|
|
637
|
-
...sdkResult.payer !== void 0 ? { payer: sdkResult.payer } : {},
|
|
638
|
-
payee: requirements.payTo,
|
|
639
|
-
amount: verification.amount,
|
|
640
|
-
currency: requirement.currency,
|
|
641
|
-
network: sdkResult.network,
|
|
642
|
-
...verification.asset !== void 0 ? { asset: verification.asset } : {},
|
|
643
|
-
...verification.replayKey !== void 0 ? { replayKey: verification.replayKey } : {},
|
|
644
|
-
settledAt: clock.nowIso()
|
|
645
|
-
};
|
|
646
|
-
}
|
|
647
|
-
async function computeHealth() {
|
|
648
|
-
const startedAt = clock.monotonicMs();
|
|
649
|
-
const checkedAt = clock.nowIso();
|
|
650
|
-
try {
|
|
651
|
-
const chainId = await healthPublicClient.getChainId();
|
|
652
|
-
if (chainId !== profile.chainId) {
|
|
653
|
-
return {
|
|
654
|
-
status: "fail",
|
|
655
|
-
detail: `RPC at ${options.rpcUrl} reports chain id ${chainId}, expected ${profile.chainId} (${profile.displayName})`,
|
|
656
|
-
checkedAt,
|
|
657
|
-
durationMs: clock.monotonicMs() - startedAt
|
|
658
|
-
};
|
|
659
|
-
}
|
|
660
|
-
const code = await healthPublicClient.getCode({ address: options.asset });
|
|
661
|
-
if (!code || code === "0x") {
|
|
662
|
-
return {
|
|
663
|
-
status: "fail",
|
|
664
|
-
detail: `No contract code found at configured asset address ${options.asset}`,
|
|
665
|
-
checkedAt,
|
|
666
|
-
durationMs: clock.monotonicMs() - startedAt
|
|
667
|
-
};
|
|
668
|
-
}
|
|
669
|
-
if (binding.kind === "remote") {
|
|
670
|
-
const kinds = await binding.supported();
|
|
671
|
-
const supportsUs = kinds.some(
|
|
672
|
-
(kind) => kind.x402Version === X402_VERSION2 && kind.scheme === "exact" && kind.network === options.network
|
|
673
|
-
);
|
|
674
|
-
if (!supportsUs) {
|
|
675
|
-
return {
|
|
676
|
-
status: "fail",
|
|
677
|
-
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`,
|
|
678
|
-
checkedAt,
|
|
679
|
-
durationMs: clock.monotonicMs() - startedAt
|
|
680
|
-
};
|
|
681
|
-
}
|
|
682
|
-
return {
|
|
683
|
-
status: "pass",
|
|
684
|
-
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}`,
|
|
685
|
-
checkedAt,
|
|
686
|
-
durationMs: clock.monotonicMs() - startedAt
|
|
687
|
-
};
|
|
688
|
-
}
|
|
689
|
-
const isAnvil = await probeIsAnvilNode(healthPublicClient);
|
|
690
|
-
if (!isAnvil) {
|
|
691
|
-
return {
|
|
692
|
-
status: "fail",
|
|
693
|
-
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.`,
|
|
694
|
-
checkedAt,
|
|
695
|
-
durationMs: clock.monotonicMs() - startedAt
|
|
696
|
-
};
|
|
697
|
-
}
|
|
698
|
-
return {
|
|
699
|
-
status: "pass",
|
|
700
|
-
detail: `${describeDeploymentMode(mode)} \u2014 RPC ${options.rpcUrl} reachable, chain id ${profile.chainId}, asset ${options.asset} has code, confirmed Anvil dev node`,
|
|
701
|
-
checkedAt,
|
|
702
|
-
durationMs: clock.monotonicMs() - startedAt
|
|
703
|
-
};
|
|
704
|
-
} catch (err) {
|
|
705
|
-
return {
|
|
706
|
-
status: "fail",
|
|
707
|
-
detail: `x402 health check failed: ${describeError(err)}`,
|
|
708
|
-
checkedAt,
|
|
709
|
-
durationMs: clock.monotonicMs() - startedAt
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
}
|
|
713
|
-
const HEALTH_CACHE_MS = 5e3;
|
|
714
|
-
let cachedHealth;
|
|
715
|
-
let inFlightHealth;
|
|
716
|
-
async function health() {
|
|
717
|
-
const now = clock.monotonicMs();
|
|
718
|
-
if (cachedHealth && now - cachedHealth.at < HEALTH_CACHE_MS) {
|
|
719
|
-
return cachedHealth.value;
|
|
720
|
-
}
|
|
721
|
-
if (inFlightHealth) return inFlightHealth;
|
|
722
|
-
inFlightHealth = computeHealth().then((value) => {
|
|
723
|
-
cachedHealth = { at: clock.monotonicMs(), value };
|
|
724
|
-
return value;
|
|
725
|
-
}).finally(() => {
|
|
726
|
-
inFlightHealth = void 0;
|
|
727
|
-
});
|
|
728
|
-
return inFlightHealth;
|
|
729
|
-
}
|
|
730
|
-
return {
|
|
731
|
-
name: "x402",
|
|
732
|
-
descriptor,
|
|
733
|
-
createRequirement,
|
|
734
|
-
verify,
|
|
735
|
-
settle,
|
|
736
|
-
health
|
|
737
|
-
};
|
|
738
|
-
}
|
|
739
|
-
function isOnChainRevertError(err) {
|
|
740
|
-
return err instanceof ContractFunctionRevertedError || err instanceof ContractFunctionExecutionError;
|
|
741
|
-
}
|
|
742
|
-
function isProviderUnavailableError(err) {
|
|
743
|
-
if (err instanceof HttpRequestError || err instanceof TimeoutError || err instanceof WaitForTransactionReceiptTimeoutError) {
|
|
744
|
-
return true;
|
|
745
|
-
}
|
|
746
|
-
const message = describeError(err).toLowerCase();
|
|
747
|
-
return message.includes("econnrefused") || message.includes("fetch failed") || message.includes("enotfound") || message.includes("etimedout") || message.includes("timed out") || message.includes("network error");
|
|
748
|
-
}
|
|
749
|
-
function describeError(err) {
|
|
750
|
-
if (err instanceof Error) return `${err.name}: ${err.message}`;
|
|
751
|
-
return String(err);
|
|
752
|
-
}
|
|
753
|
-
async function probeIsAnvilNode(client) {
|
|
754
|
-
const request = client.request;
|
|
755
|
-
try {
|
|
756
|
-
await request({ method: "anvil_nodeInfo", params: [] });
|
|
757
|
-
return true;
|
|
758
|
-
} catch {
|
|
759
|
-
return false;
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
|
|
763
|
-
export { createPaymentProof, createX402PaymentProvider, createX402PaymentProvider as x402 };
|
|
83
|
+
export { createPaymentProof };
|
|
764
84
|
//# sourceMappingURL=x402.js.map
|
|
765
85
|
//# sourceMappingURL=x402.js.map
|