@devlab.group/agent-commerce 0.1.0-alpha.0 → 0.2.0-beta.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/dist/x402.js CHANGED
@@ -1,11 +1,49 @@
1
- import { CommerceError, systemClock, NOOP_LOGGER, PACKAGE_VERSION } from './chunk-CZ6ZDAYD.js';
1
+ import { assertPayToIsNotDevAddress, resolveX402Deployment, assertDevKeyIsLocalOnly, describeDeploymentMode } from './chunk-MQMKQGAB.js';
2
+ export { SUPPORTED_NETWORK_IDS } from './chunk-MQMKQGAB.js';
3
+ import { CommerceError, systemClock, NOOP_LOGGER, PACKAGE_VERSION } from './chunk-ASK67X56.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 { encodePayment } from 'x402/schemes';
5
- import { getNetworkId } from 'x402/shared';
6
- import { PaymentRequirementsSchema, PaymentPayloadSchema } from 'x402/types';
7
- import { verify, settle } from 'x402/facilitator';
7
+ import { x402Facilitator } from '@x402/core/facilitator';
8
+ import { HTTPFacilitatorClient, FacilitatorResponseError } 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) {
20
+ return {
21
+ id: LOCAL_CHAIN_ID,
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) {
30
+ return createPublicClient({
31
+ chain: buildLocalChain(rpcUrl),
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 = PaymentRequirementsSchema.parse(options.accepts);
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 ?? nowSeconds - DEFAULT_VALID_AFTER_SKEW_SECONDS;
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.maxAmountRequired);
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: 1,
56
- scheme: "exact",
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 encodePayment(payload);
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,82 @@ function formatCanonicalAmount(baseUnits, decimals) {
114
155
  const sign = negative ? "-" : "";
115
156
  return fractionPart.length > 0 ? `${sign}${integerPart}.${fractionPart}` : `${sign}${integerPart}`;
116
157
  }
117
- var LOCAL_CHAIN_ID = 84532;
118
- function buildLocalChain(rpcUrl) {
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
- id: LOCAL_CHAIN_ID,
121
- name: "agent-commerce-local",
122
- nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 },
123
- rpcUrls: {
124
- default: { http: [rpcUrl] }
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 createLocalPublicClient(rpcUrl, timeoutMs) {
129
- return createPublicClient({
130
- chain: buildLocalChain(rpcUrl),
131
- transport: http(rpcUrl, timeoutMs !== void 0 ? { timeout: timeoutMs } : void 0)
199
+ function createRemoteFacilitatorBinding(options, isTransportError) {
200
+ const auth = options.auth;
201
+ const createAuthHeaders = auth.type === "bearer" ? async () => {
202
+ const headers = { Authorization: `Bearer ${auth.token}` };
203
+ return { verify: headers, settle: headers, supported: headers };
204
+ } : void 0;
205
+ const client = new HTTPFacilitatorClient({
206
+ url: options.url,
207
+ timeoutMs: options.timeoutMs ?? SETTLEMENT_CONFIRMATION_TIMEOUT_MS,
208
+ ...createAuthHeaders ? { createAuthHeaders } : {}
132
209
  });
133
- }
134
- function createLocalFacilitatorClient(rpcUrl, signerPrivateKey) {
135
- const chain = buildLocalChain(rpcUrl);
136
- const account = privateKeyToAccount(signerPrivateKey);
137
- return createWalletClient({
138
- account,
139
- chain,
140
- transport: http(rpcUrl)
141
- }).extend(publicActions);
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
- );
219
- }
210
+ return {
211
+ kind: "remote",
212
+ describe: `remote ${new URL(options.url).origin} (auth=${auth.type})`,
213
+ open() {
214
+ let failed = false;
215
+ const watch = async (call) => {
216
+ try {
217
+ return await call();
218
+ } catch (err) {
219
+ if (err instanceof FacilitatorResponseError || isTransportError(err)) failed = true;
220
+ throw err;
221
+ }
222
+ };
223
+ return {
224
+ verify: (payload, requirements) => watch(() => client.verify(payload, requirements)),
225
+ settle: (payload, requirements) => watch(() => client.settle(payload, requirements)),
226
+ transportFailed: () => failed
227
+ };
228
+ },
229
+ async supported() {
230
+ const response = await client.getSupported();
231
+ return response.kinds;
232
+ }
233
+ };
220
234
  }
221
235
  function decodePaymentSubmission(payloadBase64) {
222
236
  let json;
@@ -226,11 +240,11 @@ function decodePaymentSubmission(payloadBase64) {
226
240
  } catch {
227
241
  return void 0;
228
242
  }
229
- const result = PaymentPayloadSchema.safeParse(json);
243
+ const result = PaymentPayloadV2Schema.safeParse(json);
230
244
  return result.success ? result.data : void 0;
231
245
  }
232
246
  function isExactEvmPayload(payload) {
233
- return payload.scheme === "exact" && typeof payload.payload === "object" && payload.payload !== null && "authorization" in payload.payload && "signature" in payload.payload;
247
+ return payload.accepted.scheme === "exact" && typeof payload.payload === "object" && payload.payload !== null && isEIP3009Payload(payload.payload) && typeof payload.payload.signature === "string";
234
248
  }
235
249
  function computeReplayKey(input) {
236
250
  const encoded = encodeAbiParameters(
@@ -241,10 +255,12 @@ function computeReplayKey(input) {
241
255
  }
242
256
 
243
257
  // src/payments/x402/provider.ts
244
- var X402_PROTOCOL_VERSION = "1";
258
+ var X402_VERSION2 = 2;
259
+ var X402_PROTOCOL_VERSION = String(X402_VERSION2);
245
260
  var DEFAULT_MAX_TIMEOUT_SECONDS = 60;
246
261
  var DEFAULT_MIME_TYPE = "application/json";
247
262
  var HEALTH_TIMEOUT_MS = 4e3;
263
+ var SETTLEMENT_PENDING_REASON = "settlement_pending";
248
264
  var DEFAULT_IDS = {
249
265
  next: (prefix) => `${prefix ? `${prefix}_` : ""}${crypto.randomUUID()}`
250
266
  };
@@ -268,11 +284,20 @@ function createX402PaymentProvider(options) {
268
284
  `x402 provider: "assetDecimals" must be a non-negative integer`
269
285
  );
270
286
  }
271
- let facilitatorClient;
287
+ const { profile, mode } = resolveX402Deployment({
288
+ network: options.network,
289
+ payTo: options.payTo,
290
+ asset: options.asset,
291
+ facilitator: options.facilitator,
292
+ ...options.allowMainnet !== void 0 ? { allowMainnet: options.allowMainnet } : {}
293
+ });
294
+ const network = options.network;
295
+ let binding;
272
296
  if (options.facilitator.mode === "local") {
273
297
  assertDevKeyIsLocalOnly(options.rpcUrl, options.facilitator.signerPrivateKey);
298
+ let client;
274
299
  try {
275
- facilitatorClient = createLocalFacilitatorClient(
300
+ client = createLocalFacilitatorClient(
276
301
  options.rpcUrl,
277
302
  options.facilitator.signerPrivateKey
278
303
  );
@@ -283,8 +308,13 @@ function createX402PaymentProvider(options) {
283
308
  { cause }
284
309
  );
285
310
  }
311
+ binding = createLocalFacilitatorBinding(client, network, isProviderUnavailableError);
312
+ } else {
313
+ binding = createRemoteFacilitatorBinding(
314
+ { url: options.facilitator.url, auth: options.facilitator.auth },
315
+ isProviderUnavailableError
316
+ );
286
317
  }
287
- const publicClient = createLocalPublicClient(options.rpcUrl);
288
318
  const healthPublicClient = createLocalPublicClient(options.rpcUrl, HEALTH_TIMEOUT_MS);
289
319
  const clock = options.clock ?? systemClock;
290
320
  const ids = options.ids ?? DEFAULT_IDS;
@@ -294,43 +324,54 @@ function createX402PaymentProvider(options) {
294
324
  name: "x402",
295
325
  kind: "payment",
296
326
  implementationVersion: PACKAGE_VERSION,
297
- supportedSpec: "x402@1.2.0 scheme=exact family=evm",
298
- capabilities: ["exact-evm", "local-facilitator", "eip-3009"],
327
+ supportedSpec: `x402/v${X402_VERSION2} scheme=exact family=eip155`,
328
+ capabilities: [
329
+ "exact-evm",
330
+ `${binding.kind}-facilitator`,
331
+ "eip-3009",
332
+ "caip-2",
333
+ `mode=${mode}`
334
+ ],
299
335
  status: "stable",
300
- unsupported: ["svm", "deferred scheme", "remote facilitator"]
336
+ unsupported: [
337
+ "svm",
338
+ "permit2",
339
+ "upto scheme",
340
+ "per-request signed facilitator credentials (e.g. CDP JWT)"
341
+ ]
301
342
  };
302
343
  async function createRequirement(context) {
303
- const maxAmountRequired = parseCanonicalAmount(
304
- context.amount,
305
- options.assetDecimals
306
- ).toString();
344
+ const amount = parseCanonicalAmount(context.amount, options.assetDecimals).toString();
307
345
  const requirements = {
308
346
  scheme: "exact",
309
- // options.network is a canonical free-form string (frozen contract); the
310
- // x402 SDK's Network union is checked against the live chain in health()
311
- // and by verify()'s own network-mismatch guard.
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,
347
+ network,
348
+ asset: options.asset,
349
+ amount,
325
350
  payTo: options.payTo,
326
351
  maxTimeoutSeconds,
327
- asset: options.asset,
328
- // REQUIRED.name/version explicitly means
329
- // the facilitator never needs an on-chain version() call to build the
330
- // EIP-712 domain, so verification stays deterministic even against a
331
- // token that has no on-chain version() (it doesn't matter here since
332
- // MockUSDC has one, but this is what makes the domain unambiguous).
333
- extra: { name: options.assetName, version: options.assetVersion }
352
+ // `name`/`version` are what let a buyer and the facilitator build the
353
+ // same EIP-712 domain without an on-chain `version()` call, so
354
+ // verification stays deterministic against a token that has none.
355
+ //
356
+ // `assetTransferMethod` is stated rather than left to default: x402 v2's
357
+ // `exact`/EVM scheme also has a Permit2 path, and a client that picked it
358
+ // would produce a payload this provider cannot settle. Declaring the one
359
+ // supported method turns that into a client-side non-choice instead of a
360
+ // server-side rejection.
361
+ extra: {
362
+ name: options.assetName,
363
+ version: options.assetVersion,
364
+ assetTransferMethod: "eip3009"
365
+ }
366
+ };
367
+ const envelope = {
368
+ x402Version: X402_VERSION2,
369
+ resource: {
370
+ url: `resource://agent-commerce/${encodeURIComponent(options.network)}/${options.payTo}/resources/${encodeURIComponent(context.resource.id)}`,
371
+ description: context.resource.description ?? context.resource.name,
372
+ mimeType: DEFAULT_MIME_TYPE
373
+ },
374
+ accepts: [requirements]
334
375
  };
335
376
  const expiresAt = new Date(clock.now().getTime() + maxTimeoutSeconds * 1e3).toISOString();
336
377
  return {
@@ -347,11 +388,12 @@ function createX402PaymentProvider(options) {
347
388
  challenge: {
348
389
  provider: "x402",
349
390
  version: X402_PROTOCOL_VERSION,
350
- accepts: [requirements]
391
+ accepts: [requirements],
392
+ envelope
351
393
  }
352
394
  };
353
395
  }
354
- async function verify$1(context) {
396
+ async function verify(context) {
355
397
  const { requirement, submission } = context;
356
398
  const rejected = (reason) => ({
357
399
  status: "rejected",
@@ -379,6 +421,9 @@ function createX402PaymentProvider(options) {
379
421
  if (!payload) {
380
422
  return rejected("malformed_payment_payload");
381
423
  }
424
+ if (payload.x402Version !== X402_VERSION2) {
425
+ return rejected("unsupported_x402_version");
426
+ }
382
427
  if (!isExactEvmPayload(payload)) {
383
428
  return rejected("unsupported_scheme");
384
429
  }
@@ -386,35 +431,32 @@ function createX402PaymentProvider(options) {
386
431
  if (!isAddress(authorization.from) || !isAddress(authorization.to)) {
387
432
  return rejected("malformed_payment_payload");
388
433
  }
389
- if (payload.network !== options.network) {
434
+ if (payload.accepted.network !== options.network) {
390
435
  return rejected("wrong_network");
391
436
  }
392
437
  if (getAddress(authorization.to) !== getAddress(options.payTo)) {
393
438
  return rejected("wrong_recipient");
394
439
  }
395
440
  let authorizedValue;
396
- let maxRequired;
441
+ let required;
397
442
  try {
398
443
  authorizedValue = BigInt(authorization.value);
399
- maxRequired = BigInt(requirements.maxAmountRequired);
444
+ required = BigInt(requirements.amount);
400
445
  } catch {
401
446
  return rejected("malformed_payment_payload");
402
447
  }
403
- if (authorizedValue < maxRequired) {
448
+ if (authorizedValue < required) {
404
449
  return rejected("wrong_amount");
405
450
  }
451
+ const scope = binding.open();
406
452
  let sdkResult;
407
453
  try {
408
- sdkResult = await verify(
409
- publicClient,
410
- payload,
411
- requirements
412
- );
454
+ sdkResult = await scope.verify(payload, requirements);
413
455
  } catch (err) {
414
- if (isProviderUnavailableError(err)) {
456
+ if (isProviderUnavailableError(err) || scope.transportFailed()) {
415
457
  throw new CommerceError(
416
458
  "PAYMENT_PROVIDER_UNAVAILABLE",
417
- "x402 provider: RPC unreachable during verify()",
459
+ `x402 provider: ${binding.kind} facilitator unreachable during verify()`,
418
460
  {
419
461
  cause: err
420
462
  }
@@ -424,10 +466,21 @@ function createX402PaymentProvider(options) {
424
466
  return rejected("unexpected_verify_error");
425
467
  }
426
468
  if (!sdkResult.isValid) {
469
+ if (scope.transportFailed()) {
470
+ throw new CommerceError(
471
+ "PAYMENT_PROVIDER_UNAVAILABLE",
472
+ `x402 provider: ${binding.kind} facilitator unreachable during verify()`,
473
+ { details: { reportedReason: sdkResult.invalidReason ?? "invalid_payment" } }
474
+ );
475
+ }
427
476
  return rejected(sdkResult.invalidReason ?? "invalid_payment");
428
477
  }
478
+ const payloadChainId = chainIdFromCaip2(payload.accepted.network);
479
+ if (payloadChainId === void 0) {
480
+ return rejected("wrong_network");
481
+ }
429
482
  const replayKey = computeReplayKey({
430
- chainId: getNetworkId(payload.network),
483
+ chainId: payloadChainId,
431
484
  asset: getAddress(requirements.asset),
432
485
  from: getAddress(authorization.from),
433
486
  nonce: authorization.nonce
@@ -439,12 +492,12 @@ function createX402PaymentProvider(options) {
439
492
  payee: getAddress(authorization.to),
440
493
  amount: formatCanonicalAmount(authorizedValue, options.assetDecimals),
441
494
  currency: requirement.currency,
442
- network: payload.network,
495
+ network: payload.accepted.network,
443
496
  asset: getAddress(requirements.asset),
444
497
  replayKey
445
498
  };
446
499
  }
447
- async function settle$1(context) {
500
+ async function settle(context) {
448
501
  const { requirement, submission, verification } = context;
449
502
  if (verification.status !== "verified") {
450
503
  throw new CommerceError(
@@ -452,12 +505,6 @@ function createX402PaymentProvider(options) {
452
505
  "x402 provider: settle() called without a successful verify()"
453
506
  );
454
507
  }
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
508
  const requirements = requirement.challenge.accepts[0];
462
509
  if (!requirements) {
463
510
  throw new CommerceError(
@@ -472,30 +519,26 @@ function createX402PaymentProvider(options) {
472
519
  "x402 provider: settle() received an undecodable payment payload"
473
520
  );
474
521
  }
475
- let broadcastTxHash;
476
- const settlementClient = {
477
- ...facilitatorClient,
478
- waitForTransactionReceipt: (args) => {
479
- broadcastTxHash = args.hash;
480
- return facilitatorClient.waitForTransactionReceipt(args);
481
- }
482
- };
522
+ const rejectedSettlement = (rejectionReason) => ({
523
+ status: "rejected",
524
+ provider: "x402",
525
+ amount: verification.amount,
526
+ currency: requirement.currency,
527
+ ...verification.network !== void 0 ? { network: verification.network } : {},
528
+ ...verification.asset !== void 0 ? { asset: verification.asset } : {},
529
+ ...verification.replayKey !== void 0 ? { replayKey: verification.replayKey } : {},
530
+ rejectionReason
531
+ });
532
+ const scope = binding.open();
483
533
  let sdkResult;
484
534
  try {
485
- sdkResult = await settle(
486
- settlementClient,
487
- payload,
488
- requirements
489
- );
535
+ sdkResult = await scope.settle(payload, requirements);
490
536
  } catch (err) {
491
- if (isProviderUnavailableError(err)) {
537
+ if (isProviderUnavailableError(err) || scope.transportFailed()) {
492
538
  throw new CommerceError(
493
539
  "PAYMENT_PROVIDER_UNAVAILABLE",
494
- "x402 provider: RPC unreachable during settle()",
495
- {
496
- cause: err,
497
- ...broadcastTxHash !== void 0 ? { details: { transactionHash: broadcastTxHash } } : {}
498
- }
540
+ `x402 provider: ${binding.kind} facilitator unreachable during settle()`,
541
+ { cause: err }
499
542
  );
500
543
  }
501
544
  const rejectionReason = isOnChainRevertError(err) ? "transaction_reverted" : "unexpected_settle_error";
@@ -503,28 +546,22 @@ function createX402PaymentProvider(options) {
503
546
  { err: describeError(err), rejectionReason },
504
547
  "x402 settle(): settlement transaction failed"
505
548
  );
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
- };
549
+ return rejectedSettlement(rejectionReason);
516
550
  }
517
551
  if (!sdkResult.success) {
552
+ if (sdkResult.errorReason === SETTLEMENT_PENDING_REASON || scope.transportFailed()) {
553
+ throw new CommerceError(
554
+ "PAYMENT_PROVIDER_UNAVAILABLE",
555
+ "x402 provider: settlement was broadcast but could not be confirmed",
556
+ {
557
+ ...sdkResult.transaction ? { details: { transactionHash: sdkResult.transaction } } : {}
558
+ }
559
+ );
560
+ }
518
561
  return {
519
- status: "rejected",
520
- provider: "x402",
521
- amount: verification.amount,
522
- currency: requirement.currency,
562
+ ...rejectedSettlement(sdkResult.errorReason ?? "settlement_failed"),
523
563
  network: sdkResult.network,
524
- ...verification.asset !== void 0 ? { asset: verification.asset } : {},
525
- ...sdkResult.payer !== void 0 ? { payer: sdkResult.payer } : {},
526
- ...verification.replayKey !== void 0 ? { replayKey: verification.replayKey } : {},
527
- rejectionReason: sdkResult.errorReason ?? "settlement_failed"
564
+ ...sdkResult.payer !== void 0 ? { payer: sdkResult.payer } : {}
528
565
  };
529
566
  }
530
567
  return {
@@ -546,10 +583,10 @@ function createX402PaymentProvider(options) {
546
583
  const checkedAt = clock.nowIso();
547
584
  try {
548
585
  const chainId = await healthPublicClient.getChainId();
549
- if (chainId !== LOCAL_CHAIN_ID) {
586
+ if (chainId !== profile.chainId) {
550
587
  return {
551
588
  status: "fail",
552
- detail: `RPC at ${options.rpcUrl} reports chain id ${chainId}, expected ${LOCAL_CHAIN_ID}`,
589
+ detail: `RPC at ${options.rpcUrl} reports chain id ${chainId}, expected ${profile.chainId} (${profile.displayName})`,
553
590
  checkedAt,
554
591
  durationMs: clock.monotonicMs() - startedAt
555
592
  };
@@ -563,10 +600,22 @@ function createX402PaymentProvider(options) {
563
600
  durationMs: clock.monotonicMs() - startedAt
564
601
  };
565
602
  }
566
- if (options.facilitator.mode === "remote") {
603
+ if (binding.kind === "remote") {
604
+ const kinds = await binding.supported();
605
+ const supportsUs = kinds.some(
606
+ (kind) => kind.x402Version === X402_VERSION2 && kind.scheme === "exact" && kind.network === options.network
607
+ );
608
+ if (!supportsUs) {
609
+ return {
610
+ status: "fail",
611
+ 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`,
612
+ checkedAt,
613
+ durationMs: clock.monotonicMs() - startedAt
614
+ };
615
+ }
567
616
  return {
568
- status: "warn",
569
- detail: "RPC and asset are reachable; remote facilitator mode is not implemented in v0.1.0-alpha",
617
+ status: "pass",
618
+ 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
619
  checkedAt,
571
620
  durationMs: clock.monotonicMs() - startedAt
572
621
  };
@@ -575,14 +624,14 @@ function createX402PaymentProvider(options) {
575
624
  if (!isAnvil) {
576
625
  return {
577
626
  status: "fail",
578
- detail: `RPC at ${options.rpcUrl} reports chain id ${LOCAL_CHAIN_ID} 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.`,
627
+ 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
628
  checkedAt,
580
629
  durationMs: clock.monotonicMs() - startedAt
581
630
  };
582
631
  }
583
632
  return {
584
633
  status: "pass",
585
- detail: `RPC ${options.rpcUrl} reachable, chain id ${LOCAL_CHAIN_ID}, asset ${options.asset} has code, confirmed Anvil dev node`,
634
+ detail: `${describeDeploymentMode(mode)} \u2014 RPC ${options.rpcUrl} reachable, chain id ${profile.chainId}, asset ${options.asset} has code, confirmed Anvil dev node`,
586
635
  checkedAt,
587
636
  durationMs: clock.monotonicMs() - startedAt
588
637
  };
@@ -616,8 +665,8 @@ function createX402PaymentProvider(options) {
616
665
  name: "x402",
617
666
  descriptor,
618
667
  createRequirement,
619
- verify: verify$1,
620
- settle: settle$1,
668
+ verify,
669
+ settle,
621
670
  health
622
671
  };
623
672
  }