moltspay 1.4.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +187 -0
- package/dist/cli/index.js +486 -152
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/index.mjs +483 -149
- package/dist/cli/index.mjs.map +1 -1
- package/dist/client/index.d.mts +5 -0
- package/dist/client/index.d.ts +5 -0
- package/dist/client/index.js +245 -116
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs +241 -114
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/web/index.d.mts +418 -0
- package/dist/client/web/index.mjs +1289 -0
- package/dist/client/web/index.mjs.map +1 -0
- package/dist/facilitators/index.d.mts +24 -2
- package/dist/facilitators/index.d.ts +24 -2
- package/dist/facilitators/index.js +127 -13
- package/dist/facilitators/index.js.map +1 -1
- package/dist/facilitators/index.mjs +127 -13
- package/dist/facilitators/index.mjs.map +1 -1
- package/dist/index.js +463 -149
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +460 -146
- package/dist/index.mjs.map +1 -1
- package/dist/mcp/index.d.mts +1 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.js +1623 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/index.mjs +1617 -0
- package/dist/mcp/index.mjs.map +1 -0
- package/dist/server/index.d.mts +43 -1
- package/dist/server/index.d.ts +43 -1
- package/dist/server/index.js +205 -18
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +205 -18
- package/dist/server/index.mjs.map +1 -1
- package/package.json +19 -4
package/dist/cli/index.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
|
|
@@ -1300,15 +1447,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1300
1447
|
loadWallet() {
|
|
1301
1448
|
const walletPath = (0, import_path2.join)(this.configDir, "wallet.json");
|
|
1302
1449
|
if ((0, import_fs2.existsSync)(walletPath)) {
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1450
|
+
if (process.platform !== "win32") {
|
|
1451
|
+
try {
|
|
1452
|
+
const stats = (0, import_fs2.statSync)(walletPath);
|
|
1453
|
+
const mode = stats.mode & 511;
|
|
1454
|
+
if (mode !== 384) {
|
|
1455
|
+
console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
|
|
1456
|
+
console.warn(`[MoltsPay] Fixing permissions to 0600...`);
|
|
1457
|
+
(0, import_fs2.chmodSync)(walletPath, 384);
|
|
1458
|
+
}
|
|
1459
|
+
} catch {
|
|
1310
1460
|
}
|
|
1311
|
-
} catch (err) {
|
|
1312
1461
|
}
|
|
1313
1462
|
const content = (0, import_fs2.readFileSync)(walletPath, "utf-8");
|
|
1314
1463
|
return JSON.parse(content);
|
|
@@ -1320,7 +1469,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1320
1469
|
*/
|
|
1321
1470
|
static init(configDir, options) {
|
|
1322
1471
|
(0, import_fs2.mkdirSync)(configDir, { recursive: true });
|
|
1323
|
-
const wallet =
|
|
1472
|
+
const wallet = import_ethers2.Wallet.createRandom();
|
|
1324
1473
|
const walletData = {
|
|
1325
1474
|
address: wallet.address,
|
|
1326
1475
|
privateKey: wallet.privateKey,
|
|
@@ -1352,17 +1501,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1352
1501
|
} catch {
|
|
1353
1502
|
throw new Error(`Unknown chain: ${this.config.chain}`);
|
|
1354
1503
|
}
|
|
1355
|
-
const provider = new
|
|
1504
|
+
const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
|
|
1356
1505
|
const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
1357
1506
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
1358
1507
|
provider.getBalance(this.wallet.address),
|
|
1359
|
-
new
|
|
1360
|
-
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)
|
|
1361
1510
|
]);
|
|
1362
1511
|
return {
|
|
1363
|
-
usdc: parseFloat(
|
|
1364
|
-
usdt: parseFloat(
|
|
1365
|
-
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))
|
|
1366
1515
|
};
|
|
1367
1516
|
}
|
|
1368
1517
|
/**
|
|
@@ -1385,38 +1534,38 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1385
1534
|
supportedChains.map(async (chainName) => {
|
|
1386
1535
|
try {
|
|
1387
1536
|
const chain = getChain(chainName);
|
|
1388
|
-
const provider = new
|
|
1537
|
+
const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
|
|
1389
1538
|
if (chainName === "tempo_moderato") {
|
|
1390
1539
|
const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
|
|
1391
1540
|
provider.getBalance(this.wallet.address),
|
|
1392
|
-
new
|
|
1393
|
-
new
|
|
1394
|
-
new
|
|
1395
|
-
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)
|
|
1396
1545
|
]);
|
|
1397
1546
|
results[chainName] = {
|
|
1398
|
-
usdc: parseFloat(
|
|
1547
|
+
usdc: parseFloat(import_ethers2.ethers.formatUnits(pathUSD, 6)),
|
|
1399
1548
|
// pathUSD as default USDC
|
|
1400
|
-
usdt: parseFloat(
|
|
1549
|
+
usdt: parseFloat(import_ethers2.ethers.formatUnits(alphaUSD, 6)),
|
|
1401
1550
|
// alphaUSD as default USDT
|
|
1402
|
-
native: parseFloat(
|
|
1551
|
+
native: parseFloat(import_ethers2.ethers.formatEther(nativeBalance)),
|
|
1403
1552
|
tempo: {
|
|
1404
|
-
pathUSD: parseFloat(
|
|
1405
|
-
alphaUSD: parseFloat(
|
|
1406
|
-
betaUSD: parseFloat(
|
|
1407
|
-
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))
|
|
1408
1557
|
}
|
|
1409
1558
|
};
|
|
1410
1559
|
} else {
|
|
1411
1560
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
1412
1561
|
provider.getBalance(this.wallet.address),
|
|
1413
|
-
new
|
|
1414
|
-
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)
|
|
1415
1564
|
]);
|
|
1416
1565
|
results[chainName] = {
|
|
1417
|
-
usdc: parseFloat(
|
|
1418
|
-
usdt: parseFloat(
|
|
1419
|
-
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))
|
|
1420
1569
|
};
|
|
1421
1570
|
}
|
|
1422
1571
|
} catch (err) {
|
|
@@ -1774,16 +1923,40 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
1774
1923
|
|
|
1775
1924
|
// src/facilitators/tempo.ts
|
|
1776
1925
|
init_cjs_shims();
|
|
1926
|
+
var import_ethers3 = require("ethers");
|
|
1777
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
|
+
];
|
|
1778
1932
|
var TempoFacilitator = class extends BaseFacilitator {
|
|
1779
1933
|
name = "tempo";
|
|
1780
1934
|
displayName = "Tempo Testnet";
|
|
1781
1935
|
supportedNetworks = ["eip155:42431"];
|
|
1782
1936
|
// Tempo Moderato
|
|
1783
1937
|
rpcUrl;
|
|
1938
|
+
settlerWallet = null;
|
|
1784
1939
|
constructor() {
|
|
1785
1940
|
super();
|
|
1786
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;
|
|
1787
1960
|
}
|
|
1788
1961
|
async healthCheck() {
|
|
1789
1962
|
const start = Date.now();
|
|
@@ -1809,6 +1982,44 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
1809
1982
|
}
|
|
1810
1983
|
}
|
|
1811
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) {
|
|
1812
2023
|
try {
|
|
1813
2024
|
const tempoPayload = paymentPayload.payload;
|
|
1814
2025
|
if (!tempoPayload?.txHash) {
|
|
@@ -1866,7 +2077,11 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
1866
2077
|
}
|
|
1867
2078
|
}
|
|
1868
2079
|
async settle(paymentPayload, requirements) {
|
|
1869
|
-
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);
|
|
1870
2085
|
if (!verifyResult.valid) {
|
|
1871
2086
|
return { success: false, error: verifyResult.error };
|
|
1872
2087
|
}
|
|
@@ -1877,6 +2092,52 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
1877
2092
|
status: "settled"
|
|
1878
2093
|
};
|
|
1879
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
|
+
}
|
|
1880
2141
|
async getTransactionReceipt(txHash) {
|
|
1881
2142
|
const response = await fetch(this.rpcUrl, {
|
|
1882
2143
|
method: "POST",
|
|
@@ -2120,12 +2381,12 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
2120
2381
|
return this.spenderAddress;
|
|
2121
2382
|
}
|
|
2122
2383
|
async getServerAddress() {
|
|
2123
|
-
const { ethers:
|
|
2124
|
-
const wallet = new
|
|
2384
|
+
const { ethers: ethers5 } = await import("ethers");
|
|
2385
|
+
const wallet = new ethers5.Wallet(this.serverPrivateKey);
|
|
2125
2386
|
return wallet.address;
|
|
2126
2387
|
}
|
|
2127
2388
|
async recoverIntentSigner(intent, chainId) {
|
|
2128
|
-
const { ethers:
|
|
2389
|
+
const { ethers: ethers5 } = await import("ethers");
|
|
2129
2390
|
const domain = {
|
|
2130
2391
|
...EIP712_DOMAIN,
|
|
2131
2392
|
chainId
|
|
@@ -2139,7 +2400,7 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
2139
2400
|
nonce: intent.nonce,
|
|
2140
2401
|
deadline: intent.deadline
|
|
2141
2402
|
};
|
|
2142
|
-
const recoveredAddress =
|
|
2403
|
+
const recoveredAddress = ethers5.verifyTypedData(
|
|
2143
2404
|
domain,
|
|
2144
2405
|
INTENT_TYPES,
|
|
2145
2406
|
message,
|
|
@@ -2183,10 +2444,10 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
2183
2444
|
return result.result || "0x0";
|
|
2184
2445
|
}
|
|
2185
2446
|
async executeTransferFrom(from, to, amount, token, rpcUrl) {
|
|
2186
|
-
const { ethers:
|
|
2187
|
-
const provider = new
|
|
2188
|
-
const wallet = new
|
|
2189
|
-
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, [
|
|
2190
2451
|
"function transferFrom(address from, address to, uint256 amount) returns (bool)"
|
|
2191
2452
|
], wallet);
|
|
2192
2453
|
const tx = await tokenContract.transferFrom(from, to, amount);
|
|
@@ -2211,7 +2472,7 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
2211
2472
|
|
|
2212
2473
|
// src/facilitators/registry.ts
|
|
2213
2474
|
init_cjs_shims();
|
|
2214
|
-
var
|
|
2475
|
+
var import_web36 = require("@solana/web3.js");
|
|
2215
2476
|
var import_bs582 = __toESM(require("bs58"));
|
|
2216
2477
|
var FacilitatorRegistry = class {
|
|
2217
2478
|
factories = /* @__PURE__ */ new Map();
|
|
@@ -2227,7 +2488,7 @@ var FacilitatorRegistry = class {
|
|
|
2227
2488
|
const feePayerKey = config?.feePayerPrivateKey || process.env.SOLANA_FEE_PAYER_KEY;
|
|
2228
2489
|
if (feePayerKey) {
|
|
2229
2490
|
try {
|
|
2230
|
-
feePayerKeypair =
|
|
2491
|
+
feePayerKeypair = import_web36.Keypair.fromSecretKey(import_bs582.default.decode(feePayerKey));
|
|
2231
2492
|
} catch (e) {
|
|
2232
2493
|
console.warn(`[SolanaFacilitator] Invalid fee payer key: ${e.message}`);
|
|
2233
2494
|
}
|
|
@@ -2502,7 +2763,7 @@ var TOKEN_ADDRESSES = {
|
|
|
2502
2763
|
// Devnet USDC
|
|
2503
2764
|
}
|
|
2504
2765
|
};
|
|
2505
|
-
var
|
|
2766
|
+
var CHAIN_TO_NETWORK2 = {
|
|
2506
2767
|
"base": "eip155:8453",
|
|
2507
2768
|
"base_sepolia": "eip155:84532",
|
|
2508
2769
|
"polygon": "eip155:137",
|
|
@@ -2533,9 +2794,13 @@ var TOKEN_DOMAINS = {
|
|
|
2533
2794
|
USDT: { name: "(PoS) Tether USD", version: "2" }
|
|
2534
2795
|
},
|
|
2535
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".
|
|
2536
2801
|
"eip155:42431": {
|
|
2537
|
-
USDC: { name: "
|
|
2538
|
-
USDT: { name: "
|
|
2802
|
+
USDC: { name: "PathUSD", version: "1" },
|
|
2803
|
+
USDT: { name: "AlphaUSD", version: "1" }
|
|
2539
2804
|
},
|
|
2540
2805
|
// BNB Smart Chain mainnet
|
|
2541
2806
|
"eip155:56": {
|
|
@@ -2658,14 +2923,14 @@ var MoltsPayServer = class {
|
|
|
2658
2923
|
const chainName = typeof c === "string" ? c : c.chain;
|
|
2659
2924
|
const explicitWallet = typeof c === "object" ? c.wallet : null;
|
|
2660
2925
|
return {
|
|
2661
|
-
network:
|
|
2926
|
+
network: CHAIN_TO_NETWORK2[chainName] || "eip155:8453",
|
|
2662
2927
|
wallet: getWalletForChain(chainName, explicitWallet || void 0),
|
|
2663
2928
|
tokens: (typeof c === "object" ? c.tokens : null) || ["USDC"]
|
|
2664
2929
|
};
|
|
2665
2930
|
});
|
|
2666
2931
|
}
|
|
2667
2932
|
const chain = provider.chain || "base";
|
|
2668
|
-
const network =
|
|
2933
|
+
const network = CHAIN_TO_NETWORK2[chain] || this.networkId;
|
|
2669
2934
|
return [{
|
|
2670
2935
|
network,
|
|
2671
2936
|
wallet: getWalletForChain(chain),
|
|
@@ -2704,13 +2969,62 @@ var MoltsPayServer = class {
|
|
|
2704
2969
|
});
|
|
2705
2970
|
}
|
|
2706
2971
|
/**
|
|
2707
|
-
*
|
|
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`.
|
|
2708
2983
|
*/
|
|
2709
|
-
|
|
2710
|
-
|
|
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);
|
|
2711
3016
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
2712
3017
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
|
|
2713
|
-
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);
|
|
2714
3028
|
if (req.method === "OPTIONS") {
|
|
2715
3029
|
res.writeHead(204);
|
|
2716
3030
|
res.end();
|
|
@@ -2933,6 +3247,14 @@ var MoltsPayServer = class {
|
|
|
2933
3247
|
console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
|
|
2934
3248
|
} catch (err) {
|
|
2935
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
|
+
});
|
|
2936
3258
|
}
|
|
2937
3259
|
}
|
|
2938
3260
|
const responseHeaders = {};
|
|
@@ -3187,7 +3509,7 @@ var MoltsPayServer = class {
|
|
|
3187
3509
|
}
|
|
3188
3510
|
const scheme = payment.accepted?.scheme || payment.scheme;
|
|
3189
3511
|
const network = payment.accepted?.network || payment.network || this.networkId;
|
|
3190
|
-
if (scheme !== "exact") {
|
|
3512
|
+
if (scheme !== "exact" && scheme !== "permit") {
|
|
3191
3513
|
return { valid: false, error: `Unsupported scheme: ${scheme}` };
|
|
3192
3514
|
}
|
|
3193
3515
|
if (!this.isNetworkAccepted(network)) {
|
|
@@ -3209,8 +3531,10 @@ var MoltsPayServer = class {
|
|
|
3209
3531
|
const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
|
|
3210
3532
|
const tokenAddress = tokenAddresses[selectedToken];
|
|
3211
3533
|
const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
|
|
3534
|
+
const isTempo = selectedNetwork === "eip155:42431";
|
|
3535
|
+
const scheme = isTempo ? "permit" : "exact";
|
|
3212
3536
|
const requirements = {
|
|
3213
|
-
scheme
|
|
3537
|
+
scheme,
|
|
3214
3538
|
network: selectedNetwork,
|
|
3215
3539
|
asset: tokenAddress,
|
|
3216
3540
|
amount: amountInUnits,
|
|
@@ -3238,6 +3562,16 @@ var MoltsPayServer = class {
|
|
|
3238
3562
|
};
|
|
3239
3563
|
}
|
|
3240
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
|
+
}
|
|
3241
3575
|
return requirements;
|
|
3242
3576
|
}
|
|
3243
3577
|
/**
|
|
@@ -3372,10 +3706,10 @@ var MoltsPayServer = class {
|
|
|
3372
3706
|
}
|
|
3373
3707
|
const scheme = payment.accepted?.scheme || payment.scheme;
|
|
3374
3708
|
const network = payment.accepted?.network || payment.network;
|
|
3375
|
-
if (scheme !== "exact") {
|
|
3709
|
+
if (scheme !== "exact" && scheme !== "permit") {
|
|
3376
3710
|
return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
|
|
3377
3711
|
}
|
|
3378
|
-
const expectedNetwork = chain ?
|
|
3712
|
+
const expectedNetwork = chain ? CHAIN_TO_NETWORK2[chain] || this.networkId : this.networkId;
|
|
3379
3713
|
if (network !== expectedNetwork) {
|
|
3380
3714
|
return this.sendJson(res, 402, { error: `Network mismatch: expected ${expectedNetwork}, got ${network}` });
|
|
3381
3715
|
}
|
|
@@ -3637,7 +3971,7 @@ var MoltsPayServer = class {
|
|
|
3637
3971
|
buildProxyPaymentRequirements(config, wallet, token, chain) {
|
|
3638
3972
|
const amountInUnits = Math.floor(config.price * 1e6).toString();
|
|
3639
3973
|
const acceptedTokens = getAcceptedCurrencies(config);
|
|
3640
|
-
const networkId = chain ?
|
|
3974
|
+
const networkId = chain ? CHAIN_TO_NETWORK2[chain] || this.networkId : this.networkId;
|
|
3641
3975
|
const selectedToken = token && acceptedTokens.includes(token) ? token : acceptedTokens[0];
|
|
3642
3976
|
const tokenAddresses = TOKEN_ADDRESSES[networkId] || TOKEN_ADDRESSES[this.networkId] || {};
|
|
3643
3977
|
const tokenAddress = tokenAddresses[selectedToken];
|
|
@@ -3735,7 +4069,7 @@ var ERC20_APPROVE_ABI = [
|
|
|
3735
4069
|
];
|
|
3736
4070
|
async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = false) {
|
|
3737
4071
|
const chainConfig = CHAINS[chain];
|
|
3738
|
-
const provider = new
|
|
4072
|
+
const provider = new import_ethers4.ethers.JsonRpcProvider(chainConfig.rpc);
|
|
3739
4073
|
const wallet = client.getWallet();
|
|
3740
4074
|
if (!wallet) {
|
|
3741
4075
|
console.log(" \u274C No wallet found");
|
|
@@ -3744,15 +4078,15 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
|
|
|
3744
4078
|
const signer = wallet.connect(provider);
|
|
3745
4079
|
console.log(` Spender: ${spenderAddress}`);
|
|
3746
4080
|
let bnbBalance = await provider.getBalance(wallet.address);
|
|
3747
|
-
const minGasRequired =
|
|
4081
|
+
const minGasRequired = import_ethers4.ethers.parseEther("0.0005");
|
|
3748
4082
|
if (bnbBalance < minGasRequired) {
|
|
3749
4083
|
if (sponsorGas && BNB_SPONSOR_KEY) {
|
|
3750
4084
|
console.log(" \u23F3 Sponsoring BNB gas for approvals...");
|
|
3751
4085
|
try {
|
|
3752
|
-
const sponsorWallet = new
|
|
4086
|
+
const sponsorWallet = new import_ethers4.ethers.Wallet(BNB_SPONSOR_KEY, provider);
|
|
3753
4087
|
const tx = await sponsorWallet.sendTransaction({
|
|
3754
4088
|
to: wallet.address,
|
|
3755
|
-
value:
|
|
4089
|
+
value: import_ethers4.ethers.parseEther("0.001")
|
|
3756
4090
|
});
|
|
3757
4091
|
await tx.wait();
|
|
3758
4092
|
console.log(` \u2705 Sponsored 0.001 BNB (tx: ${tx.hash.slice(0, 10)}...)`);
|
|
@@ -3771,7 +4105,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
|
|
|
3771
4105
|
}
|
|
3772
4106
|
for (const tokenSymbol of ["USDT", "USDC"]) {
|
|
3773
4107
|
const tokenConfig = chainConfig.tokens[tokenSymbol];
|
|
3774
|
-
const tokenContract = new
|
|
4108
|
+
const tokenContract = new import_ethers4.ethers.Contract(tokenConfig.address, ERC20_APPROVE_ABI, signer);
|
|
3775
4109
|
const allowance = await tokenContract.allowance(wallet.address, spenderAddress);
|
|
3776
4110
|
if (allowance > 0n) {
|
|
3777
4111
|
console.log(` \u2705 ${tokenSymbol}: already approved for ${spenderAddress.slice(0, 10)}...`);
|
|
@@ -3779,7 +4113,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
|
|
|
3779
4113
|
}
|
|
3780
4114
|
console.log(` \u23F3 Approving ${tokenSymbol}...`);
|
|
3781
4115
|
try {
|
|
3782
|
-
const tx = await tokenContract.approve(spenderAddress,
|
|
4116
|
+
const tx = await tokenContract.approve(spenderAddress, import_ethers4.ethers.MaxUint256);
|
|
3783
4117
|
await tx.wait();
|
|
3784
4118
|
console.log(` \u2705 ${tokenSymbol}: approved (tx: ${tx.hash.slice(0, 10)}...)`);
|
|
3785
4119
|
} catch (err) {
|
|
@@ -3790,7 +4124,7 @@ async function setupBNBApprovals(client, chain, spenderAddress, sponsorGas = fal
|
|
|
3790
4124
|
}
|
|
3791
4125
|
async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2) {
|
|
3792
4126
|
const chainConfig = CHAINS[chain];
|
|
3793
|
-
const provider = new
|
|
4127
|
+
const provider = new import_ethers4.ethers.JsonRpcProvider(chainConfig.rpc);
|
|
3794
4128
|
let spenderAddress = null;
|
|
3795
4129
|
try {
|
|
3796
4130
|
const walletPath = (0, import_path3.join)(configDir, "wallet.json");
|
|
@@ -3804,7 +4138,7 @@ async function checkBNBApprovals(address, chain, configDir = DEFAULT_CONFIG_DIR2
|
|
|
3804
4138
|
}
|
|
3805
4139
|
for (const tokenSymbol of ["USDT", "USDC"]) {
|
|
3806
4140
|
const tokenConfig = chainConfig.tokens[tokenSymbol];
|
|
3807
|
-
const tokenContract = new
|
|
4141
|
+
const tokenContract = new import_ethers4.ethers.Contract(tokenConfig.address, ERC20_APPROVE_ABI, provider);
|
|
3808
4142
|
const allowance = await tokenContract.allowance(address, spenderAddress);
|
|
3809
4143
|
result[tokenSymbol.toLowerCase()] = allowance > 0n;
|
|
3810
4144
|
}
|