@agentcash/router 1.0.1 → 1.1.1

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/index.cjs CHANGED
@@ -30,6 +30,204 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  ));
31
31
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
32
 
33
+ // src/protocols/evm.ts
34
+ function isEvmNetwork(network) {
35
+ return network.startsWith("eip155:");
36
+ }
37
+ function filterEvmNetworks(networks) {
38
+ return networks.filter(isEvmNetwork);
39
+ }
40
+ function buildEvmExactOptions(accepts, price) {
41
+ return accepts.filter(
42
+ (accept) => accept.scheme === "exact" && isEvmNetwork(accept.network)
43
+ ).map(({ network, payTo }) => ({
44
+ scheme: "exact",
45
+ network,
46
+ price,
47
+ payTo
48
+ }));
49
+ }
50
+ var init_evm = __esm({
51
+ "src/protocols/evm.ts"() {
52
+ "use strict";
53
+ }
54
+ });
55
+
56
+ // src/protocols/solana.ts
57
+ function isSolanaNetwork(network) {
58
+ return network.startsWith("solana:");
59
+ }
60
+ function filterSolanaNetworks(networks) {
61
+ return networks.filter(isSolanaNetwork);
62
+ }
63
+ function buildSolanaExactOptions(accepts, price) {
64
+ return accepts.filter(
65
+ (accept) => accept.scheme === "exact" && isSolanaNetwork(accept.network)
66
+ ).map(({ network, payTo }) => ({
67
+ scheme: "exact",
68
+ network,
69
+ price,
70
+ payTo
71
+ }));
72
+ }
73
+ function hasSolanaAccepts(accepts) {
74
+ return accepts.some((accept) => isSolanaNetwork(accept.network));
75
+ }
76
+ async function enrichRequirementsWithFacilitatorAccepts(facilitator, resource, requirements) {
77
+ if (!facilitator.url) {
78
+ throw new Error(`Facilitator for ${facilitator.network} is missing a URL for /accepts`);
79
+ }
80
+ const authHeaders = await getAcceptsHeadersForFacilitator(facilitator);
81
+ const response = await fetch(`${facilitator.url.replace(/\/+$/, "")}/accepts`, {
82
+ method: "POST",
83
+ headers: {
84
+ ...authHeaders,
85
+ "content-type": "application/json"
86
+ },
87
+ body: JSON.stringify({
88
+ x402Version: 2,
89
+ resource,
90
+ accepts: requirements
91
+ })
92
+ });
93
+ if (!response.ok) {
94
+ throw new Error(`Facilitator /accepts failed with status ${response.status}`);
95
+ }
96
+ const body = await response.json();
97
+ if (!Array.isArray(body.accepts)) {
98
+ throw new Error("Facilitator /accepts response did not include accepts");
99
+ }
100
+ return body.accepts;
101
+ }
102
+ function isSolanaRequirement(requirement) {
103
+ return isSolanaNetwork(requirement.network);
104
+ }
105
+ var init_solana = __esm({
106
+ "src/protocols/solana.ts"() {
107
+ "use strict";
108
+ init_x402_facilitators();
109
+ }
110
+ });
111
+
112
+ // src/x402-facilitators.ts
113
+ function getResolvedX402Facilitator(config, network, defaultEvmFacilitator) {
114
+ const family = getNetworkFamily(network);
115
+ if (!family) return null;
116
+ const target = resolveX402FacilitatorTarget(config, network, defaultEvmFacilitator);
117
+ const resolvedConfig = normalizeFacilitatorTarget(target);
118
+ return {
119
+ family,
120
+ network,
121
+ url: resolvedConfig.url,
122
+ config: resolvedConfig
123
+ };
124
+ }
125
+ function getResolvedX402Facilitators(config, networks, defaultEvmFacilitator) {
126
+ return Object.fromEntries(
127
+ networks.flatMap((network) => {
128
+ const facilitator = getResolvedX402Facilitator(config, network, defaultEvmFacilitator);
129
+ return facilitator ? [[network, facilitator]] : [];
130
+ })
131
+ );
132
+ }
133
+ function getResolvedX402FacilitatorGroups(facilitatorsByNetwork) {
134
+ const groups = [];
135
+ for (const facilitator of Object.values(facilitatorsByNetwork)) {
136
+ const existing = groups.find(
137
+ (group) => group.family === facilitator.family && sameFacilitatorConfig(group.config, facilitator.config)
138
+ );
139
+ if (existing) {
140
+ existing.networks.push(facilitator.network);
141
+ continue;
142
+ }
143
+ groups.push({
144
+ family: facilitator.family,
145
+ config: facilitator.config,
146
+ networks: [facilitator.network]
147
+ });
148
+ }
149
+ return groups;
150
+ }
151
+ function getFacilitatorForRequirement(facilitatorsByNetwork, requirement) {
152
+ return facilitatorsByNetwork?.[requirement.network];
153
+ }
154
+ function sameResolvedX402Facilitator(a, b) {
155
+ return sameFacilitatorConfig(a.config, b.config);
156
+ }
157
+ async function getAcceptsHeadersForFacilitator(facilitator) {
158
+ if (facilitator.config.createAcceptsHeaders) {
159
+ return facilitator.config.createAcceptsHeaders();
160
+ }
161
+ if (facilitator.config.createAuthHeaders) {
162
+ const headers = await facilitator.config.createAuthHeaders();
163
+ return headers.supported;
164
+ }
165
+ return {};
166
+ }
167
+ function resolveX402FacilitatorTarget(config, network, defaultEvmFacilitator) {
168
+ return (isSolanaNetwork(network) ? config.x402?.facilitators?.solana : void 0) ?? (isEvmNetwork(network) ? config.x402?.facilitators?.evm : void 0) ?? (isSolanaNetwork(network) ? DEFAULT_SOLANA_FACILITATOR_URL : defaultEvmFacilitator);
169
+ }
170
+ function normalizeFacilitatorTarget(target) {
171
+ return typeof target === "string" ? { url: target } : target;
172
+ }
173
+ function getNetworkFamily(network) {
174
+ if (isEvmNetwork(network)) return "evm";
175
+ if (isSolanaNetwork(network)) return "solana";
176
+ return null;
177
+ }
178
+ function sameFacilitatorConfig(a, b) {
179
+ return a.url === b.url && a.createAuthHeaders === b.createAuthHeaders && a.createAcceptsHeaders === b.createAcceptsHeaders;
180
+ }
181
+ var DEFAULT_SOLANA_FACILITATOR_URL;
182
+ var init_x402_facilitators = __esm({
183
+ "src/x402-facilitators.ts"() {
184
+ "use strict";
185
+ init_evm();
186
+ init_solana();
187
+ DEFAULT_SOLANA_FACILITATOR_URL = "https://facilitator.corbits.dev";
188
+ }
189
+ });
190
+
191
+ // src/x402-config.ts
192
+ async function resolvePayToValue(payTo, request, fallback) {
193
+ if (!payTo) return fallback;
194
+ if (typeof payTo === "string") return payTo;
195
+ return payTo(request);
196
+ }
197
+ function getConfiguredX402Accepts(config) {
198
+ if (config.x402?.accepts?.length) {
199
+ return [...config.x402.accepts];
200
+ }
201
+ return [
202
+ {
203
+ scheme: "exact",
204
+ network: config.network ?? "eip155:8453",
205
+ payTo: config.payeeAddress
206
+ }
207
+ ];
208
+ }
209
+ function getConfiguredX402Networks(config) {
210
+ return [...new Set(getConfiguredX402Accepts(config).map((accept) => accept.network))];
211
+ }
212
+ async function resolveX402Accepts(request, routeEntry, accepts, fallbackPayTo) {
213
+ return Promise.all(
214
+ accepts.map(async (accept) => ({
215
+ network: accept.network,
216
+ scheme: accept.scheme ?? "exact",
217
+ payTo: await resolvePayToValue(accept.payTo ?? routeEntry.payTo, request, fallbackPayTo),
218
+ ...accept.asset ? { asset: accept.asset } : {},
219
+ ...accept.decimals !== void 0 ? { decimals: accept.decimals } : {},
220
+ ...accept.maxTimeoutSeconds !== void 0 ? { maxTimeoutSeconds: accept.maxTimeoutSeconds } : {},
221
+ ...accept.extra ? { extra: accept.extra } : {}
222
+ }))
223
+ );
224
+ }
225
+ var init_x402_config = __esm({
226
+ "src/x402-config.ts"() {
227
+ "use strict";
228
+ }
229
+ });
230
+
33
231
  // src/server.ts
