@relai-fi/x402 0.6.4 → 0.6.6

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.
Files changed (66) hide show
  1. package/README.md +116 -0
  2. package/dist/bridge.cjs +109 -0
  3. package/dist/bridge.cjs.map +1 -0
  4. package/dist/bridge.d.cts +78 -0
  5. package/dist/bridge.d.ts +78 -0
  6. package/dist/bridge.js +80 -0
  7. package/dist/bridge.js.map +1 -0
  8. package/dist/client.cjs +209 -1
  9. package/dist/client.cjs.map +1 -1
  10. package/dist/client.d.cts +12 -0
  11. package/dist/client.d.ts +12 -0
  12. package/dist/client.js +209 -1
  13. package/dist/client.js.map +1 -1
  14. package/dist/index.cjs +327 -74
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.js +327 -74
  17. package/dist/index.js.map +1 -1
  18. package/dist/mpp/bridge-client.cjs +23922 -0
  19. package/dist/mpp/bridge-client.cjs.map +1 -0
  20. package/dist/mpp/bridge-client.d.cts +58 -0
  21. package/dist/mpp/bridge-client.d.ts +58 -0
  22. package/dist/mpp/bridge-client.js +23892 -0
  23. package/dist/mpp/bridge-client.js.map +1 -0
  24. package/dist/mpp/bridge-method.cjs +13202 -0
  25. package/dist/mpp/bridge-method.cjs.map +1 -0
  26. package/dist/mpp/bridge-method.d.cts +69 -0
  27. package/dist/mpp/bridge-method.d.ts +69 -0
  28. package/dist/mpp/bridge-method.js +13181 -0
  29. package/dist/mpp/bridge-method.js.map +1 -0
  30. package/dist/mpp/bridge-server.cjs +13887 -0
  31. package/dist/mpp/bridge-server.cjs.map +1 -0
  32. package/dist/mpp/bridge-server.d.cts +62 -0
  33. package/dist/mpp/bridge-server.d.ts +62 -0
  34. package/dist/mpp/bridge-server.js +13866 -0
  35. package/dist/mpp/bridge-server.js.map +1 -0
  36. package/dist/mpp/evm-server.cjs +49 -33
  37. package/dist/mpp/evm-server.cjs.map +1 -1
  38. package/dist/mpp/evm-server.js +49 -33
  39. package/dist/mpp/evm-server.js.map +1 -1
  40. package/dist/mpp/verify-erc20.cjs +71 -0
  41. package/dist/mpp/verify-erc20.cjs.map +1 -0
  42. package/dist/mpp/verify-erc20.d.cts +27 -0
  43. package/dist/mpp/verify-erc20.d.ts +27 -0
  44. package/dist/mpp/verify-erc20.js +46 -0
  45. package/dist/mpp/verify-erc20.js.map +1 -0
  46. package/dist/mpp/verify-spl.cjs +96 -0
  47. package/dist/mpp/verify-spl.cjs.map +1 -0
  48. package/dist/mpp/verify-spl.d.cts +30 -0
  49. package/dist/mpp/verify-spl.d.ts +30 -0
  50. package/dist/mpp/verify-spl.js +71 -0
  51. package/dist/mpp/verify-spl.js.map +1 -0
  52. package/dist/mpp/with-bridge.cjs +23956 -0
  53. package/dist/mpp/with-bridge.cjs.map +1 -0
  54. package/dist/mpp/with-bridge.d.cts +53 -0
  55. package/dist/mpp/with-bridge.d.ts +53 -0
  56. package/dist/mpp/with-bridge.js +23926 -0
  57. package/dist/mpp/with-bridge.js.map +1 -0
  58. package/dist/react/index.cjs +209 -1
  59. package/dist/react/index.cjs.map +1 -1
  60. package/dist/react/index.js +209 -1
  61. package/dist/react/index.js.map +1 -1
  62. package/dist/server.cjs +8 -40
  63. package/dist/server.cjs.map +1 -1
  64. package/dist/server.js +8 -40
  65. package/dist/server.js.map +1 -1
  66. package/package.json +32 -1
package/dist/client.cjs CHANGED
@@ -240,6 +240,71 @@ function normalizeNetwork(network) {
240
240
  return null;
241
241
  }
242
242
 
