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.
- package/README.md +187 -0
- package/dist/cli/index.js +486 -152
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +483 -149
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.d.mts +5 -0
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.js +245 -116
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +241 -114
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/web/index.d.mts +418 -0
- package/dist/client/web/index.mjs +1289 -0
- package/dist/client/web/index.mjs.map +1 -0
- package/dist/facilitators/index.d.mts +24 -2
- package/dist/facilitators/index.d.ts +24 -2
- package/dist/facilitators/index.js +127 -13
- package/dist/facilitators/index.js.map +1 -1
- package/dist/facilitators/index.mjs +127 -13
- package/dist/facilitators/index.mjs.map +1 -1
- package/dist/index.js +463 -149
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +460 -146
- package/dist/index.mjs.map +1 -1
- package/dist/mcp/index.d.mts +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +1623 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/index.mjs +1617 -0
- package/dist/mcp/index.mjs.map +1 -0
- package/dist/server/index.d.mts +43 -1
- package/dist/server/index.d.ts +43 -1
- package/dist/server/index.js +205 -18
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +205 -18
- package/dist/server/index.mjs.map +1 -1
- package/package.json +19 -4
package/dist/cli/index.mjs
CHANGED
|
@@ -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
|
|
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
|
|
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(
|
|
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
|
|
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/
|
|
581
|
+
// src/client/core/index.ts
|
|
579
582
|
init_esm_shims();
|
|
580
583
|
|
|
581
|
-
// src/client/
|
|
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
|
|
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
|
|
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 =
|
|
1154
|
+
const minGasBalance = ethers2.parseEther("0.0005");
|
|
981
1155
|
if (nativeBalance < minGasBalance) {
|
|
982
|
-
const nativeBNB = parseFloat(
|
|
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
|
|
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
|
-
|
|
1196
|
+
tokenAddress: tokenConfig.address,
|
|
1021
1197
|
service,
|
|
1022
|
-
nonce:
|
|
1023
|
-
|
|
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.
|
|
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
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
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
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
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 =
|
|
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
|
|
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
|
|
1354
|
-
new
|
|
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(
|
|
1358
|
-
usdt: parseFloat(
|
|
1359
|
-
native: parseFloat(
|
|
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
|
|
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
|
|
1387
|
-
new
|
|
1388
|
-
new
|
|
1389
|
-
new
|
|
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(
|
|
1541
|
+
usdc: parseFloat(ethers2.formatUnits(pathUSD, 6)),
|
|
1393
1542
|
// pathUSD as default USDC
|
|
1394
|
-
usdt: parseFloat(
|
|
1543
|
+
usdt: parseFloat(ethers2.formatUnits(alphaUSD, 6)),
|
|
1395
1544
|
// alphaUSD as default USDT
|
|
1396
|
-
native: parseFloat(
|
|
1545
|
+
native: parseFloat(ethers2.formatEther(nativeBalance)),
|
|
1397
1546
|
tempo: {
|
|
1398
|
-
pathUSD: parseFloat(
|
|
1399
|
-
alphaUSD: parseFloat(
|
|
1400
|
-
betaUSD: parseFloat(
|
|
1401
|
-
thetaUSD: parseFloat(
|
|
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
|
|
1408
|
-
new
|
|
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(
|
|
1412
|
-
usdt: parseFloat(
|
|
1413
|
-
native: parseFloat(
|
|
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
|
|
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:
|
|
2118
|
-
const wallet = new
|
|
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:
|
|
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 =
|
|
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:
|
|
2181
|
-
const provider = new
|
|
2182
|
-
const wallet = new
|
|
2183
|
-
const tokenContract = new
|
|
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
|
|
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 =
|
|
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
|
|
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: "
|
|
2532
|
-
USDT: { name: "
|
|
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:
|
|
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 =
|
|
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
|
-
*
|
|
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
|
-
|
|
2704
|
-
|
|
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(
|
|
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
|
|
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 ?
|
|
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 ?
|
|
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
|
|
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 =
|
|
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
|
|
4080
|
+
const sponsorWallet = new ethers4.Wallet(BNB_SPONSOR_KEY, provider);
|
|
3747
4081
|
const tx = await sponsorWallet.sendTransaction({
|
|
3748
4082
|
to: wallet.address,
|
|
3749
|
-
value:
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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
|
}
|