@relai-fi/x402 0.6.5 → 0.6.7

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 (78) hide show
  1. package/README.md +251 -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 +131 -1
  9. package/dist/client.cjs.map +1 -1
  10. package/dist/client.d.cts +13 -1
  11. package/dist/client.d.ts +13 -1
  12. package/dist/client.js +131 -1
  13. package/dist/client.js.map +1 -1
  14. package/dist/index.cjs +586 -105
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.d.cts +291 -30
  17. package/dist/index.d.ts +291 -30
  18. package/dist/index.js +578 -105
  19. package/dist/index.js.map +1 -1
  20. package/dist/mpp/bridge-client.cjs +23922 -0
  21. package/dist/mpp/bridge-client.cjs.map +1 -0
  22. package/dist/mpp/bridge-client.d.cts +58 -0
  23. package/dist/mpp/bridge-client.d.ts +58 -0
  24. package/dist/mpp/bridge-client.js +23892 -0
  25. package/dist/mpp/bridge-client.js.map +1 -0
  26. package/dist/mpp/bridge-method.cjs +13202 -0
  27. package/dist/mpp/bridge-method.cjs.map +1 -0
  28. package/dist/mpp/bridge-method.d.cts +69 -0
  29. package/dist/mpp/bridge-method.d.ts +69 -0
  30. package/dist/mpp/bridge-method.js +13181 -0
  31. package/dist/mpp/bridge-method.js.map +1 -0
  32. package/dist/mpp/bridge-server.cjs +13887 -0
  33. package/dist/mpp/bridge-server.cjs.map +1 -0
  34. package/dist/mpp/bridge-server.d.cts +62 -0
  35. package/dist/mpp/bridge-server.d.ts +62 -0
  36. package/dist/mpp/bridge-server.js +13866 -0
  37. package/dist/mpp/bridge-server.js.map +1 -0
  38. package/dist/mpp/evm-server.cjs +49 -33
  39. package/dist/mpp/evm-server.cjs.map +1 -1
  40. package/dist/mpp/evm-server.js +49 -33
  41. package/dist/mpp/evm-server.js.map +1 -1
  42. package/dist/mpp/verify-erc20.cjs +71 -0
  43. package/dist/mpp/verify-erc20.cjs.map +1 -0
  44. package/dist/mpp/verify-erc20.d.cts +27 -0
  45. package/dist/mpp/verify-erc20.d.ts +27 -0
  46. package/dist/mpp/verify-erc20.js +46 -0
  47. package/dist/mpp/verify-erc20.js.map +1 -0
  48. package/dist/mpp/verify-spl.cjs +96 -0
  49. package/dist/mpp/verify-spl.cjs.map +1 -0
  50. package/dist/mpp/verify-spl.d.cts +30 -0
  51. package/dist/mpp/verify-spl.d.ts +30 -0
  52. package/dist/mpp/verify-spl.js +71 -0
  53. package/dist/mpp/verify-spl.js.map +1 -0
  54. package/dist/mpp/with-bridge.cjs +23956 -0
  55. package/dist/mpp/with-bridge.cjs.map +1 -0
  56. package/dist/mpp/with-bridge.d.cts +53 -0
  57. package/dist/mpp/with-bridge.d.ts +53 -0
  58. package/dist/mpp/with-bridge.js +23926 -0
  59. package/dist/mpp/with-bridge.js.map +1 -0
  60. package/dist/plugins.d.cts +2 -2
  61. package/dist/plugins.d.ts +2 -2
  62. package/dist/react/index.cjs +131 -1
  63. package/dist/react/index.cjs.map +1 -1
  64. package/dist/react/index.d.cts +1 -1
  65. package/dist/react/index.d.ts +1 -1
  66. package/dist/react/index.js +131 -1
  67. package/dist/react/index.js.map +1 -1
  68. package/dist/{server-DaySqG5H.d.ts → server-D9ZfrFFx.d.ts} +1 -1
  69. package/dist/{server-CBZ2RjEP.d.cts → server-DgMG2zhy.d.cts} +1 -1
  70. package/dist/server.cjs +6 -39
  71. package/dist/server.cjs.map +1 -1
  72. package/dist/server.d.cts +2 -2
  73. package/dist/server.d.ts +2 -2
  74. package/dist/server.js +6 -39
  75. package/dist/server.js.map +1 -1
  76. package/dist/{types-Y9ni5XwY.d.cts → types-DjEveKgt.d.cts} +1 -1
  77. package/dist/{types-Y9ni5XwY.d.ts → types-DjEveKgt.d.ts} +1 -1
  78. package/package.json +31 -1