243
+ // src/bridge.ts
244
+ var RELAI_API_BASE = "https://api.relai.fi";
245
+ var _cacheMap = /* @__PURE__ */ new Map();
246
+ var CACHE_TTL = 5 * 60 * 1e3;
247
+ async function getBridgeInfo(baseUrl = RELAI_API_BASE) {
248
+ const key = baseUrl.replace(/\/$/, "");
249
+ const now = Date.now();
250
+ const cached = _cacheMap.get(key);
251
+ if (cached && now - cached.time < CACHE_TTL) return cached.info;
252
+ const url = `${key}/bridge/info`;
253
+ const res = await fetch(url);
254
+ if (!res.ok) {
255
+ if (cached) return cached.info;
256
+ throw new Error(`[relai:bridge] Failed to fetch ${url}: ${res.status}`);
257
+ }
258
+ const data = await res.json();
259
+ const info = {
260
+ settleEndpoint: data.settleEndpoint,
261
+ supportedSourceChains: data.supportedSourceChains || [],
262
+ supportedSourceAssets: data.supportedSourceAssets || [],
263
+ payTo: data.payTo || {},
264
+ feePayerSvm: data.feePayerSvm ?? null,
265
+ feeBps: data.feeBps ?? 100,
266
+ paymentFacilitator: data.paymentFacilitator || "https://facilitator.x402.fi"
267
+ };
268
+ _cacheMap.set(key, { info, time: now });
269
+ return info;
270
+ }
271
+ async function settleBridge(settleEndpoint, body) {
272
+ const res = await fetch(settleEndpoint, {
273
+ method: "POST",
274
+ headers: { "Content-Type": "application/json" },
275
+ body: JSON.stringify(body)
276
+ });
277
+ if (!res.ok) {
278
+ const err = await res.json().catch(() => ({}));
279
+ throw new Error(`[relai:bridge] settle failed: ${err.error || res.status}${err.details ? " \u2014 " + err.details : ""}`);
280
+ }
281
+ return res.json();
282
+ }
283
+ function selectSourceChain(supportedChains, hasEvmWallet, hasSolanaWallet, preferredSourceChainId) {
284
+ if (preferredSourceChainId && hasEvmWallet) {
285
+ const preferred = `eip155:${preferredSourceChainId}`;
286
+ if (supportedChains.includes(preferred)) {
287
+ return { type: "evm", chain: preferred };
288
+ }
289
+ }
290
+ if (hasSolanaWallet) {
291
+ const sol = supportedChains.find((c) => c.startsWith("solana:"));
292
+ if (sol) return { type: "solana", chain: sol };
293
+ }
294
+ if (hasEvmWallet) {
295
+ for (const chain of supportedChains) {
296
+ if (chain.startsWith("eip155:")) {
297
+ return { type: "evm", chain };
298
+ }
299
+ }
300
+ }
301
+ return null;
302
+ }
303
+ function computeSourceAmount(targetAmount, feeBps) {
304
+ const fee = targetAmount * BigInt(feeBps) / 10000n;
305
+ return targetAmount + fee;
306
+ }
307
+
243
308
  // src/client.ts
244
309
  var PERMIT_NETWORKS = /* @__PURE__ */ new Set([]);
245
310
  var DEFAULT_EVM_RPC_URLS = {
@@ -266,7 +331,8 @@ function createX402Client(config) {
266
331
  integritas,
267
332
  verbose = false,
268
333
  defaultHeaders = {},
269
- mpp
334
+ mpp,
335
+ bridge: bridgeConfig
270
336
  } = config;
271
337
  const relayWsEnabled = relayWs?.enabled === true;
272
338
  const relayWsPreflightTimeoutMs = relayWs?.preflightTimeoutMs ?? 5e3;
@@ -685,6 +751,38 @@ function createX402Client(config) {
685
751
  }
686
752
  return null;
687
753
  }
