@devlab.group/agent-commerce 0.1.0-alpha.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 ADDED
@@ -0,0 +1,650 @@
1
+ import { CommerceError, systemClock, NOOP_LOGGER, PACKAGE_VERSION } from './chunk-CZ6ZDAYD.js';
2
+ import { getAddress, toHex, isAddress, createWalletClient, http, publicActions, createPublicClient, HttpRequestError, TimeoutError, WaitForTransactionReceiptTimeoutError, encodeAbiParameters, keccak256, ContractFunctionRevertedError, ContractFunctionExecutionError } from 'viem';
3
+ 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';
8
+
9
+ var TRANSFER_WITH_AUTHORIZATION_TYPES = {
10
+ TransferWithAuthorization: [
11
+ { name: "from", type: "address" },
12
+ { name: "to", type: "address" },
13
+ { name: "value", type: "uint256" },
14
+ { name: "validAfter", type: "uint256" },
15
+ { name: "validBefore", type: "uint256" },
16
+ { name: "nonce", type: "bytes32" }
17
+ ]
18
+ };
19
+ var DEFAULT_VALID_AFTER_SKEW_SECONDS = 600;
20
+ async function createPaymentProof(options) {
21
+ const requirements = PaymentRequirementsSchema.parse(options.accepts);
22
+ if (!requirements.extra || typeof requirements.extra["name"] !== "string" || typeof requirements.extra["version"] !== "string") {
23
+ throw new Error(
24
+ "createPaymentProof: payment requirement is missing extra.name/extra.version \u2014 cannot build the EIP-712 domain"
25
+ );
26
+ }
27
+ const account = privateKeyToAccount(options.buyerPrivateKey);
28
+ void options.rpcUrl;
29
+ const chainId = getNetworkId(requirements.network);
30
+ const nonce = options.overrides?.nonce ?? randomNonce();
31
+ const nowSeconds = Math.floor(Date.now() / 1e3);
32
+ const validAfter = options.overrides?.validAfter ?? nowSeconds - DEFAULT_VALID_AFTER_SKEW_SECONDS;
33
+ const validBefore = options.overrides?.validBefore ?? nowSeconds + requirements.maxTimeoutSeconds;
34
+ const to = getAddress(options.overrides?.payTo ?? requirements.payTo);
35
+ const value = BigInt(options.overrides?.value ?? requirements.maxAmountRequired);
36
+ const signature = await account.signTypedData({
37
+ domain: {
38
+ name: requirements.extra["name"],
39
+ version: requirements.extra["version"],
40
+ chainId,
41
+ verifyingContract: getAddress(requirements.asset)
42
+ },
43
+ types: TRANSFER_WITH_AUTHORIZATION_TYPES,
44
+ primaryType: "TransferWithAuthorization",
45
+ message: {
46
+ from: account.address,
47
+ to,
48
+ value,
49
+ validAfter: BigInt(validAfter),
50
+ validBefore: BigInt(validBefore),
51
+ nonce
52
+ }
53
+ });
54
+ const payload = {
55
+ x402Version: 1,
56
+ scheme: "exact",
57
+ network: requirements.network,
58
+ payload: {
59
+ signature,
60
+ authorization: {
61
+ from: account.address,
62
+ to,
63
+ value: value.toString(),
64
+ validAfter: validAfter.toString(),
65
+ validBefore: validBefore.toString(),
66
+ nonce
67
+ }
68
+ }
69
+ };
70
+ return encodePayment(payload);
71
+ }
72
+ function randomNonce() {
73
+ const bytes = new Uint8Array(32);
74
+ crypto.getRandomValues(bytes);
75
+ return toHex(bytes);
76
+ }
77
+
78
+ // src/payments/x402/amount.ts
79
+ var DECIMAL_STRING_PATTERN = /^\d+(?:\.\d+)?$/;
80
+ function parseCanonicalAmount(amount, decimals) {
81
+ if (!Number.isInteger(decimals) || decimals < 0) {
82
+ throw new CommerceError("INTERNAL_ERROR", `Invalid asset decimals: ${decimals}`);
83
+ }
84
+ if (!DECIMAL_STRING_PATTERN.test(amount)) {
85
+ throw new CommerceError(
86
+ "PAYMENT_INVALID",
87
+ `Amount "${amount}" is not a valid non-negative decimal string`,
88
+ {
89
+ details: { amount }
90
+ }
91
+ );
92
+ }
93
+ const [integerPart = "0", fractionPart = ""] = amount.split(".");
94
+ if (fractionPart.length > decimals) {
95
+ throw new CommerceError(
96
+ "PAYMENT_INVALID",
97
+ `Amount "${amount}" has more precision than the asset supports (${decimals} decimals)`,
98
+ { details: { amount, assetDecimals: decimals } }
99
+ );
100
+ }
101
+ const paddedFraction = fractionPart.padEnd(decimals, "0");
102
+ const combined = `${integerPart}${paddedFraction}`;
103
+ return BigInt(combined);
104
+ }
105
+ function formatCanonicalAmount(baseUnits, decimals) {
106
+ if (!Number.isInteger(decimals) || decimals < 0) {
107
+ throw new CommerceError("INTERNAL_ERROR", `Invalid asset decimals: ${decimals}`);
108
+ }
109
+ const negative = baseUnits < 0n;
110
+ const abs = negative ? -baseUnits : baseUnits;
111
+ const digits = abs.toString().padStart(decimals + 1, "0");
112
+ const integerPart = digits.slice(0, digits.length - decimals) || "0";
113
+ const fractionPart = digits.slice(digits.length - decimals).replace(/0+$/, "");
114
+ const sign = negative ? "-" : "";
115
+ return fractionPart.length > 0 ? `${sign}${integerPart}.${fractionPart}` : `${sign}${integerPart}`;
116
+ }
117
+ var LOCAL_CHAIN_ID = 84532;
118
+ function buildLocalChain(rpcUrl) {
119
+ 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] }
125
+ }
126
+ };
127
+ }
128
+ function createLocalPublicClient(rpcUrl, timeoutMs) {
129
+ return createPublicClient({
130
+ chain: buildLocalChain(rpcUrl),
131
+ transport: http(rpcUrl, timeoutMs !== void 0 ? { timeout: timeoutMs } : void 0)
132
+ });
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
+ }
220
+ }
221
+ function decodePaymentSubmission(payloadBase64) {
222
+ let json;
223
+ try {
224
+ const decoded = Buffer.from(payloadBase64, "base64").toString("utf8");
225
+ json = JSON.parse(decoded);
226
+ } catch {
227
+ return void 0;
228
+ }
229
+ const result = PaymentPayloadSchema.safeParse(json);
230
+ return result.success ? result.data : void 0;
231
+ }
232
+ function isExactEvmPayload(payload) {
233
+ return payload.scheme === "exact" && typeof payload.payload === "object" && payload.payload !== null && "authorization" in payload.payload && "signature" in payload.payload;
234
+ }
235
+ function computeReplayKey(input) {
236
+ const encoded = encodeAbiParameters(
237
+ [{ type: "uint256" }, { type: "address" }, { type: "address" }, { type: "bytes32" }],
238
+ [BigInt(input.chainId), input.asset, input.from, input.nonce]
239
+ );
240
+ return keccak256(encoded);
241
+ }
242
+
243
+ // src/payments/x402/provider.ts
244
+ var X402_PROTOCOL_VERSION = "1";
245
+ var DEFAULT_MAX_TIMEOUT_SECONDS = 60;
246
+ var DEFAULT_MIME_TYPE = "application/json";
247
+ var HEALTH_TIMEOUT_MS = 4e3;
248
+ var DEFAULT_IDS = {
249
+ next: (prefix) => `${prefix ? `${prefix}_` : ""}${crypto.randomUUID()}`
250
+ };
251
+ function createX402PaymentProvider(options) {
252
+ if (!isAddress(options.asset)) {
253
+ throw new CommerceError(
254
+ "CONFIG_INVALID",
255
+ `x402 provider: "asset" is not a valid EVM address: ${options.asset}`
256
+ );
257
+ }
258
+ if (!isAddress(options.payTo)) {
259
+ throw new CommerceError(
260
+ "CONFIG_INVALID",
261
+ `x402 provider: "payTo" is not a valid EVM address: ${options.payTo}`
262
+ );
263
+ }
264
+ assertPayToIsNotDevAddress(options.rpcUrl, options.payTo);
265
+ if (!Number.isInteger(options.assetDecimals) || options.assetDecimals < 0) {
266
+ throw new CommerceError(
267
+ "CONFIG_INVALID",
268
+ `x402 provider: "assetDecimals" must be a non-negative integer`
269
+ );
270
+ }
271
+ let facilitatorClient;
272
+ if (options.facilitator.mode === "local") {
273
+ assertDevKeyIsLocalOnly(options.rpcUrl, options.facilitator.signerPrivateKey);
274
+ try {
275
+ facilitatorClient = createLocalFacilitatorClient(
276
+ options.rpcUrl,
277
+ options.facilitator.signerPrivateKey
278
+ );
279
+ } catch (cause) {
280
+ throw new CommerceError(
281
+ "CONFIG_INVALID",
282
+ "x402 provider: could not construct a local facilitator signer from facilitator.signerPrivateKey \u2014 it must be a valid 32-byte hex private key",
283
+ { cause }
284
+ );
285
+ }
286
+ }
287
+ const publicClient = createLocalPublicClient(options.rpcUrl);
288
+ const healthPublicClient = createLocalPublicClient(options.rpcUrl, HEALTH_TIMEOUT_MS);
289
+ const clock = options.clock ?? systemClock;
290
+ const ids = options.ids ?? DEFAULT_IDS;
291
+ const logger = options.logger ?? NOOP_LOGGER;
292
+ const maxTimeoutSeconds = options.maxTimeoutSeconds ?? DEFAULT_MAX_TIMEOUT_SECONDS;
293
+ const descriptor = {
294
+ name: "x402",
295
+ kind: "payment",
296
+ implementationVersion: PACKAGE_VERSION,
297
+ supportedSpec: "x402@1.2.0 scheme=exact family=evm",
298
+ capabilities: ["exact-evm", "local-facilitator", "eip-3009"],
299
+ status: "stable",
300
+ unsupported: ["svm", "deferred scheme", "remote facilitator"]
301
+ };
302
+ async function createRequirement(context) {
303
+ const maxAmountRequired = parseCanonicalAmount(
304
+ context.amount,
305
+ options.assetDecimals
306
+ ).toString();
307
+ const requirements = {
308
+ 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,
325
+ payTo: options.payTo,
326
+ 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 }
334
+ };
335
+ const expiresAt = new Date(clock.now().getTime() + maxTimeoutSeconds * 1e3).toISOString();
336
+ return {
337
+ id: ids.next("payreq"),
338
+ requestId: context.requestId,
339
+ resourceId: context.resource.id,
340
+ provider: "x402",
341
+ amount: context.amount,
342
+ currency: context.currency,
343
+ destination: options.payTo,
344
+ network: options.network,
345
+ asset: options.asset,
346
+ expiresAt,
347
+ challenge: {
348
+ provider: "x402",
349
+ version: X402_PROTOCOL_VERSION,
350
+ accepts: [requirements]
351
+ }
352
+ };
353
+ }
354
+ async function verify$1(context) {
355
+ const { requirement, submission } = context;
356
+ const rejected = (reason) => ({
357
+ status: "rejected",
358
+ provider: "x402",
359
+ amount: requirement.amount,
360
+ currency: requirement.currency,
361
+ ...requirement.network !== void 0 ? { network: requirement.network } : {},
362
+ ...requirement.asset !== void 0 ? { asset: requirement.asset } : {},
363
+ rejectionReason: reason
364
+ });
365
+ if (requirement.provider !== "x402" || requirement.challenge.provider !== "x402") {
366
+ return rejected("wrong_provider");
367
+ }
368
+ const requirements = requirement.challenge.accepts[0];
369
+ if (!requirements) {
370
+ return rejected("missing_payment_requirements");
371
+ }
372
+ if (requirement.expiresAt !== void 0 && Date.parse(requirement.expiresAt) < clock.now().getTime()) {
373
+ return rejected("requirement_expired");
374
+ }
375
+ if (!isAddress(requirements.asset) || getAddress(requirements.asset) !== getAddress(options.asset)) {
376
+ return rejected("wrong_asset");
377
+ }
378
+ const payload = decodePaymentSubmission(submission.payload);
379
+ if (!payload) {
380
+ return rejected("malformed_payment_payload");
381
+ }
382
+ if (!isExactEvmPayload(payload)) {
383
+ return rejected("unsupported_scheme");
384
+ }
385
+ const { authorization } = payload.payload;
386
+ if (!isAddress(authorization.from) || !isAddress(authorization.to)) {
387
+ return rejected("malformed_payment_payload");
388
+ }
389
+ if (payload.network !== options.network) {
390
+ return rejected("wrong_network");
391
+ }
392
+ if (getAddress(authorization.to) !== getAddress(options.payTo)) {
393
+ return rejected("wrong_recipient");
394
+ }
395
+ let authorizedValue;
396
+ let maxRequired;
397
+ try {
398
+ authorizedValue = BigInt(authorization.value);
399
+ maxRequired = BigInt(requirements.maxAmountRequired);
400
+ } catch {
401
+ return rejected("malformed_payment_payload");
402
+ }
403
+ if (authorizedValue < maxRequired) {
404
+ return rejected("wrong_amount");
405
+ }
406
+ let sdkResult;
407
+ try {
408
+ sdkResult = await verify(
409
+ publicClient,
410
+ payload,
411
+ requirements
412
+ );
413
+ } catch (err) {
414
+ if (isProviderUnavailableError(err)) {
415
+ throw new CommerceError(
416
+ "PAYMENT_PROVIDER_UNAVAILABLE",
417
+ "x402 provider: RPC unreachable during verify()",
418
+ {
419
+ cause: err
420
+ }
421
+ );
422
+ }
423
+ logger.warn({ err: describeError(err) }, "x402 verify(): unexpected SDK error");
424
+ return rejected("unexpected_verify_error");
425
+ }
426
+ if (!sdkResult.isValid) {
427
+ return rejected(sdkResult.invalidReason ?? "invalid_payment");
428
+ }
429
+ const replayKey = computeReplayKey({
430
+ chainId: getNetworkId(payload.network),
431
+ asset: getAddress(requirements.asset),
432
+ from: getAddress(authorization.from),
433
+ nonce: authorization.nonce
434
+ });
435
+ return {
436
+ status: "verified",
437
+ provider: "x402",
438
+ payer: getAddress(authorization.from),
439
+ payee: getAddress(authorization.to),
440
+ amount: formatCanonicalAmount(authorizedValue, options.assetDecimals),
441
+ currency: requirement.currency,
442
+ network: payload.network,
443
+ asset: getAddress(requirements.asset),
444
+ replayKey
445
+ };
446
+ }
447
+ async function settle$1(context) {
448
+ const { requirement, submission, verification } = context;
449
+ if (verification.status !== "verified") {
450
+ throw new CommerceError(
451
+ "PAYMENT_INVALID",
452
+ "x402 provider: settle() called without a successful verify()"
453
+ );
454
+ }
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
+ const requirements = requirement.challenge.accepts[0];
462
+ if (!requirements) {
463
+ throw new CommerceError(
464
+ "PAYMENT_INVALID",
465
+ "x402 provider: settle() found no payment requirements to settle against"
466
+ );
467
+ }
468
+ const payload = decodePaymentSubmission(submission.payload);
469
+ if (!payload || !isExactEvmPayload(payload)) {
470
+ throw new CommerceError(
471
+ "PAYMENT_INVALID",
472
+ "x402 provider: settle() received an undecodable payment payload"
473
+ );
474
+ }
475
+ let broadcastTxHash;
476
+ const settlementClient = {
477
+ ...facilitatorClient,
478
+ waitForTransactionReceipt: (args) => {
479
+ broadcastTxHash = args.hash;
480
+ return facilitatorClient.waitForTransactionReceipt(args);
481
+ }
482
+ };
483
+ let sdkResult;
484
+ try {
485
+ sdkResult = await settle(
486
+ settlementClient,
487
+ payload,
488
+ requirements
489
+ );
490
+ } catch (err) {
491
+ if (isProviderUnavailableError(err)) {
492
+ throw new CommerceError(
493
+ "PAYMENT_PROVIDER_UNAVAILABLE",
494
+ "x402 provider: RPC unreachable during settle()",
495
+ {
496
+ cause: err,
497
+ ...broadcastTxHash !== void 0 ? { details: { transactionHash: broadcastTxHash } } : {}
498
+ }
499
+ );
500
+ }
501
+ const rejectionReason = isOnChainRevertError(err) ? "transaction_reverted" : "unexpected_settle_error";
502
+ logger.warn(
503
+ { err: describeError(err), rejectionReason },
504
+ "x402 settle(): settlement transaction failed"
505
+ );
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
+ };
516
+ }
517
+ if (!sdkResult.success) {
518
+ return {
519
+ status: "rejected",
520
+ provider: "x402",
521
+ amount: verification.amount,
522
+ currency: requirement.currency,
523
+ 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"
528
+ };
529
+ }
530
+ return {
531
+ status: "settled",
532
+ provider: "x402",
533
+ externalReference: sdkResult.transaction,
534
+ ...sdkResult.payer !== void 0 ? { payer: sdkResult.payer } : {},
535
+ payee: requirements.payTo,
536
+ amount: verification.amount,
537
+ currency: requirement.currency,
538
+ network: sdkResult.network,
539
+ ...verification.asset !== void 0 ? { asset: verification.asset } : {},
540
+ ...verification.replayKey !== void 0 ? { replayKey: verification.replayKey } : {},
541
+ settledAt: clock.nowIso()
542
+ };
543
+ }
544
+ async function computeHealth() {
545
+ const startedAt = clock.monotonicMs();
546
+ const checkedAt = clock.nowIso();
547
+ try {
548
+ const chainId = await healthPublicClient.getChainId();
549
+ if (chainId !== LOCAL_CHAIN_ID) {
550
+ return {
551
+ status: "fail",
552
+ detail: `RPC at ${options.rpcUrl} reports chain id ${chainId}, expected ${LOCAL_CHAIN_ID}`,
553
+ checkedAt,
554
+ durationMs: clock.monotonicMs() - startedAt
555
+ };
556
+ }
557
+ const code = await healthPublicClient.getCode({ address: options.asset });
558
+ if (!code || code === "0x") {
559
+ return {
560
+ status: "fail",
561
+ detail: `No contract code found at configured asset address ${options.asset}`,
562
+ checkedAt,
563
+ durationMs: clock.monotonicMs() - startedAt
564
+ };
565
+ }
566
+ if (options.facilitator.mode === "remote") {
567
+ return {
568
+ status: "warn",
569
+ detail: "RPC and asset are reachable; remote facilitator mode is not implemented in v0.1.0-alpha",
570
+ checkedAt,
571
+ durationMs: clock.monotonicMs() - startedAt
572
+ };
573
+ }
574
+ const isAnvil = await probeIsAnvilNode(healthPublicClient);
575
+ if (!isAnvil) {
576
+ return {
577
+ 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.`,
579
+ checkedAt,
580
+ durationMs: clock.monotonicMs() - startedAt
581
+ };
582
+ }
583
+ return {
584
+ status: "pass",
585
+ detail: `RPC ${options.rpcUrl} reachable, chain id ${LOCAL_CHAIN_ID}, asset ${options.asset} has code, confirmed Anvil dev node`,
586
+ checkedAt,
587
+ durationMs: clock.monotonicMs() - startedAt
588
+ };
589
+ } catch (err) {
590
+ return {
591
+ status: "fail",
592
+ detail: `x402 health check failed: ${describeError(err)}`,
593
+ checkedAt,
594
+ durationMs: clock.monotonicMs() - startedAt
595
+ };
596
+ }
597
+ }
598
+ const HEALTH_CACHE_MS = 5e3;
599
+ let cachedHealth;
600
+ let inFlightHealth;
601
+ async function health() {
602
+ const now = clock.monotonicMs();
603
+ if (cachedHealth && now - cachedHealth.at < HEALTH_CACHE_MS) {
604
+ return cachedHealth.value;
605
+ }
606
+ if (inFlightHealth) return inFlightHealth;
607
+ inFlightHealth = computeHealth().then((value) => {
608
+ cachedHealth = { at: clock.monotonicMs(), value };
609
+ return value;
610
+ }).finally(() => {
611
+ inFlightHealth = void 0;
612
+ });
613
+ return inFlightHealth;
614
+ }
615
+ return {
616
+ name: "x402",
617
+ descriptor,
618
+ createRequirement,
619
+ verify: verify$1,
620
+ settle: settle$1,
621
+ health
622
+ };
623
+ }
624
+ function isOnChainRevertError(err) {
625
+ return err instanceof ContractFunctionRevertedError || err instanceof ContractFunctionExecutionError;
626
+ }
627
+ function isProviderUnavailableError(err) {
628
+ if (err instanceof HttpRequestError || err instanceof TimeoutError || err instanceof WaitForTransactionReceiptTimeoutError) {
629
+ return true;
630
+ }
631
+ const message = describeError(err).toLowerCase();
632
+ return message.includes("econnrefused") || message.includes("fetch failed") || message.includes("enotfound") || message.includes("etimedout") || message.includes("timed out") || message.includes("network error");
633
+ }
634
+ function describeError(err) {
635
+ if (err instanceof Error) return `${err.name}: ${err.message}`;
636
+ return String(err);
637
+ }
638
+ async function probeIsAnvilNode(client) {
639
+ const request = client.request;
640
+ try {
641
+ await request({ method: "anvil_nodeInfo", params: [] });
642
+ return true;
643
+ } catch {
644
+ return false;
645
+ }
646
+ }
647
+
648
+ export { createPaymentProof, createX402PaymentProvider, createX402PaymentProvider as x402 };
649
+ //# sourceMappingURL=x402.js.map
650
+ //# sourceMappingURL=x402.js.map