@agentcash/router 1.4.1 → 1.5.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
@@ -188,6 +188,18 @@ var init_x402_facilitators = __esm({
188
188
  }
189
189
  });
190
190
 
191
+ // src/constants.ts
192
+ var BASE_NETWORK, SOLANA_MAINNET_NETWORK, TEMPO_USDC_CURRENCY, ZERO_EVM_ADDRESS;
193
+ var init_constants = __esm({
194
+ "src/constants.ts"() {
195
+ "use strict";
196
+ BASE_NETWORK = "eip155:8453";
197
+ SOLANA_MAINNET_NETWORK = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
198
+ TEMPO_USDC_CURRENCY = "0x20c000000000000000000000b9537d11c60e8b50";
199
+ ZERO_EVM_ADDRESS = "0x0000000000000000000000000000000000000000";
200
+ }
201
+ });
202
+
191
203
  // src/x402-config.ts
192
204
  async function resolvePayToValue(payTo, request, fallback, body) {
193
205
  if (!payTo) return fallback;
@@ -201,7 +213,7 @@ function getConfiguredX402Accepts(config) {
201
213
  return [
202
214
  {
203
215
  scheme: "exact",
204
- network: config.network ?? "eip155:8453",
216
+ network: config.network ?? BASE_NETWORK,
205
217
  payTo: config.payeeAddress
206
218
  }
207
219
  ];
@@ -230,6 +242,7 @@ async function resolveX402Accepts(request, routeEntry, accepts, fallbackPayTo, b
230
242
  var init_x402_config = __esm({
231
243
  "src/x402-config.ts"() {
232
244
  "use strict";
245
+ init_constants();
233
246
  }
234
247
  });
235
248
 
@@ -376,17 +389,28 @@ var init_upstash_rest = __esm({
376
389
  // src/index.ts
377
390
  var index_exports = {};
378
391
  __export(index_exports, {
392
+ BASE_NETWORK: () => BASE_NETWORK,
379
393
  HttpError: () => HttpError,
380
394
  MemoryEntitlementStore: () => MemoryEntitlementStore,
381
395
  MemoryNonceStore: () => MemoryNonceStore,
382
396
  RouteBuilder: () => RouteBuilder,
383
397
  RouteRegistry: () => RouteRegistry,
398
+ RouterConfigError: () => RouterConfigError,
384
399
  SIWX_CHALLENGE_EXPIRY_MS: () => SIWX_CHALLENGE_EXPIRY_MS,
385
400
  SIWX_ERROR_MESSAGES: () => SIWX_ERROR_MESSAGES,
401
+ SOLANA_MAINNET_NETWORK: () => SOLANA_MAINNET_NETWORK,
402
+ TEMPO_USDC_CURRENCY: () => TEMPO_USDC_CURRENCY,
403
+ ZERO_EVM_ADDRESS: () => ZERO_EVM_ADDRESS,
386
404
  consolePlugin: () => consolePlugin,
387
405
  createRedisEntitlementStore: () => createRedisEntitlementStore,
388
406
  createRedisNonceStore: () => createRedisNonceStore,
389
- createRouter: () => createRouter
407
+ createRouter: () => createRouter,
408
+ formatRouterConfigIssues: () => formatRouterConfigIssues,
409
+ getRouterConfigIssues: () => getRouterConfigIssues,
410
+ mppFromEnv: () => mppFromEnv,
411
+ paidOptionsForProtocols: () => paidOptionsForProtocols,
412
+ validateRouterConfig: () => validateRouterConfig,
413
+ x402AcceptsFromEnv: () => x402AcceptsFromEnv
390
414
  });
391
415
  module.exports = __toCommonJS(index_exports);
392
416
 
@@ -446,7 +470,7 @@ var import_server2 = require("next/server");
446
470
 
447
471
  // src/auth/normalize-wallet.ts
448
472
  function normalizeWalletAddress(address) {
449
- return address.startsWith("0x") ? address.toLowerCase() : address;
473
+ return /^0x/i.test(address) ? address.toLowerCase() : address;
450
474
  }
451
475
 
452
476
  // src/plugin.ts
@@ -611,12 +635,13 @@ var HttpError = class extends Error {
611
635
  };
612
636
 
613
637
  // src/handler.ts
614
- async function safeCallHandler(handler, ctx) {
638
+ async function safeCallHandler(handler, ctx, options = {}) {
615
639
  try {
616
640
  const result = await handler(ctx);
617
641
  if (result instanceof Response) return result;
618
642
  return import_server.NextResponse.json(result);
619
643
  } catch (error) {
644
+ options.onError?.(error);
620
645
  const status = error instanceof HttpError ? error.status : typeof error.status === "number" ? error.status : 500;
621
646
  const message = error instanceof Error ? error.message : "Internal error";
622
647
  return import_server.NextResponse.json({ success: false, error: message }, { status });
@@ -739,6 +764,9 @@ async function verifyX402Payment(opts) {
739
764
  throw err;
740
765
  }
741
766
  if (!verify.isValid) return invalidPaymentVerification();
767
+ if (typeof verify.payer !== "string" || verify.payer.length === 0) {
768
+ throw new Error("x402 verification succeeded without a payer address");
769
+ }
742
770
  return {
743
771
  valid: true,
744
772
  payer: verify.payer,
@@ -1029,6 +1057,21 @@ function getRequirementNetwork(requirements, fallback) {
1029
1057
  const network = requirements?.network;
1030
1058
  return typeof network === "string" ? network : fallback;
1031
1059
  }
1060
+ function getRequirementRecipient(requirements) {
1061
+ const payTo = requirements?.payTo;
1062
+ return typeof payTo === "string" ? payTo : void 0;
1063
+ }
1064
+ function errorStatus(error, fallback) {
1065
+ const status = error?.status;
1066
+ return typeof status === "number" ? status : fallback;
1067
+ }
1068
+ function errorMessage(error, fallback) {
1069
+ return error instanceof Error ? error.message : fallback;
1070
+ }
1071
+ function handlerFailureError(response) {
1072
+ const message = response.statusText || `Handler returned HTTP ${response.status}`;
1073
+ return Object.assign(new Error(message), { status: response.status });
1074
+ }
1032
1075
  function siwxSignatureType(network) {
1033
1076
  return network.startsWith("solana:") ? "ed25519" : "eip191";
1034
1077
  }
@@ -1047,7 +1090,7 @@ function getSupportedChains(x402Accepts, fallbackNetwork) {
1047
1090
  return chains;
1048
1091
  }
1049
1092
  function createRequestHandler(routeEntry, handler, deps) {
1050
- async function invoke(request, meta, pluginCtx, wallet, account, parsedBody) {
1093
+ async function invoke(request, meta, pluginCtx, wallet, account, parsedBody, payment) {
1051
1094
  const ctx = {
1052
1095
  body: parsedBody,
1053
1096
  query: parseQuery(request, routeEntry),
@@ -1055,6 +1098,7 @@ function createRequestHandler(routeEntry, handler, deps) {
1055
1098
  requestId: meta.requestId,
1056
1099
  route: routeEntry.key,
1057
1100
  wallet,
1101
+ payment,
1058
1102
  account,
1059
1103
  alert(level, message, alertMeta) {
1060
1104
  firePluginHook(deps.plugin, "onAlert", pluginCtx, {
@@ -1067,11 +1111,20 @@ function createRequestHandler(routeEntry, handler, deps) {
1067
1111
  setVerifiedWallet: (addr) => pluginCtx.setVerifiedWallet(addr)
1068
1112
  };
1069
1113
  let rawResult;
1070
- const response = await safeCallHandler(async (c) => {
1071
- rawResult = await handler(c);
1072
- return rawResult;
1073
- }, ctx);
1074
- return { response, rawResult };
1114
+ let handlerError;
1115
+ const response = await safeCallHandler(
1116
+ async (c) => {
1117
+ rawResult = await handler(c);
1118
+ return rawResult;
1119
+ },
1120
+ ctx,
1121
+ {
1122
+ onError(error) {
1123
+ handlerError = error;
1124
+ }
1125
+ }
1126
+ );
1127
+ return { response, rawResult, handlerError };
1075
1128
  }
1076
1129
  function finalize(response, rawResult, meta, pluginCtx, requestBody) {
1077
1130
  fireProviderQuota(routeEntry, response, rawResult, deps, pluginCtx);
@@ -1082,6 +1135,87 @@ function createRequestHandler(routeEntry, handler, deps) {
1082
1135
  firePluginResponse(deps, pluginCtx, meta, response, requestBody);
1083
1136
  return response;
1084
1137
  }
1138
+ function settlementContext(scope) {
1139
+ return {
1140
+ route: routeEntry.key,
1141
+ request: scope.request,
1142
+ body: scope.parsedBody,
1143
+ wallet: scope.wallet,
1144
+ account: scope.account,
1145
+ payment: scope.payment,
1146
+ response: scope.response,
1147
+ result: scope.rawResult
1148
+ };
1149
+ }
1150
+ async function runBeforeSettle(scope) {
1151
+ const hook = routeEntry.settlement?.beforeSettle;
1152
+ if (!hook) return null;
1153
+ try {
1154
+ await hook(settlementContext(scope));
1155
+ return null;
1156
+ } catch (error) {
1157
+ return fail(
1158
+ errorStatus(error, 500),
1159
+ errorMessage(error, "Pre-settlement validation failed"),
1160
+ scope.meta,
1161
+ scope.pluginCtx,
1162
+ scope.parsedBody
1163
+ );
1164
+ }
1165
+ }
1166
+ async function runSettlementError(scope, error, phase) {
1167
+ const hook = routeEntry.settlement?.onSettlementError;
1168
+ if (!hook) return;
1169
+ try {
1170
+ await hook({
1171
+ ...settlementContext(scope),
1172
+ error,
1173
+ phase
1174
+ });
1175
+ } catch (hookError) {
1176
+ const message = errorMessage(hookError, "Settlement error hook failed");
1177
+ console.error(`[router] ${routeEntry.key}: onSettlementError failed: ${message}`);
1178
+ firePluginHook(deps.plugin, "onAlert", scope.pluginCtx, {
1179
+ level: "error",
1180
+ message: `Settlement error hook failed: ${message}`,
1181
+ route: routeEntry.key
1182
+ });
1183
+ }
1184
+ }
1185
+ async function runAfterSettle(scope) {
1186
+ const hook = routeEntry.settlement?.afterSettle;
1187
+ if (!hook) return;
1188
+ try {
1189
+ await hook(settlementContext(scope));
1190
+ } catch (error) {
1191
+ const message = errorMessage(error, "Post-settlement hook failed");
1192
+ console.error(`[router] ${routeEntry.key}: afterSettle failed: ${message}`);
1193
+ firePluginHook(deps.plugin, "onAlert", scope.pluginCtx, {
1194
+ level: "error",
1195
+ message: `Post-settlement hook failed: ${message}`,
1196
+ route: routeEntry.key
1197
+ });
1198
+ await runSettlementError(scope, error, "afterSettle");
1199
+ }
1200
+ }
1201
+ async function runSettledHandlerError(scope, error = scope.handlerError ?? handlerFailureError(scope.response)) {
1202
+ const hook = routeEntry.settlement?.onSettledHandlerError;
1203
+ if (!hook) return;
1204
+ try {
1205
+ await hook({
1206
+ ...settlementContext(scope),
1207
+ error
1208
+ });
1209
+ } catch (hookError) {
1210
+ const message = errorMessage(hookError, "Settled handler error hook failed");
1211
+ console.error(`[router] ${routeEntry.key}: onSettledHandlerError failed: ${message}`);
1212
+ firePluginHook(deps.plugin, "onAlert", scope.pluginCtx, {
1213
+ level: "error",
1214
+ message: `Settled handler error hook failed: ${message}`,
1215
+ route: routeEntry.key
1216
+ });
1217
+ }
1218
+ }
1085
1219
  return async (request) => {
1086
1220
  await deps.initPromise;
1087
1221
  const meta = buildMeta(request, routeEntry);
@@ -1107,7 +1241,8 @@ function createRequestHandler(routeEntry, handler, deps) {
1107
1241
  pluginCtx,
1108
1242
  wallet,
1109
1243
  account2,
1110
- body2.data
1244
+ body2.data,
1245
+ null
1111
1246
  );
1112
1247
  finalize(response, rawResult, meta, pluginCtx, body2.data);
1113
1248
  return response;
@@ -1336,18 +1471,9 @@ function createRequestHandler(routeEntry, handler, deps) {
1336
1471
  return fail(status, message, meta, pluginCtx, body.data);
1337
1472
  }
1338
1473
  }
1339
- let price;
1340
- try {
1341
- price = await resolvePrice(routeEntry.pricing, body.data);
1342
- } catch (err) {
1343
- return fail(
1344
- err.status ?? 500,
1345
- err instanceof Error ? err.message : "Price resolution failed",
1346
- meta,
1347
- pluginCtx,
1348
- body.data
1349
- );
1350
- }
1474
+ const priceResult = await resolveDynamicPrice(body.data, routeEntry, deps, pluginCtx, meta);
1475
+ if ("error" in priceResult) return priceResult.error;
1476
+ const price = priceResult.price;
1351
1477
  if (!routeEntry.protocols.includes(protocol)) {
1352
1478
  const accepted = routeEntry.protocols.join(", ") || "none";
1353
1479
  console.warn(
@@ -1384,7 +1510,16 @@ function createRequestHandler(routeEntry, handler, deps) {
1384
1510
  return await build402(request, routeEntry, deps, meta, pluginCtx, body.data);
1385
1511
  const { payload: verifyPayload, requirements: verifyRequirements } = verify;
1386
1512
  const matchedNetwork = getRequirementNetwork(verifyRequirements, deps.network);
1513
+ const matchedRecipient = getRequirementRecipient(verifyRequirements);
1387
1514
  const wallet = normalizeWalletAddress(verify.payer);
1515
+ const payment = {
1516
+ protocol: "x402",
1517
+ status: "verified",
1518
+ payer: wallet,
1519
+ amount: price,
1520
+ network: matchedNetwork,
1521
+ ...matchedRecipient ? { recipient: matchedRecipient } : {}
1522
+ };
1388
1523
  pluginCtx.setVerifiedWallet(wallet);
1389
1524
  firePluginHook(deps.plugin, "onPaymentVerified", pluginCtx, {
1390
1525
  protocol: "x402",
@@ -1392,15 +1527,30 @@ function createRequestHandler(routeEntry, handler, deps) {
1392
1527
  amount: price,
1393
1528
  network: matchedNetwork
1394
1529
  });
1395
- const { response, rawResult } = await invoke(
1530
+ const { response, rawResult, handlerError } = await invoke(
1396
1531
  request,
1397
1532
  meta,
1398
1533
  pluginCtx,
1399
1534
  wallet,
1400
1535
  account,
1401
- body.data
1536
+ body.data,
1537
+ payment
1402
1538
  );
1539
+ const settleScope = {
1540
+ request,
1541
+ meta,
1542
+ pluginCtx,
1543
+ wallet,
1544
+ account,
1545
+ parsedBody: body.data,
1546
+ payment,
1547
+ response,
1548
+ rawResult,
1549
+ handlerError
1550
+ };
1403
1551
  if (response.status < 400) {
1552
+ const validationFailure = await runBeforeSettle(settleScope);
1553
+ if (validationFailure) return validationFailure;
1404
1554
  try {
1405
1555
  const settle = await settleX402Payment(
1406
1556
  deps.x402Server,
@@ -1426,13 +1576,21 @@ function createRequestHandler(routeEntry, handler, deps) {
1426
1576
  }
1427
1577
  response.headers.set("PAYMENT-RESPONSE", settle.encoded);
1428
1578
  response.headers.set("Cache-Control", "private");
1579
+ const transaction = String(settle.result?.transaction ?? "");
1580
+ const settledPayment = {
1581
+ ...payment,
1582
+ status: "settled",
1583
+ ...transaction ? { transaction } : {}
1584
+ };
1429
1585
  firePluginHook(deps.plugin, "onPaymentSettled", pluginCtx, {
1430
1586
  protocol: "x402",
1431
- payer: verify.payer,
1432
- transaction: String(settle.result?.transaction ?? ""),
1587
+ payer: wallet,
1588
+ transaction,
1433
1589
  network: matchedNetwork
1434
1590
  });
1591
+ await runAfterSettle({ ...settleScope, payment: settledPayment });
1435
1592
  } catch (err) {
1593
+ await runSettlementError(settleScope, err, "settle");
1436
1594
  const errObj = err;
1437
1595
  console.error("Settlement failed", {
1438
1596
  message: err instanceof Error ? err.message : String(err),
@@ -1491,19 +1649,44 @@ function createRequestHandler(routeEntry, handler, deps) {
1491
1649
  amount: price,
1492
1650
  network: "tempo:4217"
1493
1651
  });
1494
- const { response: response2, rawResult: rawResult2 } = await invoke(
1652
+ const mppRecipient2 = deps.mppRecipient ?? deps.payeeAddress;
1653
+ const payment2 = {
1654
+ protocol: "mpp",
1655
+ status: "verified",
1656
+ payer: wallet,
1657
+ amount: price,
1658
+ network: "tempo:4217",
1659
+ ...mppRecipient2 ? { recipient: mppRecipient2 } : {}
1660
+ };
1661
+ const { response: response2, rawResult: rawResult2, handlerError: handlerError2 } = await invoke(
1495
1662
  request,
1496
1663
  meta,
1497
1664
  pluginCtx,
1498
1665
  wallet,
1499
1666
  account,
1500
- body.data
1667
+ body.data,
1668
+ payment2
1501
1669
  );
1670
+ const settleScope2 = {
1671
+ request,
1672
+ meta,
1673
+ pluginCtx,
1674
+ wallet,
1675
+ account,
1676
+ parsedBody: body.data,
1677
+ payment: payment2,
1678
+ response: response2,
1679
+ rawResult: rawResult2,
1680
+ handlerError: handlerError2
1681
+ };
1502
1682
  if (response2.status < 400) {
1683
+ const validationFailure = await runBeforeSettle(settleScope2);
1684
+ if (validationFailure) return validationFailure;
1503
1685
  let mppResult2;
1504
1686
  try {
1505
1687
  mppResult2 = await deps.mppx.charge({ amount: price })(request);
1506
1688
  } catch (err) {
1689
+ await runSettlementError(settleScope2, err, "settle");
1507
1690
  const message = err instanceof Error ? err.message : String(err);
1508
1691
  console.error(
1509
1692
  `[router] ${routeEntry.key}: MPP broadcast failed after handler: ${message}`
@@ -1532,6 +1715,13 @@ function createRequestHandler(routeEntry, handler, deps) {
1532
1715
  } catch {
1533
1716
  }
1534
1717
  const detail = rejectReason || "transaction reverted on-chain after handler execution";
1718
+ const settlementError = Object.assign(new Error(detail), {
1719
+ status: 402,
1720
+ detail,
1721
+ mppResult: mppResult2,
1722
+ challenge: mppResult2.challenge
1723
+ });
1724
+ await runSettlementError(settleScope2, settlementError, "settle");
1535
1725
  console.error(
1536
1726
  `[router] ${routeEntry.key}: MPP payment failed after handler \u2014 ${detail}`
1537
1727
  );
@@ -1569,6 +1759,17 @@ function createRequestHandler(routeEntry, handler, deps) {
1569
1759
  transaction: txHash2,
1570
1760
  network: "tempo:4217"
1571
1761
  });
1762
+ const settledPayment = {
1763
+ ...payment2,
1764
+ status: "settled",
1765
+ ...txHash2 ? { transaction: txHash2 } : {},
1766
+ ...receiptHeader2 ? { receipt: receiptHeader2 } : {}
1767
+ };
1768
+ await runAfterSettle({
1769
+ ...settleScope2,
1770
+ payment: settledPayment,
1771
+ response: receiptResponse
1772
+ });
1572
1773
  finalize(receiptResponse, rawResult2, meta, pluginCtx, body.data);
1573
1774
  return receiptResponse;
1574
1775
  }
@@ -1624,14 +1825,38 @@ function createRequestHandler(routeEntry, handler, deps) {
1624
1825
  amount: price,
1625
1826
  network: "tempo:4217"
1626
1827
  });
1627
- const { response, rawResult } = await invoke(
1828
+ const mppRecipient = deps.mppRecipient ?? deps.payeeAddress;
1829
+ const payment = {
1830
+ protocol: "mpp",
1831
+ status: "settled",
1832
+ payer: wallet,
1833
+ amount: price,
1834
+ network: "tempo:4217",
1835
+ ...mppRecipient ? { recipient: mppRecipient } : {},
1836
+ ...txHash ? { transaction: txHash } : {},
1837
+ ...receiptHeader ? { receipt: receiptHeader } : {}
1838
+ };
1839
+ const { response, rawResult, handlerError } = await invoke(
1628
1840
  request,
1629
1841
  meta,
1630
1842
  pluginCtx,
1631
1843
  wallet,
1632
1844
  account,
1633
- body.data
1845
+ body.data,
1846
+ payment
1634
1847
  );
1848
+ const settleScope = {
1849
+ request,
1850
+ meta,
1851
+ pluginCtx,
1852
+ wallet,
1853
+ account,
1854
+ parsedBody: body.data,
1855
+ payment,
1856
+ response,
1857
+ rawResult,
1858
+ handlerError
1859
+ };
1635
1860
  if (response.status < 400) {
1636
1861
  if (routeEntry.siwxEnabled) {
1637
1862
  try {
@@ -1652,9 +1877,11 @@ function createRequestHandler(routeEntry, handler, deps) {
1652
1877
  transaction: txHash,
1653
1878
  network: "tempo:4217"
1654
1879
  });
1880
+ await runAfterSettle({ ...settleScope, response: receiptResponse });
1655
1881
  finalize(receiptResponse, rawResult, meta, pluginCtx, body.data);
1656
1882
  return receiptResponse;
1657
1883
  }
1884
+ await runSettledHandlerError(settleScope);
1658
1885
  finalize(response, rawResult, meta, pluginCtx, body.data);
1659
1886
  return response;
1660
1887
  }
@@ -1727,9 +1954,10 @@ async function resolveDynamicPrice(bodyData, routeEntry, deps, pluginCtx, meta)
1727
1954
  });
1728
1955
  return { price: routeEntry.maxPrice };
1729
1956
  } else {
1957
+ const message = errorMessage(err, "Price calculation failed");
1730
1958
  const errorResponse = import_server2.NextResponse.json(
1731
- { success: false, error: "Price calculation failed" },
1732
- { status: 500 }
1959
+ { success: false, error: message },
1960
+ { status: errorStatus(err, 500) }
1733
1961
  );
1734
1962
  firePluginResponse(deps, pluginCtx, meta, errorResponse);
1735
1963
  return { error: errorResponse };
@@ -1902,22 +2130,13 @@ function fireProviderQuota(routeEntry, response, handlerResult, deps, pluginCtx)
1902
2130
 
1903
2131
  // src/validate-examples.ts
1904
2132
  function validateExamples(key, bodySchema, querySchema, outputSchema, inputExample, hasInputExample, outputExample, hasOutputExample) {
1905
- if (bodySchema && !hasInputExample) {
1906
- throw new Error(
1907
- `route '${key}': .body() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample body to advertise.`
1908
- );
1909
- }
1910
- if (querySchema && !hasInputExample) {
1911
- throw new Error(
1912
- `route '${key}': .query() requires a matching .inputExample() \u2014 the bazaar discovery extension needs a conforming sample query to advertise.`
1913
- );
2133
+ const inputSchema = bodySchema ?? querySchema;
2134
+ if (hasInputExample && !inputSchema) {
2135
+ throw new Error(`route '${key}': .inputExample() requires .body() or .query()`);
1914
2136
  }
1915
- if (outputSchema && !hasOutputExample) {
1916
- throw new Error(
1917
- `route '${key}': .output() requires a matching .outputExample() \u2014 the bazaar discovery extension needs a conforming sample response to advertise.`
1918
- );
2137
+ if (hasOutputExample && !outputSchema) {
2138
+ throw new Error(`route '${key}': .outputExample() requires .output()`);
1919
2139
  }
1920
- const inputSchema = bodySchema ?? querySchema;
1921
2140
  if (inputSchema && hasInputExample) {
1922
2141
  const result = inputSchema.safeParse(inputExample);
1923
2142
  if (!result.success) {
@@ -1991,6 +2210,8 @@ var RouteBuilder = class {
1991
2210
  /** @internal */
1992
2211
  _validateFn;
1993
2212
  /** @internal */
2213
+ _settlement;
2214
+ /** @internal */
1994
2215
  _mppInfo;
1995
2216
  constructor(key, registry, deps) {
1996
2217
  this._key = key;
@@ -2004,11 +2225,21 @@ var RouteBuilder = class {
2004
2225
  return next;
2005
2226
  }
2006
2227
  paid(pricing, options) {
2228
+ if (this._authMode === "unprotected") {
2229
+ throw new Error(
2230
+ `route '${this._key}': Cannot combine .unprotected() and .paid() on the same route.`
2231
+ );
2232
+ }
2233
+ if (this._pricing !== void 0) {
2234
+ throw new Error(
2235
+ `route '${this._key}': Cannot call .paid() more than once on the same route.`
2236
+ );
2237
+ }
2007
2238
  const next = this.fork();
2008
2239
  next._authMode = "paid";
2009
2240
  next._pricing = pricing;
2010
2241
  if (options?.protocols) {
2011
- next._protocols = options.protocols;
2242
+ next._protocols = [...options.protocols];
2012
2243
  } else if (next._protocols.length === 0) {
2013
2244
  next._protocols = ["x402"];
2014
2245
  }
@@ -2073,6 +2304,16 @@ var RouteBuilder = class {
2073
2304
  return next;
2074
2305
  }
2075
2306
  unprotected() {
2307
+ if (this._authMode && this._authMode !== "unprotected") {
2308
+ throw new Error(
2309
+ `route '${this._key}': Cannot combine .unprotected() and .${this._authMode}() on the same route.`
2310
+ );
2311
+ }
2312
+ if (this._pricing) {
2313
+ throw new Error(
2314
+ `route '${this._key}': Cannot combine .unprotected() and .paid() on the same route.`
2315
+ );
2316
+ }
2076
2317
  const next = this.fork();
2077
2318
  next._authMode = "unprotected";
2078
2319
  next._protocols = [];
@@ -2087,32 +2328,43 @@ var RouteBuilder = class {
2087
2328
  next._providerConfig = config ?? {};
2088
2329
  return next;
2089
2330
  }
2090
- // -------------------------------------------------------------------------
2091
- // Schema methods
2092
- // -------------------------------------------------------------------------
2093
- body(schema) {
2331
+ body(schema, example) {
2094
2332
  const next = this.fork();
2095
2333
  next._bodySchema = schema;
2334
+ if (example !== void 0) {
2335
+ next._inputExample = example;
2336
+ next._hasInputExample = true;
2337
+ }
2096
2338
  return next;
2097
2339
  }
2098
- query(schema) {
2340
+ query(schema, example) {
2099
2341
  const next = this.fork();
2100
2342
  next._querySchema = schema;
2343
+ if (example !== void 0) {
2344
+ next._inputExample = example;
2345
+ next._hasInputExample = true;
2346
+ }
2101
2347
  next._method = "GET";
2102
2348
  return next;
2103
2349
  }
2104
- output(schema) {
2350
+ output(schema, example) {
2105
2351
  const next = this.fork();
2106
2352
  next._outputSchema = schema;
2353
+ if (example !== void 0) {
2354
+ next._outputExample = example;
2355
+ next._hasOutputExample = true;
2356
+ }
2107
2357
  return next;
2108
2358
  }
2109
2359
  /**
2110
2360
  * Provide a conforming example of the request input (body or query params).
2111
2361
  *
2112
- * **Required** whenever `.body()` or `.query()` is set enforced at compile time via
2113
- * `.handler()` overloads, and at route-registration time via Zod validation of the
2114
- * example against the schema. The example is embedded in the bazaar discovery extension
2115
- * so indexers can advertise a working sample call.
2362
+ * Optional. When provided, the example is validated against the request schema
2363
+ * at route registration and embedded in the bazaar discovery extension so
2364
+ * indexers can advertise a working sample call.
2365
+ *
2366
+ * For the common case, pass the example directly to `.body(schema, example)` or
2367
+ * `.query(schema, example)` instead.
2116
2368
  *
2117
2369
  * @example
2118
2370
  * ```ts
@@ -2132,10 +2384,11 @@ var RouteBuilder = class {
2132
2384
  /**
2133
2385
  * Provide a conforming example of the response output.
2134
2386
  *
2135
- * **Required** whenever `.output()` is set enforced at compile time via `.handler()`
2136
- * overloads, and at route-registration time via Zod validation of the example against
2137
- * the schema. The example is embedded in the bazaar discovery extension so indexers
2138
- * can advertise the response shape.
2387
+ * Optional. When provided, the example is validated against the output schema
2388
+ * at route registration and embedded in the bazaar discovery extension so
2389
+ * indexers can advertise the response shape.
2390
+ *
2391
+ * For the common case, pass the example directly to `.output(schema, example)` instead.
2139
2392
  *
2140
2393
  * Accepts any JSON value (objects, arrays, or primitives) — top-level array
2141
2394
  * or primitive responses (e.g. `z.array(...)`) are supported alongside the
@@ -2208,15 +2461,39 @@ var RouteBuilder = class {
2208
2461
  return next;
2209
2462
  }
2210
2463
  // -------------------------------------------------------------------------
2464
+ // Settlement lifecycle
2465
+ // -------------------------------------------------------------------------
2466
+ /**
2467
+ * Add route-specific settlement hooks.
2468
+ *
2469
+ * `beforeSettle` runs after a successful handler response but before
2470
+ * router-controlled settlement/broadcast, so it can still prevent the charge
2471
+ * for x402 and MPP transaction-payload flows. `afterSettle` runs after
2472
+ * settlement and is intended for durable ledgers or app-owned refund queues.
2473
+ */
2474
+ settlement(lifecycle) {
2475
+ const next = this.fork();
2476
+ next._settlement = lifecycle;
2477
+ return next;
2478
+ }
2479
+ // -------------------------------------------------------------------------
2211
2480
  // Terminal method
2212
2481
  // -------------------------------------------------------------------------
2213
2482
  handler(fn) {
2214
2483
  const handlerFn = fn;
2484
+ if (!this._authMode) {
2485
+ throw new Error(
2486
+ `route '${this._key}': Select an auth mode: .paid(pricing), .siwx(), .apiKey(resolver), or .unprotected()`
2487
+ );
2488
+ }
2215
2489
  if (this._validateFn && !this._bodySchema) {
2216
2490
  throw new Error(
2217
2491
  `route '${this._key}': .validate() requires .body() \u2014 validation runs on parsed body`
2218
2492
  );
2219
2493
  }
2494
+ if (this._settlement && !this._pricing) {
2495
+ throw new Error(`route '${this._key}': .settlement() requires a paid route`);
2496
+ }
2220
2497
  validateExamples(
2221
2498
  this._key,
2222
2499
  this._bodySchema,
@@ -2248,6 +2525,7 @@ var RouteBuilder = class {
2248
2525
  providerName: this._providerName,
2249
2526
  providerConfig: this._providerConfig,
2250
2527
  validateFn: this._validateFn,
2528
+ settlement: this._settlement,
2251
2529
  mppInfo: this._mppInfo
2252
2530
  };
2253
2531
  this._registry.register(entry);
@@ -2384,6 +2662,7 @@ function toDiscoveryResource(method, url, mode) {
2384
2662
 
2385
2663
  // src/discovery/openapi.ts
2386
2664
  var import_server4 = require("next/server");
2665
+ init_constants();
2387
2666
  function createOpenAPIHandler(registry, baseUrl, pricesKeys, discovery) {
2388
2667
  const normalizedBase = baseUrl.replace(/\/+$/, "");
2389
2668
  let cached = null;
@@ -2525,7 +2804,7 @@ function toProtocolObject(protocol, mppInfo) {
2525
2804
  mpp: {
2526
2805
  method: mppInfo?.method ?? "tempo",
2527
2806
  intent: mppInfo?.intent ?? "charge",
2528
- currency: mppInfo?.currency ?? "0x20c0000000000000000000000000000000000001"
2807
+ currency: mppInfo?.currency ?? TEMPO_USDC_CURRENCY
2529
2808
  }
2530
2809
  };
2531
2810
  }
@@ -2590,61 +2869,319 @@ function createLlmsTxtHandler(discovery) {
2590
2869
 
2591
2870
  // src/index.ts
2592
2871
  init_x402_config();
2872
+ init_constants();
2873
+
2874
+ // src/config.ts
2875
+ init_constants();
2593
2876
  init_evm();
2594
2877
  init_solana();
2878
+ init_x402_config();
2879
+ var RouterConfigError = class extends Error {
2880
+ issues;
2881
+ constructor(issues) {
2882
+ super(formatRouterConfigIssues(issues));
2883
+ this.name = "RouterConfigError";
2884
+ this.issues = issues;
2885
+ }
2886
+ };
2887
+ function validateRouterConfig(config, options = {}) {
2888
+ const issues = getRouterConfigIssues(config, options);
2889
+ if (issues.length > 0) throw new RouterConfigError(issues);
2890
+ }
2891
+ function getRouterConfigIssues(config, options = {}) {
2892
+ const env = options.env ?? process.env;
2893
+ const issues = [];
2894
+ const protocols = config.protocols ?? ["x402"];
2895
+ if (!config.baseUrl) {
2896
+ issues.push({
2897
+ code: "missing_base_url",
2898
+ message: '[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.'
2899
+ });
2900
+ }
2901
+ if (config.protocols && config.protocols.length === 0) {
2902
+ issues.push({
2903
+ code: "empty_protocols",
2904
+ message: "RouterConfig.protocols cannot be empty. Omit the field to use default ['x402'] or specify protocols explicitly."
2905
+ });
2906
+ }
2907
+ if (protocols.includes("x402")) {
2908
+ issues.push(...validateX402Config(config, env, options));
2909
+ }
2910
+ if (protocols.includes("mpp")) {
2911
+ issues.push(...validateMppConfig(config, env));
2912
+ }
2913
+ return issues;
2914
+ }
2915
+ function formatRouterConfigIssues(issues) {
2916
+ return issues.map((issue) => issue.message).join("\n");
2917
+ }
2918
+ function mppFromEnv(env, options = {}) {
2919
+ const secretKey = env.MPP_SECRET_KEY;
2920
+ const currency = env.MPP_CURRENCY;
2921
+ const rpcUrl = env.TEMPO_RPC_URL;
2922
+ const feePayerKey = options.feePayerKey ?? env.MPP_FEE_PAYER_KEY;
2923
+ const feePayerKeySource = options.feePayerKey !== void 0 ? "feePayerKey" : "MPP_FEE_PAYER_KEY";
2924
+ const hasAnyMppEnv = Boolean(secretKey || currency || rpcUrl || options.require);
2925
+ if (!hasAnyMppEnv) return void 0;
2926
+ const missing = [
2927
+ secretKey ? null : "MPP_SECRET_KEY",
2928
+ currency ? null : "MPP_CURRENCY",
2929
+ rpcUrl ? null : "TEMPO_RPC_URL"
2930
+ ].filter(Boolean);
2931
+ if (missing.length > 0) {
2932
+ throw new Error(`MPP env is incomplete. Missing: ${missing.join(", ")}`);
2933
+ }
2934
+ if (!isEvmAddress(currency)) {
2935
+ throw new Error("MPP_CURRENCY must be a 0x-prefixed 20-byte Tempo currency address");
2936
+ }
2937
+ if (options.recipient && !isEvmAddress(options.recipient)) {
2938
+ throw new Error("MPP recipient must be a 0x-prefixed EVM address");
2939
+ }
2940
+ if (feePayerKey && !isEvmPrivateKey(feePayerKey)) {
2941
+ throw new Error(`${feePayerKeySource} must be a 0x-prefixed 32-byte EVM private key`);
2942
+ }
2943
+ return {
2944
+ secretKey,
2945
+ currency,
2946
+ rpcUrl,
2947
+ ...options.recipient ? { recipient: options.recipient } : {},
2948
+ ...feePayerKey ? { feePayerKey } : {},
2949
+ ...options.useDefaultStore !== void 0 ? { useDefaultStore: options.useDefaultStore } : {}
2950
+ };
2951
+ }
2952
+ function x402AcceptsFromEnv(env, options = {}) {
2953
+ const payeeEnv = options.payeeEnv ?? "X402_WALLET_ADDRESS";
2954
+ const solanaPayeeEnv = options.solanaPayeeEnv ?? "SOLANA_PAYEE_ADDRESS";
2955
+ const payeeAddress = options.payeeAddress ?? env[payeeEnv];
2956
+ if (!payeeAddress) {
2957
+ throw new Error(`${payeeEnv} is required to build x402 accepts`);
2958
+ }
2959
+ const accepts = [
2960
+ {
2961
+ scheme: "exact",
2962
+ network: options.network ?? BASE_NETWORK,
2963
+ payTo: payeeAddress
2964
+ }
2965
+ ];
2966
+ const solanaPayeeAddress = options.solanaPayeeAddress ?? env[solanaPayeeEnv];
2967
+ if (solanaPayeeAddress) {
2968
+ accepts.push({
2969
+ scheme: "exact",
2970
+ network: SOLANA_MAINNET_NETWORK,
2971
+ payTo: solanaPayeeAddress
2972
+ });
2973
+ }
2974
+ return accepts;
2975
+ }
2976
+ function paidOptionsForProtocols(protocols) {
2977
+ return { protocols: [...protocols] };
2978
+ }
2979
+ function validateX402Config(config, env, options) {
2980
+ const issues = [];
2981
+ const accepts = getConfiguredX402Accepts(config);
2982
+ if (accepts.length === 0) {
2983
+ issues.push({
2984
+ code: "missing_x402_accepts",
2985
+ protocol: "x402",
2986
+ message: "x402 requires at least one accept configuration."
2987
+ });
2988
+ return issues;
2989
+ }
2990
+ const acceptWithoutNetwork = accepts.find((accept) => !accept.network);
2991
+ if (acceptWithoutNetwork) {
2992
+ issues.push({
2993
+ code: "missing_x402_network",
2994
+ protocol: "x402",
2995
+ message: "x402 accepts require a network."
2996
+ });
2997
+ }
2998
+ const unsupported = accepts.find(
2999
+ (accept) => accept.network && !isSupportedX402Network(accept.network)
3000
+ );
3001
+ if (unsupported) {
3002
+ issues.push({
3003
+ code: "unsupported_x402_network",
3004
+ protocol: "x402",
3005
+ message: `unsupported x402 network '${unsupported.network}'. Use eip155:* or solana:*.`
3006
+ });
3007
+ }
3008
+ const missingAsset = accepts.find(
3009
+ (accept) => (accept.scheme ?? "exact") !== "exact" && !accept.asset
3010
+ );
3011
+ if (missingAsset) {
3012
+ issues.push({
3013
+ code: "missing_x402_asset",
3014
+ protocol: "x402",
3015
+ message: "non-exact x402 accepts require an asset."
3016
+ });
3017
+ }
3018
+ const invalidDecimals = accepts.find(
3019
+ (accept) => accept.decimals !== void 0 && (!Number.isInteger(accept.decimals) || accept.decimals < 0)
3020
+ );
3021
+ if (invalidDecimals) {
3022
+ issues.push({
3023
+ code: "invalid_x402_decimals",
3024
+ protocol: "x402",
3025
+ message: "x402 accept decimals must be a non-negative integer."
3026
+ });
3027
+ }
3028
+ if (accepts.some((accept) => !accept.payTo) && !config.payeeAddress) {
3029
+ issues.push({
3030
+ code: "missing_x402_payee",
3031
+ protocol: "x402",
3032
+ message: "x402 requires payeeAddress in router config or payTo on every x402 accept."
3033
+ });
3034
+ }
3035
+ const placeholder = findPlaceholderPayee([
3036
+ config.payeeAddress,
3037
+ ...accepts.map((accept) => typeof accept.payTo === "string" ? accept.payTo : void 0)
3038
+ ]);
3039
+ if (placeholder) {
3040
+ issues.push({
3041
+ code: "placeholder_payee",
3042
+ protocol: "x402",
3043
+ message: `x402 payee '${placeholder}' is a placeholder address and cannot receive payments.`
3044
+ });
3045
+ }
3046
+ if (options.requireCdpKeys !== false && usesDefaultEvmFacilitator(config)) {
3047
+ const missing = [
3048
+ env.CDP_API_KEY_ID ? null : "CDP_API_KEY_ID",
3049
+ env.CDP_API_KEY_SECRET ? null : "CDP_API_KEY_SECRET"
3050
+ ].filter(Boolean);
3051
+ if (missing.length > 0) {
3052
+ issues.push({
3053
+ code: "missing_cdp_keys",
3054
+ protocol: "x402",
3055
+ message: `default EVM x402 facilitator requires ${missing.join(" and ")}.`
3056
+ });
3057
+ }
3058
+ }
3059
+ return issues;
3060
+ }
3061
+ function validateMppConfig(config, env) {
3062
+ const issues = [];
3063
+ const mpp = config.mpp;
3064
+ if (!mpp) {
3065
+ return [
3066
+ {
3067
+ code: "missing_mpp_config",
3068
+ protocol: "mpp",
3069
+ message: 'protocols includes "mpp" but mpp config is missing. Add mpp: { secretKey, currency, recipient } to your router config.'
3070
+ }
3071
+ ];
3072
+ }
3073
+ if (!mpp.secretKey) {
3074
+ issues.push({
3075
+ code: "missing_mpp_secret_key",
3076
+ protocol: "mpp",
3077
+ message: "MPP requires secretKey. Set MPP_SECRET_KEY or pass mpp.secretKey."
3078
+ });
3079
+ }
3080
+ if (!mpp.currency) {
3081
+ issues.push({
3082
+ code: "missing_mpp_currency",
3083
+ protocol: "mpp",
3084
+ message: "MPP requires currency. Set MPP_CURRENCY or pass mpp.currency."
3085
+ });
3086
+ } else if (!isEvmAddress(mpp.currency)) {
3087
+ issues.push({
3088
+ code: "invalid_mpp_currency",
3089
+ protocol: "mpp",
3090
+ message: "MPP currency must be a 0x-prefixed 20-byte Tempo currency address. Use TEMPO_USDC_CURRENCY for Tempo USDC."
3091
+ });
3092
+ }
3093
+ const mppRecipient = mpp.recipient ?? config.payeeAddress;
3094
+ if (!mppRecipient) {
3095
+ issues.push({
3096
+ code: "missing_mpp_recipient",
3097
+ protocol: "mpp",
3098
+ message: "MPP requires a recipient address. Set mpp.recipient or payeeAddress in your router config."
3099
+ });
3100
+ } else if (!isEvmAddress(mppRecipient)) {
3101
+ issues.push({
3102
+ code: "invalid_mpp_recipient",
3103
+ protocol: "mpp",
3104
+ message: "MPP recipient must be a 0x-prefixed EVM address. Solana recipients require x402."
3105
+ });
3106
+ }
3107
+ const placeholder = findPlaceholderPayee([mpp.recipient, config.payeeAddress]);
3108
+ if (placeholder) {
3109
+ issues.push({
3110
+ code: "placeholder_payee",
3111
+ protocol: "mpp",
3112
+ message: `MPP recipient '${placeholder}' is a placeholder address and cannot receive payments.`
3113
+ });
3114
+ }
3115
+ if (!(mpp.rpcUrl ?? env.TEMPO_RPC_URL)) {
3116
+ issues.push({
3117
+ code: "missing_mpp_rpc_url",
3118
+ protocol: "mpp",
3119
+ message: "MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object."
3120
+ });
3121
+ }
3122
+ if (mpp.feePayerKey && !isEvmPrivateKey(mpp.feePayerKey)) {
3123
+ issues.push({
3124
+ code: "invalid_mpp_fee_payer_key",
3125
+ protocol: "mpp",
3126
+ message: "MPP feePayerKey must be a 0x-prefixed 32-byte EVM private key."
3127
+ });
3128
+ }
3129
+ if (mpp.useDefaultStore && !mpp.store && (!env.KV_REST_API_URL || !env.KV_REST_API_TOKEN)) {
3130
+ issues.push({
3131
+ code: "missing_mpp_default_store_env",
3132
+ protocol: "mpp",
3133
+ message: "mpp.useDefaultStore requires KV_REST_API_URL and KV_REST_API_TOKEN environment variables. These are automatically set by Vercel KV."
3134
+ });
3135
+ }
3136
+ return issues;
3137
+ }
3138
+ function usesDefaultEvmFacilitator(config) {
3139
+ return getConfiguredX402Networks(config).some(
3140
+ (network) => typeof network === "string" && isEvmNetwork(network)
3141
+ ) && config.x402?.facilitators?.evm === void 0;
3142
+ }
3143
+ function isSupportedX402Network(network) {
3144
+ return isEvmNetwork(network) || isSolanaNetwork(network);
3145
+ }
3146
+ function isEvmAddress(value) {
3147
+ return /^0x[a-fA-F0-9]{40}$/.test(value);
3148
+ }
3149
+ function isEvmPrivateKey(value) {
3150
+ return /^0x[a-fA-F0-9]{64}$/.test(value);
3151
+ }
3152
+ function findPlaceholderPayee(values) {
3153
+ return values.find((value) => value !== void 0 && /^0x0{40}$/i.test(value)) ?? null;
3154
+ }
3155
+
3156
+ // src/index.ts
3157
+ init_constants();
2595
3158
  function createRouter(config) {
2596
3159
  const registry = new RouteRegistry();
2597
3160
  const nonceStore = config.siwx?.nonceStore ?? new MemoryNonceStore();
2598
3161
  const entitlementStore = config.siwx?.entitlementStore ?? new MemoryEntitlementStore();
2599
- const network = config.network ?? "eip155:8453";
3162
+ const network = config.network ?? BASE_NETWORK;
2600
3163
  const x402Accepts = getConfiguredX402Accepts(config);
2601
- if (!config.baseUrl) {
2602
- throw new Error(
2603
- '[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.'
2604
- );
2605
- }
2606
- if (config.protocols && config.protocols.length === 0) {
2607
- throw new Error(
2608
- "RouterConfig.protocols cannot be empty. Omit the field to use default ['x402'] or specify protocols explicitly."
2609
- );
2610
- }
2611
- const resolvedBaseUrl = config.baseUrl.replace(/\/+$/, "");
2612
- let x402ConfigError;
2613
- let mppConfigError;
2614
- if (!config.protocols || config.protocols.includes("x402")) {
2615
- if (x402Accepts.length === 0) {
2616
- x402ConfigError = "x402 requires at least one accept configuration.";
2617
- } else if (x402Accepts.some((accept) => !accept.network)) {
2618
- x402ConfigError = "x402 accepts require a network.";
2619
- } else if (x402Accepts.some((accept) => !isSupportedX402Network(accept.network))) {
2620
- const unsupported = x402Accepts.find((accept) => !isSupportedX402Network(accept.network));
2621
- x402ConfigError = `unsupported x402 network '${unsupported?.network}'. Use eip155:* or solana:*.`;
2622
- } else if (x402Accepts.some((accept) => (accept.scheme ?? "exact") !== "exact" && !accept.asset)) {
2623
- x402ConfigError = "non-exact x402 accepts require an asset.";
2624
- } else if (x402Accepts.some(
2625
- (accept) => accept.decimals !== void 0 && (!Number.isInteger(accept.decimals) || accept.decimals < 0)
2626
- )) {
2627
- x402ConfigError = "x402 accept decimals must be a non-negative integer.";
2628
- } else if (x402Accepts.some((accept) => !accept.payTo) && !config.payeeAddress) {
2629
- x402ConfigError = "x402 requires payeeAddress in router config or payTo on every x402 accept.";
2630
- }
2631
- }
2632
- if (config.protocols?.includes("mpp")) {
2633
- if (!config.mpp) {
2634
- mppConfigError = 'protocols includes "mpp" but mpp config is missing. Add mpp: { secretKey, currency, recipient } to your router config.';
2635
- } else if (!config.mpp.recipient && !config.payeeAddress) {
2636
- mppConfigError = "MPP requires a recipient address. Set mpp.recipient or payeeAddress in your router config.";
2637
- } else if (!(config.mpp.rpcUrl ?? process.env.TEMPO_RPC_URL)) {
2638
- mppConfigError = "MPP requires an authenticated Tempo RPC URL. Set TEMPO_RPC_URL env var or pass rpcUrl in the mpp config object.";
2639
- }
2640
- }
2641
- const allConfigErrors = [x402ConfigError, mppConfigError].filter(Boolean);
2642
- if (allConfigErrors.length > 0) {
2643
- for (const err of allConfigErrors) console.error(`[router] ${err}`);
3164
+ const configIssues = getRouterConfigIssues(config, {
3165
+ requireCdpKeys: process.env.NODE_ENV === "production"
3166
+ });
3167
+ const baseUrlIssue = configIssues.find((issue) => issue.code === "missing_base_url");
3168
+ if (baseUrlIssue) throw new RouterConfigError([baseUrlIssue]);
3169
+ const emptyProtocolsIssue = configIssues.find((issue) => issue.code === "empty_protocols");
3170
+ if (emptyProtocolsIssue) throw new RouterConfigError([emptyProtocolsIssue]);
3171
+ const protocolConfigIssues = configIssues.filter(
3172
+ (issue) => issue.code !== "missing_base_url" && issue.code !== "empty_protocols"
3173
+ );
3174
+ const x402ConfigIssues = protocolConfigIssues.filter((issue) => issue.protocol === "x402");
3175
+ const mppConfigIssues = protocolConfigIssues.filter((issue) => issue.protocol === "mpp");
3176
+ const x402ConfigError = x402ConfigIssues.length > 0 ? formatRouterConfigIssues(x402ConfigIssues) : void 0;
3177
+ const mppConfigError = mppConfigIssues.length > 0 ? formatRouterConfigIssues(mppConfigIssues) : void 0;
3178
+ if (protocolConfigIssues.length > 0) {
3179
+ for (const issue of protocolConfigIssues) console.error(`[router] ${issue.message}`);
2644
3180
  if (process.env.NODE_ENV === "production") {
2645
- throw new Error(allConfigErrors.join("\n"));
3181
+ throw new RouterConfigError(protocolConfigIssues);
2646
3182
  }
2647
3183
  }
3184
+ const resolvedBaseUrl = config.baseUrl.replace(/\/+$/, "");
2648
3185
  if (config.plugin?.init) {
2649
3186
  try {
2650
3187
  const result = config.plugin.init({ origin: resolvedBaseUrl });
@@ -2662,6 +3199,7 @@ function createRouter(config) {
2662
3199
  nonceStore,
2663
3200
  entitlementStore,
2664
3201
  payeeAddress: config.payeeAddress ?? "",
3202
+ mppRecipient: config.mpp?.recipient ?? config.payeeAddress,
2665
3203
  network,
2666
3204
  x402FacilitatorsByNetwork: void 0,
2667
3205
  x402Accepts,
@@ -2789,9 +3327,6 @@ function createRouter(config) {
2789
3327
  registry
2790
3328
  };
2791
3329
  }
2792
- function isSupportedX402Network(network) {
2793
- return isEvmNetwork(network) || isSolanaNetwork(network);
2794
- }
2795
3330
  function normalizePath(path) {
2796
3331
  let normalized = path.trim();
2797
3332
  normalized = normalized.replace(/^\/+/, "");
@@ -2800,15 +3335,26 @@ function normalizePath(path) {
2800
3335
  }
2801
3336
  // Annotate the CommonJS export names for ESM import in node:
2802
3337
  0 && (module.exports = {
3338
+ BASE_NETWORK,
2803
3339
  HttpError,
2804
3340
  MemoryEntitlementStore,
2805
3341
  MemoryNonceStore,
2806
3342
  RouteBuilder,
2807
3343
  RouteRegistry,
3344
+ RouterConfigError,
2808
3345
  SIWX_CHALLENGE_EXPIRY_MS,
2809
3346
  SIWX_ERROR_MESSAGES,
3347
+ SOLANA_MAINNET_NETWORK,
3348
+ TEMPO_USDC_CURRENCY,
3349
+ ZERO_EVM_ADDRESS,
2810
3350
  consolePlugin,
2811
3351
  createRedisEntitlementStore,
2812
3352
  createRedisNonceStore,
2813
- createRouter
3353
+ createRouter,
3354
+ formatRouterConfigIssues,
3355
+ getRouterConfigIssues,
3356
+ mppFromEnv,
3357
+ paidOptionsForProtocols,
3358
+ validateRouterConfig,
3359
+ x402AcceptsFromEnv
2814
3360
  });