package/dist/index.js CHANGED
@@ -7560,7 +7560,6 @@ var Relai = class {
7560
7560
  const integritasMode = integritasFlow === "single" ? "single_signature_fee_included" : integritasFlow === "dual" ? "dual_signature_split" : void 0;
7561
7561
  const paymentHeader = req.headers["x-payment"] || req.headers["payment-signature"] || req.headers["x-payment-signature"];
7562
7562
  const authHeader = req.headers["authorization"] || "";
7563
- console.log(`[Relai] MPP check: paymentHeader=${!!paymentHeader}, hasMpp=${!!self.mpp}, authHeader=${authHeader?.slice(0, 30)}`);
7564
7563
  if (!paymentHeader && self.mpp && /^Payment\s+/i.test(authHeader)) {
7565
7564
  try {
7566
7565
  const mppAmount = resolvedPrice.toFixed(6);
@@ -7572,10 +7571,7 @@ var Relai = class {
7572
7571
  const mppRequest = new Request(mppUrl, { method: req.method, headers: mppHeaders });
7573
7572
  const chargeHandler = self.mpp.charge({ amount: mppAmount });
7574
7573
  const mppResult = await chargeHandler(mppRequest);
7575
- console.log(`[Relai] MPP charge result: status=${mppResult.status}, keys=${Object.keys(mppResult)}, hasChallenge=${!!mppResult.challenge}, hasWithReceipt=${!!mppResult.withReceipt}`);
7576
7574
  if (mppResult.status === 402 && mppResult.challenge instanceof Response) {
7577
- const retryAuth = mppResult.challenge.headers.get("www-authenticate");
7578
- console.log(`[Relai] MPP re-challenged (credential not accepted). New WWW-Auth: ${retryAuth?.slice(0, 60)}`);
7579
7575
  }
7580
7576
  if (mppResult.status !== 200 && !mppResult.withReceipt && mppResult.status !== 402) {
7581
7577
  if (self.plugins.length > 0) {
@@ -7618,7 +7614,10 @@ var Relai = class {
7618
7614
  const dummyResponse = new Response(null);
7619
7615
  const receiptResponse = mppResult.withReceipt(dummyResponse);
7620
7616
  const receiptHeader = receiptResponse.headers.get("payment-receipt");
7621
- if (receiptHeader) res.setHeader("Payment-Receipt", receiptHeader);
7617
+ if (receiptHeader) {
7618
+ res.setHeader?.("Payment-Receipt", receiptHeader);
7619
+ res.setHeader?.("Cache-Control", "private");
7620
+ }
7622
7621
  }
7623
7622
  options.onPaymentSettled?.(req, {
7624
7623
  success: true,
@@ -7835,12 +7834,13 @@ var Relai = class {
7835
7834
  if (mppResult?.challenge instanceof Response) {
7836
7835
  const wwwAuth = mppResult.challenge.headers.get("www-authenticate");
7837
7836
  if (wwwAuth) {
7838
- res.setHeader("WWW-Authenticate", wwwAuth);
7837
+ res.setHeader?.("WWW-Authenticate", wwwAuth);
7839
7838
  }
7840
7839
  }
7841
7840
  } catch {
7842
7841
  }
7843
7842
  }
7843
+ res.setHeader?.("Cache-Control", "no-store");
7844
7844
  return res.status(402).json(paymentRequiredResponse);
7845
7845
  }
7846
7846
  let paymentProof;
@@ -7857,39 +7857,6 @@ var Relai = class {
7857
7857
  });
7858
7858
  }
7859
7859
  }
7860
- if (paymentProof.bridged === true && paymentProof.targetTxId) {
7861
- console.log(`[Relai] Bridged payment accepted: source=${paymentProof.sourceTxId}, target=${paymentProof.targetTxId}`);
7862
- const paymentInfo2 = {
7863
- verified: true,
7864
- transactionId: paymentProof.targetTxId,
7865
- payer: paymentProof.sourceTxId || "bridge",
7866
- network,
7867
- amount: resolvedPrice
7868
- };
7869
- req.payment = paymentInfo2;
7870
- req.x402Payer = paymentProof.sourceTxId || "bridge";
7871
- req.x402Paid = true;
7872
- req.x402Transaction = paymentProof.targetTxId;
7873
- req.x402Network = network;
7874
- req.x402Bridged = true;
7875
- req.x402SourceChain = paymentProof.sourceChain;
7876
- const paymentResponse2 = {
7877
- x402Version: 2,
7878
- scheme: "exact",
7879
- network: caip2,
7880
- transaction: paymentProof.targetTxId,
7881
- payer: paymentProof.sourceTxId,
7882
- amount: amount2,
7883
- asset,
7884
- bridged: true
7885
- };
7886
- res.setHeader(
7887
- "PAYMENT-RESPONSE",
7888
- Buffer.from(JSON.stringify(paymentResponse2)).toString("base64")
7889
- );
7890
- options.onPaymentSettled?.(req, { success: true, transaction: paymentProof.targetTxId, payer: paymentProof.sourceTxId });
7891
- return next();
7892
- }
7893
7860
  let settlePayTo;
7894
7861
  if (stripeConfig) {
7895
7862
  settlePayTo = paymentProof.payload?.authorization?.to || paymentProof.accepted?.payTo || "";
@@ -8047,6 +8014,73 @@ import {
8047
8014
  TOKEN_PROGRAM_ID,
8048
8015
  TOKEN_2022_PROGRAM_ID
8049
8016
  } from "@solana/spl-token";
8017
+
8018
+ // src/bridge.ts
8019
+ var RELAI_API_BASE = "https://api.relai.fi";
8020
+ var _cacheMap = /* @__PURE__ */ new Map();
8021
+ var CACHE_TTL = 5 * 60 * 1e3;
8022
+ async function getBridgeInfo(baseUrl = RELAI_API_BASE) {
8023
+ const key = baseUrl.replace(/\/$/, "");
8024
+ const now = Date.now();
8025
+ const cached2 = _cacheMap.get(key);
8026
+ if (cached2 && now - cached2.time < CACHE_TTL) return cached2.info;
8027
+ const url2 = `${key}/bridge/info`;
8028
+ const res = await fetch(url2);
8029
+ if (!res.ok) {
8030
+ if (cached2) return cached2.info;
8031
+ throw new Error(`[relai:bridge] Failed to fetch ${url2}: ${res.status}`);
8032
+ }
8033
+ const data = await res.json();
8034
+ const info = {
8035
+ settleEndpoint: data.settleEndpoint,
8036
+ supportedSourceChains: data.supportedSourceChains || [],
8037
+ supportedSourceAssets: data.supportedSourceAssets || [],
8038
+ payTo: data.payTo || {},
8039
+ feePayerSvm: data.feePayerSvm ?? null,
8040
+ feeBps: data.feeBps ?? 100,
8041
+ paymentFacilitator: data.paymentFacilitator || "https://facilitator.x402.fi"
8042
+ };
8043
+ _cacheMap.set(key, { info, time: now });
8044
+ return info;
8045
+ }
8046
+ async function settleBridge(settleEndpoint, body) {
8047
+ const res = await fetch(settleEndpoint, {
8048
+ method: "POST",
8049
+ headers: { "Content-Type": "application/json" },
8050
+ body: JSON.stringify(body)
8051
+ });
8052
+ if (!res.ok) {
8053
+ const err = await res.json().catch(() => ({}));
8054
+ throw new Error(`[relai:bridge] settle failed: ${err.error || res.status}${err.details ? " \u2014 " + err.details : ""}`);
8055
+ }
8056
+ return res.json();
8057
+ }
8058
+ function selectSourceChain(supportedChains, hasEvmWallet, hasSolanaWallet, preferredSourceChainId) {
8059
+ if (preferredSourceChainId && hasEvmWallet) {
8060
+ const preferred = `eip155:${preferredSourceChainId}`;
8061
+ if (supportedChains.includes(preferred)) {
8062
+ return { type: "evm", chain: preferred };
8063
+ }
8064
+ }
8065
+ if (hasSolanaWallet) {
8066
+ const sol = supportedChains.find((c) => c.startsWith("solana:"));
8067
+ if (sol) return { type: "solana", chain: sol };
8068
+ }
8069
+ if (hasEvmWallet) {
8070
+ for (const chain of supportedChains) {
8071
+ if (chain.startsWith("eip155:")) {
8072
+ return { type: "evm", chain };
8073
+ }
8074
+ }
8075
+ }
8076
+ return null;
8077
+ }
8078
+ function computeSourceAmount(targetAmount, feeBps) {
8079
+ const fee = targetAmount * BigInt(feeBps) / 10000n;
8080
+ return targetAmount + fee;
8081
+ }
8082
+
8083
+ // src/client.ts
8050
8084
  var PERMIT_NETWORKS = /* @__PURE__ */ new Set([]);
8051
8085
  var DEFAULT_EVM_RPC_URLS = {
8052
8086
  "skale-base": "https://skale-base.skalenodes.com/v1/base",
@@ -8072,7 +8106,8 @@ function createX402Client(config2) {
8072
8106
  integritas,
8073
8107
  verbose = false,
8074
8108
  defaultHeaders = {},
8075
- mpp
8109
+ mpp,
8110
+ bridge: bridgeConfig
8076
8111
  } = config2;
8077
8112
  const relayWsEnabled = relayWs?.enabled === true;
8078
8113
  const relayWsPreflightTimeoutMs = relayWs?.preflightTimeoutMs ?? 5e3;
@@ -9187,6 +9222,70 @@ function createX402Client(config2) {
9187
9222
  headers: { ...requestHeaders, "X-PAYMENT": paymentHeader }
9188
9223
  });
9189
9224
  }
9225
+ if (bridgeConfig?.enabled) {
9226
+ log("No direct wallet match \u2014 attempting auto-bridge via RelAI API");
9227
+ try {
9228
+ const info = await getBridgeInfo(bridgeConfig.baseUrl);
9229
+ const targetAccept = accepts[0];
9230
+ const hasEvm = !!effectiveWallets.evm;
9231
+ const hasSol = !!hasSolanaWallet;
9232
+ const source = selectSourceChain(info.supportedSourceChains, hasEvm, hasSol);
9233
+ if (source) {
9234
+ const bridgePayTo = info.payTo[source.chain];
9235
+ if (bridgePayTo) {
9236
+ const targetAmount = targetAccept.amount || targetAccept.maxAmountRequired;
9237
+ const sourceAmount = computeSourceAmount(BigInt(targetAmount), info.feeBps).toString();
9238
+ const sourceChainIdx = info.supportedSourceChains.indexOf(source.chain);
9239
+ 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")) || "");
9240
+ let sourcePaymentHeader;
9241
+ const sourceAccept = {
9242
+ scheme: "exact",
9243
+ network: source.chain,
9244
+ asset: sourceAsset,
9245
+ payTo: bridgePayTo,
9246
+ amount: sourceAmount,
9247
+ extra: {
9248
+ ...targetAccept.extra || {},
9249
+ ...source.type === "solana" && info.feePayerSvm ? { feePayer: info.feePayerSvm } : {}
9250
+ }
9251
+ };
9252
+ if (source.type === "solana" && hasSolanaWallet) {
9253
+ sourcePaymentHeader = await buildSolanaPayment(sourceAccept, requirements, url2);
9254
+ } else if (source.type === "evm") {
9255
+ const evmNetwork = normalizeNetwork(source.chain);
9256
+ const usePermit = evmNetwork && PERMIT_NETWORKS.has(evmNetwork);
9257
+ sourcePaymentHeader = usePermit ? await buildEvmPermitPayment(sourceAccept, requirements, url2) : await buildEvmPayment(sourceAccept, requirements, url2);
9258
+ } else {
9259
+ throw new Error(`[relai-x402] No wallet for source chain type: ${source.type}`);
9260
+ }
9261
+ const settleData = await settleBridge(info.settleEndpoint, {
9262
+ sourcePayment: sourcePaymentHeader,
9263
+ sourceChain: source.chain,
9264
+ targetAccept: {
9265
+ scheme: "exact",
9266
+ network: targetAccept.network,
9267
+ asset: targetAccept.asset,
9268
+ payTo: targetAccept.payTo,
9269
+ amount: targetAmount
9270
+ },
9271
+ requirements,
9272
+ resource: url2,
9273
+ paymentFacilitator: info.paymentFacilitator
9274
+ });
9275
+ if (settleData.xPayment) {
9276
+ log(`Auto-bridge settled: target=${settleData.targetTxId}`);
9277
+ return fetch(input, {
9278
+ ...requestInitWithHeaders,
9279
+ headers: { ...requestHeaders, "X-PAYMENT": settleData.xPayment }
9280
+ });
9281
+ }
9282
+ throw new Error("[relai-x402] Bridge settle did not return xPayment");
9283
+ }
9284
+ }
9285
+ } catch (bridgeErr) {
9286
+ log(`Auto-bridge failed: ${bridgeErr instanceof Error ? bridgeErr.message : bridgeErr}`);
9287
+ }
9288
+ }
9190
9289
  throw new Error(buildNoWalletError(accepts, false));
9191
9290
  }
9192
9291
  const { accept, chain } = selected;
@@ -9283,8 +9382,37 @@ function submitRelayFeedback(config2) {
9283
9382
  }
9284
9383
 
9285
9384
  // src/payment-codes.ts
9286
- var DEFAULT_FACILITATOR = "https://relai.fi/facilitator";
9287
- var DEFAULT_USDC_BASE = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
9385
+ import { ethers as ethers2 } from "ethers";
9386
+ var NETWORK_CONFIGS = {
9387
+ "base": {
9388
+ chainId: 8453,
9389
+ usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
9390
+ domainName: "USD Coin",
9391
+ rpc: "https://mainnet.base.org",
9392
+ settlementNetwork: "base"
9393
+ },
9394
+ "base-sepolia": {
9395
+ chainId: 84532,
9396
+ usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
9397
+ domainName: "USDC",
9398
+ rpc: "https://sepolia.base.org",
9399
+ settlementNetwork: "base-sepolia"
9400
+ },
9401
+ "skale-base-sepolia": {
9402
+ chainId: 324705682,
9403
+ usdc: "0x2e08028E3C4c2356572E096d8EF835cD5C6030bD",
9404
+ domainName: "Bridged USDC (SKALE Bridge)",
9405
+ rpc: "https://base-sepolia-testnet.skalenodes.com/v1/jubilant-horrible-ancha",
9406
+ settlementNetwork: "skale-base-sepolia"
9407
+ },
9408
+ "skale-base": {
9409
+ chainId: 1482601649,
9410
+ usdc: "0x85889c8c714505E0c94b30fcfcF64fE3Ac8FCb20",
9411
+ domainName: "Bridged USDC",
9412
+ rpc: "https://skale-base.skalenodes.com/v1/base",
9413
+ settlementNetwork: "skale-base"
9414
+ }
9415
+ };
9288
9416
  var EIP3009_TYPES = {
9289
9417
  TransferWithAuthorization: [
9290
9418
  { name: "from", type: "address" },
@@ -9295,6 +9423,14 @@ var EIP3009_TYPES = {
9295
9423
  { name: "nonce", type: "bytes32" }
9296
9424
  ]
9297
9425
  };
9426
+ function createPrivateKeySigner(privateKey) {
9427
+ const wallet = new ethers2.Wallet(privateKey);
9428
+ return {
9429
+ getAddress: () => Promise.resolve(wallet.address),
9430
+ signTypedData: (domain2, types, value) => wallet.signTypedData(domain2, types, value)
9431
+ };
9432
+ }
9433
+ var DEFAULT_FACILITATOR = "https://relai.fi/facilitator";
9298
9434
  function randomBytes32() {
9299
9435
  const bytes = new Uint8Array(32);
9300
9436
  if (typeof globalThis.crypto !== "undefined") {
@@ -9305,69 +9441,323 @@ function randomBytes32() {
9305
9441
  }
9306
9442
  return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
9307
9443
  }
9308
- async function generatePaymentCode(config2, params) {
9309
- const { signer, to, value, usdcContract, ttl = 120 } = params;
9310
- const facilitatorUrl = config2.facilitatorUrl || DEFAULT_FACILITATOR;
9311
- const usdc = usdcContract || DEFAULT_USDC_BASE;
9312
- const from4 = await signer.getAddress();
9313
- const now = Math.floor(Date.now() / 1e3);
9314
- const validAfter = 0;
9315
- const validBefore = now + ttl;
9316
- const nonce = randomBytes32();
9444
+ async function fetchToAddress(facilitatorUrl, network) {
9445
+ const res = await fetch(`${facilitatorUrl}/payment-codes/relayer?network=${network}`);
9446
+ if (!res.ok) throw new Error("Failed to fetch relayer address from facilitator");
9447
+ const data = await res.json();
9448
+ if (!data.toAddress) throw new Error("Facilitator returned no toAddress");
9449
+ return data.toAddress;
9450
+ }
9451
+ async function signEip3009(signer, net, from4, toAddress, value, validAfter, validBefore, nonce, usdcOverride) {
9317
9452
  const domain2 = {
9318
- name: "USD Coin",
9453
+ name: net.domainName,
9319
9454
  version: "2",
9320
- chainId: 8453,
9321
- // Base mainnet
9322
- verifyingContract: usdc
9455
+ chainId: net.chainId,
9456
+ verifyingContract: usdcOverride ?? net.usdc
9323
9457
  };
9324
- const message = {
9458
+ return signer.signTypedData(domain2, EIP3009_TYPES, {
9325
9459
  from: from4,
9326
- to,
9327
- value: BigInt(value).toString(),
9460
+ to: toAddress,
9461
+ value,
9328
9462
  validAfter,
9329
9463
  validBefore,
9330
9464
  nonce
9331
- };
9332
- const signature2 = await signer.signTypedData(domain2, EIP3009_TYPES, message);
9465
+ });
9466
+ }
9467
+ async function generatePaymentCode(config2, params) {
9468
+ const {
9469
+ signer,
9470
+ value,
9471
+ ttl = 86400,
9472
+ description,
9473
+ payee,
9474
+ usdcContract,
9475
+ network = "base-sepolia"
9476
+ } = params;
9477
+ const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
9478
+ const net = NETWORK_CONFIGS[network];
9479
+ if (!net) throw new Error(`Unsupported network: ${network}`);
9480
+ const from4 = await signer.getAddress();
9481
+ const now = Math.floor(Date.now() / 1e3);
9482
+ const validBefore = now + ttl;
9483
+ const nonce = randomBytes32();
9484
+ const usdc = usdcContract ?? net.usdc;
9485
+ const toAddress = await fetchToAddress(facilitatorUrl, network);
9486
+ const signature2 = await signEip3009(
9487
+ signer,
9488
+ net,
9489
+ from4,
9490
+ toAddress,
9491
+ BigInt(value).toString(),
9492
+ 0,
9493
+ validBefore,
9494
+ nonce,
9495
+ usdc
9496
+ );
9333
9497
  const res = await fetch(`${facilitatorUrl}/payment-codes`, {
9334
9498
  method: "POST",
9335
9499
  headers: { "Content-Type": "application/json" },
9336
9500
  body: JSON.stringify({
9337
9501
  from: from4,
9338
- to,
9339
9502
  value: BigInt(value).toString(),
9340
- validAfter,
9503
+ validAfter: 0,
9341
9504
  validBefore,
9342
9505
  nonce,
9343
9506
  signature: signature2,
9344
- usdcContract: usdc
9507
+ usdcContract: usdc,
9508
+ settlementNetwork: net.settlementNetwork,
9509
+ ...description ? { description } : {},
9510
+ ...payee ? { payee } : {}
9345
9511
  })
9346
9512
  });
9347
9513
  if (!res.ok) {
9348
9514
  const err = await res.json().catch(() => ({}));
9349
- throw new Error(`Failed to register payment code: ${err.error || res.status}`);
9515
+ throw new Error(`Failed to register payment code: ${err.error ?? res.status}`);
9350
9516
  }
9351
9517
  return res.json();
9352
9518
  }
9353
- async function redeemPaymentCode(config2, code) {
9354
- const facilitatorUrl = config2.facilitatorUrl || DEFAULT_FACILITATOR;
9355
- const res = await fetch(`${facilitatorUrl}/payment-codes/${code.toUpperCase()}/redeem`, {
9519
+ async function generatePaymentCodesBatch(config2, params) {
9520
+ const {
9521
+ signer,
9522
+ codes,
9523
+ payee,
9524
+ usdcContract,
9525
+ network = "base-sepolia",
9526
+ authToken
9527
+ } = params;
9528
+ const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
9529
+ const net = NETWORK_CONFIGS[network];
9530
+ if (!net) throw new Error(`Unsupported network: ${network}`);
9531
+ if (codes.length > 20) throw new Error("Maximum 20 codes per batch");
9532
+ const from4 = await signer.getAddress();
9533
+ const usdc = usdcContract ?? net.usdc;
9534
+ const now = Math.floor(Date.now() / 1e3);
9535
+ const toAddress = await fetchToAddress(facilitatorUrl, network);
9536
+ const signedCodes = await Promise.all(
9537
+ codes.map(async (item) => {
9538
+ const validBefore = now + (item.ttl ?? 86400);
9539
+ const nonce = randomBytes32();
9540
+ const value = BigInt(item.value).toString();
9541
+ const signature2 = await signEip3009(
9542
+ signer,
9543
+ net,
9544
+ from4,
9545
+ toAddress,
9546
+ value,
9547
+ 0,
9548
+ validBefore,
9549
+ nonce,
9550
+ usdc
9551
+ );
9552
+ return { value, validAfter: 0, validBefore, nonce, signature: signature2 };
9553
+ })
9554
+ );
9555
+ const res = await fetch(`${facilitatorUrl}/payment-codes/batch`, {
9356
9556
  method: "POST",
9357
- headers: { "Content-Type": "application/json" }
9557
+ headers: {
9558
+ "Content-Type": "application/json",
9559
+ "Authorization": `Bearer ${authToken}`
9560
+ },
9561
+ body: JSON.stringify({
9562
+ from: from4,
9563
+ settlementNetwork: net.settlementNetwork,
9564
+ usdcContract: usdc,
9565
+ ...payee ? { payee } : {},
9566
+ codes: signedCodes
9567
+ })
9358
9568
  });
9359
9569
  if (!res.ok) {
9360
9570
  const err = await res.json().catch(() => ({}));
9361
- throw new Error(`Failed to redeem payment code: ${err.error || res.status}`);
9571
+ throw new Error(`Batch registration failed: ${err.error ?? res.status}`);
9572
+ }
9573
+ return res.json();
9574
+ }
9575
+ async function redeemPaymentCode(config2, code, payee) {
9576
+ const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
9577
+ const res = await fetch(
9578
+ `${facilitatorUrl}/payment-codes/${code.trim().toUpperCase()}/redeem`,
9579
+ {
9580
+ method: "POST",
9581
+ headers: { "Content-Type": "application/json" },
9582
+ body: JSON.stringify(payee ? { payee } : {})
9583
+ }
9584
+ );
9585
+ if (!res.ok) {
9586
+ const err = await res.json().catch(() => ({}));
9587
+ throw new Error(err.error ?? `Redeem failed: ${res.status}`);
9362
9588
  }
9363
9589
  return res.json();
9364
9590
  }
9365
9591
  async function getPaymentCode(config2, code) {
9366
- const facilitatorUrl = config2.facilitatorUrl || DEFAULT_FACILITATOR;
9367
- const res = await fetch(`${facilitatorUrl}/payment-codes/${code.toUpperCase()}`);
9592
+ const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
9593
+ const res = await fetch(
9594
+ `${facilitatorUrl}/payment-codes/${code.trim().toUpperCase()}`
9595
+ );
9596
+ if (!res.ok) {
9597
+ const err = await res.json().catch(() => ({}));
9598
+ throw new Error(`Payment code not found: ${err.error ?? res.status}`);
9599
+ }
9600
+ return res.json();
9601
+ }
9602
+ async function cancelPaymentCode(config2, code) {
9603
+ const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR;
9604
+ const res = await fetch(
9605
+ `${facilitatorUrl}/payment-codes/${code.trim().toUpperCase()}`,
9606
+ { method: "DELETE" }
9607
+ );
9368
9608
  if (!res.ok) {
9369
9609
  const err = await res.json().catch(() => ({}));
9370
- throw new Error(`Payment code not found: ${err.error || res.status}`);
9610
+ throw new Error(`Cancel failed: ${err.error ?? res.status}`);
9611
+ }
9612
+ return res.json();
9613
+ }
9614
+
9615
+ // src/payment-requests.ts
9616
+ var DEFAULT_FACILITATOR2 = "https://relai.fi/facilitator";
9617
+ var EIP3009_TYPES2 = {
9618
+ TransferWithAuthorization: [
9619
+ { name: "from", type: "address" },
9620
+ { name: "to", type: "address" },
9621
+ { name: "value", type: "uint256" },
9622
+ { name: "validAfter", type: "uint256" },
9623
+ { name: "validBefore", type: "uint256" },
9624
+ { name: "nonce", type: "bytes32" }
9625
+ ]
9626
+ };
9627
+ function randomBytes322() {
9628
+ const bytes = new Uint8Array(32);
9629
+ if (typeof globalThis.crypto !== "undefined") {
9630
+ globalThis.crypto.getRandomValues(bytes);
9631
+ } else {
9632
+ const { randomBytes: randomBytes2 } = __require("crypto");
9633
+ randomBytes2(32).copy(Buffer.from(bytes.buffer));
9634
+ }
9635
+ return "0x" + Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
9636
+ }
9637
+ async function createPayRequest(config2, params) {
9638
+ const {
9639
+ to,
9640
+ amount: amount2,
9641
+ network = "base-sepolia",
9642
+ description,
9643
+ ttlSeconds
9644
+ } = params;
9645
+ const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR2;
9646
+ const res = await fetch(`${facilitatorUrl}/payment-requests`, {
9647
+ method: "POST",
9648
+ headers: { "Content-Type": "application/json" },
9649
+ body: JSON.stringify({
9650
+ to,
9651
+ amount: Number(amount2),
9652
+ network,
9653
+ ...description ? { description } : {},
9654
+ ...ttlSeconds ? { ttlSeconds } : {}
9655
+ })
9656
+ });
9657
+ if (!res.ok) {
9658
+ const err = await res.json().catch(() => ({}));
9659
+ throw new Error(`Failed to create payment request: ${err.error ?? res.status}`);
9660
+ }
9661
+ return res.json();
9662
+ }
9663
+ async function getPayRequest(config2, code) {
9664
+ const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR2;
9665
+ const res = await fetch(
9666
+ `${facilitatorUrl}/payment-requests/${code.trim().toUpperCase()}`
9667
+ );
9668
+ if (!res.ok) {
9669
+ const err = await res.json().catch(() => ({}));
9670
+ throw new Error(`Payment request not found: ${err.error ?? res.status}`);
9671
+ }
9672
+ return res.json();
9673
+ }
9674
+ async function payPayRequest(config2, code, signer) {
9675
+ const facilitatorUrl = config2.facilitatorUrl ?? DEFAULT_FACILITATOR2;
9676
+ const info = await getPayRequest(config2, code);
9677
+ if (!info.payable) {
9678
+ throw new Error(
9679
+ info.status === "paid" ? "Payment request already paid" : "Payment request expired or not payable"
9680
+ );
9681
+ }
9682
+ const netKey = info.network;
9683
+ const net = NETWORK_CONFIGS[netKey];
9684
+ if (!net) throw new Error(`Unknown network from payment request: ${info.network}`);
9685
+ const from4 = await signer.getAddress();
9686
+ const now = Math.floor(Date.now() / 1e3);
9687
+ const validBefore = Math.min(now + 300, info.validUntil);
9688
+ const nonce = randomBytes322();
9689
+ const domain2 = {
9690
+ name: net.domainName,
9691
+ version: "2",
9692
+ chainId: net.chainId,
9693
+ verifyingContract: info.usdcContract
9694
+ };
9695
+ const signature2 = await signer.signTypedData(domain2, EIP3009_TYPES2, {
9696
+ from: from4,
9697
+ to: info.toAddress,
9698
+ // settler/relayer — NOT the merchant directly
9699
+ value: String(info.amount),
9700
+ validAfter: 0,
9701
+ validBefore,
9702
+ nonce
9703
+ });
9704
+ const res = await fetch(
9705
+ `${facilitatorUrl}/payment-requests/${code.trim().toUpperCase()}/pay`,
9706
+ {
9707
+ method: "POST",
9708
+ headers: { "Content-Type": "application/json" },
9709
+ body: JSON.stringify({ from: from4, validAfter: 0, validBefore, nonce, signature: signature2 })
9710
+ }
9711
+ );
9712
+ if (!res.ok) {
9713
+ const err = await res.json().catch(() => ({}));
9714
+ throw new Error(err.error ?? `Payment failed: ${res.status}`);
9715
+ }
9716
+ return res.json();
9717
+ }
9718
+ async function payPayRequestWithCode(config2, requestCode, paymentCode, options = {}) {
9719
+ const { allowOverpayment = true, returnChange = "code" } = options;
9720
+ const info = await getPayRequest(config2, requestCode);
9721
+ if (!info.payable) {
9722
+ throw new Error(
9723
+ info.status === "paid" ? "Payment request already paid" : "Payment request expired or not payable"
9724
+ );
9725
+ }
9726
+ const codeStatus = await getPaymentCode(config2, paymentCode);
9727
+ if (!codeStatus.redeemable) {
9728
+ throw new Error(
9729
+ codeStatus.redeemed ? "Payment code already redeemed" : "Payment code expired or not redeemable"
9730
+ );
9731
+ }
9732
+ const codeValue = BigInt(codeStatus.value);
9733
+ const reqAmount = BigInt(info.amount);
9734
+ if (codeValue < reqAmount) {
9735
+ throw new Error(
9736
+ `Payment code value (${Number(codeValue) / 1e6} USDC) is less than the request amount (${Number(reqAmount) / 1e6} USDC)`
9737
+ );
9738
+ }
9739
+ if (!allowOverpayment && codeValue > reqAmount) {
9740
+ throw new Error(
9741
+ `Payment code value (${Number(codeValue) / 1e6} USDC) exceeds the request amount (${Number(reqAmount) / 1e6} USDC). Generate a code for the exact amount, or pass { allowOverpayment: true }.`
9742
+ );
9743
+ }
9744
+ const facilitatorUrl = config2.facilitatorUrl ?? "https://relai.fi/facilitator";
9745
+ const usePartial = codeValue > reqAmount;
9746
+ const res = await fetch(
9747
+ `${facilitatorUrl}/payment-codes/${paymentCode.trim().toUpperCase()}/redeem`,
9748
+ {
9749
+ method: "POST",
9750
+ headers: { "Content-Type": "application/json" },
9751
+ body: JSON.stringify({
9752
+ payee: info.to,
9753
+ ...usePartial ? { invoiceAmount: info.amount.toString() } : {},
9754
+ ...usePartial ? { returnChangeAsCode: returnChange === "code" } : {}
9755
+ })
9756
+ }
9757
+ );
9758
+ if (!res.ok) {
9759
+ const err = await res.json().catch(() => ({}));
9760
+ throw new Error(err.error ?? `Redeem failed: ${res.status}`);
9371
9761
  }
9372
9762
  return res.json();
9373
9763
  }
@@ -22882,8 +23272,50 @@ var charge = Method_exports.from({
22882
23272
  }
22883
23273
  });
22884
23274
 
22885
- // src/mpp/evm-server.ts
23275
+ // src/mpp/verify-erc20.ts
22886
23276
  var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
23277
+ async function verifyErc20Transfer(opts) {
23278
+ const { txHash, rpcUrl, tokenAddress, recipient, expectedAmount } = opts;
23279
+ let receipt = null;
23280
+ for (let attempt = 0; attempt < 5; attempt++) {
23281
+ const receiptRes = await fetch(rpcUrl, {
23282
+ method: "POST",
23283
+ headers: { "Content-Type": "application/json" },
23284
+ body: JSON.stringify({
23285
+ jsonrpc: "2.0",
23286
+ id: 1,
23287
+ method: "eth_getTransactionReceipt",
23288
+ params: [txHash]
23289
+ })
23290
+ });
23291
+ const receiptData = await receiptRes.json();
23292
+ if (receiptData.error) {
23293
+ throw new Error(`RPC error: ${receiptData.error.message}`);
23294
+ }
23295
+ receipt = receiptData.result;
23296
+ if (receipt) break;
23297
+ await new Promise((r) => setTimeout(r, (attempt + 1) * 1e3));
23298
+ }
23299
+ if (!receipt) {
23300
+ throw new Error("Transaction not found or not yet confirmed");
23301
+ }
23302
+ if (receipt.status !== "0x1") {
23303
+ throw new Error("Transaction failed on-chain");
23304
+ }
23305
+ const recipientPadded = "0x" + recipient.slice(2).toLowerCase().padStart(64, "0");
23306
+ const tokenLower = tokenAddress.toLowerCase();
23307
+ const matchingLog = receipt.logs.find((log) => {
23308
+ if (log.address.toLowerCase() !== tokenLower) return false;
23309
+ if (log.topics[0] !== TRANSFER_EVENT_TOPIC) return false;
23310
+ if (log.topics[2]?.toLowerCase() !== recipientPadded) return false;
23311
+ return BigInt(log.data) >= expectedAmount;
23312
+ });
23313
+ if (!matchingLog) {
23314
+ throw new Error("No matching ERC-20 Transfer found for recipient and amount");
23315
+ }
23316
+ }
23317
+
23318
+ // src/mpp/evm-server.ts
22887
23319
  function evmCharge(config2) {
22888
23320
  const {
22889
23321
  recipient,
@@ -22925,39 +23357,13 @@ function evmCharge(config2) {
22925
23357
  if (!txHash || !txHash.startsWith("0x")) {
22926
23358
  throw new Error("Missing or invalid transaction hash in credential payload");
22927
23359
  }
22928
- const expectedAmount = BigInt(cred.challenge.request.amount);
22929
- const receiptRes = await fetch(rpcUrl, {
22930
- method: "POST",
22931
- headers: { "Content-Type": "application/json" },
22932
- body: JSON.stringify({
22933
- jsonrpc: "2.0",
22934
- id: 1,
22935
- method: "eth_getTransactionReceipt",
22936
- params: [txHash]
22937
- })
22938
- });
22939
- const receiptData = await receiptRes.json();
22940
- if (receiptData.error) {
22941
- throw new Error(`RPC error: ${receiptData.error.message}`);
22942
- }
22943
- const receipt = receiptData.result;
22944
- if (!receipt) {
22945
- throw new Error("Transaction not found or not yet confirmed");
22946
- }
22947
- if (receipt.status !== "0x1") {
22948
- throw new Error("Transaction failed on-chain");
22949
- }
22950
- const recipientPadded = "0x" + recipient.slice(2).toLowerCase().padStart(64, "0");
22951
- const tokenLower = tokenAddress.toLowerCase();
22952
- const matchingLog = receipt.logs.find((log) => {
22953
- if (log.address.toLowerCase() !== tokenLower) return false;
22954
- if (log.topics[0] !== TRANSFER_EVENT_TOPIC) return false;
22955
- if (log.topics[2]?.toLowerCase() !== recipientPadded) return false;
22956
- return BigInt(log.data) >= expectedAmount;
23360
+ await verifyErc20Transfer({
23361
+ txHash,
23362
+ rpcUrl,
23363
+ tokenAddress,
23364
+ recipient,
23365
+ expectedAmount: BigInt(cred.challenge.request.amount)
22957
23366
  });
22958
- if (!matchingLog) {
22959
- throw new Error("No matching ERC-20 Transfer found for recipient and amount");
22960
- }
22961
23367
  return Receipt_exports.from({
22962
23368
  method: "evm",
22963
23369
  reference: txHash,
@@ -26283,12 +26689,72 @@ async function waitForReceipt(rpcUrl, txHash, timeoutMs = 3e4) {
26283
26689
  }
26284
26690
  return false;
26285
26691
  }
26692
+
26693
+ // src/mpp/bridge-method.ts
26694
+ var charge2 = Method_exports.from({
26695
+ intent: "charge",
26696
+ name: "bridge",
26697
+ schema: {
26698
+ credential: {
26699
+ payload: zod_exports.object({
26700
+ /** "settled" = client called bridge settle, targetTxHash is proof */
26701
+ type: zod_exports.string(),
26702
+ /** Target chain tx hash (0x-prefixed) — server verifies on-chain */
26703
+ targetTxHash: zod_exports.optional(zod_exports.string()),
26704
+ /** Source chain tx hash/signature (for auditing) */
26705
+ sourceTxHash: zod_exports.optional(zod_exports.string()),
26706
+ /** CAIP-2 source chain used */
26707
+ sourceChain: zod_exports.optional(zod_exports.string())
26708
+ })
26709
+ },
26710
+ request: zod_exports.object({
26711
+ /** Amount in target token base units */
26712
+ amount: zod_exports.string(),
26713
+ /** Target ERC-20 token contract address */
26714
+ currency: zod_exports.string(),
26715
+ /** Target chain recipient address (merchant) */
26716
+ recipient: zod_exports.string(),
26717
+ /** Human-readable description */
26718
+ description: zod_exports.optional(zod_exports.string()),
26719
+ methodDetails: zod_exports.object({
26720
+ /** Target chain ID (where the merchant gets paid) */
26721
+ targetChainId: zod_exports.number(),
26722
+ /** Human-readable target network name (e.g. "skale-base") */
26723
+ targetNetwork: zod_exports.optional(zod_exports.string()),
26724
+ /** Target chain RPC URL (for server-side verification) */
26725
+ targetRpcUrl: zod_exports.optional(zod_exports.string()),
26726
+ /** Target token decimals */
26727
+ targetDecimals: zod_exports.optional(zod_exports.number()),
26728
+ /** Bridge settle endpoint URL */
26729
+ settleEndpoint: zod_exports.string(),
26730
+ /** Supported source chains (CAIP-2 format, e.g. ["eip155:8453", "solana:5eykt4..."]) */
26731
+ supportedSourceChains: zod_exports.array(zod_exports.string()),
26732
+ /** Supported source token addresses */
26733
+ supportedSourceAssets: zod_exports.optional(zod_exports.array(zod_exports.string())),
26734
+ /** Bridge receiver addresses per source chain: { [caip2]: address } */
26735
+ payToMap: zod_exports.record(zod_exports.string(), zod_exports.string()),
26736
+ /** Solana fee payer address (bridge facilitator sponsors gas) */
26737
+ feePayerSvm: zod_exports.optional(zod_exports.string()),
26738
+ /** Bridge fee in basis points */
26739
+ feeBps: zod_exports.optional(zod_exports.number()),
26740
+ /** Payment facilitator URL */
26741
+ paymentFacilitator: zod_exports.optional(zod_exports.string()),
26742
+ /** Unique reference ID (for replay protection) */
26743
+ reference: zod_exports.string()
26744
+ })
26745
+ })
26746
+ }
26747
+ });
26748
+
26749
+ // src/mpp/bridge-server.ts
26750
+ var BRIDGE_INFO_TTL_MS = 5 * 60 * 1e3;
26286
26751
  export {
26287
26752
  BASE_MAINNET_NETWORK,
26288
26753
  CAIP2_TO_NETWORK,
26289
26754
  CHAIN_IDS,
26290
26755
  EXPLORER_TX_URL,
26291
26756
  NETWORK_CAIP2,
26757
+ NETWORK_CONFIGS,
26292
26758
  NETWORK_LABELS,
26293
26759
  NETWORK_TOKENS,
26294
26760
  NETWORK_V1_TO_V2,
@@ -26300,9 +26766,12 @@ export {
26300
26766
  USDC_ADDRESSES,
26301
26767
  USDC_BASE,
26302
26768
  USDC_SOLANA,
26769
+ cancelPaymentCode,
26303
26770
  convertPayloadToVersion,
26304
26771
  convertV1ToV2,
26305
26772
  convertV2ToV1,
26773
+ createPayRequest,
26774
+ createPrivateKeySigner,
26306
26775
  createX402Client,
26307
26776
  Relai as default,
26308
26777
  detectPayloadVersion,
@@ -26312,6 +26781,8 @@ export {
26312
26781
  formatUsd,
26313
26782
  fromAtomicUnits,
26314
26783
  generatePaymentCode,
26784
+ generatePaymentCodesBatch,
26785
+ getPayRequest,
26315
26786
  getPaymentCode,
26316
26787
  isEvm,
26317
26788
  isEvmNetwork,
@@ -26321,6 +26792,8 @@ export {
26321
26792
  networkV2ToV1,
26322
26793
  normalizeNetwork,
26323
26794
  normalizePaymentHeader,
26795
+ payPayRequest,
26796
+ payPayRequestWithCode,
26324
26797
  redeemPaymentCode,
26325
26798
  resolveToken,
26326
26799
  stripePayTo,