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.js
CHANGED
|
@@ -41,14 +41,17 @@ var import_os3 = require("os");
|
|
|
41
41
|
var import_path3 = require("path");
|
|
42
42
|
var import_fs5 = require("fs");
|
|
43
43
|
var import_child_process = require("child_process");
|
|
44
|
-
var
|
|
44
|
+
var import_ethers4 = require("ethers");
|
|
45
45
|
|
|
46
46
|
// src/client/index.ts
|
|
47
47
|
init_cjs_shims();
|
|
48
|
+
|
|
49
|
+
// src/client/node/index.ts
|
|
50
|
+
init_cjs_shims();
|
|
48
51
|
var import_fs2 = require("fs");
|
|
49
52
|
var import_os2 = require("os");
|
|
50
53
|
var import_path2 = require("path");
|
|
51
|
-
var
|
|
54
|
+
var import_ethers2 = require("ethers");
|
|
52
55
|
|
|
53
56
|
// src/chains/index.ts
|
|
54
57
|
init_cjs_shims();
|
|
@@ -532,16 +535,16 @@ var SolanaFacilitator = class extends BaseFacilitator {
|
|
|
532
535
|
return this.supportedNetworks.includes(network);
|
|
533
536
|
}
|
|
534
537
|
};
|
|
535
|
-
async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
|
|
538
|
+
async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey, connection) {
|
|
536
539
|
const chainConfig = SOLANA_CHAINS[chain];
|
|
537
|
-
const
|
|
540
|
+
const conn = connection ?? new import_web33.Connection(chainConfig.rpc, "confirmed");
|
|
538
541
|
const mint = new import_web33.PublicKey(chainConfig.tokens.USDC.mint);
|
|
539
542
|
const actualFeePayer = feePayerPubkey || senderPubkey;
|
|
540
543
|
const senderATA = await (0, import_spl_token2.getAssociatedTokenAddress)(mint, senderPubkey);
|
|
541
544
|
const recipientATA = await (0, import_spl_token2.getAssociatedTokenAddress)(mint, recipientPubkey);
|
|
542
545
|
const transaction = new import_web33.Transaction();
|
|
543
546
|
try {
|
|
544
|
-
await (0, import_spl_token2.getAccount)(
|
|
547
|
+
await (0, import_spl_token2.getAccount)(conn, recipientATA);
|
|
545
548
|
} catch {
|
|
546
549
|
transaction.add(
|
|
547
550
|
(0, import_spl_token2.createAssociatedTokenAccountInstruction)(
|
|
@@ -572,22 +575,202 @@ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amo
|
|
|
572
575
|
// decimals
|
|
573
576
|
)
|
|
574
577
|
);
|
|
575
|
-
const { blockhash, lastValidBlockHeight } = await
|
|
578
|
+
const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash();
|
|
576
579
|
transaction.recentBlockhash = blockhash;
|
|
577
580
|
transaction.feePayer = actualFeePayer;
|
|
578
581
|
return transaction;
|
|
579
582
|
}
|
|
580
583
|
|
|
581
|
-
// src/client/index.ts
|
|
582
|
-
var
|
|
584
|
+
// src/client/node/index.ts
|
|
585
|
+
var import_web35 = require("@solana/web3.js");
|
|
583
586
|
|
|
584
|
-
// src/client/
|
|
587
|
+
// src/client/core/index.ts
|
|
585
588
|
init_cjs_shims();
|
|
586
589
|
|
|
587
|
-
// src/client/
|
|
590
|
+
// src/client/core/types.ts
|
|
591
|
+
init_cjs_shims();
|
|
588
592
|
var X402_VERSION = 2;
|
|
589
593
|
var PAYMENT_REQUIRED_HEADER = "x-payment-required";
|
|
590
594
|
var PAYMENT_HEADER = "x-payment";
|
|
595
|
+
|
|
596
|
+
// src/client/core/chain-map.ts
|
|
597
|
+
init_cjs_shims();
|
|
598
|
+
var NETWORK_TO_CHAIN = {
|
|
599
|
+
"eip155:8453": "base",
|
|
600
|
+
"eip155:137": "polygon",
|
|
601
|
+
"eip155:84532": "base_sepolia",
|
|
602
|
+
"eip155:42431": "tempo_moderato",
|
|
603
|
+
"eip155:56": "bnb",
|
|
604
|
+
"eip155:97": "bnb_testnet",
|
|
605
|
+
"solana:mainnet": "solana",
|
|
606
|
+
"solana:devnet": "solana_devnet"
|
|
607
|
+
};
|
|
608
|
+
var CHAIN_TO_NETWORK = Object.fromEntries(
|
|
609
|
+
Object.entries(NETWORK_TO_CHAIN).map(([network, chain]) => [chain, network])
|
|
610
|
+
);
|
|
611
|
+
function networkToChainName(network) {
|
|
612
|
+
return NETWORK_TO_CHAIN[network] ?? null;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// src/client/core/base64.ts
|
|
616
|
+
init_cjs_shims();
|
|
617
|
+
var BufferCtor = globalThis.Buffer;
|
|
618
|
+
|
|
619
|
+
// src/client/core/errors.ts
|
|
620
|
+
init_cjs_shims();
|
|
621
|
+
|
|
622
|
+
// src/client/core/eip3009.ts
|
|
623
|
+
init_cjs_shims();
|
|
624
|
+
var EIP3009_TYPES = {
|
|
625
|
+
TransferWithAuthorization: [
|
|
626
|
+
{ name: "from", type: "address" },
|
|
627
|
+
{ name: "to", type: "address" },
|
|
628
|
+
{ name: "value", type: "uint256" },
|
|
629
|
+
{ name: "validAfter", type: "uint256" },
|
|
630
|
+
{ name: "validBefore", type: "uint256" },
|
|
631
|
+
{ name: "nonce", type: "bytes32" }
|
|
632
|
+
]
|
|
633
|
+
};
|
|
634
|
+
function buildEIP3009TypedData(args) {
|
|
635
|
+
const validAfter = args.validAfter ?? "0";
|
|
636
|
+
const validBefore = args.validBefore ?? (Math.floor(Date.now() / 1e3) + 3600).toString();
|
|
637
|
+
const authorization = {
|
|
638
|
+
from: args.from,
|
|
639
|
+
to: args.to,
|
|
640
|
+
value: args.value,
|
|
641
|
+
validAfter,
|
|
642
|
+
validBefore,
|
|
643
|
+
nonce: args.nonce
|
|
644
|
+
};
|
|
645
|
+
return {
|
|
646
|
+
domain: {
|
|
647
|
+
name: args.tokenName,
|
|
648
|
+
version: args.tokenVersion,
|
|
649
|
+
chainId: args.chainId,
|
|
650
|
+
verifyingContract: args.tokenAddress
|
|
651
|
+
},
|
|
652
|
+
types: EIP3009_TYPES,
|
|
653
|
+
primaryType: "TransferWithAuthorization",
|
|
654
|
+
message: authorization
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// src/client/core/eip2612.ts
|
|
659
|
+
init_cjs_shims();
|
|
660
|
+
|
|
661
|
+
// src/client/core/bnb-intent.ts
|
|
662
|
+
init_cjs_shims();
|
|
663
|
+
var BNB_INTENT_TYPES = {
|
|
664
|
+
PaymentIntent: [
|
|
665
|
+
{ name: "from", type: "address" },
|
|
666
|
+
{ name: "to", type: "address" },
|
|
667
|
+
{ name: "amount", type: "uint256" },
|
|
668
|
+
{ name: "token", type: "address" },
|
|
669
|
+
{ name: "service", type: "string" },
|
|
670
|
+
{ name: "nonce", type: "uint256" },
|
|
671
|
+
{ name: "deadline", type: "uint256" }
|
|
672
|
+
]
|
|
673
|
+
};
|
|
674
|
+
var BNB_DOMAIN_NAME = "MoltsPay";
|
|
675
|
+
var BNB_DOMAIN_VERSION = "1";
|
|
676
|
+
function buildBnbIntentTypedData(args) {
|
|
677
|
+
const intent = {
|
|
678
|
+
from: args.from,
|
|
679
|
+
to: args.to,
|
|
680
|
+
amount: args.amount,
|
|
681
|
+
token: args.tokenAddress,
|
|
682
|
+
service: args.service,
|
|
683
|
+
nonce: args.nonce,
|
|
684
|
+
deadline: args.deadline
|
|
685
|
+
};
|
|
686
|
+
return {
|
|
687
|
+
domain: {
|
|
688
|
+
name: BNB_DOMAIN_NAME,
|
|
689
|
+
version: BNB_DOMAIN_VERSION,
|
|
690
|
+
chainId: args.chainId
|
|
691
|
+
},
|
|
692
|
+
types: BNB_INTENT_TYPES,
|
|
693
|
+
primaryType: "PaymentIntent",
|
|
694
|
+
message: intent
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// src/client/core/solana-tx.ts
|
|
699
|
+
init_cjs_shims();
|
|
700
|
+
|
|
701
|
+
// src/client/core/x402.ts
|
|
702
|
+
init_cjs_shims();
|
|
703
|
+
|
|
704
|
+
// src/client/node/signer.ts
|
|
705
|
+
init_cjs_shims();
|
|
706
|
+
var import_ethers = require("ethers");
|
|
707
|
+
var import_web34 = require("@solana/web3.js");
|
|
708
|
+
var NodeSigner = class {
|
|
709
|
+
evmWallet;
|
|
710
|
+
getSolanaKeypair;
|
|
711
|
+
constructor(evmWallet, options = {}) {
|
|
712
|
+
this.evmWallet = evmWallet;
|
|
713
|
+
this.getSolanaKeypair = options.getSolanaKeypair ?? (() => null);
|
|
714
|
+
}
|
|
715
|
+
async getEvmAddress() {
|
|
716
|
+
return this.evmWallet.address;
|
|
717
|
+
}
|
|
718
|
+
async getSolanaAddress() {
|
|
719
|
+
const kp = this.getSolanaKeypair();
|
|
720
|
+
return kp ? kp.publicKey.toBase58() : null;
|
|
721
|
+
}
|
|
722
|
+
async signTypedData(envelope) {
|
|
723
|
+
const mutableTypes = {};
|
|
724
|
+
for (const [key, fields] of Object.entries(envelope.types)) {
|
|
725
|
+
mutableTypes[key] = [...fields];
|
|
726
|
+
}
|
|
727
|
+
return this.evmWallet.signTypedData(
|
|
728
|
+
envelope.domain,
|
|
729
|
+
mutableTypes,
|
|
730
|
+
envelope.message
|
|
731
|
+
);
|
|
732
|
+
}
|
|
733
|
+
async sendEvmTransaction(args) {
|
|
734
|
+
const chain = findChainByChainId(args.chainId);
|
|
735
|
+
if (!chain) {
|
|
736
|
+
throw new Error(`sendEvmTransaction: unknown chainId ${args.chainId}`);
|
|
737
|
+
}
|
|
738
|
+
const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
|
|
739
|
+
const connected = this.evmWallet.connect(provider);
|
|
740
|
+
const tx = await connected.sendTransaction({
|
|
741
|
+
to: args.to,
|
|
742
|
+
data: args.data,
|
|
743
|
+
value: args.value ? BigInt(args.value) : 0n
|
|
744
|
+
});
|
|
745
|
+
return tx.hash;
|
|
746
|
+
}
|
|
747
|
+
async signSolanaTransaction(args) {
|
|
748
|
+
const kp = this.getSolanaKeypair();
|
|
749
|
+
if (!kp) {
|
|
750
|
+
throw new Error("signSolanaTransaction: no Solana wallet configured");
|
|
751
|
+
}
|
|
752
|
+
const tx = import_web34.Transaction.from(Buffer.from(args.transactionBase64, "base64"));
|
|
753
|
+
if (args.partialSign) {
|
|
754
|
+
tx.partialSign(kp);
|
|
755
|
+
} else {
|
|
756
|
+
tx.sign(kp);
|
|
757
|
+
}
|
|
758
|
+
return tx.serialize({ requireAllSignatures: false }).toString("base64");
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
function findChainByChainId(chainId) {
|
|
762
|
+
for (const cfg of Object.values(CHAINS)) {
|
|
763
|
+
if (cfg.chainId === chainId) {
|
|
764
|
+
return cfg;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
return void 0;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// src/client/types.ts
|
|
771
|
+
init_cjs_shims();
|
|
772
|
+
|
|
773
|
+
// src/client/node/index.ts
|
|
591
774
|
var DEFAULT_CONFIG = {
|
|
592
775
|
chain: "base",
|
|
593
776
|
limits: {
|
|
@@ -600,6 +783,7 @@ var MoltsPayClient = class {
|
|
|
600
783
|
config;
|
|
601
784
|
walletData = null;
|
|
602
785
|
wallet = null;
|
|
786
|
+
signer = null;
|
|
603
787
|
todaySpending = 0;
|
|
604
788
|
lastSpendingReset = 0;
|
|
605
789
|
constructor(options = {}) {
|
|
@@ -608,7 +792,11 @@ var MoltsPayClient = class {
|
|
|
608
792
|
this.walletData = this.loadWallet();
|
|
609
793
|
this.loadSpending();
|
|
610
794
|
if (this.walletData) {
|
|
611
|
-
this.wallet = new
|
|
795
|
+
this.wallet = new import_ethers2.Wallet(this.walletData.privateKey);
|
|
796
|
+
const configDir = this.configDir;
|
|
797
|
+
this.signer = new NodeSigner(this.wallet, {
|
|
798
|
+
getSolanaKeypair: () => loadSolanaWallet(configDir)
|
|
799
|
+
});
|
|
612
800
|
}
|
|
613
801
|
}
|
|
614
802
|
/**
|
|
@@ -736,20 +924,6 @@ var MoltsPayClient = class {
|
|
|
736
924
|
} catch {
|
|
737
925
|
throw new Error("Invalid x-payment-required header");
|
|
738
926
|
}
|
|
739
|
-
const networkToChainName = (network2) => {
|
|
740
|
-
if (network2 === "solana:mainnet") return "solana";
|
|
741
|
-
if (network2 === "solana:devnet") return "solana_devnet";
|
|
742
|
-
const match = network2.match(/^eip155:(\d+)$/);
|
|
743
|
-
if (!match) return null;
|
|
744
|
-
const chainId = parseInt(match[1]);
|
|
745
|
-
if (chainId === 8453) return "base";
|
|
746
|
-
if (chainId === 137) return "polygon";
|
|
747
|
-
if (chainId === 84532) return "base_sepolia";
|
|
748
|
-
if (chainId === 42431) return "tempo_moderato";
|
|
749
|
-
if (chainId === 56) return "bnb";
|
|
750
|
-
if (chainId === 97) return "bnb_testnet";
|
|
751
|
-
return null;
|
|
752
|
-
};
|
|
753
927
|
const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
|
|
754
928
|
const userSpecifiedChain = options.chain;
|
|
755
929
|
let selectedChain;
|
|
@@ -978,14 +1152,14 @@ Please specify: --chain <chain_name>`
|
|
|
978
1152
|
async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
|
|
979
1153
|
const { to, amount, token, chainName, chain, spender } = paymentDetails;
|
|
980
1154
|
const tokenConfig = chain.tokens[token];
|
|
981
|
-
const provider = new
|
|
1155
|
+
const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
|
|
982
1156
|
const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
|
|
983
1157
|
const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
|
|
984
1158
|
if (allowance < amountWeiCheck) {
|
|
985
1159
|
const nativeBalance = await provider.getBalance(this.wallet.address);
|
|
986
|
-
const minGasBalance =
|
|
1160
|
+
const minGasBalance = import_ethers2.ethers.parseEther("0.0005");
|
|
987
1161
|
if (nativeBalance < minGasBalance) {
|
|
988
|
-
const nativeBNB = parseFloat(
|
|
1162
|
+
const nativeBNB = parseFloat(import_ethers2.ethers.formatEther(nativeBalance)).toFixed(4);
|
|
989
1163
|
const isTestnet = chainName === "bnb_testnet";
|
|
990
1164
|
if (isTestnet) {
|
|
991
1165
|
throw new Error(
|
|
@@ -1019,35 +1193,21 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1019
1193
|
);
|
|
1020
1194
|
}
|
|
1021
1195
|
const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
|
|
1022
|
-
const
|
|
1196
|
+
const intentNonce = Date.now();
|
|
1197
|
+
const intentDeadline = Date.now() + 36e5;
|
|
1198
|
+
const envelope = buildBnbIntentTypedData({
|
|
1023
1199
|
from: this.wallet.address,
|
|
1024
1200
|
to,
|
|
1025
1201
|
amount: amountWei,
|
|
1026
|
-
|
|
1202
|
+
tokenAddress: tokenConfig.address,
|
|
1027
1203
|
service,
|
|
1028
|
-
nonce:
|
|
1029
|
-
|
|
1030
|
-
deadline: Date.now() + 36e5
|
|
1031
|
-
// 1 hour
|
|
1032
|
-
};
|
|
1033
|
-
const domain = {
|
|
1034
|
-
name: "MoltsPay",
|
|
1035
|
-
version: "1",
|
|
1204
|
+
nonce: intentNonce,
|
|
1205
|
+
deadline: intentDeadline,
|
|
1036
1206
|
chainId: chain.chainId
|
|
1037
|
-
};
|
|
1038
|
-
const types = {
|
|
1039
|
-
PaymentIntent: [
|
|
1040
|
-
{ name: "from", type: "address" },
|
|
1041
|
-
{ name: "to", type: "address" },
|
|
1042
|
-
{ name: "amount", type: "uint256" },
|
|
1043
|
-
{ name: "token", type: "address" },
|
|
1044
|
-
{ name: "service", type: "string" },
|
|
1045
|
-
{ name: "nonce", type: "uint256" },
|
|
1046
|
-
{ name: "deadline", type: "uint256" }
|
|
1047
|
-
]
|
|
1048
|
-
};
|
|
1207
|
+
});
|
|
1049
1208
|
console.log(`[MoltsPay] Signing BNB payment intent...`);
|
|
1050
|
-
const signature = await this.
|
|
1209
|
+
const signature = await this.signer.signTypedData(envelope);
|
|
1210
|
+
const intent = envelope.message;
|
|
1051
1211
|
const network = `eip155:${chain.chainId}`;
|
|
1052
1212
|
const payload = {
|
|
1053
1213
|
x402Version: 2,
|
|
@@ -1108,11 +1268,11 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1108
1268
|
throw new Error("Missing payTo address in payment requirements");
|
|
1109
1269
|
}
|
|
1110
1270
|
const solanaFeePayer = requirements.extra?.solanaFeePayer;
|
|
1111
|
-
const feePayerPubkey = solanaFeePayer ? new
|
|
1271
|
+
const feePayerPubkey = solanaFeePayer ? new import_web35.PublicKey(solanaFeePayer) : void 0;
|
|
1112
1272
|
if (feePayerPubkey) {
|
|
1113
1273
|
console.log(`[MoltsPay] Gasless mode: server pays fees`);
|
|
1114
1274
|
}
|
|
1115
|
-
const recipientPubkey = new
|
|
1275
|
+
const recipientPubkey = new import_web35.PublicKey(requirements.payTo);
|
|
1116
1276
|
const transaction = await createSolanaPaymentTransaction(
|
|
1117
1277
|
solanaWallet.publicKey,
|
|
1118
1278
|
recipientPubkey,
|
|
@@ -1121,12 +1281,11 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1121
1281
|
feePayerPubkey
|
|
1122
1282
|
// Optional fee payer for gasless mode
|
|
1123
1283
|
);
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
}
|
|
1129
|
-
const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
|
|
1284
|
+
const unsignedBase64 = transaction.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
|
|
1285
|
+
const signedTx = await this.signer.signSolanaTransaction({
|
|
1286
|
+
transactionBase64: unsignedBase64,
|
|
1287
|
+
partialSign: !!feePayerPubkey
|
|
1288
|
+
});
|
|
1130
1289
|
console.log(`[MoltsPay] Transaction signed, sending to server...`);
|
|
1131
1290
|
const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
|
|
1132
1291
|
const payload = {
|
|
@@ -1173,7 +1332,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1173
1332
|
* Check ERC20 allowance for a spender
|
|
1174
1333
|
*/
|
|
1175
1334
|
async checkAllowance(tokenAddress, spender, provider) {
|
|
1176
|
-
const contract = new
|
|
1335
|
+
const contract = new import_ethers2.ethers.Contract(
|
|
1177
1336
|
tokenAddress,
|
|
1178
1337
|
["function allowance(address owner, address spender) view returns (uint256)"],
|
|
1179
1338
|
provider
|
|
@@ -1184,41 +1343,29 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1184
1343
|
* Sign EIP-3009 transferWithAuthorization (GASLESS)
|
|
1185
1344
|
* This only signs - no on-chain transaction, no gas needed.
|
|
1186
1345
|
* Supports both USDC and USDT.
|
|
1346
|
+
*
|
|
1347
|
+
* Delegates typed-data construction to `core/eip3009.ts` and the signature
|
|
1348
|
+
* itself to `this.signer`. That way Web Client (Phase 4) can reuse the same
|
|
1349
|
+
* flow with an EIP-1193 signer without duplicating typed-data layout.
|
|
1187
1350
|
*/
|
|
1188
1351
|
async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
|
|
1189
|
-
const validAfter = 0;
|
|
1190
|
-
const validBefore = Math.floor(Date.now() / 1e3) + 3600;
|
|
1191
|
-
const nonce = import_ethers.ethers.hexlify(import_ethers.ethers.randomBytes(32));
|
|
1192
1352
|
const tokenConfig = chain.tokens[token];
|
|
1193
1353
|
const value = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
|
|
1194
|
-
const
|
|
1354
|
+
const nonce = import_ethers2.ethers.hexlify(import_ethers2.ethers.randomBytes(32));
|
|
1355
|
+
const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
|
|
1356
|
+
const tokenVersion = domainOverride?.version || "2";
|
|
1357
|
+
const envelope = buildEIP3009TypedData({
|
|
1195
1358
|
from: this.wallet.address,
|
|
1196
1359
|
to,
|
|
1197
1360
|
value,
|
|
1198
|
-
|
|
1199
|
-
validBefore: validBefore.toString(),
|
|
1200
|
-
nonce
|
|
1201
|
-
};
|
|
1202
|
-
const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
|
|
1203
|
-
const tokenVersion = domainOverride?.version || "2";
|
|
1204
|
-
const domain = {
|
|
1205
|
-
name: tokenName,
|
|
1206
|
-
version: tokenVersion,
|
|
1361
|
+
nonce,
|
|
1207
1362
|
chainId: chain.chainId,
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
{ name: "value", type: "uint256" },
|
|
1215
|
-
{ name: "validAfter", type: "uint256" },
|
|
1216
|
-
{ name: "validBefore", type: "uint256" },
|
|
1217
|
-
{ name: "nonce", type: "bytes32" }
|
|
1218
|
-
]
|
|
1219
|
-
};
|
|
1220
|
-
const signature = await this.wallet.signTypedData(domain, types, authorization);
|
|
1221
|
-
return { authorization, signature };
|
|
1363
|
+
tokenAddress: tokenConfig.address,
|
|
1364
|
+
tokenName,
|
|
1365
|
+
tokenVersion
|
|
1366
|
+
});
|
|
1367
|
+
const signature = await this.signer.signTypedData(envelope);
|
|
1368
|
+
return { authorization: envelope.message, signature };
|
|
1222
1369
|
}
|
|
1223
1370
|
/**
|
|
1224
1371
|
* Check spending limits
|
|
@@ -1322,7 +1469,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1322
1469
|
*/
|
|
1323
1470
|
static init(configDir, options) {
|
|
1324
1471
|
(0, import_fs2.mkdirSync)(configDir, { recursive: true });
|
|
1325
|
-
const wallet =
|
|
1472
|
+
const wallet = import_ethers2.Wallet.createRandom();
|
|
1326
1473
|
const walletData = {
|
|
1327
1474
|
address: wallet.address,
|
|
1328
1475
|
privateKey: wallet.privateKey,
|
|
@@ -1354,17 +1501,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1354
1501
|
} catch {
|
|
1355
1502
|
throw new Error(`Unknown chain: ${this.config.chain}`);
|
|
1356
1503
|
}
|
|
1357
|
-
const provider = new
|
|
1504
|
+
const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
|
|
1358
1505
|
const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
1359
1506
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
1360
1507
|
provider.getBalance(this.wallet.address),
|
|
1361
|
-
new
|
|
1362
|
-
new
|
|
1508
|
+
new import_ethers2.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
1509
|
+
new import_ethers2.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
1363
1510
|
]);
|
|
1364
1511
|
return {
|
|
1365
|
-
usdc: parseFloat(
|
|
1366
|
-
usdt: parseFloat(
|
|
1367
|
-
native: parseFloat(
|
|
1512
|
+
usdc: parseFloat(import_ethers2.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
1513
|
+
usdt: parseFloat(import_ethers2.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
1514
|
+
native: parseFloat(import_ethers2.ethers.formatEther(nativeBalance))
|
|
1368
1515
|
};
|
|
1369
1516
|
}
|
|
1370
1517
|
/**
|
|
@@ -1387,38 +1534,38 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1387
1534
|
supportedChains.map(async (chainName) => {
|
|
1388
1535
|
try {
|
|
1389
1536
|
const chain = getChain(chainName);
|
|
1390
|
-
const provider = new
|
|
1537
|
+
const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
|
|
1391
1538
|
if (chainName === "tempo_moderato") {
|
|
1392
1539
|
const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
|
|
1393
1540
|
provider.getBalance(this.wallet.address),
|
|
1394
|
-
new
|
|
1395
|
-
new
|
|
1396
|
-
new
|
|
1397
|
-
new
|
|
1541
|
+
new import_ethers2.ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
1542
|
+
new import_ethers2.ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
1543
|
+
new import_ethers2.ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
1544
|
+
new import_ethers2.ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
1398
1545
|
]);
|
|
1399
1546
|
results[chainName] = {
|
|
1400
|
-
usdc: parseFloat(
|
|
1547
|
+
usdc: parseFloat(import_ethers2.ethers.formatUnits(pathUSD, 6)),
|
|
1401
1548
|
// pathUSD as default USDC
|
|
1402
|
-
usdt: parseFloat(
|
|
1549
|
+
usdt: parseFloat(import_ethers2.ethers.formatUnits(alphaUSD, 6)),
|
|
1403
1550
|
// alphaUSD as default USDT
|
|
1404
|
-
native: parseFloat(
|
|
1551
|
+
native: parseFloat(import_ethers2.ethers.formatEther(nativeBalance)),
|
|
1405
1552
|
tempo: {
|
|
1406
|
-
pathUSD: parseFloat(
|
|
1407
|
-
alphaUSD: parseFloat(
|
|
1408
|
-
betaUSD: parseFloat(
|
|
1409
|
-
thetaUSD: parseFloat(
|
|
1553
|
+
pathUSD: parseFloat(import_ethers2.ethers.formatUnits(pathUSD, 6)),
|
|
1554
|
+
alphaUSD: parseFloat(import_ethers2.ethers.formatUnits(alphaUSD, 6)),
|
|
1555
|
+
betaUSD: parseFloat(import_ethers2.ethers.formatUnits(betaUSD, 6)),
|
|
1556
|
+
thetaUSD: parseFloat(import_ethers2.ethers.formatUnits(thetaUSD, 6))
|
|
1410
1557
|
}
|
|
1411
1558
|
};
|
|
1412
1559
|
} else {
|
|
1413
1560
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
1414
1561
|
provider.getBalance(this.wallet.address),
|
|
1415
|
-
new
|
|
1416
|
-
new
|
|
1562
|
+
new import_ethers2.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
1563
|
+
new import_ethers2.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
1417
1564
|
]);
|
|
1418
1565
|
results[chainName] = {
|
|
1419
|
-
usdc: parseFloat(
|
|
1420
|
-
usdt: parseFloat(
|
|
1421
|
-
native: parseFloat(
|
|
1566
|
+
usdc: parseFloat(import_ethers2.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
1567
|
+
usdt: parseFloat(import_ethers2.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
1568
|
+
native: parseFloat(import_ethers2.ethers.formatEther(nativeBalance))
|
|
1422
1569
|
};
|
|
1423
1570
|
}
|
|
1424
1571
|
} catch (err) {
|
|
@@ -1776,16 +1923,40 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
1776
1923
|
|
|
1777
1924
|
// src/facilitators/tempo.ts
|
|
1778
1925
|
init_cjs_shims();
|
|
1926
|
+
var import_ethers3 = require("ethers");
|
|
1779
1927
|
var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
1928
|
+
var TIP20_PERMIT_ABI = [
|
|
1929
|
+
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
1930
|
+
"function transferFrom(address from, address to, uint256 value) returns (bool)"
|
|
1931
|
+
];
|
|
1780
1932
|
var TempoFacilitator = class extends BaseFacilitator {
|
|
1781
1933
|
name = "tempo";
|
|
1782
1934
|
displayName = "Tempo Testnet";
|
|
1783
1935
|
supportedNetworks = ["eip155:42431"];
|
|
1784
1936
|
// Tempo Moderato
|
|
1785
1937
|
rpcUrl;
|
|
1938
|
+
settlerWallet = null;
|
|
1786
1939
|
constructor() {
|
|
1787
1940
|
super();
|
|
1788
1941
|
this.rpcUrl = CHAINS.tempo_moderato.rpc;
|
|
1942
|
+
const settlerKey = process.env.TEMPO_SETTLER_KEY;
|
|
1943
|
+
if (settlerKey) {
|
|
1944
|
+
try {
|
|
1945
|
+
const provider = new import_ethers3.ethers.JsonRpcProvider(this.rpcUrl);
|
|
1946
|
+
this.settlerWallet = new import_ethers3.ethers.Wallet(settlerKey, provider);
|
|
1947
|
+
} catch (err) {
|
|
1948
|
+
console.warn("[TempoFacilitator] Invalid TEMPO_SETTLER_KEY, permit settlement disabled:", err);
|
|
1949
|
+
this.settlerWallet = null;
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
/**
|
|
1954
|
+
* Settler EOA address advertised to clients via `X-Payment-Required.extra.tempoSpender`.
|
|
1955
|
+
* Web Client uses this as the `spender` field in the signed EIP-2612 Permit.
|
|
1956
|
+
* Returns null if no TEMPO_SETTLER_KEY is configured — permit settlement unavailable.
|
|
1957
|
+
*/
|
|
1958
|
+
getSpenderAddress() {
|
|
1959
|
+
return this.settlerWallet?.address ?? null;
|
|
1789
1960
|
}
|
|
1790
1961
|
async healthCheck() {
|
|
1791
1962
|
const start = Date.now();
|
|
@@ -1811,6 +1982,44 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
1811
1982
|
}
|
|
1812
1983
|
}
|
|
1813
1984
|
async verify(paymentPayload, requirements) {
|
|
1985
|
+
const inner = paymentPayload.payload;
|
|
1986
|
+
if (inner && "permit" in inner && inner.permit) {
|
|
1987
|
+
return this.verifyPermit(inner, requirements);
|
|
1988
|
+
}
|
|
1989
|
+
return this.verifyTxHash(paymentPayload, requirements);
|
|
1990
|
+
}
|
|
1991
|
+
/**
|
|
1992
|
+
* Structural validation of an EIP-2612 permit payload. Does NOT submit
|
|
1993
|
+
* anything on-chain — actual submission happens in settlePermit().
|
|
1994
|
+
*/
|
|
1995
|
+
async verifyPermit(payload, requirements) {
|
|
1996
|
+
if (!this.settlerWallet) {
|
|
1997
|
+
return { valid: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
|
|
1998
|
+
}
|
|
1999
|
+
const p = payload.permit;
|
|
2000
|
+
if (!p || !p.owner || !p.spender || !p.value || !p.deadline) {
|
|
2001
|
+
return { valid: false, error: "Invalid permit payload: missing fields" };
|
|
2002
|
+
}
|
|
2003
|
+
if (p.spender.toLowerCase() !== this.settlerWallet.address.toLowerCase()) {
|
|
2004
|
+
return {
|
|
2005
|
+
valid: false,
|
|
2006
|
+
error: `Permit spender ${p.spender} does not match configured settler ${this.settlerWallet.address}`
|
|
2007
|
+
};
|
|
2008
|
+
}
|
|
2009
|
+
const deadline = BigInt(p.deadline);
|
|
2010
|
+
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
2011
|
+
if (deadline <= now) {
|
|
2012
|
+
return { valid: false, error: "Permit deadline has expired" };
|
|
2013
|
+
}
|
|
2014
|
+
if (BigInt(p.value) < BigInt(requirements.amount || "0")) {
|
|
2015
|
+
return {
|
|
2016
|
+
valid: false,
|
|
2017
|
+
error: `Permit value ${p.value} is less than required ${requirements.amount}`
|
|
2018
|
+
};
|
|
2019
|
+
}
|
|
2020
|
+
return { valid: true, details: { scheme: "permit", owner: p.owner } };
|
|
2021
|
+
}
|
|
2022
|
+
async verifyTxHash(paymentPayload, requirements) {
|
|
1814
2023
|
try {
|
|
1815
2024
|
const tempoPayload = paymentPayload.payload;
|
|
1816
2025
|
if (!tempoPayload?.txHash) {
|
|
@@ -1868,7 +2077,11 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
1868
2077
|
}
|
|
1869
2078
|
}
|
|
1870
2079
|
async settle(paymentPayload, requirements) {
|
|
1871
|
-
const
|
|
2080
|
+
const inner = paymentPayload.payload;
|
|
2081
|
+
if (inner && "permit" in inner && inner.permit) {
|
|
2082
|
+
return this.settlePermit(inner, requirements);
|
|
2083
|
+
}
|
|
2084
|
+
const verifyResult = await this.verifyTxHash(paymentPayload, requirements);
|
|
1872
2085
|
if (!verifyResult.valid) {
|
|
1873
2086
|
return { success: false, error: verifyResult.error };
|
|
1874
2087
|
}
|
|
@@ -1879,6 +2092,52 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
1879
2092
|
status: "settled"
|
|
1880
2093
|
};
|
|
1881
2094
|
}
|
|
2095
|
+
/**
|
|
2096
|
+
* EIP-2612 permit settlement path. Submits two transactions on Tempo:
|
|
2097
|
+
* 1. pathUSD.permit(owner, spender=settler, value, deadline, v, r, s)
|
|
2098
|
+
* 2. pathUSD.transferFrom(owner, payTo, value)
|
|
2099
|
+
*
|
|
2100
|
+
* The settler EOA pays Tempo gas (via the TIP-20 `feeToken` mechanism — no
|
|
2101
|
+
* native tTEMPO required; any held TIP-20 token balance covers fees).
|
|
2102
|
+
*/
|
|
2103
|
+
async settlePermit(payload, requirements) {
|
|
2104
|
+
if (!this.settlerWallet) {
|
|
2105
|
+
return { success: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
|
|
2106
|
+
}
|
|
2107
|
+
if (!requirements.asset || !requirements.payTo) {
|
|
2108
|
+
return { success: false, error: "Missing asset or payTo in requirements" };
|
|
2109
|
+
}
|
|
2110
|
+
const verifyResult = await this.verifyPermit(payload, requirements);
|
|
2111
|
+
if (!verifyResult.valid) {
|
|
2112
|
+
return { success: false, error: verifyResult.error };
|
|
2113
|
+
}
|
|
2114
|
+
const token = new import_ethers3.ethers.Contract(requirements.asset, TIP20_PERMIT_ABI, this.settlerWallet);
|
|
2115
|
+
const p = payload.permit;
|
|
2116
|
+
try {
|
|
2117
|
+
const permitTx = await token.permit(
|
|
2118
|
+
p.owner,
|
|
2119
|
+
p.spender,
|
|
2120
|
+
p.value,
|
|
2121
|
+
p.deadline,
|
|
2122
|
+
p.v,
|
|
2123
|
+
p.r,
|
|
2124
|
+
p.s
|
|
2125
|
+
);
|
|
2126
|
+
await permitTx.wait();
|
|
2127
|
+
const transferTx = await token.transferFrom(p.owner, requirements.payTo, p.value);
|
|
2128
|
+
await transferTx.wait();
|
|
2129
|
+
return {
|
|
2130
|
+
success: true,
|
|
2131
|
+
transaction: transferTx.hash,
|
|
2132
|
+
status: "settled"
|
|
2133
|
+
};
|
|
2134
|
+
} catch (err) {
|
|
2135
|
+
return {
|
|
2136
|
+
success: false,
|
|
2137
|
+
error: `Tempo permit settlement failed: ${err.message}`
|
|
2138
|
+
};
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
1882
2141
|
async getTransactionReceipt(txHash) {
|
|
1883
2142
|
const response = await fetch(this.rpcUrl, {
|
|
1884
2143
|
method: "POST",
|
|
@@ -2122,12 +2381,12 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
2122
2381
|
return this.spenderAddress;
|
|
2123
2382
|
}
|
|
2124
2383
|
async getServerAddress() {
|
|
2125
|
-
const { ethers:
|
|
2126
|
-
const wallet = new
|
|
2384
|
+
const { ethers: ethers5 } = await import("ethers");
|
|
2385
|
+
const wallet = new ethers5.Wallet(this.serverPrivateKey);
|
|
2127
2386
|
return wallet.address;
|
|
2128
2387
|
}
|
|
2129
2388
|
async recoverIntentSigner(intent, chainId) {
|
|
2130
|
-
const { ethers:
|
|
2389
|
+
const { ethers: ethers5 } = await import("ethers");
|
|
2131
2390
|
const domain = {
|
|
2132
2391
|
...EIP712_DOMAIN,
|
|
2133
2392
|
chainId
|
|
@@ -2141,7 +2400,7 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
2141
2400
|
nonce: intent.nonce,
|
|
2142
2401
|
deadline: intent.deadline
|
|
2143
2402
|
};
|
|
2144
|
-
const recoveredAddress =
|
|
2403
|
+
const recoveredAddress = ethers5.verifyTypedData(
|
|
2145
2404
|
domain,
|
|
2146
2405
|
INTENT_TYPES,
|
|
2147
2406
|
message,
|
|
@@ -2185,10 +2444,10 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
2185
2444
|
return result.result || "0x0";
|
|
2186
2445
|
}
|
|
2187
2446
|
async executeTransferFrom(from, to, amount, token, rpcUrl) {
|
|
2188
|
-
const { ethers:
|
|
2189
|
-
const provider = new
|
|
2190
|
-
const wallet = new
|
|
2191
|
-
const tokenContract = new
|
|
2447
|
+
const { ethers: ethers5 } = await import("ethers");
|
|
2448
|
+
const provider = new ethers5.JsonRpcProvider(rpcUrl);
|
|
2449
|
+
const wallet = new ethers5.Wallet(this.serverPrivateKey, provider);
|
|
2450
|
+
const tokenContract = new ethers5.Contract(token, [
|
|
2192
2451
|
"function transferFrom(address from, address to, uint256 amount) returns (bool)"
|
|
2193
2452
|
], wallet);
|
|
2194
2453
|
const tx = await tokenContract.transferFrom(from, to, amount);
|
|
@@ -2213,7 +2472,7 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
2213
2472
|
|
|
2214
2473
|
// src/facilitators/registry.ts
|
|
2215
2474
|
init_cjs_shims();
|
|
2216
|
-
var
|
|
2475
|
+
var import_web36 = require("@solana/web3.js");
|
|
2217
2476
|
var import_bs582 = __toESM(require("bs58"));
|
|
2218
2477
|
var FacilitatorRegistry = class {
|
|
2219
2478
|
factories = /* @__PURE__ */ new Map();
|
|
@@ -2229,7 +2488,7 @@ var FacilitatorRegistry = class {
|
|
|
2229
2488
|
const feePayerKey = config?.feePayerPrivateKey || process.env.SOLANA_FEE_PAYER_KEY;
|
|
2230
2489
|
if (feePayerKey) {
|
|
2231
2490
|
try {
|
|
2232
|
-
feePayerKeypair =
|
|
2491
|
+
feePayerKeypair = import_web36.Keypair.fromSecretKey(import_bs582.default.decode(feePayerKey));
|
|
2233
2492
|
} catch (e) {
|
|
2234
2493
|
console.warn(`[SolanaFacilitator] Invalid fee payer key: ${e.message}`);
|
|
2235
2494
|
}
|
|
@@ -2504,7 +2763,7 @@ var TOKEN_ADDRESSES = {
|
|
|
2504
2763
|
// Devnet USDC
|
|
2505
2764
|
}
|
|
2506
2765
|
};
|
|
2507
|
-
var
|
|
2766
|
+
var CHAIN_TO_NETWORK2 = {
|
|
2508
2767
|
"base": "eip155:8453",
|
|
2509
2768
|
"base_sepolia": "eip155:84532",
|
|
2510
2769
|
"polygon": "eip155:137",
|
|
@@ -2535,9 +2794,13 @@ var TOKEN_DOMAINS = {
|
|
|
2535
2794
|
USDT: { name: "(PoS) Tether USD", version: "2" }
|
|
2536
2795
|
},
|
|
2537
2796
|
// Tempo Moderato testnet - TIP-20 stablecoins
|
|
2797
|
+
// Domain names verified against on-chain DOMAIN_SEPARATOR values on 2026-04-21.
|
|
2798
|
+
// See docs/TEMPO-WEB-SUPPORT.md Section 2 and test/server/tempo-domain.test.ts.
|
|
2799
|
+
// All 4 Tempo TIP-20 tokens (pathUSD / AlphaUSD / BetaUSD / ThetaUSD) use
|
|
2800
|
+
// the token symbol with first letter capitalized + version "1".
|
|
2538
2801
|
"eip155:42431": {
|
|
2539
|
-
USDC: { name: "
|
|
2540
|
-
USDT: { name: "
|
|
2802
|
+
USDC: { name: "PathUSD", version: "1" },
|
|
2803
|
+
USDT: { name: "AlphaUSD", version: "1" }
|
|
2541
2804
|
},
|
|
2542
2805
|
// BNB Smart Chain mainnet
|
|
2543
2806
|
"eip155:56": {
|
|
@@ -2660,14 +2923,14 @@ var MoltsPayServer = class {
|
|
|
2660
2923
|
const chainName = typeof c === "string" ? c : c.chain;
|
|
2661
2924
|
const explicitWallet = typeof c === "object" ? c.wallet : null;
|
|
2662
2925
|
return {
|
|
2663
|
-
network:
|
|
2926
|
+
network: CHAIN_TO_NETWORK2[chainName] || "eip155:8453",
|
|
2664
2927
|
wallet: getWalletForChain(chainName, explicitWallet || void 0),
|
|
2665
2928
|
tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
|
|
2666
2929
|
};
|
|
2667
2930
|
});
|
|
2668
2931
|
}
|
|
2669
2932
|
const chain = provider.chain || "base";
|
|
2670
|
-
const network =
|
|
2933
|
+
const network = CHAIN_TO_NETWORK2[chain] || this.networkId;
|
|
2671
2934
|
return [{
|
|
2672
2935
|
network,
|
|
2673
2936
|
wallet: getWalletForChain(chain),
|
|
@@ -2706,13 +2969,62 @@ var MoltsPayServer = class {
|
|
|
2706
2969
|
});
|
|
2707
2970
|
}
|
|
2708
2971
|
/**
|
|
2709
|
-
*
|
|
2972
|
+
* Apply CORS response headers according to the `cors` option.
|
|
2973
|
+
*
|
|
2974
|
+
* Default (`cors` unset or `true`): `Access-Control-Allow-Origin: *`. Matches 1.5.x behavior
|
|
2975
|
+
* and works for every browser client whose origin does not need to send cookies.
|
|
2976
|
+
*
|
|
2977
|
+
* `cors: false`: emit no CORS headers. Same-origin only.
|
|
2978
|
+
* `cors: string[]`: origin allowlist — echo the origin back iff it matches.
|
|
2979
|
+
* `cors: CorsOptions`: full control (allowlist + credentials + maxAge).
|
|
2980
|
+
*
|
|
2981
|
+
* The required-for-Web response headers are always exposed when CORS is active:
|
|
2982
|
+
* `X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt`.
|
|
2710
2983
|
*/
|
|
2711
|
-
|
|
2712
|
-
|
|
2984
|
+
applyCorsHeaders(req, res) {
|
|
2985
|
+
const cors = this.options.cors;
|
|
2986
|
+
if (cors === false) {
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
const requestOrigin = req.headers.origin ?? "*";
|
|
2990
|
+
if (cors === void 0 || cors === true) {
|
|
2991
|
+
this.writeCorsHeaders(res, "*");
|
|
2992
|
+
return;
|
|
2993
|
+
}
|
|
2994
|
+
if (Array.isArray(cors)) {
|
|
2995
|
+
if (cors.includes(requestOrigin)) {
|
|
2996
|
+
this.writeCorsHeaders(res, requestOrigin);
|
|
2997
|
+
res.setHeader("Vary", "Origin");
|
|
2998
|
+
}
|
|
2999
|
+
return;
|
|
3000
|
+
}
|
|
3001
|
+
const opt = cors;
|
|
3002
|
+
const isAllowed = typeof opt.origins === "function" ? opt.origins(requestOrigin) : opt.origins.includes(requestOrigin);
|
|
3003
|
+
if (!isAllowed) {
|
|
3004
|
+
return;
|
|
3005
|
+
}
|
|
3006
|
+
this.writeCorsHeaders(res, requestOrigin);
|
|
3007
|
+
res.setHeader("Vary", "Origin");
|
|
3008
|
+
if (opt.credentials) {
|
|
3009
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
3010
|
+
}
|
|
3011
|
+
const maxAge = opt.maxAge ?? 600;
|
|
3012
|
+
res.setHeader("Access-Control-Max-Age", String(maxAge));
|
|
3013
|
+
}
|
|
3014
|
+
writeCorsHeaders(res, origin) {
|
|
3015
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
2713
3016
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
2714
3017
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
|
|
2715
|
-
res.setHeader(
|
|
3018
|
+
res.setHeader(
|
|
3019
|
+
"Access-Control-Expose-Headers",
|
|
3020
|
+
"X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt"
|
|
3021
|
+
);
|
|
3022
|
+
}
|
|
3023
|
+
/**
|
|
3024
|
+
* Handle incoming request
|
|
3025
|
+
*/
|
|
3026
|
+
async handleRequest(req, res) {
|
|
3027
|
+
this.applyCorsHeaders(req, res);
|
|
2716
3028
|
if (req.method === "OPTIONS") {
|
|
2717
3029
|
res.writeHead(204);
|
|
2718
3030
|
res.end();
|
|
@@ -2935,6 +3247,14 @@ var MoltsPayServer = class {
|
|
|
2935
3247
|
console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
|
|
2936
3248
|
} catch (err) {
|
|
2937
3249
|
console.error("[MoltsPay] Settlement failed:", err.message);
|
|
3250
|
+
settlement = { success: false, error: err.message, facilitator: "none" };
|
|
3251
|
+
}
|
|
3252
|
+
if (!settlement?.success) {
|
|
3253
|
+
return this.sendJson(res, 402, {
|
|
3254
|
+
error: "Payment settlement failed",
|
|
3255
|
+
message: settlement?.error || "Settlement returned no success state",
|
|
3256
|
+
facilitator: settlement?.facilitator
|
|
3257
|
+
});
|
|
2938
3258
|
}
|
|
2939
3259
|
}
|
|
2940
3260
|
const responseHeaders = {};
|
|
@@ -3189,7 +3509,7 @@ var MoltsPayServer = class {
|
|
|
3189
3509
|
}
|
|
3190
3510
|
const scheme = payment.accepted?.scheme || payment.scheme;
|
|
3191
3511
|
const network = payment.accepted?.network || payment.network || this.networkId;
|
|
3192
|
-
if (scheme !== "exact") {
|
|
3512
|
+
if (scheme !== "exact" && scheme !== "permit") {
|
|
3193
3513
|
return { valid: false, error: `Unsupported scheme: ${scheme}` };
|
|
3194
3514
|
}
|
|
3195
3515
|
if (!this.isNetworkAccepted(network)) {
|
|
@@ -3211,8 +3531,10 @@ var MoltsPayServer = class {
|
|
|
3211
3531
|
const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
|
|
3212
3532
|
const tokenAddress = tokenAddresses[selectedToken];
|
|
3213
3533
|
const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
|
|
3534
|
+
const isTempo = selectedNetwork === "eip155:42431";
|
|
3535
|
+
const scheme = isTempo ? "permit" : "exact";
|
|
3214
3536
|
const requirements = {
|
|
3215
|
-
scheme
|
|
3537
|
+
scheme,
|
|
3216
3538
|
network: selectedNetwork,
|
|
3217
3539
|
asset: tokenAddress,
|
|
3218
3540
|
amount: amountInUnits,
|
|
@@ -3240,6 +3562,16 @@ var MoltsPayServer = class {
|
|
|
3240
3562
|
};
|
|
3241
3563
|
}
|
|
3242
3564
|
}
|
|
3565
|
+
if (isTempo) {
|
|
3566
|
+
const tempoFacilitator = this.registry.get("tempo");
|
|
3567
|
+
const tempoSpender = tempoFacilitator?.getSpenderAddress?.();
|
|
3568
|
+
if (tempoSpender) {
|
|
3569
|
+
requirements.extra = {
|
|
3570
|
+
...requirements.extra || {},
|
|
3571
|
+
tempoSpender
|
|
3572
|
+
};
|
|
3573
|
+
}
|
|
3574
|
+
}
|
|
3243
3575
|
return requirements;
|
|
3244
3576
|
}
|
|
3245
3577
|
/**
|
|
@@ -3374,10 +3706,10 @@ var MoltsPayServer = class {
|
|
|
3374
3706
|
}
|
|
3375
3707
|
const scheme = payment.accepted?.scheme || payment.scheme;
|
|
3376
3708
|
const network = payment.accepted?.network || payment.network;
|
|
3377
|
-
if (scheme !== "exact") {
|
|
3709
|
+
if (scheme !== "exact" && scheme !== "permit") {
|
|
3378
3710
|
return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
|
|
3379
3711
|
}
|
|
3380
|
-
const expectedNetwork = chain ?
|
|
3712
|
+
const expectedNetwork = chain ? CHAIN_TO_NETWORK2[chain] || this.networkId : this.networkId;
|
|
3381
3713
|
if (network !== expectedNetwork) {
|
|
3382
3714
|
return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
|
|
3383
3715
|
}
|
|
@@ -3639,7 +3971,7 @@ var MoltsPayServer = class {
|
|
|
3639
3971
|
buildProxyPaymentRequirements(config, wallet, token, chain) {
|
|
3640
3972
|
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
3641
3973
|
const acceptedTokens = getAcceptedCurrencies(config);
|
|
3642
|
-
const networkId = chain ?
|
|
3974
|
+
const networkId = chain ? CHAIN_TO_NETWORK2[chain] || this.networkId : this.networkId;
|
|
3643
3975
|
const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
|
|
3644
3976
|
const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
|
|
3645
3977
|
const tokenAddress = tokenAddresses[selectedToken];
|
|
@@ -3737,7 +4069,7 @@ var ERC20_APPROVE_ABI = [
|
|
|
3737
4069
|
];
|
|
3738
4070
|
async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = false) {
|
|
3739
4071
|
const chainConfig = CHAINS[chain];
|
|
3740
|
-
const provider = new
|
|
4072
|
+
const provider = new import_ethers4.ethers.JsonRpcProvider(chainConfig.rpc);
|
|
3741
4073
|
const wallet = client.getWallet();
|
|
3742
4074
|
if (!wallet) {
|
|
3743
4075
|
console.log(" \u274C No wallet found");
|
|
@@ -3746,15 +4078,15 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
|
|
|
3746
4078
|
const signer = wallet.connect(provider);
|
|
3747
4079
|
console.log(` Spender: ${spenderAddress}`);
|
|
3748
4080
|
let bnbBalance = await provider.getBalance(wallet.address);
|
|
3749
|
-
const minGasRequired =
|
|
4081
|
+
const minGasRequired = import_ethers4.ethers.parseEther("0.0005");
|
|
3750
4082
|
if (bnbBalance < minGasRequired) {
|
|
3751
4083
|
if (sponsorGas && BNB_SPONSOR_KEY) {
|
|
3752
4084
|
console.log(" \u23F3 Sponsoring BNB gas for approvals...");
|
|
3753
4085
|
try {
|
|
3754
|
-
const sponsorWallet = new
|
|
4086
|
+
const sponsorWallet = new import_ethers4.ethers.Wallet(BNB_SPONSOR_KEY, provider);
|
|
3755
4087
|
const tx = await sponsorWallet.sendTransaction({
|
|
3756
4088
|
to: wallet.address,
|
|
3757
|
-
value:
|
|
4089
|
+
value: import_ethers4.ethers.parseEther("0.001")
|
|
3758
4090
|
});
|
|
3759
4091
|
await tx.wait();
|
|
3760
4092
|
console.log(` \u2705 Sponsored 0.001 BNB (tx: ${tx.hash.slice(0, 10)}...)`);
|
|
@@ -3773,7 +4105,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
|
|
|
3773
4105
|
}
|
|
3774
4106
|
for (const tokenSymbol of ["USDT", "USDC"]) {
|
|
3775
4107
|
const tokenConfig = chainConfig.tokens[tokenSymbol];
|
|
3776
|
-
const tokenContract = new
|
|
4108
|
+
const tokenContract = new import_ethers4.ethers.Contract(tokenConfig.address, ERC20_APPROVE_ABI, signer);
|
|
3777
4109
|
const allowance = await tokenContract.allowance(wallet.address, spenderAddress);
|
|
3778
4110
|
if (allowance > 0n) {
|
|
3779
4111
|
console.log(` \u2705 ${tokenSymbol}: already approved for ${spenderAddress.slice(0, 10)}...`);
|
|
@@ -3781,7 +4113,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
|
|
|
3781
4113
|
}
|
|
3782
4114
|
console.log(` \u23F3 Approving ${tokenSymbol}...`);
|
|
3783
4115
|
try {
|
|
3784
|
-
const tx = await tokenContract.approve(spenderAddress,
|
|
4116
|
+
const tx = await tokenContract.approve(spenderAddress, import_ethers4.ethers.MaxUint256);
|
|
3785
4117
|
await tx.wait();
|
|
3786
4118
|
console.log(` \u2705 ${tokenSymbol}: approved (tx: ${tx.hash.slice(0, 10)}...)`);
|
|
3787
4119
|
} catch (err) {
|
|
@@ -3792,7 +4124,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
|
|
|
3792
4124
|
}
|
|
3793
4125
|
async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2) {
|
|
3794
4126
|
const chainConfig = CHAINS[chain];
|
|
3795
|
-
const provider = new
|
|
4127
|
+
const provider = new import_ethers4.ethers.JsonRpcProvider(chainConfig.rpc);
|
|
3796
4128
|
let spenderAddress = null;
|
|
3797
4129
|
try {
|
|
3798
4130
|
const walletPath = (0, import_path3.join)(configDir, "wallet.json");
|
|
@@ -3806,7 +4138,7 @@ async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2
|
|
|
3806
4138
|
}
|
|
3807
4139
|
for (const tokenSymbol of ["USDT", "USDC"]) {
|
|
3808
4140
|
const tokenConfig = chainConfig.tokens[tokenSymbol];
|
|
3809
|
-
const tokenContract = new
|
|
4141
|
+
const tokenContract = new import_ethers4.ethers.Contract(tokenConfig.address, ERC20_APPROVE_ABI, provider);
|
|
3810
4142
|
const allowance = await tokenContract.allowance(address, spenderAddress);
|
|
3811
4143
|
result[tokenSymbol.toLowerCase()] = allowance > 0n;
|
|
3812
4144
|
}
|