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/index.mjs
CHANGED
|
@@ -236,6 +236,9 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
236
236
|
}
|
|
237
237
|
};
|
|
238
238
|
|
|
239
|
+
// src/facilitators/tempo.ts
|
|
240
|
+
import { ethers } from "ethers";
|
|
241
|
+
|
|
239
242
|
// src/chains/index.ts
|
|
240
243
|
var CHAINS = {
|
|
241
244
|
// ============ Mainnet ============
|
|
@@ -431,15 +434,38 @@ var ERC20_ABI = [
|
|
|
431
434
|
|
|
432
435
|
// src/facilitators/tempo.ts
|
|
433
436
|
var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
437
|
+
var TIP20_PERMIT_ABI = [
|
|
438
|
+
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
439
|
+
"function transferFrom(address from, address to, uint256 value) returns (bool)"
|
|
440
|
+
];
|
|
434
441
|
var TempoFacilitator = class extends BaseFacilitator {
|
|
435
442
|
name = "tempo";
|
|
436
443
|
displayName = "Tempo Testnet";
|
|
437
444
|
supportedNetworks = ["eip155:42431"];
|
|
438
445
|
// Tempo Moderato
|
|
439
446
|
rpcUrl;
|
|
447
|
+
settlerWallet = null;
|
|
440
448
|
constructor() {
|
|
441
449
|
super();
|
|
442
450
|
this.rpcUrl = CHAINS.tempo_moderato.rpc;
|
|
451
|
+
const settlerKey = process.env.TEMPO_SETTLER_KEY;
|
|
452
|
+
if (settlerKey) {
|
|
453
|
+
try {
|
|
454
|
+
const provider = new ethers.JsonRpcProvider(this.rpcUrl);
|
|
455
|
+
this.settlerWallet = new ethers.Wallet(settlerKey, provider);
|
|
456
|
+
} catch (err) {
|
|
457
|
+
console.warn("[TempoFacilitator] Invalid TEMPO_SETTLER_KEY, permit settlement disabled:", err);
|
|
458
|
+
this.settlerWallet = null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
/**
|
|
463
|
+
* Settler EOA address advertised to clients via `X-Payment-Required.extra.tempoSpender`.
|
|
464
|
+
* Web Client uses this as the `spender` field in the signed EIP-2612 Permit.
|
|
465
|
+
* Returns null if no TEMPO_SETTLER_KEY is configured — permit settlement unavailable.
|
|
466
|
+
*/
|
|
467
|
+
getSpenderAddress() {
|
|
468
|
+
return this.settlerWallet?.address ?? null;
|
|
443
469
|
}
|
|
444
470
|
async healthCheck() {
|
|
445
471
|
const start = Date.now();
|
|
@@ -465,6 +491,44 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
465
491
|
}
|
|
466
492
|
}
|
|
467
493
|
async verify(paymentPayload, requirements) {
|
|
494
|
+
const inner = paymentPayload.payload;
|
|
495
|
+
if (inner && "permit" in inner && inner.permit) {
|
|
496
|
+
return this.verifyPermit(inner, requirements);
|
|
497
|
+
}
|
|
498
|
+
return this.verifyTxHash(paymentPayload, requirements);
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* Structural validation of an EIP-2612 permit payload. Does NOT submit
|
|
502
|
+
* anything on-chain — actual submission happens in settlePermit().
|
|
503
|
+
*/
|
|
504
|
+
async verifyPermit(payload, requirements) {
|
|
505
|
+
if (!this.settlerWallet) {
|
|
506
|
+
return { valid: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
|
|
507
|
+
}
|
|
508
|
+
const p = payload.permit;
|
|
509
|
+
if (!p || !p.owner || !p.spender || !p.value || !p.deadline) {
|
|
510
|
+
return { valid: false, error: "Invalid permit payload: missing fields" };
|
|
511
|
+
}
|
|
512
|
+
if (p.spender.toLowerCase() !== this.settlerWallet.address.toLowerCase()) {
|
|
513
|
+
return {
|
|
514
|
+
valid: false,
|
|
515
|
+
error: `Permit spender ${p.spender} does not match configured settler ${this.settlerWallet.address}`
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
const deadline = BigInt(p.deadline);
|
|
519
|
+
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
520
|
+
if (deadline <= now) {
|
|
521
|
+
return { valid: false, error: "Permit deadline has expired" };
|
|
522
|
+
}
|
|
523
|
+
if (BigInt(p.value) < BigInt(requirements.amount || "0")) {
|
|
524
|
+
return {
|
|
525
|
+
valid: false,
|
|
526
|
+
error: `Permit value ${p.value} is less than required ${requirements.amount}`
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
return { valid: true, details: { scheme: "permit", owner: p.owner } };
|
|
530
|
+
}
|
|
531
|
+
async verifyTxHash(paymentPayload, requirements) {
|
|
468
532
|
try {
|
|
469
533
|
const tempoPayload = paymentPayload.payload;
|
|
470
534
|
if (!tempoPayload?.txHash) {
|
|
@@ -522,7 +586,11 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
522
586
|
}
|
|
523
587
|
}
|
|
524
588
|
async settle(paymentPayload, requirements) {
|
|
525
|
-
const
|
|
589
|
+
const inner = paymentPayload.payload;
|
|
590
|
+
if (inner && "permit" in inner && inner.permit) {
|
|
591
|
+
return this.settlePermit(inner, requirements);
|
|
592
|
+
}
|
|
593
|
+
const verifyResult = await this.verifyTxHash(paymentPayload, requirements);
|
|
526
594
|
if (!verifyResult.valid) {
|
|
527
595
|
return { success: false, error: verifyResult.error };
|
|
528
596
|
}
|
|
@@ -533,6 +601,52 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
533
601
|
status: "settled"
|
|
534
602
|
};
|
|
535
603
|
}
|
|
604
|
+
/**
|
|
605
|
+
* EIP-2612 permit settlement path. Submits two transactions on Tempo:
|
|
606
|
+
* 1. pathUSD.permit(owner, spender=settler, value, deadline, v, r, s)
|
|
607
|
+
* 2. pathUSD.transferFrom(owner, payTo, value)
|
|
608
|
+
*
|
|
609
|
+
* The settler EOA pays Tempo gas (via the TIP-20 `feeToken` mechanism — no
|
|
610
|
+
* native tTEMPO required; any held TIP-20 token balance covers fees).
|
|
611
|
+
*/
|
|
612
|
+
async settlePermit(payload, requirements) {
|
|
613
|
+
if (!this.settlerWallet) {
|
|
614
|
+
return { success: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
|
|
615
|
+
}
|
|
616
|
+
if (!requirements.asset || !requirements.payTo) {
|
|
617
|
+
return { success: false, error: "Missing asset or payTo in requirements" };
|
|
618
|
+
}
|
|
619
|
+
const verifyResult = await this.verifyPermit(payload, requirements);
|
|
620
|
+
if (!verifyResult.valid) {
|
|
621
|
+
return { success: false, error: verifyResult.error };
|
|
622
|
+
}
|
|
623
|
+
const token = new ethers.Contract(requirements.asset, TIP20_PERMIT_ABI, this.settlerWallet);
|
|
624
|
+
const p = payload.permit;
|
|
625
|
+
try {
|
|
626
|
+
const permitTx = await token.permit(
|
|
627
|
+
p.owner,
|
|
628
|
+
p.spender,
|
|
629
|
+
p.value,
|
|
630
|
+
p.deadline,
|
|
631
|
+
p.v,
|
|
632
|
+
p.r,
|
|
633
|
+
p.s
|
|
634
|
+
);
|
|
635
|
+
await permitTx.wait();
|
|
636
|
+
const transferTx = await token.transferFrom(p.owner, requirements.payTo, p.value);
|
|
637
|
+
await transferTx.wait();
|
|
638
|
+
return {
|
|
639
|
+
success: true,
|
|
640
|
+
transaction: transferTx.hash,
|
|
641
|
+
status: "settled"
|
|
642
|
+
};
|
|
643
|
+
} catch (err) {
|
|
644
|
+
return {
|
|
645
|
+
success: false,
|
|
646
|
+
error: `Tempo permit settlement failed: ${err.message}`
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
}
|
|
536
650
|
async getTransactionReceipt(txHash) {
|
|
537
651
|
const response = await fetch(this.rpcUrl, {
|
|
538
652
|
method: "POST",
|
|
@@ -775,12 +889,12 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
775
889
|
return this.spenderAddress;
|
|
776
890
|
}
|
|
777
891
|
async getServerAddress() {
|
|
778
|
-
const { ethers:
|
|
779
|
-
const wallet = new
|
|
892
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
893
|
+
const wallet = new ethers7.Wallet(this.serverPrivateKey);
|
|
780
894
|
return wallet.address;
|
|
781
895
|
}
|
|
782
896
|
async recoverIntentSigner(intent, chainId) {
|
|
783
|
-
const { ethers:
|
|
897
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
784
898
|
const domain = {
|
|
785
899
|
...EIP712_DOMAIN,
|
|
786
900
|
chainId
|
|
@@ -794,7 +908,7 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
794
908
|
nonce: intent.nonce,
|
|
795
909
|
deadline: intent.deadline
|
|
796
910
|
};
|
|
797
|
-
const recoveredAddress =
|
|
911
|
+
const recoveredAddress = ethers7.verifyTypedData(
|
|
798
912
|
domain,
|
|
799
913
|
INTENT_TYPES,
|
|
800
914
|
message,
|
|
@@ -838,10 +952,10 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
838
952
|
return result.result || "0x0";
|
|
839
953
|
}
|
|
840
954
|
async executeTransferFrom(from, to, amount, token, rpcUrl) {
|
|
841
|
-
const { ethers:
|
|
842
|
-
const provider = new
|
|
843
|
-
const wallet = new
|
|
844
|
-
const tokenContract = new
|
|
955
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
956
|
+
const provider = new ethers7.JsonRpcProvider(rpcUrl);
|
|
957
|
+
const wallet = new ethers7.Wallet(this.serverPrivateKey, provider);
|
|
958
|
+
const tokenContract = new ethers7.Contract(token, [
|
|
845
959
|
"function transferFrom(address from, address to, uint256 amount) returns (bool)"
|
|
846
960
|
], wallet);
|
|
847
961
|
const tx = await tokenContract.transferFrom(from, to, amount);
|
|
@@ -1077,16 +1191,16 @@ var SolanaFacilitator = class extends BaseFacilitator {
|
|
|
1077
1191
|
return this.supportedNetworks.includes(network);
|
|
1078
1192
|
}
|
|
1079
1193
|
};
|
|
1080
|
-
async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
|
|
1194
|
+
async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey, connection) {
|
|
1081
1195
|
const chainConfig = SOLANA_CHAINS[chain];
|
|
1082
|
-
const
|
|
1196
|
+
const conn = connection ?? new Connection2(chainConfig.rpc, "confirmed");
|
|
1083
1197
|
const mint = new PublicKey2(chainConfig.tokens.USDC.mint);
|
|
1084
1198
|
const actualFeePayer = feePayerPubkey || senderPubkey;
|
|
1085
1199
|
const senderATA = await getAssociatedTokenAddress(mint, senderPubkey);
|
|
1086
1200
|
const recipientATA = await getAssociatedTokenAddress(mint, recipientPubkey);
|
|
1087
1201
|
const transaction = new Transaction();
|
|
1088
1202
|
try {
|
|
1089
|
-
await getAccount(
|
|
1203
|
+
await getAccount(conn, recipientATA);
|
|
1090
1204
|
} catch {
|
|
1091
1205
|
transaction.add(
|
|
1092
1206
|
createAssociatedTokenAccountInstruction(
|
|
@@ -1117,7 +1231,7 @@ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amo
|
|
|
1117
1231
|
// decimals
|
|
1118
1232
|
)
|
|
1119
1233
|
);
|
|
1120
|
-
const { blockhash, lastValidBlockHeight } = await
|
|
1234
|
+
const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash();
|
|
1121
1235
|
transaction.recentBlockhash = blockhash;
|
|
1122
1236
|
transaction.feePayer = actualFeePayer;
|
|
1123
1237
|
return transaction;
|
|
@@ -1453,9 +1567,13 @@ var TOKEN_DOMAINS = {
|
|
|
1453
1567
|
USDT: { name: "(PoS) Tether USD", version: "2" }
|
|
1454
1568
|
},
|
|
1455
1569
|
// Tempo Moderato testnet - TIP-20 stablecoins
|
|
1570
|
+
// Domain names verified against on-chain DOMAIN_SEPARATOR values on 2026-04-21.
|
|
1571
|
+
// See docs/TEMPO-WEB-SUPPORT.md Section 2 and test/server/tempo-domain.test.ts.
|
|
1572
|
+
// All 4 Tempo TIP-20 tokens (pathUSD / AlphaUSD / BetaUSD / ThetaUSD) use
|
|
1573
|
+
// the token symbol with first letter capitalized + version "1".
|
|
1456
1574
|
"eip155:42431": {
|
|
1457
|
-
USDC: { name: "
|
|
1458
|
-
USDT: { name: "
|
|
1575
|
+
USDC: { name: "PathUSD", version: "1" },
|
|
1576
|
+
USDT: { name: "AlphaUSD", version: "1" }
|
|
1459
1577
|
},
|
|
1460
1578
|
// BNB Smart Chain mainnet
|
|
1461
1579
|
"eip155:56": {
|
|
@@ -1624,13 +1742,62 @@ var MoltsPayServer = class {
|
|
|
1624
1742
|
});
|
|
1625
1743
|
}
|
|
1626
1744
|
/**
|
|
1627
|
-
*
|
|
1745
|
+
* Apply CORS response headers according to the `cors` option.
|
|
1746
|
+
*
|
|
1747
|
+
* Default (`cors` unset or `true`): `Access-Control-Allow-Origin: *`. Matches 1.5.x behavior
|
|
1748
|
+
* and works for every browser client whose origin does not need to send cookies.
|
|
1749
|
+
*
|
|
1750
|
+
* `cors: false`: emit no CORS headers. Same-origin only.
|
|
1751
|
+
* `cors: string[]`: origin allowlist — echo the origin back iff it matches.
|
|
1752
|
+
* `cors: CorsOptions`: full control (allowlist + credentials + maxAge).
|
|
1753
|
+
*
|
|
1754
|
+
* The required-for-Web response headers are always exposed when CORS is active:
|
|
1755
|
+
* `X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt`.
|
|
1628
1756
|
*/
|
|
1629
|
-
|
|
1630
|
-
|
|
1757
|
+
applyCorsHeaders(req, res) {
|
|
1758
|
+
const cors = this.options.cors;
|
|
1759
|
+
if (cors === false) {
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
const requestOrigin = req.headers.origin ?? "*";
|
|
1763
|
+
if (cors === void 0 || cors === true) {
|
|
1764
|
+
this.writeCorsHeaders(res, "*");
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1767
|
+
if (Array.isArray(cors)) {
|
|
1768
|
+
if (cors.includes(requestOrigin)) {
|
|
1769
|
+
this.writeCorsHeaders(res, requestOrigin);
|
|
1770
|
+
res.setHeader("Vary", "Origin");
|
|
1771
|
+
}
|
|
1772
|
+
return;
|
|
1773
|
+
}
|
|
1774
|
+
const opt = cors;
|
|
1775
|
+
const isAllowed = typeof opt.origins === "function" ? opt.origins(requestOrigin) : opt.origins.includes(requestOrigin);
|
|
1776
|
+
if (!isAllowed) {
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
this.writeCorsHeaders(res, requestOrigin);
|
|
1780
|
+
res.setHeader("Vary", "Origin");
|
|
1781
|
+
if (opt.credentials) {
|
|
1782
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
1783
|
+
}
|
|
1784
|
+
const maxAge = opt.maxAge ?? 600;
|
|
1785
|
+
res.setHeader("Access-Control-Max-Age", String(maxAge));
|
|
1786
|
+
}
|
|
1787
|
+
writeCorsHeaders(res, origin) {
|
|
1788
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
1631
1789
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1632
1790
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
|
|
1633
|
-
res.setHeader(
|
|
1791
|
+
res.setHeader(
|
|
1792
|
+
"Access-Control-Expose-Headers",
|
|
1793
|
+
"X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt"
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* Handle incoming request
|
|
1798
|
+
*/
|
|
1799
|
+
async handleRequest(req, res) {
|
|
1800
|
+
this.applyCorsHeaders(req, res);
|
|
1634
1801
|
if (req.method === "OPTIONS") {
|
|
1635
1802
|
res.writeHead(204);
|
|
1636
1803
|
res.end();
|
|
@@ -1853,6 +2020,14 @@ var MoltsPayServer = class {
|
|
|
1853
2020
|
console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
|
|
1854
2021
|
} catch (err) {
|
|
1855
2022
|
console.error("[MoltsPay] Settlement failed:", err.message);
|
|
2023
|
+
settlement = { success: false, error: err.message, facilitator: "none" };
|
|
2024
|
+
}
|
|
2025
|
+
if (!settlement?.success) {
|
|
2026
|
+
return this.sendJson(res, 402, {
|
|
2027
|
+
error: "Payment settlement failed",
|
|
2028
|
+
message: settlement?.error || "Settlement returned no success state",
|
|
2029
|
+
facilitator: settlement?.facilitator
|
|
2030
|
+
});
|
|
1856
2031
|
}
|
|
1857
2032
|
}
|
|
1858
2033
|
const responseHeaders = {};
|
|
@@ -2107,7 +2282,7 @@ var MoltsPayServer = class {
|
|
|
2107
2282
|
}
|
|
2108
2283
|
const scheme = payment.accepted?.scheme || payment.scheme;
|
|
2109
2284
|
const network = payment.accepted?.network || payment.network || this.networkId;
|
|
2110
|
-
if (scheme !== "exact") {
|
|
2285
|
+
if (scheme !== "exact" && scheme !== "permit") {
|
|
2111
2286
|
return { valid: false, error: `Unsupported scheme: ${scheme}` };
|
|
2112
2287
|
}
|
|
2113
2288
|
if (!this.isNetworkAccepted(network)) {
|
|
@@ -2129,8 +2304,10 @@ var MoltsPayServer = class {
|
|
|
2129
2304
|
const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
|
|
2130
2305
|
const tokenAddress = tokenAddresses[selectedToken];
|
|
2131
2306
|
const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
|
|
2307
|
+
const isTempo = selectedNetwork === "eip155:42431";
|
|
2308
|
+
const scheme = isTempo ? "permit" : "exact";
|
|
2132
2309
|
const requirements = {
|
|
2133
|
-
scheme
|
|
2310
|
+
scheme,
|
|
2134
2311
|
network: selectedNetwork,
|
|
2135
2312
|
asset: tokenAddress,
|
|
2136
2313
|
amount: amountInUnits,
|
|
@@ -2158,6 +2335,16 @@ var MoltsPayServer = class {
|
|
|
2158
2335
|
};
|
|
2159
2336
|
}
|
|
2160
2337
|
}
|
|
2338
|
+
if (isTempo) {
|
|
2339
|
+
const tempoFacilitator = this.registry.get("tempo");
|
|
2340
|
+
const tempoSpender = tempoFacilitator?.getSpenderAddress?.();
|
|
2341
|
+
if (tempoSpender) {
|
|
2342
|
+
requirements.extra = {
|
|
2343
|
+
...requirements.extra || {},
|
|
2344
|
+
tempoSpender
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
}
|
|
2161
2348
|
return requirements;
|
|
2162
2349
|
}
|
|
2163
2350
|
/**
|
|
@@ -2292,7 +2479,7 @@ var MoltsPayServer = class {
|
|
|
2292
2479
|
}
|
|
2293
2480
|
const scheme = payment.accepted?.scheme || payment.scheme;
|
|
2294
2481
|
const network = payment.accepted?.network || payment.network;
|
|
2295
|
-
if (scheme !== "exact") {
|
|
2482
|
+
if (scheme !== "exact" && scheme !== "permit") {
|
|
2296
2483
|
return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
|
|
2297
2484
|
}
|
|
2298
2485
|
const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
|
|
@@ -2612,11 +2799,11 @@ var MoltsPayServer = class {
|
|
|
2612
2799
|
}
|
|
2613
2800
|
};
|
|
2614
2801
|
|
|
2615
|
-
// src/client/index.ts
|
|
2802
|
+
// src/client/node/index.ts
|
|
2616
2803
|
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2, statSync, chmodSync } from "fs";
|
|
2617
2804
|
import { homedir as homedir2 } from "os";
|
|
2618
2805
|
import { join as join4 } from "path";
|
|
2619
|
-
import { Wallet, ethers } from "ethers";
|
|
2806
|
+
import { Wallet as Wallet2, ethers as ethers3 } from "ethers";
|
|
2620
2807
|
|
|
2621
2808
|
// src/wallet/solana.ts
|
|
2622
2809
|
import { Keypair as Keypair3, PublicKey as PublicKey3, LAMPORTS_PER_SOL } from "@solana/web3.js";
|
|
@@ -2645,11 +2832,172 @@ function loadSolanaWallet(configDir = DEFAULT_CONFIG_DIR) {
|
|
|
2645
2832
|
}
|
|
2646
2833
|
}
|
|
2647
2834
|
|
|
2648
|
-
// src/client/index.ts
|
|
2835
|
+
// src/client/node/index.ts
|
|
2649
2836
|
import { PublicKey as PublicKey4 } from "@solana/web3.js";
|
|
2837
|
+
|
|
2838
|
+
// src/client/core/types.ts
|
|
2650
2839
|
var X402_VERSION3 = 2;
|
|
2651
2840
|
var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
|
|
2652
2841
|
var PAYMENT_HEADER2 = "x-payment";
|
|
2842
|
+
|
|
2843
|
+
// src/client/core/chain-map.ts
|
|
2844
|
+
var NETWORK_TO_CHAIN = {
|
|
2845
|
+
"eip155:8453": "base",
|
|
2846
|
+
"eip155:137": "polygon",
|
|
2847
|
+
"eip155:84532": "base_sepolia",
|
|
2848
|
+
"eip155:42431": "tempo_moderato",
|
|
2849
|
+
"eip155:56": "bnb",
|
|
2850
|
+
"eip155:97": "bnb_testnet",
|
|
2851
|
+
"solana:mainnet": "solana",
|
|
2852
|
+
"solana:devnet": "solana_devnet"
|
|
2853
|
+
};
|
|
2854
|
+
var CHAIN_TO_NETWORK2 = Object.fromEntries(
|
|
2855
|
+
Object.entries(NETWORK_TO_CHAIN).map(([network, chain]) => [chain, network])
|
|
2856
|
+
);
|
|
2857
|
+
function networkToChainName(network) {
|
|
2858
|
+
return NETWORK_TO_CHAIN[network] ?? null;
|
|
2859
|
+
}
|
|
2860
|
+
|
|
2861
|
+
// src/client/core/base64.ts
|
|
2862
|
+
var BufferCtor = globalThis.Buffer;
|
|
2863
|
+
|
|
2864
|
+
// src/client/core/eip3009.ts
|
|
2865
|
+
var EIP3009_TYPES = {
|
|
2866
|
+
TransferWithAuthorization: [
|
|
2867
|
+
{ name: "from", type: "address" },
|
|
2868
|
+
{ name: "to", type: "address" },
|
|
2869
|
+
{ name: "value", type: "uint256" },
|
|
2870
|
+
{ name: "validAfter", type: "uint256" },
|
|
2871
|
+
{ name: "validBefore", type: "uint256" },
|
|
2872
|
+
{ name: "nonce", type: "bytes32" }
|
|
2873
|
+
]
|
|
2874
|
+
};
|
|
2875
|
+
function buildEIP3009TypedData(args) {
|
|
2876
|
+
const validAfter = args.validAfter ?? "0";
|
|
2877
|
+
const validBefore = args.validBefore ?? (Math.floor(Date.now() / 1e3) + 3600).toString();
|
|
2878
|
+
const authorization = {
|
|
2879
|
+
from: args.from,
|
|
2880
|
+
to: args.to,
|
|
2881
|
+
value: args.value,
|
|
2882
|
+
validAfter,
|
|
2883
|
+
validBefore,
|
|
2884
|
+
nonce: args.nonce
|
|
2885
|
+
};
|
|
2886
|
+
return {
|
|
2887
|
+
domain: {
|
|
2888
|
+
name: args.tokenName,
|
|
2889
|
+
version: args.tokenVersion,
|
|
2890
|
+
chainId: args.chainId,
|
|
2891
|
+
verifyingContract: args.tokenAddress
|
|
2892
|
+
},
|
|
2893
|
+
types: EIP3009_TYPES,
|
|
2894
|
+
primaryType: "TransferWithAuthorization",
|
|
2895
|
+
message: authorization
|
|
2896
|
+
};
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2899
|
+
// src/client/core/bnb-intent.ts
|
|
2900
|
+
var BNB_INTENT_TYPES = {
|
|
2901
|
+
PaymentIntent: [
|
|
2902
|
+
{ name: "from", type: "address" },
|
|
2903
|
+
{ name: "to", type: "address" },
|
|
2904
|
+
{ name: "amount", type: "uint256" },
|
|
2905
|
+
{ name: "token", type: "address" },
|
|
2906
|
+
{ name: "service", type: "string" },
|
|
2907
|
+
{ name: "nonce", type: "uint256" },
|
|
2908
|
+
{ name: "deadline", type: "uint256" }
|
|
2909
|
+
]
|
|
2910
|
+
};
|
|
2911
|
+
var BNB_DOMAIN_NAME = "MoltsPay";
|
|
2912
|
+
var BNB_DOMAIN_VERSION = "1";
|
|
2913
|
+
function buildBnbIntentTypedData(args) {
|
|
2914
|
+
const intent = {
|
|
2915
|
+
from: args.from,
|
|
2916
|
+
to: args.to,
|
|
2917
|
+
amount: args.amount,
|
|
2918
|
+
token: args.tokenAddress,
|
|
2919
|
+
service: args.service,
|
|
2920
|
+
nonce: args.nonce,
|
|
2921
|
+
deadline: args.deadline
|
|
2922
|
+
};
|
|
2923
|
+
return {
|
|
2924
|
+
domain: {
|
|
2925
|
+
name: BNB_DOMAIN_NAME,
|
|
2926
|
+
version: BNB_DOMAIN_VERSION,
|
|
2927
|
+
chainId: args.chainId
|
|
2928
|
+
},
|
|
2929
|
+
types: BNB_INTENT_TYPES,
|
|
2930
|
+
primaryType: "PaymentIntent",
|
|
2931
|
+
message: intent
|
|
2932
|
+
};
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
// src/client/node/signer.ts
|
|
2936
|
+
import { ethers as ethers2 } from "ethers";
|
|
2937
|
+
import { Transaction as Transaction2 } from "@solana/web3.js";
|
|
2938
|
+
var NodeSigner = class {
|
|
2939
|
+
evmWallet;
|
|
2940
|
+
getSolanaKeypair;
|
|
2941
|
+
constructor(evmWallet, options = {}) {
|
|
2942
|
+
this.evmWallet = evmWallet;
|
|
2943
|
+
this.getSolanaKeypair = options.getSolanaKeypair ?? (() => null);
|
|
2944
|
+
}
|
|
2945
|
+
async getEvmAddress() {
|
|
2946
|
+
return this.evmWallet.address;
|
|
2947
|
+
}
|
|
2948
|
+
async getSolanaAddress() {
|
|
2949
|
+
const kp = this.getSolanaKeypair();
|
|
2950
|
+
return kp ? kp.publicKey.toBase58() : null;
|
|
2951
|
+
}
|
|
2952
|
+
async signTypedData(envelope) {
|
|
2953
|
+
const mutableTypes = {};
|
|
2954
|
+
for (const [key, fields] of Object.entries(envelope.types)) {
|
|
2955
|
+
mutableTypes[key] = [...fields];
|
|
2956
|
+
}
|
|
2957
|
+
return this.evmWallet.signTypedData(
|
|
2958
|
+
envelope.domain,
|
|
2959
|
+
mutableTypes,
|
|
2960
|
+
envelope.message
|
|
2961
|
+
);
|
|
2962
|
+
}
|
|
2963
|
+
async sendEvmTransaction(args) {
|
|
2964
|
+
const chain = findChainByChainId(args.chainId);
|
|
2965
|
+
if (!chain) {
|
|
2966
|
+
throw new Error(`sendEvmTransaction: unknown chainId ${args.chainId}`);
|
|
2967
|
+
}
|
|
2968
|
+
const provider = new ethers2.JsonRpcProvider(chain.rpc);
|
|
2969
|
+
const connected = this.evmWallet.connect(provider);
|
|
2970
|
+
const tx = await connected.sendTransaction({
|
|
2971
|
+
to: args.to,
|
|
2972
|
+
data: args.data,
|
|
2973
|
+
value: args.value ? BigInt(args.value) : 0n
|
|
2974
|
+
});
|
|
2975
|
+
return tx.hash;
|
|
2976
|
+
}
|
|
2977
|
+
async signSolanaTransaction(args) {
|
|
2978
|
+
const kp = this.getSolanaKeypair();
|
|
2979
|
+
if (!kp) {
|
|
2980
|
+
throw new Error("signSolanaTransaction: no Solana wallet configured");
|
|
2981
|
+
}
|
|
2982
|
+
const tx = Transaction2.from(Buffer.from(args.transactionBase64, "base64"));
|
|
2983
|
+
if (args.partialSign) {
|
|
2984
|
+
tx.partialSign(kp);
|
|
2985
|
+
} else {
|
|
2986
|
+
tx.sign(kp);
|
|
2987
|
+
}
|
|
2988
|
+
return tx.serialize({ requireAllSignatures: false }).toString("base64");
|
|
2989
|
+
}
|
|
2990
|
+
};
|
|
2991
|
+
function findChainByChainId(chainId) {
|
|
2992
|
+
for (const cfg of Object.values(CHAINS)) {
|
|
2993
|
+
if (cfg.chainId === chainId) {
|
|
2994
|
+
return cfg;
|
|
2995
|
+
}
|
|
2996
|
+
}
|
|
2997
|
+
return void 0;
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
// src/client/node/index.ts
|
|
2653
3001
|
var DEFAULT_CONFIG = {
|
|
2654
3002
|
chain: "base",
|
|
2655
3003
|
limits: {
|
|
@@ -2662,6 +3010,7 @@ var MoltsPayClient = class {
|
|
|
2662
3010
|
config;
|
|
2663
3011
|
walletData = null;
|
|
2664
3012
|
wallet = null;
|
|
3013
|
+
signer = null;
|
|
2665
3014
|
todaySpending = 0;
|
|
2666
3015
|
lastSpendingReset = 0;
|
|
2667
3016
|
constructor(options = {}) {
|
|
@@ -2670,7 +3019,11 @@ var MoltsPayClient = class {
|
|
|
2670
3019
|
this.walletData = this.loadWallet();
|
|
2671
3020
|
this.loadSpending();
|
|
2672
3021
|
if (this.walletData) {
|
|
2673
|
-
this.wallet = new
|
|
3022
|
+
this.wallet = new Wallet2(this.walletData.privateKey);
|
|
3023
|
+
const configDir = this.configDir;
|
|
3024
|
+
this.signer = new NodeSigner(this.wallet, {
|
|
3025
|
+
getSolanaKeypair: () => loadSolanaWallet(configDir)
|
|
3026
|
+
});
|
|
2674
3027
|
}
|
|
2675
3028
|
}
|
|
2676
3029
|
/**
|
|
@@ -2798,20 +3151,6 @@ var MoltsPayClient = class {
|
|
|
2798
3151
|
} catch {
|
|
2799
3152
|
throw new Error("Invalid x-payment-required header");
|
|
2800
3153
|
}
|
|
2801
|
-
const networkToChainName = (network2) => {
|
|
2802
|
-
if (network2 === "solana:mainnet") return "solana";
|
|
2803
|
-
if (network2 === "solana:devnet") return "solana_devnet";
|
|
2804
|
-
const match = network2.match(/^eip155:(\d+)$/);
|
|
2805
|
-
if (!match) return null;
|
|
2806
|
-
const chainId = parseInt(match[1]);
|
|
2807
|
-
if (chainId === 8453) return "base";
|
|
2808
|
-
if (chainId === 137) return "polygon";
|
|
2809
|
-
if (chainId === 84532) return "base_sepolia";
|
|
2810
|
-
if (chainId === 42431) return "tempo_moderato";
|
|
2811
|
-
if (chainId === 56) return "bnb";
|
|
2812
|
-
if (chainId === 97) return "bnb_testnet";
|
|
2813
|
-
return null;
|
|
2814
|
-
};
|
|
2815
3154
|
const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
|
|
2816
3155
|
const userSpecifiedChain = options.chain;
|
|
2817
3156
|
let selectedChain;
|
|
@@ -3040,14 +3379,14 @@ Please specify: --chain <chain_name>`
|
|
|
3040
3379
|
async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
|
|
3041
3380
|
const { to, amount, token, chainName, chain, spender } = paymentDetails;
|
|
3042
3381
|
const tokenConfig = chain.tokens[token];
|
|
3043
|
-
const provider = new
|
|
3382
|
+
const provider = new ethers3.JsonRpcProvider(chain.rpc);
|
|
3044
3383
|
const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
|
|
3045
3384
|
const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
|
|
3046
3385
|
if (allowance < amountWeiCheck) {
|
|
3047
3386
|
const nativeBalance = await provider.getBalance(this.wallet.address);
|
|
3048
|
-
const minGasBalance =
|
|
3387
|
+
const minGasBalance = ethers3.parseEther("0.0005");
|
|
3049
3388
|
if (nativeBalance < minGasBalance) {
|
|
3050
|
-
const nativeBNB = parseFloat(
|
|
3389
|
+
const nativeBNB = parseFloat(ethers3.formatEther(nativeBalance)).toFixed(4);
|
|
3051
3390
|
const isTestnet = chainName === "bnb_testnet";
|
|
3052
3391
|
if (isTestnet) {
|
|
3053
3392
|
throw new Error(
|
|
@@ -3081,35 +3420,21 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3081
3420
|
);
|
|
3082
3421
|
}
|
|
3083
3422
|
const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
|
|
3084
|
-
const
|
|
3423
|
+
const intentNonce = Date.now();
|
|
3424
|
+
const intentDeadline = Date.now() + 36e5;
|
|
3425
|
+
const envelope = buildBnbIntentTypedData({
|
|
3085
3426
|
from: this.wallet.address,
|
|
3086
3427
|
to,
|
|
3087
3428
|
amount: amountWei,
|
|
3088
|
-
|
|
3429
|
+
tokenAddress: tokenConfig.address,
|
|
3089
3430
|
service,
|
|
3090
|
-
nonce:
|
|
3091
|
-
|
|
3092
|
-
deadline: Date.now() + 36e5
|
|
3093
|
-
// 1 hour
|
|
3094
|
-
};
|
|
3095
|
-
const domain = {
|
|
3096
|
-
name: "MoltsPay",
|
|
3097
|
-
version: "1",
|
|
3431
|
+
nonce: intentNonce,
|
|
3432
|
+
deadline: intentDeadline,
|
|
3098
3433
|
chainId: chain.chainId
|
|
3099
|
-
};
|
|
3100
|
-
const types = {
|
|
3101
|
-
PaymentIntent: [
|
|
3102
|
-
{ name: "from", type: "address" },
|
|
3103
|
-
{ name: "to", type: "address" },
|
|
3104
|
-
{ name: "amount", type: "uint256" },
|
|
3105
|
-
{ name: "token", type: "address" },
|
|
3106
|
-
{ name: "service", type: "string" },
|
|
3107
|
-
{ name: "nonce", type: "uint256" },
|
|
3108
|
-
{ name: "deadline", type: "uint256" }
|
|
3109
|
-
]
|
|
3110
|
-
};
|
|
3434
|
+
});
|
|
3111
3435
|
console.log(`[MoltsPay] Signing BNB payment intent...`);
|
|
3112
|
-
const signature = await this.
|
|
3436
|
+
const signature = await this.signer.signTypedData(envelope);
|
|
3437
|
+
const intent = envelope.message;
|
|
3113
3438
|
const network = `eip155:${chain.chainId}`;
|
|
3114
3439
|
const payload = {
|
|
3115
3440
|
x402Version: 2,
|
|
@@ -3183,12 +3508,11 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3183
3508
|
feePayerPubkey
|
|
3184
3509
|
// Optional fee payer for gasless mode
|
|
3185
3510
|
);
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
}
|
|
3191
|
-
const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
|
|
3511
|
+
const unsignedBase64 = transaction.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
|
|
3512
|
+
const signedTx = await this.signer.signSolanaTransaction({
|
|
3513
|
+
transactionBase64: unsignedBase64,
|
|
3514
|
+
partialSign: !!feePayerPubkey
|
|
3515
|
+
});
|
|
3192
3516
|
console.log(`[MoltsPay] Transaction signed, sending to server...`);
|
|
3193
3517
|
const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
|
|
3194
3518
|
const payload = {
|
|
@@ -3235,7 +3559,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3235
3559
|
* Check ERC20 allowance for a spender
|
|
3236
3560
|
*/
|
|
3237
3561
|
async checkAllowance(tokenAddress, spender, provider) {
|
|
3238
|
-
const contract = new
|
|
3562
|
+
const contract = new ethers3.Contract(
|
|
3239
3563
|
tokenAddress,
|
|
3240
3564
|
["function allowance(address owner, address spender) view returns (uint256)"],
|
|
3241
3565
|
provider
|
|
@@ -3246,41 +3570,29 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3246
3570
|
* Sign EIP-3009 transferWithAuthorization (GASLESS)
|
|
3247
3571
|
* This only signs - no on-chain transaction, no gas needed.
|
|
3248
3572
|
* Supports both USDC and USDT.
|
|
3573
|
+
*
|
|
3574
|
+
* Delegates typed-data construction to `core/eip3009.ts` and the signature
|
|
3575
|
+
* itself to `this.signer`. That way Web Client (Phase 4) can reuse the same
|
|
3576
|
+
* flow with an EIP-1193 signer without duplicating typed-data layout.
|
|
3249
3577
|
*/
|
|
3250
3578
|
async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
|
|
3251
|
-
const validAfter = 0;
|
|
3252
|
-
const validBefore = Math.floor(Date.now() / 1e3) + 3600;
|
|
3253
|
-
const nonce = ethers.hexlify(ethers.randomBytes(32));
|
|
3254
3579
|
const tokenConfig = chain.tokens[token];
|
|
3255
3580
|
const value = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
|
|
3256
|
-
const
|
|
3581
|
+
const nonce = ethers3.hexlify(ethers3.randomBytes(32));
|
|
3582
|
+
const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
|
|
3583
|
+
const tokenVersion = domainOverride?.version || "2";
|
|
3584
|
+
const envelope = buildEIP3009TypedData({
|
|
3257
3585
|
from: this.wallet.address,
|
|
3258
3586
|
to,
|
|
3259
3587
|
value,
|
|
3260
|
-
|
|
3261
|
-
validBefore: validBefore.toString(),
|
|
3262
|
-
nonce
|
|
3263
|
-
};
|
|
3264
|
-
const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
|
|
3265
|
-
const tokenVersion = domainOverride?.version || "2";
|
|
3266
|
-
const domain = {
|
|
3267
|
-
name: tokenName,
|
|
3268
|
-
version: tokenVersion,
|
|
3588
|
+
nonce,
|
|
3269
3589
|
chainId: chain.chainId,
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
{ name: "value", type: "uint256" },
|
|
3277
|
-
{ name: "validAfter", type: "uint256" },
|
|
3278
|
-
{ name: "validBefore", type: "uint256" },
|
|
3279
|
-
{ name: "nonce", type: "bytes32" }
|
|
3280
|
-
]
|
|
3281
|
-
};
|
|
3282
|
-
const signature = await this.wallet.signTypedData(domain, types, authorization);
|
|
3283
|
-
return { authorization, signature };
|
|
3590
|
+
tokenAddress: tokenConfig.address,
|
|
3591
|
+
tokenName,
|
|
3592
|
+
tokenVersion
|
|
3593
|
+
});
|
|
3594
|
+
const signature = await this.signer.signTypedData(envelope);
|
|
3595
|
+
return { authorization: envelope.message, signature };
|
|
3284
3596
|
}
|
|
3285
3597
|
/**
|
|
3286
3598
|
* Check spending limits
|
|
@@ -3362,15 +3674,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3362
3674
|
loadWallet() {
|
|
3363
3675
|
const walletPath = join4(this.configDir, "wallet.json");
|
|
3364
3676
|
if (existsSync4(walletPath)) {
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3677
|
+
if (process.platform !== "win32") {
|
|
3678
|
+
try {
|
|
3679
|
+
const stats = statSync(walletPath);
|
|
3680
|
+
const mode = stats.mode & 511;
|
|
3681
|
+
if (mode !== 384) {
|
|
3682
|
+
console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
|
|
3683
|
+
console.warn(`[MoltsPay] Fixing permissions to 0600...`);
|
|
3684
|
+
chmodSync(walletPath, 384);
|
|
3685
|
+
}
|
|
3686
|
+
} catch {
|
|
3372
3687
|
}
|
|
3373
|
-
} catch (err) {
|
|
3374
3688
|
}
|
|
3375
3689
|
const content = readFileSync4(walletPath, "utf-8");
|
|
3376
3690
|
return JSON.parse(content);
|
|
@@ -3382,7 +3696,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3382
3696
|
*/
|
|
3383
3697
|
static init(configDir, options) {
|
|
3384
3698
|
mkdirSync2(configDir, { recursive: true });
|
|
3385
|
-
const wallet =
|
|
3699
|
+
const wallet = Wallet2.createRandom();
|
|
3386
3700
|
const walletData = {
|
|
3387
3701
|
address: wallet.address,
|
|
3388
3702
|
privateKey: wallet.privateKey,
|
|
@@ -3414,17 +3728,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3414
3728
|
} catch {
|
|
3415
3729
|
throw new Error(`Unknown chain: ${this.config.chain}`);
|
|
3416
3730
|
}
|
|
3417
|
-
const provider = new
|
|
3731
|
+
const provider = new ethers3.JsonRpcProvider(chain.rpc);
|
|
3418
3732
|
const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
3419
3733
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
3420
3734
|
provider.getBalance(this.wallet.address),
|
|
3421
|
-
new
|
|
3422
|
-
new
|
|
3735
|
+
new ethers3.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
3736
|
+
new ethers3.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
3423
3737
|
]);
|
|
3424
3738
|
return {
|
|
3425
|
-
usdc: parseFloat(
|
|
3426
|
-
usdt: parseFloat(
|
|
3427
|
-
native: parseFloat(
|
|
3739
|
+
usdc: parseFloat(ethers3.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
3740
|
+
usdt: parseFloat(ethers3.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
3741
|
+
native: parseFloat(ethers3.formatEther(nativeBalance))
|
|
3428
3742
|
};
|
|
3429
3743
|
}
|
|
3430
3744
|
/**
|
|
@@ -3447,38 +3761,38 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3447
3761
|
supportedChains.map(async (chainName) => {
|
|
3448
3762
|
try {
|
|
3449
3763
|
const chain = getChain(chainName);
|
|
3450
|
-
const provider = new
|
|
3764
|
+
const provider = new ethers3.JsonRpcProvider(chain.rpc);
|
|
3451
3765
|
if (chainName === "tempo_moderato") {
|
|
3452
3766
|
const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
|
|
3453
3767
|
provider.getBalance(this.wallet.address),
|
|
3454
|
-
new
|
|
3455
|
-
new
|
|
3456
|
-
new
|
|
3457
|
-
new
|
|
3768
|
+
new ethers3.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
3769
|
+
new ethers3.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
3770
|
+
new ethers3.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
3771
|
+
new ethers3.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
3458
3772
|
]);
|
|
3459
3773
|
results[chainName] = {
|
|
3460
|
-
usdc: parseFloat(
|
|
3774
|
+
usdc: parseFloat(ethers3.formatUnits(pathUSD, 6)),
|
|
3461
3775
|
// pathUSD as default USDC
|
|
3462
|
-
usdt: parseFloat(
|
|
3776
|
+
usdt: parseFloat(ethers3.formatUnits(alphaUSD, 6)),
|
|
3463
3777
|
// alphaUSD as default USDT
|
|
3464
|
-
native: parseFloat(
|
|
3778
|
+
native: parseFloat(ethers3.formatEther(nativeBalance)),
|
|
3465
3779
|
tempo: {
|
|
3466
|
-
pathUSD: parseFloat(
|
|
3467
|
-
alphaUSD: parseFloat(
|
|
3468
|
-
betaUSD: parseFloat(
|
|
3469
|
-
thetaUSD: parseFloat(
|
|
3780
|
+
pathUSD: parseFloat(ethers3.formatUnits(pathUSD, 6)),
|
|
3781
|
+
alphaUSD: parseFloat(ethers3.formatUnits(alphaUSD, 6)),
|
|
3782
|
+
betaUSD: parseFloat(ethers3.formatUnits(betaUSD, 6)),
|
|
3783
|
+
thetaUSD: parseFloat(ethers3.formatUnits(thetaUSD, 6))
|
|
3470
3784
|
}
|
|
3471
3785
|
};
|
|
3472
3786
|
} else {
|
|
3473
3787
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
3474
3788
|
provider.getBalance(this.wallet.address),
|
|
3475
|
-
new
|
|
3476
|
-
new
|
|
3789
|
+
new ethers3.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
3790
|
+
new ethers3.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
3477
3791
|
]);
|
|
3478
3792
|
results[chainName] = {
|
|
3479
|
-
usdc: parseFloat(
|
|
3480
|
-
usdt: parseFloat(
|
|
3481
|
-
native: parseFloat(
|
|
3793
|
+
usdc: parseFloat(ethers3.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
3794
|
+
usdt: parseFloat(ethers3.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
3795
|
+
native: parseFloat(ethers3.formatEther(nativeBalance))
|
|
3482
3796
|
};
|
|
3483
3797
|
}
|
|
3484
3798
|
} catch (err) {
|
|
@@ -3606,10 +3920,10 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3606
3920
|
};
|
|
3607
3921
|
|
|
3608
3922
|
// src/wallet/Wallet.ts
|
|
3609
|
-
import { ethers as
|
|
3923
|
+
import { ethers as ethers4 } from "ethers";
|
|
3610
3924
|
|
|
3611
3925
|
// src/wallet/createWallet.ts
|
|
3612
|
-
import { ethers as
|
|
3926
|
+
import { ethers as ethers5 } from "ethers";
|
|
3613
3927
|
import { writeFileSync as writeFileSync3, readFileSync as readFileSync5, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
|
|
3614
3928
|
import { join as join5, dirname } from "path";
|
|
3615
3929
|
import { createCipheriv, createDecipheriv, randomBytes, scryptSync } from "crypto";
|
|
@@ -3654,7 +3968,7 @@ function createWallet(options = {}) {
|
|
|
3654
3968
|
}
|
|
3655
3969
|
}
|
|
3656
3970
|
try {
|
|
3657
|
-
const wallet =
|
|
3971
|
+
const wallet = ethers5.Wallet.createRandom();
|
|
3658
3972
|
const walletData = {
|
|
3659
3973
|
address: wallet.address,
|
|
3660
3974
|
label: options.label,
|
|
@@ -3726,8 +4040,8 @@ function walletExists(storagePath) {
|
|
|
3726
4040
|
}
|
|
3727
4041
|
|
|
3728
4042
|
// src/verify/index.ts
|
|
3729
|
-
import { ethers as
|
|
3730
|
-
var TRANSFER_EVENT_TOPIC3 =
|
|
4043
|
+
import { ethers as ethers6 } from "ethers";
|
|
4044
|
+
var TRANSFER_EVENT_TOPIC3 = ethers6.id("Transfer(address,address,uint256)");
|
|
3731
4045
|
async function verifyPayment(params) {
|
|
3732
4046
|
const { txHash, expectedAmount, expectedTo, expectedToken } = params;
|
|
3733
4047
|
let chain;
|
|
@@ -3744,7 +4058,7 @@ async function verifyPayment(params) {
|
|
|
3744
4058
|
return { verified: false, error: `Unsupported chain: ${params.chain}` };
|
|
3745
4059
|
}
|
|
3746
4060
|
try {
|
|
3747
|
-
const provider = new
|
|
4061
|
+
const provider = new ethers6.JsonRpcProvider(chain.rpc);
|
|
3748
4062
|
const receipt = await provider.getTransactionReceipt(txHash);
|
|
3749
4063
|
if (!receipt) {
|
|
3750
4064
|
return { verified: false, error: "Transaction not found or not confirmed" };
|
|
@@ -3816,7 +4130,7 @@ async function getTransactionStatus(txHash, chain = "base") {
|
|
|
3816
4130
|
return { status: "not_found" };
|
|
3817
4131
|
}
|
|
3818
4132
|
try {
|
|
3819
|
-
const provider = new
|
|
4133
|
+
const provider = new ethers6.JsonRpcProvider(chainConfig.rpc);
|
|
3820
4134
|
const receipt = await provider.getTransactionReceipt(txHash);
|
|
3821
4135
|
if (!receipt) {
|
|
3822
4136
|
const tx = await provider.getTransaction(txHash);
|
|
@@ -3853,7 +4167,7 @@ async function waitForTransaction(txHash, chain = "base", confirmations = 1, tim
|
|
|
3853
4167
|
} catch (e) {
|
|
3854
4168
|
return { verified: false, confirmed: false, error: `Unsupported chain: ${chain}` };
|
|
3855
4169
|
}
|
|
3856
|
-
const provider = new
|
|
4170
|
+
const provider = new ethers6.JsonRpcProvider(chainConfig.rpc);
|
|
3857
4171
|
try {
|
|
3858
4172
|
const receipt = await provider.waitForTransaction(txHash, confirmations, timeoutMs);
|
|
3859
4173
|
if (!receipt) {
|
|
@@ -3993,9 +4307,9 @@ var CDPWallet = class {
|
|
|
3993
4307
|
* Get USDC balance
|
|
3994
4308
|
*/
|
|
3995
4309
|
async getBalance() {
|
|
3996
|
-
const { ethers:
|
|
3997
|
-
const provider = new
|
|
3998
|
-
const usdcContract = new
|
|
4310
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
4311
|
+
const provider = new ethers7.JsonRpcProvider(this.chainConfig.rpc);
|
|
4312
|
+
const usdcContract = new ethers7.Contract(
|
|
3999
4313
|
this.chainConfig.usdc,
|
|
4000
4314
|
["function balanceOf(address) view returns (uint256)"],
|
|
4001
4315
|
provider
|
|
@@ -4006,7 +4320,7 @@ var CDPWallet = class {
|
|
|
4006
4320
|
]);
|
|
4007
4321
|
return {
|
|
4008
4322
|
usdc: (Number(usdcBalance) / 1e6).toFixed(2),
|
|
4009
|
-
eth:
|
|
4323
|
+
eth: ethers7.formatEther(ethBalance)
|
|
4010
4324
|
};
|
|
4011
4325
|
}
|
|
4012
4326
|
/**
|
|
@@ -4024,7 +4338,7 @@ var CDPWallet = class {
|
|
|
4024
4338
|
}
|
|
4025
4339
|
try {
|
|
4026
4340
|
const { CdpClient } = await import("@coinbase/cdp-sdk");
|
|
4027
|
-
const { ethers:
|
|
4341
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
4028
4342
|
const cdp = new CdpClient({
|
|
4029
4343
|
apiKeyId: creds.apiKeyId,
|
|
4030
4344
|
apiKeySecret: creds.apiKeySecret,
|
|
@@ -4032,7 +4346,7 @@ var CDPWallet = class {
|
|
|
4032
4346
|
});
|
|
4033
4347
|
const account = await cdp.evm.getAccount({ address: this.address });
|
|
4034
4348
|
const amountWei = BigInt(Math.floor(params.amount * 1e6));
|
|
4035
|
-
const iface = new
|
|
4349
|
+
const iface = new ethers7.Interface([
|
|
4036
4350
|
"function transfer(address to, uint256 amount) returns (bool)"
|
|
4037
4351
|
]);
|
|
4038
4352
|
const callData = iface.encodeFunctionData("transfer", [params.to, amountWei]);
|