moltspay 1.5.0 → 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 +142 -0
- package/dist/cli/index.js +476 -144
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +473 -141
- 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 +235 -108
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +231 -106
- 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 +453 -141
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +450 -138
- package/dist/index.mjs.map +1 -1
- package/dist/mcp/index.js +234 -109
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/index.mjs +231 -106
- package/dist/mcp/index.mjs.map +1 -1
- 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 +9 -2
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
|
|
@@ -1316,7 +1463,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1316
1463
|
*/
|
|
1317
1464
|
static init(configDir, options) {
|
|
1318
1465
|
mkdirSync2(configDir, { recursive: true });
|
|
1319
|
-
const wallet =
|
|
1466
|
+
const wallet = Wallet2.createRandom();
|
|
1320
1467
|
const walletData = {
|
|
1321
1468
|
address: wallet.address,
|
|
1322
1469
|
privateKey: wallet.privateKey,
|
|
@@ -1348,17 +1495,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1348
1495
|
} catch {
|
|
1349
1496
|
throw new Error(`Unknown chain: ${this.config.chain}`);
|
|
1350
1497
|
}
|
|
1351
|
-
const provider = new
|
|
1498
|
+
const provider = new ethers2.JsonRpcProvider(chain.rpc);
|
|
1352
1499
|
const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
1353
1500
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
1354
1501
|
provider.getBalance(this.wallet.address),
|
|
1355
|
-
new
|
|
1356
|
-
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)
|
|
1357
1504
|
]);
|
|
1358
1505
|
return {
|
|
1359
|
-
usdc: parseFloat(
|
|
1360
|
-
usdt: parseFloat(
|
|
1361
|
-
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))
|
|
1362
1509
|
};
|
|
1363
1510
|
}
|
|
1364
1511
|
/**
|
|
@@ -1381,38 +1528,38 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1381
1528
|
supportedChains.map(async (chainName) => {
|
|
1382
1529
|
try {
|
|
1383
1530
|
const chain = getChain(chainName);
|
|
1384
|
-
const provider = new
|
|
1531
|
+
const provider = new ethers2.JsonRpcProvider(chain.rpc);
|
|
1385
1532
|
if (chainName === "tempo_moderato") {
|
|
1386
1533
|
const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
|
|
1387
1534
|
provider.getBalance(this.wallet.address),
|
|
1388
|
-
new
|
|
1389
|
-
new
|
|
1390
|
-
new
|
|
1391
|
-
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)
|
|
1392
1539
|
]);
|
|
1393
1540
|
results[chainName] = {
|
|
1394
|
-
usdc: parseFloat(
|
|
1541
|
+
usdc: parseFloat(ethers2.formatUnits(pathUSD, 6)),
|
|
1395
1542
|
// pathUSD as default USDC
|
|
1396
|
-
usdt: parseFloat(
|
|
1543
|
+
usdt: parseFloat(ethers2.formatUnits(alphaUSD, 6)),
|
|
1397
1544
|
// alphaUSD as default USDT
|
|
1398
|
-
native: parseFloat(
|
|
1545
|
+
native: parseFloat(ethers2.formatEther(nativeBalance)),
|
|
1399
1546
|
tempo: {
|
|
1400
|
-
pathUSD: parseFloat(
|
|
1401
|
-
alphaUSD: parseFloat(
|
|
1402
|
-
betaUSD: parseFloat(
|
|
1403
|
-
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))
|
|
1404
1551
|
}
|
|
1405
1552
|
};
|
|
1406
1553
|
} else {
|
|
1407
1554
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
1408
1555
|
provider.getBalance(this.wallet.address),
|
|
1409
|
-
new
|
|
1410
|
-
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)
|
|
1411
1558
|
]);
|
|
1412
1559
|
results[chainName] = {
|
|
1413
|
-
usdc: parseFloat(
|
|
1414
|
-
usdt: parseFloat(
|
|
1415
|
-
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))
|
|
1416
1563
|
};
|
|
1417
1564
|
}
|
|
1418
1565
|
} catch (err) {
|
|
@@ -1770,16 +1917,40 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
1770
1917
|
|
|
1771
1918
|
// src/facilitators/tempo.ts
|
|
1772
1919
|
init_esm_shims();
|
|
1920
|
+
import { ethers as ethers3 } from "ethers";
|
|
1773
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
|
+
];
|
|
1774
1926
|
var TempoFacilitator = class extends BaseFacilitator {
|
|
1775
1927
|
name = "tempo";
|
|
1776
1928
|
displayName = "Tempo Testnet";
|
|
1777
1929
|
supportedNetworks = ["eip155:42431"];
|
|
1778
1930
|
// Tempo Moderato
|
|
1779
1931
|
rpcUrl;
|
|
1932
|
+
settlerWallet = null;
|
|
1780
1933
|
constructor() {
|
|
1781
1934
|
super();
|
|
1782
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;
|
|
1783
1954
|
}
|
|
1784
1955
|
async healthCheck() {
|
|
1785
1956
|
const start = Date.now();
|
|
@@ -1805,6 +1976,44 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
1805
1976
|
}
|
|
1806
1977
|
}
|
|
1807
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) {
|
|
1808
2017
|
try {
|
|
1809
2018
|
const tempoPayload = paymentPayload.payload;
|
|
1810
2019
|
if (!tempoPayload?.txHash) {
|
|
@@ -1862,7 +2071,11 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
1862
2071
|
}
|
|
1863
2072
|
}
|
|
1864
2073
|
async settle(paymentPayload, requirements) {
|
|
1865
|
-
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);
|
|
1866
2079
|
if (!verifyResult.valid) {
|
|
1867
2080
|
return { success: false, error: verifyResult.error };
|
|
1868
2081
|
}
|
|
@@ -1873,6 +2086,52 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
1873
2086
|
status: "settled"
|
|
1874
2087
|
};
|
|
1875
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
|
+
}
|
|
1876
2135
|
async getTransactionReceipt(txHash) {
|
|
1877
2136
|
const response = await fetch(this.rpcUrl, {
|
|
1878
2137
|
method: "POST",
|
|
@@ -2116,12 +2375,12 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
2116
2375
|
return this.spenderAddress;
|
|
2117
2376
|
}
|
|
2118
2377
|
async getServerAddress() {
|
|
2119
|
-
const { ethers:
|
|
2120
|
-
const wallet = new
|
|
2378
|
+
const { ethers: ethers5 } = await import("ethers");
|
|
2379
|
+
const wallet = new ethers5.Wallet(this.serverPrivateKey);
|
|
2121
2380
|
return wallet.address;
|
|
2122
2381
|
}
|
|
2123
2382
|
async recoverIntentSigner(intent, chainId) {
|
|
2124
|
-
const { ethers:
|
|
2383
|
+
const { ethers: ethers5 } = await import("ethers");
|
|
2125
2384
|
const domain = {
|
|
2126
2385
|
...EIP712_DOMAIN,
|
|
2127
2386
|
chainId
|
|
@@ -2135,7 +2394,7 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
2135
2394
|
nonce: intent.nonce,
|
|
2136
2395
|
deadline: intent.deadline
|
|
2137
2396
|
};
|
|
2138
|
-
const recoveredAddress =
|
|
2397
|
+
const recoveredAddress = ethers5.verifyTypedData(
|
|
2139
2398
|
domain,
|
|
2140
2399
|
INTENT_TYPES,
|
|
2141
2400
|
message,
|
|
@@ -2179,10 +2438,10 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
2179
2438
|
return result.result || "0x0";
|
|
2180
2439
|
}
|
|
2181
2440
|
async executeTransferFrom(from, to, amount, token, rpcUrl) {
|
|
2182
|
-
const { ethers:
|
|
2183
|
-
const provider = new
|
|
2184
|
-
const wallet = new
|
|
2185
|
-
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, [
|
|
2186
2445
|
"function transferFrom(address from, address to, uint256 amount) returns (bool)"
|
|
2187
2446
|
], wallet);
|
|
2188
2447
|
const tx = await tokenContract.transferFrom(from, to, amount);
|
|
@@ -2207,7 +2466,7 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
2207
2466
|
|
|
2208
2467
|
// src/facilitators/registry.ts
|
|
2209
2468
|
init_esm_shims();
|
|
2210
|
-
import { Keypair as
|
|
2469
|
+
import { Keypair as Keypair5 } from "@solana/web3.js";
|
|
2211
2470
|
import bs582 from "bs58";
|
|
2212
2471
|
var FacilitatorRegistry = class {
|
|
2213
2472
|
factories = /* @__PURE__ */ new Map();
|
|
@@ -2223,7 +2482,7 @@ var FacilitatorRegistry = class {
|
|
|
2223
2482
|
const feePayerKey = config?.feePayerPrivateKey || process.env.SOLANA_FEE_PAYER_KEY;
|
|
2224
2483
|
if (feePayerKey) {
|
|
2225
2484
|
try {
|
|
2226
|
-
feePayerKeypair =
|
|
2485
|
+
feePayerKeypair = Keypair5.fromSecretKey(bs582.decode(feePayerKey));
|
|
2227
2486
|
} catch (e) {
|
|
2228
2487
|
console.warn(`[SolanaFacilitator] Invalid fee payer key: ${e.message}`);
|
|
2229
2488
|
}
|
|
@@ -2498,7 +2757,7 @@ var TOKEN_ADDRESSES = {
|
|
|
2498
2757
|
// Devnet USDC
|
|
2499
2758
|
}
|
|
2500
2759
|
};
|
|
2501
|
-
var
|
|
2760
|
+
var CHAIN_TO_NETWORK2 = {
|
|
2502
2761
|
"base": "eip155:8453",
|
|
2503
2762
|
"base_sepolia": "eip155:84532",
|
|
2504
2763
|
"polygon": "eip155:137",
|
|
@@ -2529,9 +2788,13 @@ var TOKEN_DOMAINS = {
|
|
|
2529
2788
|
USDT: { name: "(PoS) Tether USD", version: "2" }
|
|
2530
2789
|
},
|
|
2531
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".
|
|
2532
2795
|
"eip155:42431": {
|
|
2533
|
-
USDC: { name: "
|
|
2534
|
-
USDT: { name: "
|
|
2796
|
+
USDC: { name: "PathUSD", version: "1" },
|
|
2797
|
+
USDT: { name: "AlphaUSD", version: "1" }
|
|
2535
2798
|
},
|
|
2536
2799
|
// BNB Smart Chain mainnet
|
|
2537
2800
|
"eip155:56": {
|
|
@@ -2654,14 +2917,14 @@ var MoltsPayServer = class {
|
|
|
2654
2917
|
const chainName = typeof c === "string" ? c : c.chain;
|
|
2655
2918
|
const explicitWallet = typeof c === "object" ? c.wallet : null;
|
|
2656
2919
|
return {
|
|
2657
|
-
network:
|
|
2920
|
+
network: CHAIN_TO_NETWORK2[chainName] || "eip155:8453",
|
|
2658
2921
|
wallet: getWalletForChain(chainName, explicitWallet || void 0),
|
|
2659
2922
|
tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
|
|
2660
2923
|
};
|
|
2661
2924
|
});
|
|
2662
2925
|
}
|
|
2663
2926
|
const chain = provider.chain || "base";
|
|
2664
|
-
const network =
|
|
2927
|
+
const network = CHAIN_TO_NETWORK2[chain] || this.networkId;
|
|
2665
2928
|
return [{
|
|
2666
2929
|
network,
|
|
2667
2930
|
wallet: getWalletForChain(chain),
|
|
@@ -2700,13 +2963,62 @@ var MoltsPayServer = class {
|
|
|
2700
2963
|
});
|
|
2701
2964
|
}
|
|
2702
2965
|
/**
|
|
2703
|
-
*
|
|
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`.
|
|
2704
2977
|
*/
|
|
2705
|
-
|
|
2706
|
-
|
|
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);
|
|
2707
3010
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
2708
3011
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
|
|
2709
|
-
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);
|
|
2710
3022
|
if (req.method === "OPTIONS") {
|
|
2711
3023
|
res.writeHead(204);
|
|
2712
3024
|
res.end();
|
|
@@ -2929,6 +3241,14 @@ var MoltsPayServer = class {
|
|
|
2929
3241
|
console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
|
|
2930
3242
|
} catch (err) {
|
|
2931
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
|
+
});
|
|
2932
3252
|
}
|
|
2933
3253
|
}
|
|
2934
3254
|
const responseHeaders = {};
|
|
@@ -3183,7 +3503,7 @@ var MoltsPayServer = class {
|
|
|
3183
3503
|
}
|
|
3184
3504
|
const scheme = payment.accepted?.scheme || payment.scheme;
|
|
3185
3505
|
const network = payment.accepted?.network || payment.network || this.networkId;
|
|
3186
|
-
if (scheme !== "exact") {
|
|
3506
|
+
if (scheme !== "exact" && scheme !== "permit") {
|
|
3187
3507
|
return { valid: false, error: `Unsupported scheme: ${scheme}` };
|
|
3188
3508
|
}
|
|
3189
3509
|
if (!this.isNetworkAccepted(network)) {
|
|
@@ -3205,8 +3525,10 @@ var MoltsPayServer = class {
|
|
|
3205
3525
|
const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
|
|
3206
3526
|
const tokenAddress = tokenAddresses[selectedToken];
|
|
3207
3527
|
const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
|
|
3528
|
+
const isTempo = selectedNetwork === "eip155:42431";
|
|
3529
|
+
const scheme = isTempo ? "permit" : "exact";
|
|
3208
3530
|
const requirements = {
|
|
3209
|
-
scheme
|
|
3531
|
+
scheme,
|
|
3210
3532
|
network: selectedNetwork,
|
|
3211
3533
|
asset: tokenAddress,
|
|
3212
3534
|
amount: amountInUnits,
|
|
@@ -3234,6 +3556,16 @@ var MoltsPayServer = class {
|
|
|
3234
3556
|
};
|
|
3235
3557
|
}
|
|
3236
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
|
+
}
|
|
3237
3569
|
return requirements;
|
|
3238
3570
|
}
|
|
3239
3571
|
/**
|
|
@@ -3368,10 +3700,10 @@ var MoltsPayServer = class {
|
|
|
3368
3700
|
}
|
|
3369
3701
|
const scheme = payment.accepted?.scheme || payment.scheme;
|
|
3370
3702
|
const network = payment.accepted?.network || payment.network;
|
|
3371
|
-
if (scheme !== "exact") {
|
|
3703
|
+
if (scheme !== "exact" && scheme !== "permit") {
|
|
3372
3704
|
return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
|
|
3373
3705
|
}
|
|
3374
|
-
const expectedNetwork = chain ?
|
|
3706
|
+
const expectedNetwork = chain ? CHAIN_TO_NETWORK2[chain] || this.networkId : this.networkId;
|
|
3375
3707
|
if (network !== expectedNetwork) {
|
|
3376
3708
|
return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
|
|
3377
3709
|
}
|
|
@@ -3633,7 +3965,7 @@ var MoltsPayServer = class {
|
|
|
3633
3965
|
buildProxyPaymentRequirements(config, wallet, token, chain) {
|
|
3634
3966
|
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
3635
3967
|
const acceptedTokens = getAcceptedCurrencies(config);
|
|
3636
|
-
const networkId = chain ?
|
|
3968
|
+
const networkId = chain ? CHAIN_TO_NETWORK2[chain] || this.networkId : this.networkId;
|
|
3637
3969
|
const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
|
|
3638
3970
|
const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
|
|
3639
3971
|
const tokenAddress = tokenAddresses[selectedToken];
|
|
@@ -3731,7 +4063,7 @@ var ERC20_APPROVE_ABI = [
|
|
|
3731
4063
|
];
|
|
3732
4064
|
async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = false) {
|
|
3733
4065
|
const chainConfig = CHAINS[chain];
|
|
3734
|
-
const provider = new
|
|
4066
|
+
const provider = new ethers4.JsonRpcProvider(chainConfig.rpc);
|
|
3735
4067
|
const wallet = client.getWallet();
|
|
3736
4068
|
if (!wallet) {
|
|
3737
4069
|
console.log(" \u274C No wallet found");
|
|
@@ -3740,15 +4072,15 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
|
|
|
3740
4072
|
const signer = wallet.connect(provider);
|
|
3741
4073
|
console.log(` Spender: ${spenderAddress}`);
|
|
3742
4074
|
let bnbBalance = await provider.getBalance(wallet.address);
|
|
3743
|
-
const minGasRequired =
|
|
4075
|
+
const minGasRequired = ethers4.parseEther("0.0005");
|
|
3744
4076
|
if (bnbBalance < minGasRequired) {
|
|
3745
4077
|
if (sponsorGas && BNB_SPONSOR_KEY) {
|
|
3746
4078
|
console.log(" \u23F3 Sponsoring BNB gas for approvals...");
|
|
3747
4079
|
try {
|
|
3748
|
-
const sponsorWallet = new
|
|
4080
|
+
const sponsorWallet = new ethers4.Wallet(BNB_SPONSOR_KEY, provider);
|
|
3749
4081
|
const tx = await sponsorWallet.sendTransaction({
|
|
3750
4082
|
to: wallet.address,
|
|
3751
|
-
value:
|
|
4083
|
+
value: ethers4.parseEther("0.001")
|
|
3752
4084
|
});
|
|
3753
4085
|
await tx.wait();
|
|
3754
4086
|
console.log(` \u2705 Sponsored 0.001 BNB (tx: ${tx.hash.slice(0, 10)}...)`);
|
|
@@ -3767,7 +4099,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
|
|
|
3767
4099
|
}
|
|
3768
4100
|
for (const tokenSymbol of ["USDT", "USDC"]) {
|
|
3769
4101
|
const tokenConfig = chainConfig.tokens[tokenSymbol];
|
|
3770
|
-
const tokenContract = new
|
|
4102
|
+
const tokenContract = new ethers4.Contract(tokenConfig.address, ERC20_APPROVE_ABI, signer);
|
|
3771
4103
|
const allowance = await tokenContract.allowance(wallet.address, spenderAddress);
|
|
3772
4104
|
if (allowance > 0n) {
|
|
3773
4105
|
console.log(` \u2705 ${tokenSymbol}: already approved for ${spenderAddress.slice(0, 10)}...`);
|
|
@@ -3775,7 +4107,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
|
|
|
3775
4107
|
}
|
|
3776
4108
|
console.log(` \u23F3 Approving ${tokenSymbol}...`);
|
|
3777
4109
|
try {
|
|
3778
|
-
const tx = await tokenContract.approve(spenderAddress,
|
|
4110
|
+
const tx = await tokenContract.approve(spenderAddress, ethers4.MaxUint256);
|
|
3779
4111
|
await tx.wait();
|
|
3780
4112
|
console.log(` \u2705 ${tokenSymbol}: approved (tx: ${tx.hash.slice(0, 10)}...)`);
|
|
3781
4113
|
} catch (err) {
|
|
@@ -3786,7 +4118,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
|
|
|
3786
4118
|
}
|
|
3787
4119
|
async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2) {
|
|
3788
4120
|
const chainConfig = CHAINS[chain];
|
|
3789
|
-
const provider = new
|
|
4121
|
+
const provider = new ethers4.JsonRpcProvider(chainConfig.rpc);
|
|
3790
4122
|
let spenderAddress = null;
|
|
3791
4123
|
try {
|
|
3792
4124
|
const walletPath = join5(configDir, "wallet.json");
|
|
@@ -3800,7 +4132,7 @@ async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2
|
|
|
3800
4132
|
}
|
|
3801
4133
|
for (const tokenSymbol of ["USDT", "USDC"]) {
|
|
3802
4134
|
const tokenConfig = chainConfig.tokens[tokenSymbol];
|
|
3803
|
-
const tokenContract = new
|
|
4135
|
+
const tokenContract = new ethers4.Contract(tokenConfig.address, ERC20_APPROVE_ABI, provider);
|
|
3804
4136
|
const allowance = await tokenContract.allowance(address, spenderAddress);
|
|
3805
4137
|
result[tokenSymbol.toLowerCase()] = allowance > 0n;
|
|
3806
4138
|
}
|