34
232
  var server_exports = {};
35
233
  __export(server_exports, {
@@ -41,32 +239,59 @@ async function createX402Server(config) {
41
239
  const { bazaarResourceServerExtension } = await import("@x402/extensions/bazaar");
42
240
  const { siwxResourceServerExtension } = await import("@x402/extensions/sign-in-with-x");
43
241
  const { facilitator: defaultFacilitator } = await import("@coinbase/x402");
44
- const raw = config.facilitatorUrl ?? defaultFacilitator;
45
- const facilitatorConfig = typeof raw === "string" ? { url: raw } : raw;
46
- const httpClient = new HTTPFacilitatorClient(facilitatorConfig);
47
- const network = config.network ?? "eip155:8453";
48
- const client = cachedClient(httpClient, network);
49
- const server = new x402ResourceServer(client);
50
- registerExactEvmScheme(server);
242
+ const configuredNetworks = getConfiguredX402Networks(config);
243
+ const facilitatorsByNetwork = getResolvedX402Facilitators(
244
+ config,
245
+ configuredNetworks,
246
+ defaultFacilitator
247
+ );
248
+ const evmNetworks = filterEvmNetworks(configuredNetworks);
249
+ const svmNetworks = filterSolanaNetworks(configuredNetworks);
250
+ const facilitatorClients = createFacilitatorClients(facilitatorsByNetwork, HTTPFacilitatorClient);
251
+ const server = new x402ResourceServer(
252
+ facilitatorClients.length === 1 ? facilitatorClients[0] : facilitatorClients
253
+ );
254
+ if (evmNetworks.length > 0) {
255
+ registerExactEvmScheme(server, { networks: evmNetworks });
256
+ }
257
+ if (svmNetworks.length > 0) {
258
+ const { registerExactSvmScheme } = await import("@x402/svm/exact/server");
259
+ registerExactSvmScheme(server, { networks: svmNetworks });
260
+ }
51
261
  server.registerExtension(bazaarResourceServerExtension);
52
262
  server.registerExtension(siwxResourceServerExtension);
53
263
  const initPromise = server.initialize();
54
- return { server, initPromise };
264
+ return {
265
+ server,
266
+ initPromise,
267
+ facilitatorsByNetwork
268
+ };
55
269
  }
56
- function cachedClient(inner, network) {
270
+ function cachedClient(inner, networks) {
57
271
  return {
58
272
  verify: inner.verify.bind(inner),
59
273
  settle: inner.settle.bind(inner),
60
274
  getSupported: async () => ({
61
- kinds: [{ x402Version: 2, scheme: "exact", network }],
275
+ kinds: networks.map((network) => ({ x402Version: 2, scheme: "exact", network })),
62
276
  extensions: [],
63
277
  signers: {}
64
278
  })
65
279
  };
66
280
  }
281
+ function createFacilitatorClients(facilitatorsByNetwork, HTTPFacilitatorClient) {
282
+ const groups = getResolvedX402FacilitatorGroups(facilitatorsByNetwork);
283
+ return groups.map((group) => {
284
+ const inner = new HTTPFacilitatorClient(group.config);
285
+ return group.family === "evm" ? cachedClient(inner, group.networks) : inner;
286
+ });
287
+ }
67
288
  var init_server = __esm({
68
289
  "src/server.ts"() {
69
290
  "use strict";
291
+ init_evm();
292
+ init_solana();
293
+ init_x402_facilitators();
294
+ init_x402_config();
70
295
  }
71
296
  });
72
297
 
@@ -387,51 +612,41 @@ var import_mppx = require("mppx");
387
612
  var import_viem = require("viem");
388
613
 
389
614
  // src/protocols/x402.ts
390
- async function buildX402Challenge(server, routeEntry, request, price, payeeAddress, network, extensions) {
615
+ init_x402_facilitators();
616
+ init_evm();
617
+ init_solana();
618
+ async function buildX402Challenge(opts) {
619
+ const { server, routeEntry, request, price, accepts, facilitatorsByNetwork, extensions } = opts;
391
620
  const { encodePaymentRequiredHeader } = await import("@x402/core/http");
392
- const options = {
393
- scheme: "exact",
394
- network,
621
+ const resource = buildChallengeResource(request, routeEntry);
622
+ const requirements = await buildChallengeRequirements(
623
+ server,
624
+ request,
395
625
  price,
396
- payTo: payeeAddress
397
- };
398
- const resource = {
399
- url: request.url,
400
- method: routeEntry.method,
401
- description: routeEntry.description,
402
- mimeType: "application/json"
403
- };
404
- const requirements = await server.buildPaymentRequirementsFromOptions([options], {
405
- request
406
- });
626
+ accepts,
627
+ resource,
628
+ facilitatorsByNetwork
629
+ );
407
630
  const paymentRequired = await server.createPaymentRequiredResponse(
408
631
  requirements,
409
632
  resource,
410
- null,
633
+ void 0,
411
634
  extensions
412
635
  );
413
636
  const encoded = encodePaymentRequiredHeader(paymentRequired);
414
637
  return { encoded, requirements };
415
638
  }
416
- async function verifyX402Payment(server, request, routeEntry, price, payeeAddress, network) {
417
- const { decodePaymentSignatureHeader } = await import("@x402/core/http");
418
- const paymentHeader = request.headers.get("PAYMENT-SIGNATURE") ?? request.headers.get("X-PAYMENT");
419
- if (!paymentHeader) return null;
420
- const payload = decodePaymentSignatureHeader(paymentHeader);
421
- const options = {
422
- scheme: "exact",
423
- network,
424
- price,
425
- payTo: payeeAddress
426
- };
427
- const requirements = await server.buildPaymentRequirementsFromOptions([options], {
428
- request
429
- });
430
- const matching = server.findMatchingRequirements(requirements, payload);
431
- const verify = await server.verifyPayment(payload, matching);
432
- if (!verify.isValid) {
433
- return { valid: false, payload: null, requirements: null, payer: null };
639
+ async function verifyX402Payment(opts) {
640
+ const { server, request, price, accepts } = opts;
641
+ const payload = await readPaymentPayload(request);
642
+ if (!payload) return null;
643
+ const requirements = await buildExpectedRequirements(server, request, price, accepts);
644
+ const matching = findVerifiableRequirements(server, requirements, payload);
645
+ if (!matching) {
646
+ return invalidPaymentVerification();
434
647
  }
648
+ const verify = await server.verifyPayment(payload, matching);
649
+ if (!verify.isValid) return invalidPaymentVerification();
435
650
  return {
436
651
  valid: true,
437
652
  payer: verify.payer,
@@ -439,14 +654,150 @@ async function verifyX402Payment(server, request, routeEntry, price, payeeAddres
439
654
  requirements: matching
440
655
  };
441
656
  }
657
+ function findVerifiableRequirements(server, requirements, payload) {
658
+ const strictMatch = server.findMatchingRequirements(requirements, payload);
659
+ if (strictMatch) {
660
+ return payload.x402Version === 2 ? payload.accepted : strictMatch;
661
+ }
662
+ if (payload.x402Version !== 2) {
663
+ return null;
664
+ }
665
+ const stableMatch = requirements.find(
666
+ (requirement) => matchesStableFields(requirement, payload.accepted)
667
+ );
668
+ return stableMatch ? payload.accepted : null;
669
+ }
670
+ function matchesStableFields(requirement, accepted) {
671
+ return requirement.scheme === accepted.scheme && requirement.network === accepted.network && requirement.payTo === accepted.payTo && requirement.asset === accepted.asset && requirement.amount === accepted.amount && requirement.maxTimeoutSeconds === accepted.maxTimeoutSeconds;
672
+ }
673
+ async function buildExpectedRequirements(server, request, price, accepts) {
674
+ const exactRequirements = await buildExactRequirements(server, request, price, accepts);
675
+ const customRequirements = buildCustomRequirements(price, accepts);
676
+ return [...exactRequirements, ...customRequirements];
677
+ }
678
+ async function buildExactRequirements(server, request, price, accepts) {
679
+ const exactOptions = [
680
+ ...buildEvmExactOptions(accepts, price),
681
+ ...buildSolanaExactOptions(accepts, price)
682
+ ];
683
+ if (exactOptions.length === 0) return [];
684
+ return server.buildPaymentRequirementsFromOptions(exactOptions, { request });
685
+ }
686
+ function buildCustomRequirements(price, accepts) {
687
+ return accepts.filter((accept) => accept.scheme !== "exact").map((accept) => buildCustomRequirement(price, accept));
688
+ }
689
+ async function buildChallengeRequirements(server, request, price, accepts, resource, facilitatorsByNetwork) {
690
+ const requirements = await buildExpectedRequirements(server, request, price, accepts);
691
+ if (!needsFacilitatorEnrichment(accepts)) return requirements;
692
+ return enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork);
693
+ }
694
+ function needsFacilitatorEnrichment(accepts) {
695
+ return accepts.some((accept) => accept.scheme !== "exact") || hasSolanaAccepts(accepts);
696
+ }
697
+ async function enrichChallengeRequirements(requirements, resource, facilitatorsByNetwork) {
698
+ const groups = collectEnrichmentGroups(requirements, facilitatorsByNetwork);
699
+ if (groups.length === 0) return requirements;
700
+ const enrichedRequirements = [...requirements];
701
+ await Promise.all(
702
+ groups.map(async (group) => {
703
+ const enriched = await enrichRequirementsWithFacilitatorAccepts(
704
+ group.facilitator,
705
+ resource,
706
+ group.items.map(({ requirement }) => requirement)
707
+ );
708
+ if (enriched.length !== group.items.length) {
709
+ throw new Error(
710
+ `Facilitator /accepts returned ${enriched.length} requirements for ${group.items.length} inputs on ${group.facilitator.url ?? group.facilitator.network}`
711
+ );
712
+ }
713
+ enriched.forEach((requirement, offset) => {
714
+ const index = group.items[offset]?.index;
715
+ if (index === void 0) return;
716
+ enrichedRequirements[index] = requirement;
717
+ });
718
+ })
719
+ );
720
+ return enrichedRequirements;
721
+ }
722
+ function collectEnrichmentGroups(requirements, facilitatorsByNetwork) {
723
+ const groups = [];
724
+ requirements.forEach((requirement, index) => {
725
+ if (!requiresFacilitatorEnrichment(requirement)) return;
726
+ const facilitator = getRequiredFacilitator(requirement, facilitatorsByNetwork);
727
+ const existing = groups.find(
728
+ (group) => sameResolvedX402Facilitator(group.facilitator, facilitator)
729
+ );
730
+ if (existing) {
731
+ existing.items.push({ index, requirement });
732
+ return;
733
+ }
734
+ groups.push({
735
+ facilitator,
736
+ items: [{ index, requirement }]
737
+ });
738
+ });
739
+ return groups;
740
+ }
741
+ function getRequiredFacilitator(requirement, facilitatorsByNetwork) {
742
+ const facilitator = getFacilitatorForRequirement(facilitatorsByNetwork, requirement);
743
+ if (!facilitator) {
744
+ throw new Error(
745
+ `Missing x402 facilitator for ${requirement.scheme} requirement on ${requirement.network}`
746
+ );
747
+ }
748
+ return facilitator;
749
+ }
750
+ function requiresFacilitatorEnrichment(requirement) {
751
+ return requirement.scheme !== "exact" || isSolanaRequirement(requirement);
752
+ }
753
+ function buildCustomRequirement(price, accept) {
754
+ if (!accept.asset) {
755
+ throw new Error(
756
+ `Custom x402 accept '${accept.scheme}' on '${accept.network}' is missing asset`
757
+ );
758
+ }
759
+ return {
760
+ scheme: accept.scheme,
761
+ network: accept.network,
762
+ amount: decimalToAtomicUnits(price, accept.decimals ?? 6),
763
+ asset: accept.asset,
764
+ payTo: accept.payTo,
765
+ maxTimeoutSeconds: accept.maxTimeoutSeconds ?? 300,
766
+ extra: accept.extra ?? {}
767
+ };
768
+ }
769
+ function buildChallengeResource(request, routeEntry) {
770
+ return {
771
+ url: request.url,
772
+ method: routeEntry.method,
773
+ description: routeEntry.description,
774
+ mimeType: "application/json"
775
+ };
776
+ }
777
+ async function readPaymentPayload(request) {
778
+ const paymentHeader = request.headers.get("PAYMENT-SIGNATURE") ?? request.headers.get("X-PAYMENT");
779
+ if (!paymentHeader) return null;
780
+ const { decodePaymentSignatureHeader } = await import("@x402/core/http");
781
+ return decodePaymentSignatureHeader(paymentHeader);
782
+ }
783
+ function invalidPaymentVerification() {
784
+ return { valid: false, payload: null, requirements: null, payer: null };
785
+ }
786
+ function decimalToAtomicUnits(amount, decimals) {
787
+ const match = /^(?<whole>\d+)(?:\.(?<fraction>\d+))?$/.exec(amount);
788
+ if (!match?.groups) {
789
+ throw new Error(`Invalid decimal amount '${amount}'`);
790
+ }
791
+ const whole = match.groups.whole;
792
+ const fraction = match.groups.fraction ?? "";
793
+ if (fraction.length > decimals) {
794
+ throw new Error(`Amount '${amount}' exceeds ${decimals} decimal places`);
795
+ }
796
+ const normalized = `${whole}${fraction.padEnd(decimals, "0")}`.replace(/^0+(?=\d)/, "");
797
+ return normalized === "" ? "0" : normalized;
798
+ }
442
799
  async function settleX402Payment(server, payload, requirements) {
443
800
  const { encodePaymentResponseHeader } = await import("@x402/core/http");
444
- const payloadKeys = typeof payload === "object" && payload !== null ? Object.keys(payload).sort().join(",") : "n/a";
445
- const reqKeys = typeof requirements === "object" && requirements !== null ? Object.keys(requirements).sort().join(",") : "n/a";
446
- console.info("x402 settle input", {
447
- payloadKeys,
448
- requirementsKeys: reqKeys
449
- });
450
801
  const result = await server.settlePayment(payload, requirements);
451
802
  const encoded = encodePaymentResponseHeader(result);
452
803
  return { encoded, result };
@@ -521,10 +872,10 @@ function extractBearerToken(header) {
521
872
  }
522
873
 
523
874
  // src/orchestrate.ts
524
- async function resolvePayTo(routeEntry, request, fallback) {
525
- if (!routeEntry.payTo) return fallback;
526
- if (typeof routeEntry.payTo === "string") return routeEntry.payTo;
527
- return routeEntry.payTo(request);
875
+ init_x402_config();
876
+ function getRequirementNetwork(requirements, fallback) {
877
+ const network = requirements?.network;
878
+ return typeof network === "string" ? network : fallback;
528
879
  }
529
880
  function createRequestHandler(routeEntry, handler, deps) {
530
881
  async function invoke(request, meta, pluginCtx, wallet, account, parsedBody) {
@@ -805,24 +1156,28 @@ function createRequestHandler(routeEntry, handler, deps) {
805
1156
  console.error(`[router] ${routeEntry.key}: ${reason}`);
806
1157
  return fail(500, reason, meta, pluginCtx, body.data);
807
1158
  }
808
- const payTo = await resolvePayTo(routeEntry, request, deps.payeeAddress);
809
- const verify = await verifyX402Payment(
810
- deps.x402Server,
1159
+ const accepts = await resolveX402Accepts(
811
1160
  request,
812
1161
  routeEntry,
813
- price,
814
- payTo,
815
- deps.network
1162
+ deps.x402Accepts,
1163
+ deps.payeeAddress
816
1164
  );
1165
+ const verify = await verifyX402Payment({
1166
+ server: deps.x402Server,
1167
+ request,
1168
+ price,
1169
+ accepts
1170
+ });
817
1171
  if (!verify?.valid) return await build402(request, routeEntry, deps, meta, pluginCtx);
818
1172
  const { payload: verifyPayload, requirements: verifyRequirements } = verify;
1173
+ const matchedNetwork = getRequirementNetwork(verifyRequirements, deps.network);
819
1174
  const wallet = verify.payer.toLowerCase();
820
1175
  pluginCtx.setVerifiedWallet(wallet);
821
1176
  firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
822
1177
  protocol: "x402",
823
1178
  payer: wallet,
824
1179
  amount: price,
825
- network: deps.network
1180
+ network: matchedNetwork
826
1181
  });
827
1182
  const { response, rawResult } = await invoke(
828
1183
  request,
@@ -834,20 +1189,17 @@ function createRequestHandler(routeEntry, handler, deps) {
834
1189
  );
835
1190
  if (response.status < 400) {
836
1191
  try {
837
- const payloadFingerprint = typeof verifyPayload === "object" && verifyPayload !== null ? {
838
- keys: Object.keys(verifyPayload).sort().join(","),
839
- payloadType: typeof verifyPayload
840
- } : { payloadType: typeof verifyPayload };
841
- console.info("Settlement attempt", {
842
- route: routeEntry.key,
843
- network: deps.network,
844
- ...payloadFingerprint
845
- });
846
1192
  const settle = await settleX402Payment(
847
1193
  deps.x402Server,
848
1194
  verifyPayload,
849
1195
  verifyRequirements
850
1196
  );
1197
+ if (!settle.result?.success) {
1198
+ const reason = settle.result?.errorReason || "x402 settlement returned success=false";
1199
+ const error = new Error(reason);
1200
+ error.errorReason = reason;
1201
+ throw error;
1202
+ }
851
1203
  if (routeEntry.siwxEnabled) {
852
1204
  try {
853
1205
  await deps.entitlementStore.grant(routeEntry.key, wallet);
@@ -864,14 +1216,15 @@ function createRequestHandler(routeEntry, handler, deps) {
864
1216
  protocol: "x402",
865
1217
  payer: verify.payer,
866
1218
  transaction: String(settle.result?.transaction ?? ""),
867
- network: deps.network
1219
+ network: matchedNetwork
868
1220
  });
869
1221
  } catch (err) {
870
1222
  const errObj = err;
871
1223
  console.error("Settlement failed", {
872
1224
  message: err instanceof Error ? err.message : String(err),
873
1225
  route: routeEntry.key,
874
- network: deps.network,
1226
+ network: matchedNetwork,
1227
+ errorReason: errObj.errorReason,
875
1228
  facilitatorStatus: errObj.response?.status,
876
1229
  facilitatorBody: errObj.response?.data ?? errObj.response?.body
877
1230
  });
@@ -1086,16 +1439,21 @@ async function build402(request, routeEntry, deps, meta, pluginCtx, bodyData) {
1086
1439
  }
1087
1440
  if (routeEntry.protocols.includes("x402") && deps.x402Server) {
1088
1441
  try {
1089
- const payTo = await resolvePayTo(routeEntry, request, deps.payeeAddress);
1090
- const { encoded } = await buildX402Challenge(
1091
- deps.x402Server,
1442
+ const accepts = await resolveX402Accepts(
1443
+ request,
1444
+ routeEntry,
1445
+ deps.x402Accepts,
1446
+ deps.payeeAddress
1447
+ );
1448
+ const { encoded } = await buildX402Challenge({
1449
+ server: deps.x402Server,
1092
1450
  routeEntry,
1093
1451
  request,
1094
- challengePrice,
1095
- payTo,
1096
- deps.network,
1452
+ price: challengePrice,
1453
+ accepts,
1454
+ facilitatorsByNetwork: deps.x402FacilitatorsByNetwork,
1097
1455
  extensions
1098
- );
1456
+ });
1099
1457
  response.headers.set("PAYMENT-REQUIRED", encoded);
1100
1458
  } catch (err) {
1101
1459
  const message = `x402 challenge build failed: ${err instanceof Error ? err.message : String(err)}`;
@@ -1719,11 +2077,15 @@ function createLlmsTxtHandler(discovery) {
1719
2077
  }
1720
2078
 
1721
2079
  // src/index.ts
2080
+ init_x402_config();
2081
+ init_evm();
2082
+ init_solana();
1722
2083
  function createRouter(config) {
1723
2084
  const registry = new RouteRegistry();
1724
2085
  const nonceStore = config.siwx?.nonceStore ?? new MemoryNonceStore();
1725
2086
  const entitlementStore = config.siwx?.entitlementStore ?? new MemoryEntitlementStore();
1726
2087
  const network = config.network ?? "eip155:8453";
2088
+ const x402Accepts = getConfiguredX402Accepts(config);
1727
2089
  if (!config.baseUrl) {
1728
2090
  throw new Error(
1729
2091
  '[router] baseUrl is required in RouterConfig. Set it to your production domain (e.g., "https://api.example.com"). The realm is used for payment matching and must be correct.'
@@ -1737,8 +2099,23 @@ function createRouter(config) {
1737
2099
  const resolvedBaseUrl = config.baseUrl.replace(/\/+$/, "");
1738
2100
  let x402ConfigError;
1739
2101
  let mppConfigError;
1740
- if ((!config.protocols || config.protocols.includes("x402")) && !config.payeeAddress) {
1741
- x402ConfigError = "x402 requires payeeAddress in router config.";
2102
+ if (!config.protocols || config.protocols.includes("x402")) {
2103
+ if (x402Accepts.length === 0) {
2104
+ x402ConfigError = "x402 requires at least one accept configuration.";
2105
+ } else if (x402Accepts.some((accept) => !accept.network)) {
2106
+ x402ConfigError = "x402 accepts require a network.";
2107
+ } else if (x402Accepts.some((accept) => !isSupportedX402Network(accept.network))) {
2108
+ const unsupported = x402Accepts.find((accept) => !isSupportedX402Network(accept.network));
2109
+ x402ConfigError = `unsupported x402 network '${unsupported?.network}'. Use eip155:* or solana:*.`;
2110
+ } else if (x402Accepts.some((accept) => (accept.scheme ?? "exact") !== "exact" && !accept.asset)) {
2111
+ x402ConfigError = "non-exact x402 accepts require an asset.";
2112
+ } else if (x402Accepts.some(
2113
+ (accept) => accept.decimals !== void 0 && (!Number.isInteger(accept.decimals) || accept.decimals < 0)
2114
+ )) {
2115
+ x402ConfigError = "x402 accept decimals must be a non-negative integer.";
2116
+ } else if (x402Accepts.some((accept) => !accept.payTo) && !config.payeeAddress) {
2117
+ x402ConfigError = "x402 requires payeeAddress in router config or payTo on every x402 accept.";
2118
+ }
1742
2119
  }
1743
2120
  if (config.protocols?.includes("mpp")) {
1744
2121
  if (!config.mpp) {
@@ -1772,8 +2149,10 @@ function createRouter(config) {
1772
2149
  plugin: config.plugin,
1773
2150
  nonceStore,
1774
2151
  entitlementStore,
1775
- payeeAddress: config.payeeAddress,
2152
+ payeeAddress: config.payeeAddress ?? "",
1776
2153
  network,
2154
+ x402FacilitatorsByNetwork: void 0,
2155
+ x402Accepts,
1777
2156
  mppx: null
1778
2157
  };
1779
2158
  deps.initPromise = (async () => {
@@ -1784,6 +2163,7 @@ function createRouter(config) {
1784
2163
  const { createX402Server: createX402Server2 } = await Promise.resolve().then(() => (init_server(), server_exports));
1785
2164
  const result = await createX402Server2(config);
1786
2165
  deps.x402Server = result.server;
2166
+ deps.x402FacilitatorsByNetwork = result.facilitatorsByNetwork;
1787
2167
  await result.initPromise;
1788
2168
  } catch (err) {
1789
2169
  deps.x402Server = null;
@@ -1874,6 +2254,9 @@ function createRouter(config) {
1874
2254
  registry
1875
2255
  };
1876
2256
  }
2257
+ function isSupportedX402Network(network) {
2258
+ return isEvmNetwork(network) || isSolanaNetwork(network);
2259
+ }
1877
2260
  function normalizePath(path) {
1878
2261
  let normalized = path.trim();
1879
2262
  normalized = normalized.replace(/^\/+/, "");