moltspay 1.4.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,14 +25,17 @@ import { homedir as homedir3 } from "os";
25
25
  import { join as join5, dirname, resolve } from "path";
26
26
  import { existsSync as existsSync5, writeFileSync as writeFileSync3, readFileSync as readFileSync5, unlinkSync, mkdirSync as mkdirSync3 } from "fs";
27
27
  import { spawn } from "child_process";
28
- import { ethers as ethers2 } from "ethers";
28
+ import { ethers as ethers4 } from "ethers";
29
29
 
30
30
  // src/client/index.ts
31
31
  init_esm_shims();
32
+
33
+ // src/client/node/index.ts
34
+ init_esm_shims();
32
35
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync, chmodSync } from "fs";
33
36
  import { homedir as homedir2 } from "os";
34
37
  import { join as join2 } from "path";
35
- import { Wallet, ethers } from "ethers";
38
+ import { Wallet as Wallet2, ethers as ethers2 } from "ethers";
36
39
 
37
40
  // src/chains/index.ts
38
41
  init_esm_shims();
@@ -526,16 +529,16 @@ var SolanaFacilitator = class extends BaseFacilitator {
526
529
  return this.supportedNetworks.includes(network);
527
530
  }
528
531
  };
529
- async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
532
+ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey, connection) {
530
533
  const chainConfig = SOLANA_CHAINS[chain];
531
- const connection = new Connection3(chainConfig.rpc, "confirmed");
534
+ const conn = connection ?? new Connection3(chainConfig.rpc, "confirmed");
532
535
  const mint = new PublicKey3(chainConfig.tokens.USDC.mint);
533
536
  const actualFeePayer = feePayerPubkey || senderPubkey;
534
537
  const senderATA = await getAssociatedTokenAddress2(mint, senderPubkey);
535
538
  const recipientATA = await getAssociatedTokenAddress2(mint, recipientPubkey);
536
539
  const transaction = new Transaction();
537
540
  try {
538
- await getAccount2(connection, recipientATA);
541
+ await getAccount2(conn, recipientATA);
539
542
  } catch {
540
543
  transaction.add(
541
544
  createAssociatedTokenAccountInstruction(
@@ -566,22 +569,202 @@ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amo
566
569
  // decimals
567
570
  )
568
571
  );
569
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
572
+ const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash();
570
573
  transaction.recentBlockhash = blockhash;
571
574
  transaction.feePayer = actualFeePayer;
572
575
  return transaction;
573
576
  }
574
577
 
575
- // src/client/index.ts
578
+ // src/client/node/index.ts
576
579
  import { PublicKey as PublicKey4 } from "@solana/web3.js";
577
580
 
578
- // src/client/types.ts
581
+ // src/client/core/index.ts
579
582
  init_esm_shims();
580
583
 
581
- // src/client/index.ts
584
+ // src/client/core/types.ts
585
+ init_esm_shims();
582
586
  var X402_VERSION = 2;
583
587
  var PAYMENT_REQUIRED_HEADER = "x-payment-required";
584
588
  var PAYMENT_HEADER = "x-payment";
589
+
590
+ // src/client/core/chain-map.ts
591
+ init_esm_shims();
592
+ var NETWORK_TO_CHAIN = {
593
+ "eip155:8453": "base",
594
+ "eip155:137": "polygon",
595
+ "eip155:84532": "base_sepolia",
596
+ "eip155:42431": "tempo_moderato",
597
+ "eip155:56": "bnb",
598
+ "eip155:97": "bnb_testnet",
599
+ "solana:mainnet": "solana",
600
+ "solana:devnet": "solana_devnet"
601
+ };
602
+ var CHAIN_TO_NETWORK = Object.fromEntries(
603
+ Object.entries(NETWORK_TO_CHAIN).map(([network, chain]) => [chain, network])
604
+ );
605
+ function networkToChainName(network) {
606
+ return NETWORK_TO_CHAIN[network] ?? null;
607
+ }
608
+
609
+ // src/client/core/base64.ts
610
+ init_esm_shims();
611
+ var BufferCtor = globalThis.Buffer;
612
+
613
+ // src/client/core/errors.ts
614
+ init_esm_shims();
615
+
616
+ // src/client/core/eip3009.ts
617
+ init_esm_shims();
618
+ var EIP3009_TYPES = {
619
+ TransferWithAuthorization: [
620
+ { name: "from", type: "address" },
621
+ { name: "to", type: "address" },
622
+ { name: "value", type: "uint256" },
623
+ { name: "validAfter", type: "uint256" },
624
+ { name: "validBefore", type: "uint256" },
625
+ { name: "nonce", type: "bytes32" }
626
+ ]
627
+ };
628
+ function buildEIP3009TypedData(args) {
629
+ const validAfter = args.validAfter ?? "0";
630
+ const validBefore = args.validBefore ?? (Math.floor(Date.now() / 1e3) + 3600).toString();
631
+ const authorization = {
632
+ from: args.from,
633
+ to: args.to,
634
+ value: args.value,
635
+ validAfter,
636
+ validBefore,
637
+ nonce: args.nonce
638
+ };
639
+ return {
640
+ domain: {
641
+ name: args.tokenName,
642
+ version: args.tokenVersion,
643
+ chainId: args.chainId,
644
+ verifyingContract: args.tokenAddress
645
+ },
646
+ types: EIP3009_TYPES,
647
+ primaryType: "TransferWithAuthorization",
648
+ message: authorization
649
+ };
650
+ }
651
+
652
+ // src/client/core/eip2612.ts
653
+ init_esm_shims();
654
+
655
+ // src/client/core/bnb-intent.ts
656
+ init_esm_shims();
657
+ var BNB_INTENT_TYPES = {
658
+ PaymentIntent: [
659
+ { name: "from", type: "address" },
660
+ { name: "to", type: "address" },
661
+ { name: "amount", type: "uint256" },
662
+ { name: "token", type: "address" },
663
+ { name: "service", type: "string" },
664
+ { name: "nonce", type: "uint256" },
665
+ { name: "deadline", type: "uint256" }
666
+ ]
667
+ };
668
+ var BNB_DOMAIN_NAME = "MoltsPay";
669
+ var BNB_DOMAIN_VERSION = "1";
670
+ function buildBnbIntentTypedData(args) {
671
+ const intent = {
672
+ from: args.from,
673
+ to: args.to,
674
+ amount: args.amount,
675
+ token: args.tokenAddress,
676
+ service: args.service,
677
+ nonce: args.nonce,
678
+ deadline: args.deadline
679
+ };
680
+ return {
681
+ domain: {
682
+ name: BNB_DOMAIN_NAME,
683
+ version: BNB_DOMAIN_VERSION,
684
+ chainId: args.chainId
685
+ },
686
+ types: BNB_INTENT_TYPES,
687
+ primaryType: "PaymentIntent",
688
+ message: intent
689
+ };
690
+ }
691
+
692
+ // src/client/core/solana-tx.ts
693
+ init_esm_shims();
694
+
695
+ // src/client/core/x402.ts
696
+ init_esm_shims();
697
+
698
+ // src/client/node/signer.ts
699
+ init_esm_shims();
700
+ import { ethers } from "ethers";
701
+ import { Transaction as Transaction2 } from "@solana/web3.js";
702
+ var NodeSigner = class {
703
+ evmWallet;
704
+ getSolanaKeypair;
705
+ constructor(evmWallet, options = {}) {
706
+ this.evmWallet = evmWallet;
707
+ this.getSolanaKeypair = options.getSolanaKeypair ?? (() => null);
708
+ }
709
+ async getEvmAddress() {
710
+ return this.evmWallet.address;
711
+ }
712
+ async getSolanaAddress() {
713
+ const kp = this.getSolanaKeypair();
714
+ return kp ? kp.publicKey.toBase58() : null;
715
+ }
716
+ async signTypedData(envelope) {
717
+ const mutableTypes = {};
718
+ for (const [key, fields] of Object.entries(envelope.types)) {
719
+ mutableTypes[key] = [...fields];
720
+ }
721
+ return this.evmWallet.signTypedData(
722
+ envelope.domain,
723
+ mutableTypes,
724
+ envelope.message
725
+ );
726
+ }
727
+ async sendEvmTransaction(args) {
728
+ const chain = findChainByChainId(args.chainId);
729
+ if (!chain) {
730
+ throw new Error(`sendEvmTransaction: unknown chainId ${args.chainId}`);
731
+ }
732
+ const provider = new ethers.JsonRpcProvider(chain.rpc);
733
+ const connected = this.evmWallet.connect(provider);
734
+ const tx = await connected.sendTransaction({
735
+ to: args.to,
736
+ data: args.data,
737
+ value: args.value ? BigInt(args.value) : 0n
738
+ });
739
+ return tx.hash;
740
+ }
741
+ async signSolanaTransaction(args) {
742
+ const kp = this.getSolanaKeypair();
743
+ if (!kp) {
744
+ throw new Error("signSolanaTransaction: no Solana wallet configured");
745
+ }
746
+ const tx = Transaction2.from(Buffer.from(args.transactionBase64, "base64"));
747
+ if (args.partialSign) {
748
+ tx.partialSign(kp);
749
+ } else {
750
+ tx.sign(kp);
751
+ }
752
+ return tx.serialize({ requireAllSignatures: false }).toString("base64");
753
+ }
754
+ };
755
+ function findChainByChainId(chainId) {
756
+ for (const cfg of Object.values(CHAINS)) {
757
+ if (cfg.chainId === chainId) {
758
+ return cfg;
759
+ }
760
+ }
761
+ return void 0;
762
+ }
763
+
764
+ // src/client/types.ts
765
+ init_esm_shims();
766
+
767
+ // src/client/node/index.ts
585
768
  var DEFAULT_CONFIG = {
586
769
  chain: "base",
587
770
  limits: {
@@ -594,6 +777,7 @@ var MoltsPayClient = class {
594
777
  config;
595
778
  walletData = null;
596
779
  wallet = null;
780
+ signer = null;
597
781
  todaySpending = 0;
598
782
  lastSpendingReset = 0;
599
783
  constructor(options = {}) {
@@ -602,7 +786,11 @@ var MoltsPayClient = class {
602
786
  this.walletData = this.loadWallet();
603
787
  this.loadSpending();
604
788
  if (this.walletData) {
605
- this.wallet = new Wallet(this.walletData.privateKey);
789
+ this.wallet = new Wallet2(this.walletData.privateKey);
790
+ const configDir = this.configDir;
791
+ this.signer = new NodeSigner(this.wallet, {
792
+ getSolanaKeypair: () => loadSolanaWallet(configDir)
793
+ });
606
794
  }
607
795
  }
608
796
  /**
@@ -730,20 +918,6 @@ var MoltsPayClient = class {
730
918
  } catch {
731
919
  throw new Error("Invalid x-payment-required header");
732
920
  }
733
- const networkToChainName = (network2) => {
734
- if (network2 === "solana:mainnet") return "solana";
735
- if (network2 === "solana:devnet") return "solana_devnet";
736
- const match = network2.match(/^eip155:(\d+)$/);
737
- if (!match) return null;
738
- const chainId = parseInt(match[1]);
739
- if (chainId === 8453) return "base";
740
- if (chainId === 137) return "polygon";
741
- if (chainId === 84532) return "base_sepolia";
742
- if (chainId === 42431) return "tempo_moderato";
743
- if (chainId === 56) return "bnb";
744
- if (chainId === 97) return "bnb_testnet";
745
- return null;
746
- };
747
921
  const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
748
922
  const userSpecifiedChain = options.chain;
749
923
  let selectedChain;
@@ -972,14 +1146,14 @@ Please specify: --chain <chain_name>`
972
1146
  async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
973
1147
  const { to, amount, token, chainName, chain, spender } = paymentDetails;
974
1148
  const tokenConfig = chain.tokens[token];
975
- const provider = new ethers.JsonRpcProvider(chain.rpc);
1149
+ const provider = new ethers2.JsonRpcProvider(chain.rpc);
976
1150
  const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
977
1151
  const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
978
1152
  if (allowance < amountWeiCheck) {
979
1153
  const nativeBalance = await provider.getBalance(this.wallet.address);
980
- const minGasBalance = ethers.parseEther("0.0005");
1154
+ const minGasBalance = ethers2.parseEther("0.0005");
981
1155
  if (nativeBalance < minGasBalance) {
982
- const nativeBNB = parseFloat(ethers.formatEther(nativeBalance)).toFixed(4);
1156
+ const nativeBNB = parseFloat(ethers2.formatEther(nativeBalance)).toFixed(4);
983
1157
  const isTestnet = chainName === "bnb_testnet";
984
1158
  if (isTestnet) {
985
1159
  throw new Error(
@@ -1013,35 +1187,21 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1013
1187
  );
1014
1188
  }
1015
1189
  const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
1016
- const intent = {
1190
+ const intentNonce = Date.now();
1191
+ const intentDeadline = Date.now() + 36e5;
1192
+ const envelope = buildBnbIntentTypedData({
1017
1193
  from: this.wallet.address,
1018
1194
  to,
1019
1195
  amount: amountWei,
1020
- token: tokenConfig.address,
1196
+ tokenAddress: tokenConfig.address,
1021
1197
  service,
1022
- nonce: Date.now(),
1023
- // Use timestamp as nonce for simplicity
1024
- deadline: Date.now() + 36e5
1025
- // 1 hour
1026
- };
1027
- const domain = {
1028
- name: "MoltsPay",
1029
- version: "1",
1198
+ nonce: intentNonce,
1199
+ deadline: intentDeadline,
1030
1200
  chainId: chain.chainId
1031
- };
1032
- const types = {
1033
- PaymentIntent: [
1034
- { name: "from", type: "address" },
1035
- { name: "to", type: "address" },
1036
- { name: "amount", type: "uint256" },
1037
- { name: "token", type: "address" },
1038
- { name: "service", type: "string" },
1039
- { name: "nonce", type: "uint256" },
1040
- { name: "deadline", type: "uint256" }
1041
- ]
1042
- };
1201
+ });
1043
1202
  console.log(`[MoltsPay] Signing BNB payment intent...`);
1044
- const signature = await this.wallet.signTypedData(domain, types, intent);
1203
+ const signature = await this.signer.signTypedData(envelope);
1204
+ const intent = envelope.message;
1045
1205
  const network = `eip155:${chain.chainId}`;
1046
1206
  const payload = {
1047
1207
  x402Version: 2,
@@ -1115,12 +1275,11 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1115
1275
  feePayerPubkey
1116
1276
  // Optional fee payer for gasless mode
1117
1277
  );
1118
- if (feePayerPubkey) {
1119
- transaction.partialSign(solanaWallet);
1120
- } else {
1121
- transaction.sign(solanaWallet);
1122
- }
1123
- const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
1278
+ const unsignedBase64 = transaction.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
1279
+ const signedTx = await this.signer.signSolanaTransaction({
1280
+ transactionBase64: unsignedBase64,
1281
+ partialSign: !!feePayerPubkey
1282
+ });
1124
1283
  console.log(`[MoltsPay] Transaction signed, sending to server...`);
1125
1284
  const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
1126
1285
  const payload = {
@@ -1167,7 +1326,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1167
1326
  * Check ERC20 allowance for a spender
1168
1327
  */
1169
1328
  async checkAllowance(tokenAddress, spender, provider) {
1170
- const contract = new ethers.Contract(
1329
+ const contract = new ethers2.Contract(
1171
1330
  tokenAddress,
1172
1331
  ["function allowance(address owner, address spender) view returns (uint256)"],
1173
1332
  provider
@@ -1178,41 +1337,29 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1178
1337
  * Sign EIP-3009 transferWithAuthorization (GASLESS)
1179
1338
  * This only signs - no on-chain transaction, no gas needed.
1180
1339
  * Supports both USDC and USDT.
1340
+ *
1341
+ * Delegates typed-data construction to `core/eip3009.ts` and the signature
1342
+ * itself to `this.signer`. That way Web Client (Phase 4) can reuse the same
1343
+ * flow with an EIP-1193 signer without duplicating typed-data layout.
1181
1344
  */
1182
1345
  async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
1183
- const validAfter = 0;
1184
- const validBefore = Math.floor(Date.now() / 1e3) + 3600;
1185
- const nonce = ethers.hexlify(ethers.randomBytes(32));
1186
1346
  const tokenConfig = chain.tokens[token];
1187
1347
  const value = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
1188
- const authorization = {
1348
+ const nonce = ethers2.hexlify(ethers2.randomBytes(32));
1349
+ const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
1350
+ const tokenVersion = domainOverride?.version || "2";
1351
+ const envelope = buildEIP3009TypedData({
1189
1352
  from: this.wallet.address,
1190
1353
  to,
1191
1354
  value,
1192
- validAfter: validAfter.toString(),
1193
- validBefore: validBefore.toString(),
1194
- nonce
1195
- };
1196
- const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
1197
- const tokenVersion = domainOverride?.version || "2";
1198
- const domain = {
1199
- name: tokenName,
1200
- version: tokenVersion,
1355
+ nonce,
1201
1356
  chainId: chain.chainId,
1202
- verifyingContract: tokenConfig.address
1203
- };
1204
- const types = {
1205
- TransferWithAuthorization: [
1206
- { name: "from", type: "address" },
1207
- { name: "to", type: "address" },
1208
- { name: "value", type: "uint256" },
1209
- { name: "validAfter", type: "uint256" },
1210
- { name: "validBefore", type: "uint256" },
1211
- { name: "nonce", type: "bytes32" }
1212
- ]
1213
- };
1214
- const signature = await this.wallet.signTypedData(domain, types, authorization);
1215
- return { authorization, signature };
1357
+ tokenAddress: tokenConfig.address,
1358
+ tokenName,
1359
+ tokenVersion
1360
+ });
1361
+ const signature = await this.signer.signTypedData(envelope);
1362
+ return { authorization: envelope.message, signature };
1216
1363
  }
1217
1364
  /**
1218
1365
  * Check spending limits
@@ -1294,15 +1441,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1294
1441
  loadWallet() {
1295
1442
  const walletPath = join2(this.configDir, "wallet.json");
1296
1443
  if (existsSync2(walletPath)) {
1297
- try {
1298
- const stats = statSync(walletPath);
1299
- const mode = stats.mode & 511;
1300
- if (mode !== 384) {
1301
- console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
1302
- console.warn(`[MoltsPay] Fixing permissions to 0600...`);
1303
- chmodSync(walletPath, 384);
1444
+ if (process.platform !== "win32") {
1445
+ try {
1446
+ const stats = statSync(walletPath);
1447
+ const mode = stats.mode & 511;
1448
+ if (mode !== 384) {
1449
+ console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
1450
+ console.warn(`[MoltsPay] Fixing permissions to 0600...`);
1451
+ chmodSync(walletPath, 384);
1452
+ }
1453
+ } catch {
1304
1454
  }
1305
- } catch (err) {
1306
1455
  }
1307
1456
  const content = readFileSync2(walletPath, "utf-8");
1308
1457
  return JSON.parse(content);
@@ -1314,7 +1463,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1314
1463
  */
1315
1464
  static init(configDir, options) {
1316
1465
  mkdirSync2(configDir, { recursive: true });
1317
- const wallet = Wallet.createRandom();
1466
+ const wallet = Wallet2.createRandom();
1318
1467
  const walletData = {
1319
1468
  address: wallet.address,
1320
1469
  privateKey: wallet.privateKey,
@@ -1346,17 +1495,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1346
1495
  } catch {
1347
1496
  throw new Error(`Unknown chain: ${this.config.chain}`);
1348
1497
  }
1349
- const provider = new ethers.JsonRpcProvider(chain.rpc);
1498
+ const provider = new ethers2.JsonRpcProvider(chain.rpc);
1350
1499
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
1351
1500
  const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
1352
1501
  provider.getBalance(this.wallet.address),
1353
- new ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
1354
- new ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
1502
+ new ethers2.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
1503
+ new ethers2.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
1355
1504
  ]);
1356
1505
  return {
1357
- usdc: parseFloat(ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
1358
- usdt: parseFloat(ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
1359
- native: parseFloat(ethers.formatEther(nativeBalance))
1506
+ usdc: parseFloat(ethers2.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
1507
+ usdt: parseFloat(ethers2.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
1508
+ native: parseFloat(ethers2.formatEther(nativeBalance))
1360
1509
  };
1361
1510
  }
1362
1511
  /**
@@ -1379,38 +1528,38 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
1379
1528
  supportedChains.map(async (chainName) => {
1380
1529
  try {
1381
1530
  const chain = getChain(chainName);
1382
- const provider = new ethers.JsonRpcProvider(chain.rpc);
1531
+ const provider = new ethers2.JsonRpcProvider(chain.rpc);
1383
1532
  if (chainName === "tempo_moderato") {
1384
1533
  const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
1385
1534
  provider.getBalance(this.wallet.address),
1386
- new ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1387
- new ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1388
- new ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1389
- new ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
1535
+ new ethers2.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1536
+ new ethers2.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1537
+ new ethers2.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1538
+ new ethers2.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
1390
1539
  ]);
1391
1540
  results[chainName] = {
1392
- usdc: parseFloat(ethers.formatUnits(pathUSD, 6)),
1541
+ usdc: parseFloat(ethers2.formatUnits(pathUSD, 6)),
1393
1542
  // pathUSD as default USDC
1394
- usdt: parseFloat(ethers.formatUnits(alphaUSD, 6)),
1543
+ usdt: parseFloat(ethers2.formatUnits(alphaUSD, 6)),
1395
1544
  // alphaUSD as default USDT
1396
- native: parseFloat(ethers.formatEther(nativeBalance)),
1545
+ native: parseFloat(ethers2.formatEther(nativeBalance)),
1397
1546
  tempo: {
1398
- pathUSD: parseFloat(ethers.formatUnits(pathUSD, 6)),
1399
- alphaUSD: parseFloat(ethers.formatUnits(alphaUSD, 6)),
1400
- betaUSD: parseFloat(ethers.formatUnits(betaUSD, 6)),
1401
- thetaUSD: parseFloat(ethers.formatUnits(thetaUSD, 6))
1547
+ pathUSD: parseFloat(ethers2.formatUnits(pathUSD, 6)),
1548
+ alphaUSD: parseFloat(ethers2.formatUnits(alphaUSD, 6)),
1549
+ betaUSD: parseFloat(ethers2.formatUnits(betaUSD, 6)),
1550
+ thetaUSD: parseFloat(ethers2.formatUnits(thetaUSD, 6))
1402
1551
  }
1403
1552
  };
1404
1553
  } else {
1405
1554
  const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
1406
1555
  provider.getBalance(this.wallet.address),
1407
- new ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
1408
- new ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
1556
+ new ethers2.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
1557
+ new ethers2.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
1409
1558
  ]);
1410
1559
  results[chainName] = {
1411
- usdc: parseFloat(ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
1412
- usdt: parseFloat(ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
1413
- native: parseFloat(ethers.formatEther(nativeBalance))
1560
+ usdc: parseFloat(ethers2.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
1561
+ usdt: parseFloat(ethers2.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
1562
+ native: parseFloat(ethers2.formatEther(nativeBalance))
1414
1563
  };
1415
1564
  }
1416
1565
  } catch (err) {
@@ -1768,16 +1917,40 @@ var CDPFacilitator = class extends BaseFacilitator {
1768
1917
 
1769
1918
  // src/facilitators/tempo.ts
1770
1919
  init_esm_shims();
1920
+ import { ethers as ethers3 } from "ethers";
1771
1921
  var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
1922
+ var TIP20_PERMIT_ABI = [
1923
+ "function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
1924
+ "function transferFrom(address from, address to, uint256 value) returns (bool)"
1925
+ ];
1772
1926
  var TempoFacilitator = class extends BaseFacilitator {
1773
1927
  name = "tempo";
1774
1928
  displayName = "Tempo Testnet";
1775
1929
  supportedNetworks = ["eip155:42431"];
1776
1930
  // Tempo Moderato
1777
1931
  rpcUrl;
1932
+ settlerWallet = null;
1778
1933
  constructor() {
1779
1934
  super();
1780
1935
  this.rpcUrl = CHAINS.tempo_moderato.rpc;
1936
+ const settlerKey = process.env.TEMPO_SETTLER_KEY;
1937
+ if (settlerKey) {
1938
+ try {
1939
+ const provider = new ethers3.JsonRpcProvider(this.rpcUrl);
1940
+ this.settlerWallet = new ethers3.Wallet(settlerKey, provider);
1941
+ } catch (err) {
1942
+ console.warn("[TempoFacilitator] Invalid TEMPO_SETTLER_KEY, permit settlement disabled:", err);
1943
+ this.settlerWallet = null;
1944
+ }
1945
+ }
1946
+ }
1947
+ /**
1948
+ * Settler EOA address advertised to clients via `X-Payment-Required.extra.tempoSpender`.
1949
+ * Web Client uses this as the `spender` field in the signed EIP-2612 Permit.
1950
+ * Returns null if no TEMPO_SETTLER_KEY is configured — permit settlement unavailable.
1951
+ */
1952
+ getSpenderAddress() {
1953
+ return this.settlerWallet?.address ?? null;
1781
1954
  }
1782
1955
  async healthCheck() {
1783
1956
  const start = Date.now();
@@ -1803,6 +1976,44 @@ var TempoFacilitator = class extends BaseFacilitator {
1803
1976
  }
1804
1977
  }
1805
1978
  async verify(paymentPayload, requirements) {
1979
+ const inner = paymentPayload.payload;
1980
+ if (inner && "permit" in inner && inner.permit) {
1981
+ return this.verifyPermit(inner, requirements);
1982
+ }
1983
+ return this.verifyTxHash(paymentPayload, requirements);
1984
+ }
1985
+ /**
1986
+ * Structural validation of an EIP-2612 permit payload. Does NOT submit
1987
+ * anything on-chain — actual submission happens in settlePermit().
1988
+ */
1989
+ async verifyPermit(payload, requirements) {
1990
+ if (!this.settlerWallet) {
1991
+ return { valid: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
1992
+ }
1993
+ const p = payload.permit;
1994
+ if (!p || !p.owner || !p.spender || !p.value || !p.deadline) {
1995
+ return { valid: false, error: "Invalid permit payload: missing fields" };
1996
+ }
1997
+ if (p.spender.toLowerCase() !== this.settlerWallet.address.toLowerCase()) {
1998
+ return {
1999
+ valid: false,
2000
+ error: `Permit spender ${p.spender} does not match configured settler ${this.settlerWallet.address}`
2001
+ };
2002
+ }
2003
+ const deadline = BigInt(p.deadline);
2004
+ const now = BigInt(Math.floor(Date.now() / 1e3));
2005
+ if (deadline <= now) {
2006
+ return { valid: false, error: "Permit deadline has expired" };
2007
+ }
2008
+ if (BigInt(p.value) < BigInt(requirements.amount || "0")) {
2009
+ return {
2010
+ valid: false,
2011
+ error: `Permit value ${p.value} is less than required ${requirements.amount}`
2012
+ };
2013
+ }
2014
+ return { valid: true, details: { scheme: "permit", owner: p.owner } };
2015
+ }
2016
+ async verifyTxHash(paymentPayload, requirements) {
1806
2017
  try {
1807
2018
  const tempoPayload = paymentPayload.payload;
1808
2019
  if (!tempoPayload?.txHash) {
@@ -1860,7 +2071,11 @@ var TempoFacilitator = class extends BaseFacilitator {
1860
2071
  }
1861
2072
  }
1862
2073
  async settle(paymentPayload, requirements) {
1863
- const verifyResult = await this.verify(paymentPayload, requirements);
2074
+ const inner = paymentPayload.payload;
2075
+ if (inner && "permit" in inner && inner.permit) {
2076
+ return this.settlePermit(inner, requirements);
2077
+ }
2078
+ const verifyResult = await this.verifyTxHash(paymentPayload, requirements);
1864
2079
  if (!verifyResult.valid) {
1865
2080
  return { success: false, error: verifyResult.error };
1866
2081
  }
@@ -1871,6 +2086,52 @@ var TempoFacilitator = class extends BaseFacilitator {
1871
2086
  status: "settled"
1872
2087
  };
1873
2088
  }
2089
+ /**
2090
+ * EIP-2612 permit settlement path. Submits two transactions on Tempo:
2091
+ * 1. pathUSD.permit(owner, spender=settler, value, deadline, v, r, s)
2092
+ * 2. pathUSD.transferFrom(owner, payTo, value)
2093
+ *
2094
+ * The settler EOA pays Tempo gas (via the TIP-20 `feeToken` mechanism — no
2095
+ * native tTEMPO required; any held TIP-20 token balance covers fees).
2096
+ */
2097
+ async settlePermit(payload, requirements) {
2098
+ if (!this.settlerWallet) {
2099
+ return { success: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
2100
+ }
2101
+ if (!requirements.asset || !requirements.payTo) {
2102
+ return { success: false, error: "Missing asset or payTo in requirements" };
2103
+ }
2104
+ const verifyResult = await this.verifyPermit(payload, requirements);
2105
+ if (!verifyResult.valid) {
2106
+ return { success: false, error: verifyResult.error };
2107
+ }
2108
+ const token = new ethers3.Contract(requirements.asset, TIP20_PERMIT_ABI, this.settlerWallet);
2109
+ const p = payload.permit;
2110
+ try {
2111
+ const permitTx = await token.permit(
2112
+ p.owner,
2113
+ p.spender,
2114
+ p.value,
2115
+ p.deadline,
2116
+ p.v,
2117
+ p.r,
2118
+ p.s
2119
+ );
2120
+ await permitTx.wait();
2121
+ const transferTx = await token.transferFrom(p.owner, requirements.payTo, p.value);
2122
+ await transferTx.wait();
2123
+ return {
2124
+ success: true,
2125
+ transaction: transferTx.hash,
2126
+ status: "settled"
2127
+ };
2128
+ } catch (err) {
2129
+ return {
2130
+ success: false,
2131
+ error: `Tempo permit settlement failed: ${err.message}`
2132
+ };
2133
+ }
2134
+ }
1874
2135
  async getTransactionReceipt(txHash) {
1875
2136
  const response = await fetch(this.rpcUrl, {
1876
2137
  method: "POST",
@@ -2114,12 +2375,12 @@ var BNBFacilitator = class extends BaseFacilitator {
2114
2375
  return this.spenderAddress;
2115
2376
  }
2116
2377
  async getServerAddress() {
2117
- const { ethers: ethers3 } = await import("ethers");
2118
- const wallet = new ethers3.Wallet(this.serverPrivateKey);
2378
+ const { ethers: ethers5 } = await import("ethers");
2379
+ const wallet = new ethers5.Wallet(this.serverPrivateKey);
2119
2380
  return wallet.address;
2120
2381
  }
2121
2382
  async recoverIntentSigner(intent, chainId) {
2122
- const { ethers: ethers3 } = await import("ethers");
2383
+ const { ethers: ethers5 } = await import("ethers");
2123
2384
  const domain = {
2124
2385
  ...EIP712_DOMAIN,
2125
2386
  chainId
@@ -2133,7 +2394,7 @@ var BNBFacilitator = class extends BaseFacilitator {
2133
2394
  nonce: intent.nonce,
2134
2395
  deadline: intent.deadline
2135
2396
  };
2136
- const recoveredAddress = ethers3.verifyTypedData(
2397
+ const recoveredAddress = ethers5.verifyTypedData(
2137
2398
  domain,
2138
2399
  INTENT_TYPES,
2139
2400
  message,
@@ -2177,10 +2438,10 @@ var BNBFacilitator = class extends BaseFacilitator {
2177
2438
  return result.result || "0x0";
2178
2439
  }
2179
2440
  async executeTransferFrom(from, to, amount, token, rpcUrl) {
2180
- const { ethers: ethers3 } = await import("ethers");
2181
- const provider = new ethers3.JsonRpcProvider(rpcUrl);
2182
- const wallet = new ethers3.Wallet(this.serverPrivateKey, provider);
2183
- const tokenContract = new ethers3.Contract(token, [
2441
+ const { ethers: ethers5 } = await import("ethers");
2442
+ const provider = new ethers5.JsonRpcProvider(rpcUrl);
2443
+ const wallet = new ethers5.Wallet(this.serverPrivateKey, provider);
2444
+ const tokenContract = new ethers5.Contract(token, [
2184
2445
  "function transferFrom(address from, address to, uint256 amount) returns (bool)"
2185
2446
  ], wallet);
2186
2447
  const tx = await tokenContract.transferFrom(from, to, amount);
@@ -2205,7 +2466,7 @@ var BNBFacilitator = class extends BaseFacilitator {
2205
2466
 
2206
2467
  // src/facilitators/registry.ts
2207
2468
  init_esm_shims();
2208
- import { Keypair as Keypair4 } from "@solana/web3.js";
2469
+ import { Keypair as Keypair5 } from "@solana/web3.js";
2209
2470
  import bs582 from "bs58";
2210
2471
  var FacilitatorRegistry = class {
2211
2472
  factories = /* @__PURE__ */ new Map();
@@ -2221,7 +2482,7 @@ var FacilitatorRegistry = class {
2221
2482
  const feePayerKey = config?.feePayerPrivateKey || process.env.SOLANA_FEE_PAYER_KEY;
2222
2483
  if (feePayerKey) {
2223
2484
  try {
2224
- feePayerKeypair = Keypair4.fromSecretKey(bs582.decode(feePayerKey));
2485
+ feePayerKeypair = Keypair5.fromSecretKey(bs582.decode(feePayerKey));
2225
2486
  } catch (e) {
2226
2487
  console.warn(`[SolanaFacilitator] Invalid fee payer key: ${e.message}`);
2227
2488
  }
@@ -2496,7 +2757,7 @@ var TOKEN_ADDRESSES = {
2496
2757
  // Devnet USDC
2497
2758
  }
2498
2759
  };
2499
- var CHAIN_TO_NETWORK = {
2760
+ var CHAIN_TO_NETWORK2 = {
2500
2761
  "base": "eip155:8453",
2501
2762
  "base_sepolia": "eip155:84532",
2502
2763
  "polygon": "eip155:137",
@@ -2527,9 +2788,13 @@ var TOKEN_DOMAINS = {
2527
2788
  USDT: { name: "(PoS) Tether USD", version: "2" }
2528
2789
  },
2529
2790
  // Tempo Moderato testnet - TIP-20 stablecoins
2791
+ // Domain names verified against on-chain DOMAIN_SEPARATOR values on 2026-04-21.
2792
+ // See docs/TEMPO-WEB-SUPPORT.md Section 2 and test/server/tempo-domain.test.ts.
2793
+ // All 4 Tempo TIP-20 tokens (pathUSD / AlphaUSD / BetaUSD / ThetaUSD) use
2794
+ // the token symbol with first letter capitalized + version "1".
2530
2795
  "eip155:42431": {
2531
- USDC: { name: "pathUSD", version: "1" },
2532
- USDT: { name: "alphaUSD", version: "1" }
2796
+ USDC: { name: "PathUSD", version: "1" },
2797
+ USDT: { name: "AlphaUSD", version: "1" }
2533
2798
  },
2534
2799
  // BNB Smart Chain mainnet
2535
2800
  "eip155:56": {
@@ -2652,14 +2917,14 @@ var MoltsPayServer = class {
2652
2917
  const chainName = typeof c === "string" ? c : c.chain;
2653
2918
  const explicitWallet = typeof c === "object" ? c.wallet : null;
2654
2919
  return {
2655
- network: CHAIN_TO_NETWORK[chainName] || "eip155:8453",
2920
+ network: CHAIN_TO_NETWORK2[chainName] || "eip155:8453",
2656
2921
  wallet: getWalletForChain(chainName, explicitWallet || void 0),
2657
2922
  tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
2658
2923
  };
2659
2924
  });
2660
2925
  }
2661
2926
  const chain = provider.chain || "base";
2662
- const network = CHAIN_TO_NETWORK[chain] || this.networkId;
2927
+ const network = CHAIN_TO_NETWORK2[chain] || this.networkId;
2663
2928
  return [{
2664
2929
  network,
2665
2930
  wallet: getWalletForChain(chain),
@@ -2698,13 +2963,62 @@ var MoltsPayServer = class {
2698
2963
  });
2699
2964
  }
2700
2965
  /**
2701
- * Handle incoming request
2966
+ * Apply CORS response headers according to the `cors` option.
2967
+ *
2968
+ * Default (`cors` unset or `true`): `Access-Control-Allow-Origin: *`. Matches 1.5.x behavior
2969
+ * and works for every browser client whose origin does not need to send cookies.
2970
+ *
2971
+ * `cors: false`: emit no CORS headers. Same-origin only.
2972
+ * `cors: string[]`: origin allowlist — echo the origin back iff it matches.
2973
+ * `cors: CorsOptions`: full control (allowlist + credentials + maxAge).
2974
+ *
2975
+ * The required-for-Web response headers are always exposed when CORS is active:
2976
+ * `X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt`.
2702
2977
  */
2703
- async handleRequest(req, res) {
2704
- res.setHeader("Access-Control-Allow-Origin", "*");
2978
+ applyCorsHeaders(req, res) {
2979
+ const cors = this.options.cors;
2980
+ if (cors === false) {
2981
+ return;
2982
+ }
2983
+ const requestOrigin = req.headers.origin ?? "*";
2984
+ if (cors === void 0 || cors === true) {
2985
+ this.writeCorsHeaders(res, "*");
2986
+ return;
2987
+ }
2988
+ if (Array.isArray(cors)) {
2989
+ if (cors.includes(requestOrigin)) {
2990
+ this.writeCorsHeaders(res, requestOrigin);
2991
+ res.setHeader("Vary", "Origin");
2992
+ }
2993
+ return;
2994
+ }
2995
+ const opt = cors;
2996
+ const isAllowed = typeof opt.origins === "function" ? opt.origins(requestOrigin) : opt.origins.includes(requestOrigin);
2997
+ if (!isAllowed) {
2998
+ return;
2999
+ }
3000
+ this.writeCorsHeaders(res, requestOrigin);
3001
+ res.setHeader("Vary", "Origin");
3002
+ if (opt.credentials) {
3003
+ res.setHeader("Access-Control-Allow-Credentials", "true");
3004
+ }
3005
+ const maxAge = opt.maxAge ?? 600;
3006
+ res.setHeader("Access-Control-Max-Age", String(maxAge));
3007
+ }
3008
+ writeCorsHeaders(res, origin) {
3009
+ res.setHeader("Access-Control-Allow-Origin", origin);
2705
3010
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
2706
3011
  res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
2707
- res.setHeader("Access-Control-Expose-Headers", "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt");
3012
+ res.setHeader(
3013
+ "Access-Control-Expose-Headers",
3014
+ "X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt"
3015
+ );
3016
+ }
3017
+ /**
3018
+ * Handle incoming request
3019
+ */
3020
+ async handleRequest(req, res) {
3021
+ this.applyCorsHeaders(req, res);
2708
3022
  if (req.method === "OPTIONS") {
2709
3023
  res.writeHead(204);
2710
3024
  res.end();
@@ -2927,6 +3241,14 @@ var MoltsPayServer = class {
2927
3241
  console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
2928
3242
  } catch (err) {
2929
3243
  console.error("[MoltsPay] Settlement failed:", err.message);
3244
+ settlement = { success: false, error: err.message, facilitator: "none" };
3245
+ }
3246
+ if (!settlement?.success) {
3247
+ return this.sendJson(res, 402, {
3248
+ error: "Payment settlement failed",
3249
+ message: settlement?.error || "Settlement returned no success state",
3250
+ facilitator: settlement?.facilitator
3251
+ });
2930
3252
  }
2931
3253
  }
2932
3254
  const responseHeaders = {};
@@ -3181,7 +3503,7 @@ var MoltsPayServer = class {
3181
3503
  }
3182
3504
  const scheme = payment.accepted?.scheme || payment.scheme;
3183
3505
  const network = payment.accepted?.network || payment.network || this.networkId;
3184
- if (scheme !== "exact") {
3506
+ if (scheme !== "exact" && scheme !== "permit") {
3185
3507
  return { valid: false, error: `Unsupported scheme: ${scheme}` };
3186
3508
  }
3187
3509
  if (!this.isNetworkAccepted(network)) {
@@ -3203,8 +3525,10 @@ var MoltsPayServer = class {
3203
3525
  const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
3204
3526
  const tokenAddress = tokenAddresses[selectedToken];
3205
3527
  const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
3528
+ const isTempo = selectedNetwork === "eip155:42431";
3529
+ const scheme = isTempo ? "permit" : "exact";
3206
3530
  const requirements = {
3207
- scheme: "exact",
3531
+ scheme,
3208
3532
  network: selectedNetwork,
3209
3533
  asset: tokenAddress,
3210
3534
  amount: amountInUnits,
@@ -3232,6 +3556,16 @@ var MoltsPayServer = class {
3232
3556
  };
3233
3557
  }
3234
3558
  }
3559
+ if (isTempo) {
3560
+ const tempoFacilitator = this.registry.get("tempo");
3561
+ const tempoSpender = tempoFacilitator?.getSpenderAddress?.();
3562
+ if (tempoSpender) {
3563
+ requirements.extra = {
3564
+ ...requirements.extra || {},
3565
+ tempoSpender
3566
+ };
3567
+ }
3568
+ }
3235
3569
  return requirements;
3236
3570
  }
3237
3571
  /**
@@ -3366,10 +3700,10 @@ var MoltsPayServer = class {
3366
3700
  }
3367
3701
  const scheme = payment.accepted?.scheme || payment.scheme;
3368
3702
  const network = payment.accepted?.network || payment.network;
3369
- if (scheme !== "exact") {
3703
+ if (scheme !== "exact" && scheme !== "permit") {
3370
3704
  return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
3371
3705
  }
3372
- const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
3706
+ const expectedNetwork = chain ? CHAIN_TO_NETWORK2[chain] || this.networkId : this.networkId;
3373
3707
  if (network !== expectedNetwork) {
3374
3708
  return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
3375
3709
  }
@@ -3631,7 +3965,7 @@ var MoltsPayServer = class {
3631
3965
  buildProxyPaymentRequirements(config, wallet, token, chain) {
3632
3966
  const amountInUnits = Math.floor(config.price * 1e6).toString();
3633
3967
  const acceptedTokens = getAcceptedCurrencies(config);
3634
- const networkId = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
3968
+ const networkId = chain ? CHAIN_TO_NETWORK2[chain] || this.networkId : this.networkId;
3635
3969
  const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
3636
3970
  const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
3637
3971
  const tokenAddress = tokenAddresses[selectedToken];
@@ -3729,7 +4063,7 @@ var ERC20_APPROVE_ABI = [
3729
4063
  ];
3730
4064
  async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = false) {
3731
4065
  const chainConfig = CHAINS[chain];
3732
- const provider = new ethers2.JsonRpcProvider(chainConfig.rpc);
4066
+ const provider = new ethers4.JsonRpcProvider(chainConfig.rpc);
3733
4067
  const wallet = client.getWallet();
3734
4068
  if (!wallet) {
3735
4069
  console.log(" \u274C No wallet found");
@@ -3738,15 +4072,15 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
3738
4072
  const signer = wallet.connect(provider);
3739
4073
  console.log(` Spender: ${spenderAddress}`);
3740
4074
  let bnbBalance = await provider.getBalance(wallet.address);
3741
- const minGasRequired = ethers2.parseEther("0.0005");
4075
+ const minGasRequired = ethers4.parseEther("0.0005");
3742
4076
  if (bnbBalance < minGasRequired) {
3743
4077
  if (sponsorGas && BNB_SPONSOR_KEY) {
3744
4078
  console.log(" \u23F3 Sponsoring BNB gas for approvals...");
3745
4079
  try {
3746
- const sponsorWallet = new ethers2.Wallet(BNB_SPONSOR_KEY, provider);
4080
+ const sponsorWallet = new ethers4.Wallet(BNB_SPONSOR_KEY, provider);
3747
4081
  const tx = await sponsorWallet.sendTransaction({
3748
4082
  to: wallet.address,
3749
- value: ethers2.parseEther("0.001")
4083
+ value: ethers4.parseEther("0.001")
3750
4084
  });
3751
4085
  await tx.wait();
3752
4086
  console.log(` \u2705 Sponsored 0.001 BNB (tx: ${tx.hash.slice(0, 10)}...)`);
@@ -3765,7 +4099,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
3765
4099
  }
3766
4100
  for (const tokenSymbol of ["USDT", "USDC"]) {
3767
4101
  const tokenConfig = chainConfig.tokens[tokenSymbol];
3768
- const tokenContract = new ethers2.Contract(tokenConfig.address, ERC20_APPROVE_ABI, signer);
4102
+ const tokenContract = new ethers4.Contract(tokenConfig.address, ERC20_APPROVE_ABI, signer);
3769
4103
  const allowance = await tokenContract.allowance(wallet.address, spenderAddress);
3770
4104
  if (allowance > 0n) {
3771
4105
  console.log(` \u2705 ${tokenSymbol}: already approved for ${spenderAddress.slice(0, 10)}...`);
@@ -3773,7 +4107,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
3773
4107
  }
3774
4108
  console.log(` \u23F3 Approving ${tokenSymbol}...`);
3775
4109
  try {
3776
- const tx = await tokenContract.approve(spenderAddress, ethers2.MaxUint256);
4110
+ const tx = await tokenContract.approve(spenderAddress, ethers4.MaxUint256);
3777
4111
  await tx.wait();
3778
4112
  console.log(` \u2705 ${tokenSymbol}: approved (tx: ${tx.hash.slice(0, 10)}...)`);
3779
4113
  } catch (err) {
@@ -3784,7 +4118,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
3784
4118
  }
3785
4119
  async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2) {
3786
4120
  const chainConfig = CHAINS[chain];
3787
- const provider = new ethers2.JsonRpcProvider(chainConfig.rpc);
4121
+ const provider = new ethers4.JsonRpcProvider(chainConfig.rpc);
3788
4122
  let spenderAddress = null;
3789
4123
  try {
3790
4124
  const walletPath = join5(configDir, "wallet.json");
@@ -3798,7 +4132,7 @@ async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2
3798
4132
  }
3799
4133
  for (const tokenSymbol of ["USDT", "USDC"]) {
3800
4134
  const tokenConfig = chainConfig.tokens[tokenSymbol];
3801
- const tokenContract = new ethers2.Contract(tokenConfig.address, ERC20_APPROVE_ABI, provider);
4135
+ const tokenContract = new ethers4.Contract(tokenConfig.address, ERC20_APPROVE_ABI, provider);
3802
4136
  const allowance = await tokenContract.allowance(address, spenderAddress);
3803
4137
  result[tokenSymbol.toLowerCase()] = allowance > 0n;
3804
4138
  }