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.js
CHANGED
|
@@ -288,6 +288,9 @@ var CDPFacilitator = class extends BaseFacilitator {
|
|
|
288
288
|
}
|
|
289
289
|
};
|
|
290
290
|
|
|
291
|
+
// src/facilitators/tempo.ts
|
|
292
|
+
var import_ethers = require("ethers");
|
|
293
|
+
|
|
291
294
|
// src/chains/index.ts
|
|
292
295
|
var CHAINS = {
|
|
293
296
|
// ============ Mainnet ============
|
|
@@ -483,15 +486,38 @@ var ERC20_ABI = [
|
|
|
483
486
|
|
|
484
487
|
// src/facilitators/tempo.ts
|
|
485
488
|
var TRANSFER_EVENT_TOPIC = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef";
|
|
489
|
+
var TIP20_PERMIT_ABI = [
|
|
490
|
+
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
491
|
+
"function transferFrom(address from, address to, uint256 value) returns (bool)"
|
|
492
|
+
];
|
|
486
493
|
var TempoFacilitator = class extends BaseFacilitator {
|
|
487
494
|
name = "tempo";
|
|
488
495
|
displayName = "Tempo Testnet";
|
|
489
496
|
supportedNetworks = ["eip155:42431"];
|
|
490
497
|
// Tempo Moderato
|
|
491
498
|
rpcUrl;
|
|
499
|
+
settlerWallet = null;
|
|
492
500
|
constructor() {
|
|
493
501
|
super();
|
|
494
502
|
this.rpcUrl = CHAINS.tempo_moderato.rpc;
|
|
503
|
+
const settlerKey = process.env.TEMPO_SETTLER_KEY;
|
|
504
|
+
if (settlerKey) {
|
|
505
|
+
try {
|
|
506
|
+
const provider = new import_ethers.ethers.JsonRpcProvider(this.rpcUrl);
|
|
507
|
+
this.settlerWallet = new import_ethers.ethers.Wallet(settlerKey, provider);
|
|
508
|
+
} catch (err) {
|
|
509
|
+
console.warn("[TempoFacilitator] Invalid TEMPO_SETTLER_KEY, permit settlement disabled:", err);
|
|
510
|
+
this.settlerWallet = null;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Settler EOA address advertised to clients via `X-Payment-Required.extra.tempoSpender`.
|
|
516
|
+
* Web Client uses this as the `spender` field in the signed EIP-2612 Permit.
|
|
517
|
+
* Returns null if no TEMPO_SETTLER_KEY is configured — permit settlement unavailable.
|
|
518
|
+
*/
|
|
519
|
+
getSpenderAddress() {
|
|
520
|
+
return this.settlerWallet?.address ?? null;
|
|
495
521
|
}
|
|
496
522
|
async healthCheck() {
|
|
497
523
|
const start = Date.now();
|
|
@@ -517,6 +543,44 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
517
543
|
}
|
|
518
544
|
}
|
|
519
545
|
async verify(paymentPayload, requirements) {
|
|
546
|
+
const inner = paymentPayload.payload;
|
|
547
|
+
if (inner && "permit" in inner && inner.permit) {
|
|
548
|
+
return this.verifyPermit(inner, requirements);
|
|
549
|
+
}
|
|
550
|
+
return this.verifyTxHash(paymentPayload, requirements);
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Structural validation of an EIP-2612 permit payload. Does NOT submit
|
|
554
|
+
* anything on-chain — actual submission happens in settlePermit().
|
|
555
|
+
*/
|
|
556
|
+
async verifyPermit(payload, requirements) {
|
|
557
|
+
if (!this.settlerWallet) {
|
|
558
|
+
return { valid: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
|
|
559
|
+
}
|
|
560
|
+
const p = payload.permit;
|
|
561
|
+
if (!p || !p.owner || !p.spender || !p.value || !p.deadline) {
|
|
562
|
+
return { valid: false, error: "Invalid permit payload: missing fields" };
|
|
563
|
+
}
|
|
564
|
+
if (p.spender.toLowerCase() !== this.settlerWallet.address.toLowerCase()) {
|
|
565
|
+
return {
|
|
566
|
+
valid: false,
|
|
567
|
+
error: `Permit spender ${p.spender} does not match configured settler ${this.settlerWallet.address}`
|
|
568
|
+
};
|
|
569
|
+
}
|
|
570
|
+
const deadline = BigInt(p.deadline);
|
|
571
|
+
const now = BigInt(Math.floor(Date.now() / 1e3));
|
|
572
|
+
if (deadline <= now) {
|
|
573
|
+
return { valid: false, error: "Permit deadline has expired" };
|
|
574
|
+
}
|
|
575
|
+
if (BigInt(p.value) < BigInt(requirements.amount || "0")) {
|
|
576
|
+
return {
|
|
577
|
+
valid: false,
|
|
578
|
+
error: `Permit value ${p.value} is less than required ${requirements.amount}`
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
return { valid: true, details: { scheme: "permit", owner: p.owner } };
|
|
582
|
+
}
|
|
583
|
+
async verifyTxHash(paymentPayload, requirements) {
|
|
520
584
|
try {
|
|
521
585
|
const tempoPayload = paymentPayload.payload;
|
|
522
586
|
if (!tempoPayload?.txHash) {
|
|
@@ -574,7 +638,11 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
574
638
|
}
|
|
575
639
|
}
|
|
576
640
|
async settle(paymentPayload, requirements) {
|
|
577
|
-
const
|
|
641
|
+
const inner = paymentPayload.payload;
|
|
642
|
+
if (inner && "permit" in inner && inner.permit) {
|
|
643
|
+
return this.settlePermit(inner, requirements);
|
|
644
|
+
}
|
|
645
|
+
const verifyResult = await this.verifyTxHash(paymentPayload, requirements);
|
|
578
646
|
if (!verifyResult.valid) {
|
|
579
647
|
return { success: false, error: verifyResult.error };
|
|
580
648
|
}
|
|
@@ -585,6 +653,52 @@ var TempoFacilitator = class extends BaseFacilitator {
|
|
|
585
653
|
status: "settled"
|
|
586
654
|
};
|
|
587
655
|
}
|
|
656
|
+
/**
|
|
657
|
+
* EIP-2612 permit settlement path. Submits two transactions on Tempo:
|
|
658
|
+
* 1. pathUSD.permit(owner, spender=settler, value, deadline, v, r, s)
|
|
659
|
+
* 2. pathUSD.transferFrom(owner, payTo, value)
|
|
660
|
+
*
|
|
661
|
+
* The settler EOA pays Tempo gas (via the TIP-20 `feeToken` mechanism — no
|
|
662
|
+
* native tTEMPO required; any held TIP-20 token balance covers fees).
|
|
663
|
+
*/
|
|
664
|
+
async settlePermit(payload, requirements) {
|
|
665
|
+
if (!this.settlerWallet) {
|
|
666
|
+
return { success: false, error: "Permit settlement not configured (TEMPO_SETTLER_KEY missing)" };
|
|
667
|
+
}
|
|
668
|
+
if (!requirements.asset || !requirements.payTo) {
|
|
669
|
+
return { success: false, error: "Missing asset or payTo in requirements" };
|
|
670
|
+
}
|
|
671
|
+
const verifyResult = await this.verifyPermit(payload, requirements);
|
|
672
|
+
if (!verifyResult.valid) {
|
|
673
|
+
return { success: false, error: verifyResult.error };
|
|
674
|
+
}
|
|
675
|
+
const token = new import_ethers.ethers.Contract(requirements.asset, TIP20_PERMIT_ABI, this.settlerWallet);
|
|
676
|
+
const p = payload.permit;
|
|
677
|
+
try {
|
|
678
|
+
const permitTx = await token.permit(
|
|
679
|
+
p.owner,
|
|
680
|
+
p.spender,
|
|
681
|
+
p.value,
|
|
682
|
+
p.deadline,
|
|
683
|
+
p.v,
|
|
684
|
+
p.r,
|
|
685
|
+
p.s
|
|
686
|
+
);
|
|
687
|
+
await permitTx.wait();
|
|
688
|
+
const transferTx = await token.transferFrom(p.owner, requirements.payTo, p.value);
|
|
689
|
+
await transferTx.wait();
|
|
690
|
+
return {
|
|
691
|
+
success: true,
|
|
692
|
+
transaction: transferTx.hash,
|
|
693
|
+
status: "settled"
|
|
694
|
+
};
|
|
695
|
+
} catch (err) {
|
|
696
|
+
return {
|
|
697
|
+
success: false,
|
|
698
|
+
error: `Tempo permit settlement failed: ${err.message}`
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
}
|
|
588
702
|
async getTransactionReceipt(txHash) {
|
|
589
703
|
const response = await fetch(this.rpcUrl, {
|
|
590
704
|
method: "POST",
|
|
@@ -827,12 +941,12 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
827
941
|
return this.spenderAddress;
|
|
828
942
|
}
|
|
829
943
|
async getServerAddress() {
|
|
830
|
-
const { ethers:
|
|
831
|
-
const wallet = new
|
|
944
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
945
|
+
const wallet = new ethers7.Wallet(this.serverPrivateKey);
|
|
832
946
|
return wallet.address;
|
|
833
947
|
}
|
|
834
948
|
async recoverIntentSigner(intent, chainId) {
|
|
835
|
-
const { ethers:
|
|
949
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
836
950
|
const domain = {
|
|
837
951
|
...EIP712_DOMAIN,
|
|
838
952
|
chainId
|
|
@@ -846,7 +960,7 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
846
960
|
nonce: intent.nonce,
|
|
847
961
|
deadline: intent.deadline
|
|
848
962
|
};
|
|
849
|
-
const recoveredAddress =
|
|
963
|
+
const recoveredAddress = ethers7.verifyTypedData(
|
|
850
964
|
domain,
|
|
851
965
|
INTENT_TYPES,
|
|
852
966
|
message,
|
|
@@ -890,10 +1004,10 @@ var BNBFacilitator = class extends BaseFacilitator {
|
|
|
890
1004
|
return result.result || "0x0";
|
|
891
1005
|
}
|
|
892
1006
|
async executeTransferFrom(from, to, amount, token, rpcUrl) {
|
|
893
|
-
const { ethers:
|
|
894
|
-
const provider = new
|
|
895
|
-
const wallet = new
|
|
896
|
-
const tokenContract = new
|
|
1007
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
1008
|
+
const provider = new ethers7.JsonRpcProvider(rpcUrl);
|
|
1009
|
+
const wallet = new ethers7.Wallet(this.serverPrivateKey, provider);
|
|
1010
|
+
const tokenContract = new ethers7.Contract(token, [
|
|
897
1011
|
"function transferFrom(address from, address to, uint256 amount) returns (bool)"
|
|
898
1012
|
], wallet);
|
|
899
1013
|
const tx = await tokenContract.transferFrom(from, to, amount);
|
|
@@ -1119,16 +1233,16 @@ var SolanaFacilitator = class extends BaseFacilitator {
|
|
|
1119
1233
|
return this.supportedNetworks.includes(network);
|
|
1120
1234
|
}
|
|
1121
1235
|
};
|
|
1122
|
-
async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
|
|
1236
|
+
async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey, connection) {
|
|
1123
1237
|
const chainConfig = SOLANA_CHAINS[chain];
|
|
1124
|
-
const
|
|
1238
|
+
const conn = connection ?? new import_web32.Connection(chainConfig.rpc, "confirmed");
|
|
1125
1239
|
const mint = new import_web32.PublicKey(chainConfig.tokens.USDC.mint);
|
|
1126
1240
|
const actualFeePayer = feePayerPubkey || senderPubkey;
|
|
1127
1241
|
const senderATA = await (0, import_spl_token.getAssociatedTokenAddress)(mint, senderPubkey);
|
|
1128
1242
|
const recipientATA = await (0, import_spl_token.getAssociatedTokenAddress)(mint, recipientPubkey);
|
|
1129
1243
|
const transaction = new import_web32.Transaction();
|
|
1130
1244
|
try {
|
|
1131
|
-
await (0, import_spl_token.getAccount)(
|
|
1245
|
+
await (0, import_spl_token.getAccount)(conn, recipientATA);
|
|
1132
1246
|
} catch {
|
|
1133
1247
|
transaction.add(
|
|
1134
1248
|
(0, import_spl_token.createAssociatedTokenAccountInstruction)(
|
|
@@ -1159,7 +1273,7 @@ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amo
|
|
|
1159
1273
|
// decimals
|
|
1160
1274
|
)
|
|
1161
1275
|
);
|
|
1162
|
-
const { blockhash, lastValidBlockHeight } = await
|
|
1276
|
+
const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash();
|
|
1163
1277
|
transaction.recentBlockhash = blockhash;
|
|
1164
1278
|
transaction.feePayer = actualFeePayer;
|
|
1165
1279
|
return transaction;
|
|
@@ -1495,9 +1609,13 @@ var TOKEN_DOMAINS = {
|
|
|
1495
1609
|
USDT: { name: "(PoS) Tether USD", version: "2" }
|
|
1496
1610
|
},
|
|
1497
1611
|
// Tempo Moderato testnet - TIP-20 stablecoins
|
|
1612
|
+
// Domain names verified against on-chain DOMAIN_SEPARATOR values on 2026-04-21.
|
|
1613
|
+
// See docs/TEMPO-WEB-SUPPORT.md Section 2 and test/server/tempo-domain.test.ts.
|
|
1614
|
+
// All 4 Tempo TIP-20 tokens (pathUSD / AlphaUSD / BetaUSD / ThetaUSD) use
|
|
1615
|
+
// the token symbol with first letter capitalized + version "1".
|
|
1498
1616
|
"eip155:42431": {
|
|
1499
|
-
USDC: { name: "
|
|
1500
|
-
USDT: { name: "
|
|
1617
|
+
USDC: { name: "PathUSD", version: "1" },
|
|
1618
|
+
USDT: { name: "AlphaUSD", version: "1" }
|
|
1501
1619
|
},
|
|
1502
1620
|
// BNB Smart Chain mainnet
|
|
1503
1621
|
"eip155:56": {
|
|
@@ -1666,13 +1784,62 @@ var MoltsPayServer = class {
|
|
|
1666
1784
|
});
|
|
1667
1785
|
}
|
|
1668
1786
|
/**
|
|
1669
|
-
*
|
|
1787
|
+
* Apply CORS response headers according to the `cors` option.
|
|
1788
|
+
*
|
|
1789
|
+
* Default (`cors` unset or `true`): `Access-Control-Allow-Origin: *`. Matches 1.5.x behavior
|
|
1790
|
+
* and works for every browser client whose origin does not need to send cookies.
|
|
1791
|
+
*
|
|
1792
|
+
* `cors: false`: emit no CORS headers. Same-origin only.
|
|
1793
|
+
* `cors: string[]`: origin allowlist — echo the origin back iff it matches.
|
|
1794
|
+
* `cors: CorsOptions`: full control (allowlist + credentials + maxAge).
|
|
1795
|
+
*
|
|
1796
|
+
* The required-for-Web response headers are always exposed when CORS is active:
|
|
1797
|
+
* `X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt`.
|
|
1670
1798
|
*/
|
|
1671
|
-
|
|
1672
|
-
|
|
1799
|
+
applyCorsHeaders(req, res) {
|
|
1800
|
+
const cors = this.options.cors;
|
|
1801
|
+
if (cors === false) {
|
|
1802
|
+
return;
|
|
1803
|
+
}
|
|
1804
|
+
const requestOrigin = req.headers.origin ?? "*";
|
|
1805
|
+
if (cors === void 0 || cors === true) {
|
|
1806
|
+
this.writeCorsHeaders(res, "*");
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1809
|
+
if (Array.isArray(cors)) {
|
|
1810
|
+
if (cors.includes(requestOrigin)) {
|
|
1811
|
+
this.writeCorsHeaders(res, requestOrigin);
|
|
1812
|
+
res.setHeader("Vary", "Origin");
|
|
1813
|
+
}
|
|
1814
|
+
return;
|
|
1815
|
+
}
|
|
1816
|
+
const opt = cors;
|
|
1817
|
+
const isAllowed = typeof opt.origins === "function" ? opt.origins(requestOrigin) : opt.origins.includes(requestOrigin);
|
|
1818
|
+
if (!isAllowed) {
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
this.writeCorsHeaders(res, requestOrigin);
|
|
1822
|
+
res.setHeader("Vary", "Origin");
|
|
1823
|
+
if (opt.credentials) {
|
|
1824
|
+
res.setHeader("Access-Control-Allow-Credentials", "true");
|
|
1825
|
+
}
|
|
1826
|
+
const maxAge = opt.maxAge ?? 600;
|
|
1827
|
+
res.setHeader("Access-Control-Max-Age", String(maxAge));
|
|
1828
|
+
}
|
|
1829
|
+
writeCorsHeaders(res, origin) {
|
|
1830
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
1673
1831
|
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
1674
1832
|
res.setHeader("Access-Control-Allow-Headers", "Content-Type, X-Payment, Authorization");
|
|
1675
|
-
res.setHeader(
|
|
1833
|
+
res.setHeader(
|
|
1834
|
+
"Access-Control-Expose-Headers",
|
|
1835
|
+
"X-Payment-Required, X-Payment-Response, WWW-Authenticate, Payment-Receipt"
|
|
1836
|
+
);
|
|
1837
|
+
}
|
|
1838
|
+
/**
|
|
1839
|
+
* Handle incoming request
|
|
1840
|
+
*/
|
|
1841
|
+
async handleRequest(req, res) {
|
|
1842
|
+
this.applyCorsHeaders(req, res);
|
|
1676
1843
|
if (req.method === "OPTIONS") {
|
|
1677
1844
|
res.writeHead(204);
|
|
1678
1845
|
res.end();
|
|
@@ -1895,6 +2062,14 @@ var MoltsPayServer = class {
|
|
|
1895
2062
|
console.log(`[MoltsPay] Payment settled by ${settlement.facilitator}: ${settlement.transaction || "pending"}`);
|
|
1896
2063
|
} catch (err) {
|
|
1897
2064
|
console.error("[MoltsPay] Settlement failed:", err.message);
|
|
2065
|
+
settlement = { success: false, error: err.message, facilitator: "none" };
|
|
2066
|
+
}
|
|
2067
|
+
if (!settlement?.success) {
|
|
2068
|
+
return this.sendJson(res, 402, {
|
|
2069
|
+
error: "Payment settlement failed",
|
|
2070
|
+
message: settlement?.error || "Settlement returned no success state",
|
|
2071
|
+
facilitator: settlement?.facilitator
|
|
2072
|
+
});
|
|
1898
2073
|
}
|
|
1899
2074
|
}
|
|
1900
2075
|
const responseHeaders = {};
|
|
@@ -2149,7 +2324,7 @@ var MoltsPayServer = class {
|
|
|
2149
2324
|
}
|
|
2150
2325
|
const scheme = payment.accepted?.scheme || payment.scheme;
|
|
2151
2326
|
const network = payment.accepted?.network || payment.network || this.networkId;
|
|
2152
|
-
if (scheme !== "exact") {
|
|
2327
|
+
if (scheme !== "exact" && scheme !== "permit") {
|
|
2153
2328
|
return { valid: false, error: `Unsupported scheme: ${scheme}` };
|
|
2154
2329
|
}
|
|
2155
2330
|
if (!this.isNetworkAccepted(network)) {
|
|
@@ -2171,8 +2346,10 @@ var MoltsPayServer = class {
|
|
|
2171
2346
|
const tokenAddresses = TOKEN_ADDRESSES[selectedNetwork] || {};
|
|
2172
2347
|
const tokenAddress = tokenAddresses[selectedToken];
|
|
2173
2348
|
const tokenDomain = getTokenDomain(selectedNetwork, selectedToken);
|
|
2349
|
+
const isTempo = selectedNetwork === "eip155:42431";
|
|
2350
|
+
const scheme = isTempo ? "permit" : "exact";
|
|
2174
2351
|
const requirements = {
|
|
2175
|
-
scheme
|
|
2352
|
+
scheme,
|
|
2176
2353
|
network: selectedNetwork,
|
|
2177
2354
|
asset: tokenAddress,
|
|
2178
2355
|
amount: amountInUnits,
|
|
@@ -2200,6 +2377,16 @@ var MoltsPayServer = class {
|
|
|
2200
2377
|
};
|
|
2201
2378
|
}
|
|
2202
2379
|
}
|
|
2380
|
+
if (isTempo) {
|
|
2381
|
+
const tempoFacilitator = this.registry.get("tempo");
|
|
2382
|
+
const tempoSpender = tempoFacilitator?.getSpenderAddress?.();
|
|
2383
|
+
if (tempoSpender) {
|
|
2384
|
+
requirements.extra = {
|
|
2385
|
+
...requirements.extra || {},
|
|
2386
|
+
tempoSpender
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
}
|
|
2203
2390
|
return requirements;
|
|
2204
2391
|
}
|
|
2205
2392
|
/**
|
|
@@ -2334,7 +2521,7 @@ var MoltsPayServer = class {
|
|
|
2334
2521
|
}
|
|
2335
2522
|
const scheme = payment.accepted?.scheme || payment.scheme;
|
|
2336
2523
|
const network = payment.accepted?.network || payment.network;
|
|
2337
|
-
if (scheme !== "exact") {
|
|
2524
|
+
if (scheme !== "exact" && scheme !== "permit") {
|
|
2338
2525
|
return this.sendJson(res, 402, { error: `Unsupported scheme: ${scheme}` });
|
|
2339
2526
|
}
|
|
2340
2527
|
const expectedNetwork = chain ? CHAIN_TO_NETWORK[chain] || this.networkId : this.networkId;
|
|
@@ -2654,11 +2841,11 @@ var MoltsPayServer = class {
|
|
|
2654
2841
|
}
|
|
2655
2842
|
};
|
|
2656
2843
|
|
|
2657
|
-
// src/client/index.ts
|
|
2844
|
+
// src/client/node/index.ts
|
|
2658
2845
|
var import_fs4 = require("fs");
|
|
2659
2846
|
var import_os2 = require("os");
|
|
2660
2847
|
var import_path2 = require("path");
|
|
2661
|
-
var
|
|
2848
|
+
var import_ethers3 = require("ethers");
|
|
2662
2849
|
|
|
2663
2850
|
// src/wallet/solana.ts
|
|
2664
2851
|
var import_web34 = require("@solana/web3.js");
|
|
@@ -2687,11 +2874,172 @@ function loadSolanaWallet(configDir = DEFAULT_CONFIG_DIR) {
|
|
|
2687
2874
|
}
|
|
2688
2875
|
}
|
|
2689
2876
|
|
|
2690
|
-
// src/client/index.ts
|
|
2691
|
-
var
|
|
2877
|
+
// src/client/node/index.ts
|
|
2878
|
+
var import_web36 = require("@solana/web3.js");
|
|
2879
|
+
|
|
2880
|
+
// src/client/core/types.ts
|
|
2692
2881
|
var X402_VERSION3 = 2;
|
|
2693
2882
|
var PAYMENT_REQUIRED_HEADER2 = "x-payment-required";
|
|
2694
2883
|
var PAYMENT_HEADER2 = "x-payment";
|
|
2884
|
+
|
|
2885
|
+
// src/client/core/chain-map.ts
|
|
2886
|
+
var NETWORK_TO_CHAIN = {
|
|
2887
|
+
"eip155:8453": "base",
|
|
2888
|
+
"eip155:137": "polygon",
|
|
2889
|
+
"eip155:84532": "base_sepolia",
|
|
2890
|
+
"eip155:42431": "tempo_moderato",
|
|
2891
|
+
"eip155:56": "bnb",
|
|
2892
|
+
"eip155:97": "bnb_testnet",
|
|
2893
|
+
"solana:mainnet": "solana",
|
|
2894
|
+
"solana:devnet": "solana_devnet"
|
|
2895
|
+
};
|
|
2896
|
+
var CHAIN_TO_NETWORK2 = Object.fromEntries(
|
|
2897
|
+
Object.entries(NETWORK_TO_CHAIN).map(([network, chain]) => [chain, network])
|
|
2898
|
+
);
|
|
2899
|
+
function networkToChainName(network) {
|
|
2900
|
+
return NETWORK_TO_CHAIN[network] ?? null;
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2903
|
+
// src/client/core/base64.ts
|
|
2904
|
+
var BufferCtor = globalThis.Buffer;
|
|
2905
|
+
|
|
2906
|
+
// src/client/core/eip3009.ts
|
|
2907
|
+
var EIP3009_TYPES = {
|
|
2908
|
+
TransferWithAuthorization: [
|
|
2909
|
+
{ name: "from", type: "address" },
|
|
2910
|
+
{ name: "to", type: "address" },
|
|
2911
|
+
{ name: "value", type: "uint256" },
|
|
2912
|
+
{ name: "validAfter", type: "uint256" },
|
|
2913
|
+
{ name: "validBefore", type: "uint256" },
|
|
2914
|
+
{ name: "nonce", type: "bytes32" }
|
|
2915
|
+
]
|
|
2916
|
+
};
|
|
2917
|
+
function buildEIP3009TypedData(args) {
|
|
2918
|
+
const validAfter = args.validAfter ?? "0";
|
|
2919
|
+
const validBefore = args.validBefore ?? (Math.floor(Date.now() / 1e3) + 3600).toString();
|
|
2920
|
+
const authorization = {
|
|
2921
|
+
from: args.from,
|
|
2922
|
+
to: args.to,
|
|
2923
|
+
value: args.value,
|
|
2924
|
+
validAfter,
|
|
2925
|
+
validBefore,
|
|
2926
|
+
nonce: args.nonce
|
|
2927
|
+
};
|
|
2928
|
+
return {
|
|
2929
|
+
domain: {
|
|
2930
|
+
name: args.tokenName,
|
|
2931
|
+
version: args.tokenVersion,
|
|
2932
|
+
chainId: args.chainId,
|
|
2933
|
+
verifyingContract: args.tokenAddress
|
|
2934
|
+
},
|
|
2935
|
+
types: EIP3009_TYPES,
|
|
2936
|
+
primaryType: "TransferWithAuthorization",
|
|
2937
|
+
message: authorization
|
|
2938
|
+
};
|
|
2939
|
+
}
|
|
2940
|
+
|
|
2941
|
+
// src/client/core/bnb-intent.ts
|
|
2942
|
+
var BNB_INTENT_TYPES = {
|
|
2943
|
+
PaymentIntent: [
|
|
2944
|
+
{ name: "from", type: "address" },
|
|
2945
|
+
{ name: "to", type: "address" },
|
|
2946
|
+
{ name: "amount", type: "uint256" },
|
|
2947
|
+
{ name: "token", type: "address" },
|
|
2948
|
+
{ name: "service", type: "string" },
|
|
2949
|
+
{ name: "nonce", type: "uint256" },
|
|
2950
|
+
{ name: "deadline", type: "uint256" }
|
|
2951
|
+
]
|
|
2952
|
+
};
|
|
2953
|
+
var BNB_DOMAIN_NAME = "MoltsPay";
|
|
2954
|
+
var BNB_DOMAIN_VERSION = "1";
|
|
2955
|
+
function buildBnbIntentTypedData(args) {
|
|
2956
|
+
const intent = {
|
|
2957
|
+
from: args.from,
|
|
2958
|
+
to: args.to,
|
|
2959
|
+
amount: args.amount,
|
|
2960
|
+
token: args.tokenAddress,
|
|
2961
|
+
service: args.service,
|
|
2962
|
+
nonce: args.nonce,
|
|
2963
|
+
deadline: args.deadline
|
|
2964
|
+
};
|
|
2965
|
+
return {
|
|
2966
|
+
domain: {
|
|
2967
|
+
name: BNB_DOMAIN_NAME,
|
|
2968
|
+
version: BNB_DOMAIN_VERSION,
|
|
2969
|
+
chainId: args.chainId
|
|
2970
|
+
},
|
|
2971
|
+
types: BNB_INTENT_TYPES,
|
|
2972
|
+
primaryType: "PaymentIntent",
|
|
2973
|
+
message: intent
|
|
2974
|
+
};
|
|
2975
|
+
}
|
|
2976
|
+
|
|
2977
|
+
// src/client/node/signer.ts
|
|
2978
|
+
var import_ethers2 = require("ethers");
|
|
2979
|
+
var import_web35 = require("@solana/web3.js");
|
|
2980
|
+
var NodeSigner = class {
|
|
2981
|
+
evmWallet;
|
|
2982
|
+
getSolanaKeypair;
|
|
2983
|
+
constructor(evmWallet, options = {}) {
|
|
2984
|
+
this.evmWallet = evmWallet;
|
|
2985
|
+
this.getSolanaKeypair = options.getSolanaKeypair ?? (() => null);
|
|
2986
|
+
}
|
|
2987
|
+
async getEvmAddress() {
|
|
2988
|
+
return this.evmWallet.address;
|
|
2989
|
+
}
|
|
2990
|
+
async getSolanaAddress() {
|
|
2991
|
+
const kp = this.getSolanaKeypair();
|
|
2992
|
+
return kp ? kp.publicKey.toBase58() : null;
|
|
2993
|
+
}
|
|
2994
|
+
async signTypedData(envelope) {
|
|
2995
|
+
const mutableTypes = {};
|
|
2996
|
+
for (const [key, fields] of Object.entries(envelope.types)) {
|
|
2997
|
+
mutableTypes[key] = [...fields];
|
|
2998
|
+
}
|
|
2999
|
+
return this.evmWallet.signTypedData(
|
|
3000
|
+
envelope.domain,
|
|
3001
|
+
mutableTypes,
|
|
3002
|
+
envelope.message
|
|
3003
|
+
);
|
|
3004
|
+
}
|
|
3005
|
+
async sendEvmTransaction(args) {
|
|
3006
|
+
const chain = findChainByChainId(args.chainId);
|
|
3007
|
+
if (!chain) {
|
|
3008
|
+
throw new Error(`sendEvmTransaction: unknown chainId ${args.chainId}`);
|
|
3009
|
+
}
|
|
3010
|
+
const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
|
|
3011
|
+
const connected = this.evmWallet.connect(provider);
|
|
3012
|
+
const tx = await connected.sendTransaction({
|
|
3013
|
+
to: args.to,
|
|
3014
|
+
data: args.data,
|
|
3015
|
+
value: args.value ? BigInt(args.value) : 0n
|
|
3016
|
+
});
|
|
3017
|
+
return tx.hash;
|
|
3018
|
+
}
|
|
3019
|
+
async signSolanaTransaction(args) {
|
|
3020
|
+
const kp = this.getSolanaKeypair();
|
|
3021
|
+
if (!kp) {
|
|
3022
|
+
throw new Error("signSolanaTransaction: no Solana wallet configured");
|
|
3023
|
+
}
|
|
3024
|
+
const tx = import_web35.Transaction.from(Buffer.from(args.transactionBase64, "base64"));
|
|
3025
|
+
if (args.partialSign) {
|
|
3026
|
+
tx.partialSign(kp);
|
|
3027
|
+
} else {
|
|
3028
|
+
tx.sign(kp);
|
|
3029
|
+
}
|
|
3030
|
+
return tx.serialize({ requireAllSignatures: false }).toString("base64");
|
|
3031
|
+
}
|
|
3032
|
+
};
|
|
3033
|
+
function findChainByChainId(chainId) {
|
|
3034
|
+
for (const cfg of Object.values(CHAINS)) {
|
|
3035
|
+
if (cfg.chainId === chainId) {
|
|
3036
|
+
return cfg;
|
|
3037
|
+
}
|
|
3038
|
+
}
|
|
3039
|
+
return void 0;
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
// src/client/node/index.ts
|
|
2695
3043
|
var DEFAULT_CONFIG = {
|
|
2696
3044
|
chain: "base",
|
|
2697
3045
|
limits: {
|
|
@@ -2704,6 +3052,7 @@ var MoltsPayClient = class {
|
|
|
2704
3052
|
config;
|
|
2705
3053
|
walletData = null;
|
|
2706
3054
|
wallet = null;
|
|
3055
|
+
signer = null;
|
|
2707
3056
|
todaySpending = 0;
|
|
2708
3057
|
lastSpendingReset = 0;
|
|
2709
3058
|
constructor(options = {}) {
|
|
@@ -2712,7 +3061,11 @@ var MoltsPayClient = class {
|
|
|
2712
3061
|
this.walletData = this.loadWallet();
|
|
2713
3062
|
this.loadSpending();
|
|
2714
3063
|
if (this.walletData) {
|
|
2715
|
-
this.wallet = new
|
|
3064
|
+
this.wallet = new import_ethers3.Wallet(this.walletData.privateKey);
|
|
3065
|
+
const configDir = this.configDir;
|
|
3066
|
+
this.signer = new NodeSigner(this.wallet, {
|
|
3067
|
+
getSolanaKeypair: () => loadSolanaWallet(configDir)
|
|
3068
|
+
});
|
|
2716
3069
|
}
|
|
2717
3070
|
}
|
|
2718
3071
|
/**
|
|
@@ -2840,20 +3193,6 @@ var MoltsPayClient = class {
|
|
|
2840
3193
|
} catch {
|
|
2841
3194
|
throw new Error("Invalid x-payment-required header");
|
|
2842
3195
|
}
|
|
2843
|
-
const networkToChainName = (network2) => {
|
|
2844
|
-
if (network2 === "solana:mainnet") return "solana";
|
|
2845
|
-
if (network2 === "solana:devnet") return "solana_devnet";
|
|
2846
|
-
const match = network2.match(/^eip155:(\d+)$/);
|
|
2847
|
-
if (!match) return null;
|
|
2848
|
-
const chainId = parseInt(match[1]);
|
|
2849
|
-
if (chainId === 8453) return "base";
|
|
2850
|
-
if (chainId === 137) return "polygon";
|
|
2851
|
-
if (chainId === 84532) return "base_sepolia";
|
|
2852
|
-
if (chainId === 42431) return "tempo_moderato";
|
|
2853
|
-
if (chainId === 56) return "bnb";
|
|
2854
|
-
if (chainId === 97) return "bnb_testnet";
|
|
2855
|
-
return null;
|
|
2856
|
-
};
|
|
2857
3196
|
const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
|
|
2858
3197
|
const userSpecifiedChain = options.chain;
|
|
2859
3198
|
let selectedChain;
|
|
@@ -3082,14 +3421,14 @@ Please specify: --chain <chain_name>`
|
|
|
3082
3421
|
async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
|
|
3083
3422
|
const { to, amount, token, chainName, chain, spender } = paymentDetails;
|
|
3084
3423
|
const tokenConfig = chain.tokens[token];
|
|
3085
|
-
const provider = new
|
|
3424
|
+
const provider = new import_ethers3.ethers.JsonRpcProvider(chain.rpc);
|
|
3086
3425
|
const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
|
|
3087
3426
|
const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
|
|
3088
3427
|
if (allowance < amountWeiCheck) {
|
|
3089
3428
|
const nativeBalance = await provider.getBalance(this.wallet.address);
|
|
3090
|
-
const minGasBalance =
|
|
3429
|
+
const minGasBalance = import_ethers3.ethers.parseEther("0.0005");
|
|
3091
3430
|
if (nativeBalance < minGasBalance) {
|
|
3092
|
-
const nativeBNB = parseFloat(
|
|
3431
|
+
const nativeBNB = parseFloat(import_ethers3.ethers.formatEther(nativeBalance)).toFixed(4);
|
|
3093
3432
|
const isTestnet = chainName === "bnb_testnet";
|
|
3094
3433
|
if (isTestnet) {
|
|
3095
3434
|
throw new Error(
|
|
@@ -3123,35 +3462,21 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3123
3462
|
);
|
|
3124
3463
|
}
|
|
3125
3464
|
const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
|
|
3126
|
-
const
|
|
3465
|
+
const intentNonce = Date.now();
|
|
3466
|
+
const intentDeadline = Date.now() + 36e5;
|
|
3467
|
+
const envelope = buildBnbIntentTypedData({
|
|
3127
3468
|
from: this.wallet.address,
|
|
3128
3469
|
to,
|
|
3129
3470
|
amount: amountWei,
|
|
3130
|
-
|
|
3471
|
+
tokenAddress: tokenConfig.address,
|
|
3131
3472
|
service,
|
|
3132
|
-
nonce:
|
|
3133
|
-
|
|
3134
|
-
deadline: Date.now() + 36e5
|
|
3135
|
-
// 1 hour
|
|
3136
|
-
};
|
|
3137
|
-
const domain = {
|
|
3138
|
-
name: "MoltsPay",
|
|
3139
|
-
version: "1",
|
|
3473
|
+
nonce: intentNonce,
|
|
3474
|
+
deadline: intentDeadline,
|
|
3140
3475
|
chainId: chain.chainId
|
|
3141
|
-
};
|
|
3142
|
-
const types = {
|
|
3143
|
-
PaymentIntent: [
|
|
3144
|
-
{ name: "from", type: "address" },
|
|
3145
|
-
{ name: "to", type: "address" },
|
|
3146
|
-
{ name: "amount", type: "uint256" },
|
|
3147
|
-
{ name: "token", type: "address" },
|
|
3148
|
-
{ name: "service", type: "string" },
|
|
3149
|
-
{ name: "nonce", type: "uint256" },
|
|
3150
|
-
{ name: "deadline", type: "uint256" }
|
|
3151
|
-
]
|
|
3152
|
-
};
|
|
3476
|
+
});
|
|
3153
3477
|
console.log(`[MoltsPay] Signing BNB payment intent...`);
|
|
3154
|
-
const signature = await this.
|
|
3478
|
+
const signature = await this.signer.signTypedData(envelope);
|
|
3479
|
+
const intent = envelope.message;
|
|
3155
3480
|
const network = `eip155:${chain.chainId}`;
|
|
3156
3481
|
const payload = {
|
|
3157
3482
|
x402Version: 2,
|
|
@@ -3212,11 +3537,11 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3212
3537
|
throw new Error("Missing payTo address in payment requirements");
|
|
3213
3538
|
}
|
|
3214
3539
|
const solanaFeePayer = requirements.extra?.solanaFeePayer;
|
|
3215
|
-
const feePayerPubkey = solanaFeePayer ? new
|
|
3540
|
+
const feePayerPubkey = solanaFeePayer ? new import_web36.PublicKey(solanaFeePayer) : void 0;
|
|
3216
3541
|
if (feePayerPubkey) {
|
|
3217
3542
|
console.log(`[MoltsPay] Gasless mode: server pays fees`);
|
|
3218
3543
|
}
|
|
3219
|
-
const recipientPubkey = new
|
|
3544
|
+
const recipientPubkey = new import_web36.PublicKey(requirements.payTo);
|
|
3220
3545
|
const transaction = await createSolanaPaymentTransaction(
|
|
3221
3546
|
solanaWallet.publicKey,
|
|
3222
3547
|
recipientPubkey,
|
|
@@ -3225,12 +3550,11 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3225
3550
|
feePayerPubkey
|
|
3226
3551
|
// Optional fee payer for gasless mode
|
|
3227
3552
|
);
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
}
|
|
3233
|
-
const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
|
|
3553
|
+
const unsignedBase64 = transaction.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
|
|
3554
|
+
const signedTx = await this.signer.signSolanaTransaction({
|
|
3555
|
+
transactionBase64: unsignedBase64,
|
|
3556
|
+
partialSign: !!feePayerPubkey
|
|
3557
|
+
});
|
|
3234
3558
|
console.log(`[MoltsPay] Transaction signed, sending to server...`);
|
|
3235
3559
|
const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
|
|
3236
3560
|
const payload = {
|
|
@@ -3277,7 +3601,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3277
3601
|
* Check ERC20 allowance for a spender
|
|
3278
3602
|
*/
|
|
3279
3603
|
async checkAllowance(tokenAddress, spender, provider) {
|
|
3280
|
-
const contract = new
|
|
3604
|
+
const contract = new import_ethers3.ethers.Contract(
|
|
3281
3605
|
tokenAddress,
|
|
3282
3606
|
["function allowance(address owner, address spender) view returns (uint256)"],
|
|
3283
3607
|
provider
|
|
@@ -3288,41 +3612,29 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3288
3612
|
* Sign EIP-3009 transferWithAuthorization (GASLESS)
|
|
3289
3613
|
* This only signs - no on-chain transaction, no gas needed.
|
|
3290
3614
|
* Supports both USDC and USDT.
|
|
3615
|
+
*
|
|
3616
|
+
* Delegates typed-data construction to `core/eip3009.ts` and the signature
|
|
3617
|
+
* itself to `this.signer`. That way Web Client (Phase 4) can reuse the same
|
|
3618
|
+
* flow with an EIP-1193 signer without duplicating typed-data layout.
|
|
3291
3619
|
*/
|
|
3292
3620
|
async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
|
|
3293
|
-
const validAfter = 0;
|
|
3294
|
-
const validBefore = Math.floor(Date.now() / 1e3) + 3600;
|
|
3295
|
-
const nonce = import_ethers.ethers.hexlify(import_ethers.ethers.randomBytes(32));
|
|
3296
3621
|
const tokenConfig = chain.tokens[token];
|
|
3297
3622
|
const value = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
|
|
3298
|
-
const
|
|
3623
|
+
const nonce = import_ethers3.ethers.hexlify(import_ethers3.ethers.randomBytes(32));
|
|
3624
|
+
const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
|
|
3625
|
+
const tokenVersion = domainOverride?.version || "2";
|
|
3626
|
+
const envelope = buildEIP3009TypedData({
|
|
3299
3627
|
from: this.wallet.address,
|
|
3300
3628
|
to,
|
|
3301
3629
|
value,
|
|
3302
|
-
|
|
3303
|
-
validBefore: validBefore.toString(),
|
|
3304
|
-
nonce
|
|
3305
|
-
};
|
|
3306
|
-
const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
|
|
3307
|
-
const tokenVersion = domainOverride?.version || "2";
|
|
3308
|
-
const domain = {
|
|
3309
|
-
name: tokenName,
|
|
3310
|
-
version: tokenVersion,
|
|
3630
|
+
nonce,
|
|
3311
3631
|
chainId: chain.chainId,
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
{ name: "value", type: "uint256" },
|
|
3319
|
-
{ name: "validAfter", type: "uint256" },
|
|
3320
|
-
{ name: "validBefore", type: "uint256" },
|
|
3321
|
-
{ name: "nonce", type: "bytes32" }
|
|
3322
|
-
]
|
|
3323
|
-
};
|
|
3324
|
-
const signature = await this.wallet.signTypedData(domain, types, authorization);
|
|
3325
|
-
return { authorization, signature };
|
|
3632
|
+
tokenAddress: tokenConfig.address,
|
|
3633
|
+
tokenName,
|
|
3634
|
+
tokenVersion
|
|
3635
|
+
});
|
|
3636
|
+
const signature = await this.signer.signTypedData(envelope);
|
|
3637
|
+
return { authorization: envelope.message, signature };
|
|
3326
3638
|
}
|
|
3327
3639
|
/**
|
|
3328
3640
|
* Check spending limits
|
|
@@ -3404,15 +3716,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3404
3716
|
loadWallet() {
|
|
3405
3717
|
const walletPath = (0, import_path2.join)(this.configDir, "wallet.json");
|
|
3406
3718
|
if ((0, import_fs4.existsSync)(walletPath)) {
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3719
|
+
if (process.platform !== "win32") {
|
|
3720
|
+
try {
|
|
3721
|
+
const stats = (0, import_fs4.statSync)(walletPath);
|
|
3722
|
+
const mode = stats.mode & 511;
|
|
3723
|
+
if (mode !== 384) {
|
|
3724
|
+
console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
|
|
3725
|
+
console.warn(`[MoltsPay] Fixing permissions to 0600...`);
|
|
3726
|
+
(0, import_fs4.chmodSync)(walletPath, 384);
|
|
3727
|
+
}
|
|
3728
|
+
} catch {
|
|
3414
3729
|
}
|
|
3415
|
-
} catch (err) {
|
|
3416
3730
|
}
|
|
3417
3731
|
const content = (0, import_fs4.readFileSync)(walletPath, "utf-8");
|
|
3418
3732
|
return JSON.parse(content);
|
|
@@ -3424,7 +3738,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3424
3738
|
*/
|
|
3425
3739
|
static init(configDir, options) {
|
|
3426
3740
|
(0, import_fs4.mkdirSync)(configDir, { recursive: true });
|
|
3427
|
-
const wallet =
|
|
3741
|
+
const wallet = import_ethers3.Wallet.createRandom();
|
|
3428
3742
|
const walletData = {
|
|
3429
3743
|
address: wallet.address,
|
|
3430
3744
|
privateKey: wallet.privateKey,
|
|
@@ -3456,17 +3770,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3456
3770
|
} catch {
|
|
3457
3771
|
throw new Error(`Unknown chain: ${this.config.chain}`);
|
|
3458
3772
|
}
|
|
3459
|
-
const provider = new
|
|
3773
|
+
const provider = new import_ethers3.ethers.JsonRpcProvider(chain.rpc);
|
|
3460
3774
|
const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
3461
3775
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
3462
3776
|
provider.getBalance(this.wallet.address),
|
|
3463
|
-
new
|
|
3464
|
-
new
|
|
3777
|
+
new import_ethers3.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
3778
|
+
new import_ethers3.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
3465
3779
|
]);
|
|
3466
3780
|
return {
|
|
3467
|
-
usdc: parseFloat(
|
|
3468
|
-
usdt: parseFloat(
|
|
3469
|
-
native: parseFloat(
|
|
3781
|
+
usdc: parseFloat(import_ethers3.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
3782
|
+
usdt: parseFloat(import_ethers3.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
3783
|
+
native: parseFloat(import_ethers3.ethers.formatEther(nativeBalance))
|
|
3470
3784
|
};
|
|
3471
3785
|
}
|
|
3472
3786
|
/**
|
|
@@ -3489,38 +3803,38 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3489
3803
|
supportedChains.map(async (chainName) => {
|
|
3490
3804
|
try {
|
|
3491
3805
|
const chain = getChain(chainName);
|
|
3492
|
-
const provider = new
|
|
3806
|
+
const provider = new import_ethers3.ethers.JsonRpcProvider(chain.rpc);
|
|
3493
3807
|
if (chainName === "tempo_moderato") {
|
|
3494
3808
|
const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
|
|
3495
3809
|
provider.getBalance(this.wallet.address),
|
|
3496
|
-
new
|
|
3497
|
-
new
|
|
3498
|
-
new
|
|
3499
|
-
new
|
|
3810
|
+
new import_ethers3.ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
3811
|
+
new import_ethers3.ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
3812
|
+
new import_ethers3.ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
3813
|
+
new import_ethers3.ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
3500
3814
|
]);
|
|
3501
3815
|
results[chainName] = {
|
|
3502
|
-
usdc: parseFloat(
|
|
3816
|
+
usdc: parseFloat(import_ethers3.ethers.formatUnits(pathUSD, 6)),
|
|
3503
3817
|
// pathUSD as default USDC
|
|
3504
|
-
usdt: parseFloat(
|
|
3818
|
+
usdt: parseFloat(import_ethers3.ethers.formatUnits(alphaUSD, 6)),
|
|
3505
3819
|
// alphaUSD as default USDT
|
|
3506
|
-
native: parseFloat(
|
|
3820
|
+
native: parseFloat(import_ethers3.ethers.formatEther(nativeBalance)),
|
|
3507
3821
|
tempo: {
|
|
3508
|
-
pathUSD: parseFloat(
|
|
3509
|
-
alphaUSD: parseFloat(
|
|
3510
|
-
betaUSD: parseFloat(
|
|
3511
|
-
thetaUSD: parseFloat(
|
|
3822
|
+
pathUSD: parseFloat(import_ethers3.ethers.formatUnits(pathUSD, 6)),
|
|
3823
|
+
alphaUSD: parseFloat(import_ethers3.ethers.formatUnits(alphaUSD, 6)),
|
|
3824
|
+
betaUSD: parseFloat(import_ethers3.ethers.formatUnits(betaUSD, 6)),
|
|
3825
|
+
thetaUSD: parseFloat(import_ethers3.ethers.formatUnits(thetaUSD, 6))
|
|
3512
3826
|
}
|
|
3513
3827
|
};
|
|
3514
3828
|
} else {
|
|
3515
3829
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
3516
3830
|
provider.getBalance(this.wallet.address),
|
|
3517
|
-
new
|
|
3518
|
-
new
|
|
3831
|
+
new import_ethers3.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
3832
|
+
new import_ethers3.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
3519
3833
|
]);
|
|
3520
3834
|
results[chainName] = {
|
|
3521
|
-
usdc: parseFloat(
|
|
3522
|
-
usdt: parseFloat(
|
|
3523
|
-
native: parseFloat(
|
|
3835
|
+
usdc: parseFloat(import_ethers3.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
3836
|
+
usdt: parseFloat(import_ethers3.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
3837
|
+
native: parseFloat(import_ethers3.ethers.formatEther(nativeBalance))
|
|
3524
3838
|
};
|
|
3525
3839
|
}
|
|
3526
3840
|
} catch (err) {
|
|
@@ -3648,10 +3962,10 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
3648
3962
|
};
|
|
3649
3963
|
|
|
3650
3964
|
// src/wallet/Wallet.ts
|
|
3651
|
-
var
|
|
3965
|
+
var import_ethers4 = require("ethers");
|
|
3652
3966
|
|
|
3653
3967
|
// src/wallet/createWallet.ts
|
|
3654
|
-
var
|
|
3968
|
+
var import_ethers5 = require("ethers");
|
|
3655
3969
|
var import_fs5 = require("fs");
|
|
3656
3970
|
var import_path3 = require("path");
|
|
3657
3971
|
var import_crypto = require("crypto");
|
|
@@ -3696,7 +4010,7 @@ function createWallet(options = {}) {
|
|
|
3696
4010
|
}
|
|
3697
4011
|
}
|
|
3698
4012
|
try {
|
|
3699
|
-
const wallet =
|
|
4013
|
+
const wallet = import_ethers5.ethers.Wallet.createRandom();
|
|
3700
4014
|
const walletData = {
|
|
3701
4015
|
address: wallet.address,
|
|
3702
4016
|
label: options.label,
|
|
@@ -3768,8 +4082,8 @@ function walletExists(storagePath) {
|
|
|
3768
4082
|
}
|
|
3769
4083
|
|
|
3770
4084
|
// src/verify/index.ts
|
|
3771
|
-
var
|
|
3772
|
-
var TRANSFER_EVENT_TOPIC3 =
|
|
4085
|
+
var import_ethers6 = require("ethers");
|
|
4086
|
+
var TRANSFER_EVENT_TOPIC3 = import_ethers6.ethers.id("Transfer(address,address,uint256)");
|
|
3773
4087
|
async function verifyPayment(params) {
|
|
3774
4088
|
const { txHash, expectedAmount, expectedTo, expectedToken } = params;
|
|
3775
4089
|
let chain;
|
|
@@ -3786,7 +4100,7 @@ async function verifyPayment(params) {
|
|
|
3786
4100
|
return { verified: false, error: `Unsupported chain: ${params.chain}` };
|
|
3787
4101
|
}
|
|
3788
4102
|
try {
|
|
3789
|
-
const provider = new
|
|
4103
|
+
const provider = new import_ethers6.ethers.JsonRpcProvider(chain.rpc);
|
|
3790
4104
|
const receipt = await provider.getTransactionReceipt(txHash);
|
|
3791
4105
|
if (!receipt) {
|
|
3792
4106
|
return { verified: false, error: "Transaction not found or not confirmed" };
|
|
@@ -3858,7 +4172,7 @@ async function getTransactionStatus(txHash, chain = "base") {
|
|
|
3858
4172
|
return { status: "not_found" };
|
|
3859
4173
|
}
|
|
3860
4174
|
try {
|
|
3861
|
-
const provider = new
|
|
4175
|
+
const provider = new import_ethers6.ethers.JsonRpcProvider(chainConfig.rpc);
|
|
3862
4176
|
const receipt = await provider.getTransactionReceipt(txHash);
|
|
3863
4177
|
if (!receipt) {
|
|
3864
4178
|
const tx = await provider.getTransaction(txHash);
|
|
@@ -3895,7 +4209,7 @@ async function waitForTransaction(txHash, chain = "base", confirmations = 1, tim
|
|
|
3895
4209
|
} catch (e) {
|
|
3896
4210
|
return { verified: false, confirmed: false, error: `Unsupported chain: ${chain}` };
|
|
3897
4211
|
}
|
|
3898
|
-
const provider = new
|
|
4212
|
+
const provider = new import_ethers6.ethers.JsonRpcProvider(chainConfig.rpc);
|
|
3899
4213
|
try {
|
|
3900
4214
|
const receipt = await provider.waitForTransaction(txHash, confirmations, timeoutMs);
|
|
3901
4215
|
if (!receipt) {
|
|
@@ -4035,9 +4349,9 @@ var CDPWallet = class {
|
|
|
4035
4349
|
* Get USDC balance
|
|
4036
4350
|
*/
|
|
4037
4351
|
async getBalance() {
|
|
4038
|
-
const { ethers:
|
|
4039
|
-
const provider = new
|
|
4040
|
-
const usdcContract = new
|
|
4352
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
4353
|
+
const provider = new ethers7.JsonRpcProvider(this.chainConfig.rpc);
|
|
4354
|
+
const usdcContract = new ethers7.Contract(
|
|
4041
4355
|
this.chainConfig.usdc,
|
|
4042
4356
|
["function balanceOf(address) view returns (uint256)"],
|
|
4043
4357
|
provider
|
|
@@ -4048,7 +4362,7 @@ var CDPWallet = class {
|
|
|
4048
4362
|
]);
|
|
4049
4363
|
return {
|
|
4050
4364
|
usdc: (Number(usdcBalance) / 1e6).toFixed(2),
|
|
4051
|
-
eth:
|
|
4365
|
+
eth: ethers7.formatEther(ethBalance)
|
|
4052
4366
|
};
|
|
4053
4367
|
}
|
|
4054
4368
|
/**
|
|
@@ -4066,7 +4380,7 @@ var CDPWallet = class {
|
|
|
4066
4380
|
}
|
|
4067
4381
|
try {
|
|
4068
4382
|
const { CdpClient } = await import("@coinbase/cdp-sdk");
|
|
4069
|
-
const { ethers:
|
|
4383
|
+
const { ethers: ethers7 } = await import("ethers");
|
|
4070
4384
|
const cdp = new CdpClient({
|
|
4071
4385
|
apiKeyId: creds.apiKeyId,
|
|
4072
4386
|
apiKeySecret: creds.apiKeySecret,
|
|
@@ -4074,7 +4388,7 @@ var CDPWallet = class {
|
|
|
4074
4388
|
});
|
|
4075
4389
|
const account = await cdp.evm.getAccount({ address: this.address });
|
|
4076
4390
|
const amountWei = BigInt(Math.floor(params.amount * 1e6));
|
|
4077
|
-
const iface = new
|
|
4391
|
+
const iface = new ethers7.Interface([
|
|
4078
4392
|
"function transfer(address to, uint256 amount) returns (bool)"
|
|
4079
4393
|
]);
|
|
4080
4394
|
const callData = iface.encodeFunctionData("transfer", [params.to, amountWei]);
|