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.
@@ -224,6 +224,9 @@ var CDPFacilitator = class extends BaseFacilitator {
224
224
  }
225
225
  };
226
226
 
227
+ // src/facilitators/tempo.ts
228
+ import { ethers } from "ethers";
229
+
227
230
  // src/chains/index.ts
228
231
  var CHAINS = {
229
232
  // ============ Mainnet ============
@@ -393,15 +396,38 @@ var CHAINS = {
393
396
 
394
397
  // src/facilitators/tempo.ts
395
398
  var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
399
+ var TIP20_PERMIT_ABI = [
400
+ "function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
401
+ "function transferFrom(address from, address to, uint256 value) returns (bool)"
402
+ ];
396
403
  var TempoFacilitator = class extends BaseFacilitator {
397
404
  name = "tempo";
398
405
  displayName = "Tempo Testnet";
399
406
  supportedNetworks = ["eip155:42431"];
400
407
  // Tempo Moderato
401
408
  rpcUrl;
409
+ settlerWallet = null;
402
410
  constructor() {
403
411
  super();
404
412
  this.rpcUrl = CHAINS.tempo_moderato.rpc;
413
+ const settlerKey = process.env.TEMPO_SETTLER_KEY;
414
+ if (settlerKey) {
415
+ try {
416
+ const provider = new ethers.JsonRpcProvider(this.rpcUrl);
417
+ this.settlerWallet = new ethers.Wallet(settlerKey, provider);
418
+ } catch (err) {
419
+ console.warn("[TempoFacilitator] Invalid TEMPO_SETTLER_KEY, permit settlement disabled:", err);
420
+ this.settlerWallet = null;
421
+ }
422
+ }
423
+ }
424
+ /**
425
+ * Settler EOA address advertised to clients via `X-Payment-Required.extra.tempoSpender`.
426
+ * Web Client uses this as the `spender` field in the signed EIP-2612 Permit.
427
+ * Returns null if no TEMPO_SETTLER_KEY is configured — permit settlement unavailable.
428
+ */
429
+ getSpenderAddress() {
430
+ return this.settlerWallet?.address ?? null;
405
431
  }
406
432
  async healthCheck() {
407
433
  const start = Date.now();
@@ -427,6 +453,44 @@ var TempoFacilitator = class extends BaseFacilitator {
427
453
  }
428
454
  }
429
455
  async verify(paymentPayload, requirements) {
456
+ const inner = paymentPayload.payload;
457
+ if (inner && "permit" in inner && inner.permit) {
458
+ return this.verifyPermit(inner, requirements);
459
+ }
460
+ return this.verifyTxHash(paymentPayload, requirements);
461
+ }
462
+ /**
463
+ * Structural validation of an EIP-2612 permit payload. Does NOT submit
464
+ * anything on-chain — actual submission happens in settlePermit().
465
+ */
466
+ async verifyPermit(payload, requirements) {
467
+ if (!this.settlerWallet) {
468
+ return { valid: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
469
+ }
470
+ const p = payload.permit;
471
+ if (!p || !p.owner || !p.spender || !p.value || !p.deadline) {
472
+ return { valid: false, error: "Invalid permit payload: missing fields" };
473
+ }
474
+ if (p.spender.toLowerCase() !== this.settlerWallet.address.toLowerCase()) {
475
+ return {
476
+ valid: false,
477
+ error: `Permit spender ${p.spender} does not match configured settler ${this.settlerWallet.address}`
478
+ };
479
+ }
480
+ const deadline = BigInt(p.deadline);
481
+ const now = BigInt(Math.floor(Date.now() / 1e3));
482
+ if (deadline <= now) {
483
+ return { valid: false, error: "Permit deadline has expired" };
484
+ }
485
+ if (BigInt(p.value) < BigInt(requirements.amount || "0")) {
486
+ return {
487
+ valid: false,
488
+ error: `Permit value ${p.value} is less than required ${requirements.amount}`
489
+ };
490
+ }
491
+ return { valid: true, details: { scheme: "permit", owner: p.owner } };
492
+ }
493
+ async verifyTxHash(paymentPayload, requirements) {
430
494
  try {
431
495
  const tempoPayload = paymentPayload.payload;
432
496
  if (!tempoPayload?.txHash) {
@@ -484,7 +548,11 @@ var TempoFacilitator = class extends BaseFacilitator {
484
548
  }
485
549
  }
486
550
  async settle(paymentPayload, requirements) {
487
- const verifyResult = await this.verify(paymentPayload, requirements);
551
+ const inner = paymentPayload.payload;
552
+ if (inner && "permit" in inner && inner.permit) {
553
+ return this.settlePermit(inner, requirements);
554
+ }
555
+ const verifyResult = await this.verifyTxHash(paymentPayload, requirements);
488
556
  if (!verifyResult.valid) {
489
557
  return { success: false, error: verifyResult.error };
490
558
  }
@@ -495,6 +563,52 @@ var TempoFacilitator = class extends BaseFacilitator {
495
563
  status: "settled"
496
564
  };
497
565
  }
566
+ /**
567
+ * EIP-2612 permit settlement path. Submits two transactions on Tempo:
568
+ * 1. pathUSD.permit(owner, spender=settler, value, deadline, v, r, s)
569
+ * 2. pathUSD.transferFrom(owner, payTo, value)
570
+ *
571
+ * The settler EOA pays Tempo gas (via the TIP-20 `feeToken` mechanism — no
572
+ * native tTEMPO required; any held TIP-20 token balance covers fees).
573
+ */
574
+ async settlePermit(payload, requirements) {
575
+ if (!this.settlerWallet) {
576
+ return { success: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
577
+ }
578
+ if (!requirements.asset || !requirements.payTo) {
579
+ return { success: false, error: "Missing asset or payTo in requirements" };
580
+ }
581
+ const verifyResult = await this.verifyPermit(payload, requirements);
582
+ if (!verifyResult.valid) {
583
+ return { success: false, error: verifyResult.error };
584
+ }
585
+ const token = new ethers.Contract(requirements.asset, TIP20_PERMIT_ABI, this.settlerWallet);
586
+ const p = payload.permit;
587
+ try {
588
+ const permitTx = await token.permit(
589
+ p.owner,
590
+ p.spender,
591
+ p.value,
592
+ p.deadline,
593
+ p.v,
594
+ p.r,
595
+ p.s
596
+ );
597
+ await permitTx.wait();
598
+ const transferTx = await token.transferFrom(p.owner, requirements.payTo, p.value);
599
+ await transferTx.wait();
600
+ return {
601
+ success: true,
602
+ transaction: transferTx.hash,
603
+ status: "settled"
604
+ };
605
+ } catch (err) {
606
+ return {
607
+ success: false,
608
+ error: `Tempo permit settlement failed: ${err.message}`
609
+ };
610
+ }
611
+ }
498
612
  async getTransactionReceipt(txHash) {
499
613
  const response = await fetch(this.rpcUrl, {
500
614
  method: "POST",
@@ -737,12 +851,12 @@ var BNBFacilitator = class extends BaseFacilitator {
737
851
  return this.spenderAddress;
738
852
  }
739
853
  async getServerAddress() {
740
- const { ethers } = await import("ethers");
741
- const wallet = new ethers.Wallet(this.serverPrivateKey);
854
+ const { ethers: ethers2 } = await import("ethers");
855
+ const wallet = new ethers2.Wallet(this.serverPrivateKey);
742
856
  return wallet.address;
743
857
  }
744
858
  async recoverIntentSigner(intent, chainId) {
745
- const { ethers } = await import("ethers");
859
+ const { ethers: ethers2 } = await import("ethers");
746
860
  const domain = {
747
861
  ...EIP712_DOMAIN,
748
862
  chainId
@@ -756,7 +870,7 @@ var BNBFacilitator = class extends BaseFacilitator {
756
870
  nonce: intent.nonce,
757
871
  deadline: intent.deadline
758
872
  };
759
- const recoveredAddress = ethers.verifyTypedData(
873
+ const recoveredAddress = ethers2.verifyTypedData(
760
874
  domain,
761
875
  INTENT_TYPES,
762
876
  message,
@@ -800,10 +914,10 @@ var BNBFacilitator = class extends BaseFacilitator {
800
914
  return result.result || "0x0";
801
915
  }
802
916
  async executeTransferFrom(from, to, amount, token, rpcUrl) {
803
- const { ethers } = await import("ethers");
804
- const provider = new ethers.JsonRpcProvider(rpcUrl);
805
- const wallet = new ethers.Wallet(this.serverPrivateKey, provider);
806
- const tokenContract = new ethers.Contract(token, [
917
+ const { ethers: ethers2 } = await import("ethers");
918
+ const provider = new ethers2.JsonRpcProvider(rpcUrl);
919
+ const wallet = new ethers2.Wallet(this.serverPrivateKey, provider);
920
+ const tokenContract = new ethers2.Contract(token, [
807
921
  "function transferFrom(address from, address to, uint256 amount) returns (bool)"
808
922
  ], wallet);
809
923
  const tx = await tokenContract.transferFrom(from, to, amount);
@@ -1058,16 +1172,16 @@ var SolanaFacilitator = class extends BaseFacilitator {
1058
1172
  return this.supportedNetworks.includes(network);
1059
1173
  }
1060
1174
  };
1061
- async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
1175
+ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey, connection) {
1062
1176
  const chainConfig = SOLANA_CHAINS[chain];
1063
- const connection = new Connection2(chainConfig.rpc, "confirmed");
1177
+ const conn = connection ?? new Connection2(chainConfig.rpc, "confirmed");
1064
1178
  const mint = new PublicKey2(chainConfig.tokens.USDC.mint);
1065
1179
  const actualFeePayer = feePayerPubkey || senderPubkey;
1066
1180
  const senderATA = await getAssociatedTokenAddress(mint, senderPubkey);
1067
1181
  const recipientATA = await getAssociatedTokenAddress(mint, recipientPubkey);
1068
1182
  const transaction = new Transaction();
1069
1183
  try {
1070
- await getAccount(connection, recipientATA);
1184
+ await getAccount(conn, recipientATA);
1071
1185
  } catch {
1072
1186
  transaction.add(
1073
1187
  createAssociatedTokenAccountInstruction(
@@ -1098,7 +1212,7 @@ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amo
1098
1212
  // decimals
1099
1213
  )
1100
1214
  );
1101
- const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
1215
+ const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash();
1102
1216
  transaction.recentBlockhash = blockhash;
1103
1217
  transaction.feePayer = actualFeePayer;
1104
1218
  return transaction;