754
+ function extractMppChallengeFromWsError(error) {
755
+ if (isRecord(error) && typeof error.mppChallenge === "string") {
756
+ return error.mppChallenge;
757
+ }
758
+ if (isRecord(error.data) && typeof error.data.mppChallenge === "string") {
759
+ return error.data.mppChallenge;
760
+ }
761
+ const metadata = isRecord(error) ? error.responseHeaders : void 0;
762
+ if (isRecord(metadata)) {
763
+ for (const [key, value] of Object.entries(metadata)) {
764
+ if (key.toLowerCase() === "www-authenticate" && typeof value === "string") {
765
+ if (/^Payment\s+/i.test(value.trim())) {
766
+ return value;
767
+ }
768
+ }
769
+ }
770
+ }
771
+ return null;
772
+ }
773
+ function buildSyntheticMppResponse(mppChallenge, wsError) {
774
+ const headers = new Headers();
775
+ headers.set("WWW-Authenticate", mppChallenge);
776
+ const responseHeaders = isRecord(wsError) ? wsError.responseHeaders : void 0;
777
+ if (isRecord(responseHeaders)) {
778
+ for (const [key, value] of Object.entries(responseHeaders)) {
779
+ if (typeof value === "string" && key.toLowerCase() !== "www-authenticate") {
780
+ headers.set(key, value);
781
+ }
782
+ }
783
+ }
784
+ return new Response(null, { status: 402, headers });
785
+ }
688
786
  function buildWsResponse(wsResponse) {
689
787
  const statusFromMetadata = isRecord(wsResponse.metadata) && typeof wsResponse.metadata.status === "number" ? wsResponse.metadata.status : 200;
690
788
  const status = Number.isInteger(statusFromMetadata) && statusFromMetadata >= 100 && statusFromMetadata <= 599 ? statusFromMetadata : 200;
@@ -1179,6 +1277,41 @@ function createX402Client(config) {
1179
1277
  if (Number(wsPreflightResponse.error.code) !== 402) {
1180
1278
  throw new Error(wsPreflightResponse.error.message || "[relai-x402] WebSocket relay request failed");
1181
1279
  }
1280
+ if (mpp) {
1281
+ const mppChallenge = extractMppChallengeFromWsError(wsPreflightResponse.error);
1282
+ if (mppChallenge) {
1283
+ log("MPP challenge detected in WS 402 response");
1284
+ try {
1285
+ const syntheticResponse = buildSyntheticMppResponse(mppChallenge, wsPreflightResponse.error);
1286
+ const credential = await mpp.createCredential(syntheticResponse);
1287
+ if (credential) {
1288
+ log("MPP credential created, retrying via WS with Authorization: Payment");
1289
+ wsPaymentPhaseStarted = true;
1290
+ const wsMppResponse = await relayCallOverWebSocket({
1291
+ relayUrl: url,
1292
+ requestMethod,
1293
+ requestHeaders: {
1294
+ ...requestHeaders,
1295
+ "Authorization": credential.startsWith("Payment ") ? credential : `Payment ${credential}`
1296
+ },
1297
+ requestBody,
1298
+ timeoutMs: relayWsPaymentTimeoutMs
1299
+ });
1300
+ if (!wsMppResponse.error) {
1301
+ return buildWsResponse(wsMppResponse);
1302
+ }
1303
+ if (Number(wsMppResponse.error.code) !== 402) {
1304
+ throw new Error(wsMppResponse.error.message || "[relai-x402] WebSocket MPP retry failed");
1305
+ }
1306
+ log("MPP retry via WS still returned 402, falling through to x402 WS flow");
1307
+ wsPaymentPhaseStarted = false;
1308
+ }
1309
+ } catch (mppWsErr) {
1310
+ if (wsPaymentPhaseStarted) throw mppWsErr;
1311
+ log(`MPP over WS failed (${mppWsErr instanceof Error ? mppWsErr.message : mppWsErr}), falling through to x402 WS flow`);
1312
+ }
1313
+ }
1314
+ }
1182
1315
  const wsRequirements = extractPaymentRequirementsFromWsError(wsPreflightResponse.error);
1183
1316
  if (!wsRequirements) {
1184
1317
  throw new Error(
@@ -1196,6 +1329,17 @@ function createX402Client(config) {
1196
1329
  }
1197
1330
  const wsSelected = selectAccept(wsAccepts);
1198
1331
  if (!wsSelected) {
1332
+ const wsBridge = getBridgeExtension(wsRequirements);
1333
+ if (wsBridge && selectBridgeSource(wsBridge)) {
1334
+ log("No direct wallet match in WS flow \u2014 attempting bridge extension");
1335
+ wsPaymentPhaseStarted = true;
1336
+ const bridgePaymentHeader = await executeBridgePayment(wsBridge, wsAccepts, wsRequirements, url);
1337
+ log("Retrying with X-PAYMENT header (bridge via WS fallback to HTTP)");
1338
+ return fetch(input, {
1339
+ ...stripInternalInit(init) || {},
1340
+ headers: { ...requestHeaders, "X-PAYMENT": bridgePaymentHeader }
1341
+ });
1342
+ }
1199
1343
  throw new Error(buildNoWalletError(wsAccepts, true));
1200
1344
  }
1201
1345
  const { accept: accept2, chain: chain2 } = wsSelected;
@@ -1303,6 +1447,70 @@ function createX402Client(config) {
1303
1447
  headers: { ...requestHeaders, "X-PAYMENT": paymentHeader }
1304
1448
  });
1305
1449
  }
1450
+ if (bridgeConfig?.enabled) {
1451
+ log("No direct wallet match \u2014 attempting auto-bridge via RelAI API");
1452
+ try {
1453
+ const info = await getBridgeInfo(bridgeConfig.baseUrl);
1454
+ const targetAccept = accepts[0];
1455
+ const hasEvm = !!effectiveWallets.evm;
1456
+ const hasSol = !!hasSolanaWallet;
1457
+ const source = selectSourceChain(info.supportedSourceChains, hasEvm, hasSol);
1458
+ if (source) {
1459
+ const bridgePayTo = info.payTo[source.chain];
1460
+ if (bridgePayTo) {
1461
+ const targetAmount = targetAccept.amount || targetAccept.maxAmountRequired;
1462
+ const sourceAmount = computeSourceAmount(BigInt(targetAmount), info.feeBps).toString();
1463
+ const sourceChainIdx = info.supportedSourceChains.indexOf(source.chain);
1464
+ const sourceAsset = sourceChainIdx >= 0 && info.supportedSourceAssets[sourceChainIdx] || (source.type === "evm" ? info.supportedSourceAssets.find((a) => a.startsWith("0x")) || targetAccept.asset : info.supportedSourceAssets.find((a) => !a.startsWith("0x")) || "");
1465
+ let sourcePaymentHeader;
1466
+ const sourceAccept = {
1467
+ scheme: "exact",
1468
+ network: source.chain,
1469
+ asset: sourceAsset,
1470
+ payTo: bridgePayTo,
1471
+ amount: sourceAmount,
1472
+ extra: {
1473
+ ...targetAccept.extra || {},
1474
+ ...source.type === "solana" && info.feePayerSvm ? { feePayer: info.feePayerSvm } : {}
1475
+ }
1476
+ };
1477
+ if (source.type === "solana" && hasSolanaWallet) {
1478
+ sourcePaymentHeader = await buildSolanaPayment(sourceAccept, requirements, url);
1479
+ } else if (source.type === "evm") {
1480
+ const evmNetwork = normalizeNetwork(source.chain);
1481
+ const usePermit = evmNetwork && PERMIT_NETWORKS.has(evmNetwork);
1482
+ sourcePaymentHeader = usePermit ? await buildEvmPermitPayment(sourceAccept, requirements, url) : await buildEvmPayment(sourceAccept, requirements, url);
1483
+ } else {
1484
+ throw new Error(`[relai-x402] No wallet for source chain type: ${source.type}`);
1485
+ }
1486
+ const settleData = await settleBridge(info.settleEndpoint, {
1487
+ sourcePayment: sourcePaymentHeader,
1488
+ sourceChain: source.chain,
1489
+ targetAccept: {
1490
+ scheme: "exact",
1491
+ network: targetAccept.network,
1492
+ asset: targetAccept.asset,
1493
+ payTo: targetAccept.payTo,
1494
+ amount: targetAmount
1495
+ },
1496
+ requirements,
1497
+ resource: url,
1498
+ paymentFacilitator: info.paymentFacilitator
1499
+ });
1500
+ if (settleData.xPayment) {
1501
+ log(`Auto-bridge settled: target=${settleData.targetTxId}`);
1502
+ return fetch(input, {
1503
+ ...requestInitWithHeaders,
1504
+ headers: { ...requestHeaders, "X-PAYMENT": settleData.xPayment }
1505
+ });
1506
+ }
1507
+ throw new Error("[relai-x402] Bridge settle did not return xPayment");
1508
+ }
1509
+ }
1510
+ } catch (bridgeErr) {
1511
+ log(`Auto-bridge failed: ${bridgeErr instanceof Error ? bridgeErr.message : bridgeErr}`);
1512
+ }
1513
+ }
1306
1514
  throw new Error(buildNoWalletError(accepts, false));
1307
1515
  }
1308
1516
  const { accept, chain } = selected;