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/client/index.d.mts
CHANGED
|
@@ -90,6 +90,7 @@ declare class MoltsPayClient {
|
|
|
90
90
|
private config;
|
|
91
91
|
private walletData;
|
|
92
92
|
private wallet;
|
|
93
|
+
private signer;
|
|
93
94
|
private todaySpending;
|
|
94
95
|
private lastSpendingReset;
|
|
95
96
|
constructor(options?: MoltsPayClientOptions);
|
|
@@ -161,6 +162,10 @@ declare class MoltsPayClient {
|
|
|
161
162
|
* Sign EIP-3009 transferWithAuthorization (GASLESS)
|
|
162
163
|
* This only signs - no on-chain transaction, no gas needed.
|
|
163
164
|
* Supports both USDC and USDT.
|
|
165
|
+
*
|
|
166
|
+
* Delegates typed-data construction to `core/eip3009.ts` and the signature
|
|
167
|
+
* itself to `this.signer`. That way Web Client (Phase 4) can reuse the same
|
|
168
|
+
* flow with an EIP-1193 signer without duplicating typed-data layout.
|
|
164
169
|
*/
|
|
165
170
|
private signEIP3009;
|
|
166
171
|
/**
|
package/dist/client/index.d.ts
CHANGED
|
@@ -90,6 +90,7 @@ declare class MoltsPayClient {
|
|
|
90
90
|
private config;
|
|
91
91
|
private walletData;
|
|
92
92
|
private wallet;
|
|
93
|
+
private signer;
|
|
93
94
|
private todaySpending;
|
|
94
95
|
private lastSpendingReset;
|
|
95
96
|
constructor(options?: MoltsPayClientOptions);
|
|
@@ -161,6 +162,10 @@ declare class MoltsPayClient {
|
|
|
161
162
|
* Sign EIP-3009 transferWithAuthorization (GASLESS)
|
|
162
163
|
* This only signs - no on-chain transaction, no gas needed.
|
|
163
164
|
* Supports both USDC and USDT.
|
|
165
|
+
*
|
|
166
|
+
* Delegates typed-data construction to `core/eip3009.ts` and the signature
|
|
167
|
+
* itself to `this.signer`. That way Web Client (Phase 4) can reuse the same
|
|
168
|
+
* flow with an EIP-1193 signer without duplicating typed-data layout.
|
|
164
169
|
*/
|
|
165
170
|
private signEIP3009;
|
|
166
171
|
/**
|
package/dist/client/index.js
CHANGED
|
@@ -33,10 +33,12 @@ __export(client_exports, {
|
|
|
33
33
|
MoltsPayClient: () => MoltsPayClient
|
|
34
34
|
});
|
|
35
35
|
module.exports = __toCommonJS(client_exports);
|
|
36
|
+
|
|
37
|
+
// src/client/node/index.ts
|
|
36
38
|
var import_fs2 = require("fs");
|
|
37
39
|
var import_os2 = require("os");
|
|
38
40
|
var import_path2 = require("path");
|
|
39
|
-
var
|
|
41
|
+
var import_ethers2 = require("ethers");
|
|
40
42
|
|
|
41
43
|
// src/chains/index.ts
|
|
42
44
|
var CHAINS = {
|
|
@@ -277,16 +279,16 @@ function loadSolanaWallet(configDir = DEFAULT_CONFIG_DIR) {
|
|
|
277
279
|
// src/facilitators/solana.ts
|
|
278
280
|
var import_web33 = require("@solana/web3.js");
|
|
279
281
|
var import_spl_token2 = require("@solana/spl-token");
|
|
280
|
-
async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
|
|
282
|
+
async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey, connection) {
|
|
281
283
|
const chainConfig = SOLANA_CHAINS[chain];
|
|
282
|
-
const
|
|
284
|
+
const conn = connection ?? new import_web33.Connection(chainConfig.rpc, "confirmed");
|
|
283
285
|
const mint = new import_web33.PublicKey(chainConfig.tokens.USDC.mint);
|
|
284
286
|
const actualFeePayer = feePayerPubkey || senderPubkey;
|
|
285
287
|
const senderATA = await (0, import_spl_token2.getAssociatedTokenAddress)(mint, senderPubkey);
|
|
286
288
|
const recipientATA = await (0, import_spl_token2.getAssociatedTokenAddress)(mint, recipientPubkey);
|
|
287
289
|
const transaction = new import_web33.Transaction();
|
|
288
290
|
try {
|
|
289
|
-
await (0, import_spl_token2.getAccount)(
|
|
291
|
+
await (0, import_spl_token2.getAccount)(conn, recipientATA);
|
|
290
292
|
} catch {
|
|
291
293
|
transaction.add(
|
|
292
294
|
(0, import_spl_token2.createAssociatedTokenAccountInstruction)(
|
|
@@ -317,17 +319,178 @@ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amo
|
|
|
317
319
|
// decimals
|
|
318
320
|
)
|
|
319
321
|
);
|
|
320
|
-
const { blockhash, lastValidBlockHeight } = await
|
|
322
|
+
const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash();
|
|
321
323
|
transaction.recentBlockhash = blockhash;
|
|
322
324
|
transaction.feePayer = actualFeePayer;
|
|
323
325
|
return transaction;
|
|
324
326
|
}
|
|
325
327
|
|
|
326
|
-
// src/client/index.ts
|
|
327
|
-
var
|
|
328
|
+
// src/client/node/index.ts
|
|
329
|
+
var import_web35 = require("@solana/web3.js");
|
|
330
|
+
|
|
331
|
+
// src/client/core/types.ts
|
|
328
332
|
var X402_VERSION = 2;
|
|
329
333
|
var PAYMENT_REQUIRED_HEADER = "x-payment-required";
|
|
330
334
|
var PAYMENT_HEADER = "x-payment";
|
|
335
|
+
|
|
336
|
+
// src/client/core/chain-map.ts
|
|
337
|
+
var NETWORK_TO_CHAIN = {
|
|
338
|
+
"eip155:8453": "base",
|
|
339
|
+
"eip155:137": "polygon",
|
|
340
|
+
"eip155:84532": "base_sepolia",
|
|
341
|
+
"eip155:42431": "tempo_moderato",
|
|
342
|
+
"eip155:56": "bnb",
|
|
343
|
+
"eip155:97": "bnb_testnet",
|
|
344
|
+
"solana:mainnet": "solana",
|
|
345
|
+
"solana:devnet": "solana_devnet"
|
|
346
|
+
};
|
|
347
|
+
var CHAIN_TO_NETWORK = Object.fromEntries(
|
|
348
|
+
Object.entries(NETWORK_TO_CHAIN).map(([network, chain]) => [chain, network])
|
|
349
|
+
);
|
|
350
|
+
function networkToChainName(network) {
|
|
351
|
+
return NETWORK_TO_CHAIN[network] ?? null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/client/core/base64.ts
|
|
355
|
+
var BufferCtor = globalThis.Buffer;
|
|
356
|
+
|
|
357
|
+
// src/client/core/eip3009.ts
|
|
358
|
+
var EIP3009_TYPES = {
|
|
359
|
+
TransferWithAuthorization: [
|
|
360
|
+
{ name: "from", type: "address" },
|
|
361
|
+
{ name: "to", type: "address" },
|
|
362
|
+
{ name: "value", type: "uint256" },
|
|
363
|
+
{ name: "validAfter", type: "uint256" },
|
|
364
|
+
{ name: "validBefore", type: "uint256" },
|
|
365
|
+
{ name: "nonce", type: "bytes32" }
|
|
366
|
+
]
|
|
367
|
+
};
|
|
368
|
+
function buildEIP3009TypedData(args) {
|
|
369
|
+
const validAfter = args.validAfter ?? "0";
|
|
370
|
+
const validBefore = args.validBefore ?? (Math.floor(Date.now() / 1e3) + 3600).toString();
|
|
371
|
+
const authorization = {
|
|
372
|
+
from: args.from,
|
|
373
|
+
to: args.to,
|
|
374
|
+
value: args.value,
|
|
375
|
+
validAfter,
|
|
376
|
+
validBefore,
|
|
377
|
+
nonce: args.nonce
|
|
378
|
+
};
|
|
379
|
+
return {
|
|
380
|
+
domain: {
|
|
381
|
+
name: args.tokenName,
|
|
382
|
+
version: args.tokenVersion,
|
|
383
|
+
chainId: args.chainId,
|
|
384
|
+
verifyingContract: args.tokenAddress
|
|
385
|
+
},
|
|
386
|
+
types: EIP3009_TYPES,
|
|
387
|
+
primaryType: "TransferWithAuthorization",
|
|
388
|
+
message: authorization
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/client/core/bnb-intent.ts
|
|
393
|
+
var BNB_INTENT_TYPES = {
|
|
394
|
+
PaymentIntent: [
|
|
395
|
+
{ name: "from", type: "address" },
|
|
396
|
+
{ name: "to", type: "address" },
|
|
397
|
+
{ name: "amount", type: "uint256" },
|
|
398
|
+
{ name: "token", type: "address" },
|
|
399
|
+
{ name: "service", type: "string" },
|
|
400
|
+
{ name: "nonce", type: "uint256" },
|
|
401
|
+
{ name: "deadline", type: "uint256" }
|
|
402
|
+
]
|
|
403
|
+
};
|
|
404
|
+
var BNB_DOMAIN_NAME = "MoltsPay";
|
|
405
|
+
var BNB_DOMAIN_VERSION = "1";
|
|
406
|
+
function buildBnbIntentTypedData(args) {
|
|
407
|
+
const intent = {
|
|
408
|
+
from: args.from,
|
|
409
|
+
to: args.to,
|
|
410
|
+
amount: args.amount,
|
|
411
|
+
token: args.tokenAddress,
|
|
412
|
+
service: args.service,
|
|
413
|
+
nonce: args.nonce,
|
|
414
|
+
deadline: args.deadline
|
|
415
|
+
};
|
|
416
|
+
return {
|
|
417
|
+
domain: {
|
|
418
|
+
name: BNB_DOMAIN_NAME,
|
|
419
|
+
version: BNB_DOMAIN_VERSION,
|
|
420
|
+
chainId: args.chainId
|
|
421
|
+
},
|
|
422
|
+
types: BNB_INTENT_TYPES,
|
|
423
|
+
primaryType: "PaymentIntent",
|
|
424
|
+
message: intent
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
// src/client/node/signer.ts
|
|
429
|
+
var import_ethers = require("ethers");
|
|
430
|
+
var import_web34 = require("@solana/web3.js");
|
|
431
|
+
var NodeSigner = class {
|
|
432
|
+
evmWallet;
|
|
433
|
+
getSolanaKeypair;
|
|
434
|
+
constructor(evmWallet, options = {}) {
|
|
435
|
+
this.evmWallet = evmWallet;
|
|
436
|
+
this.getSolanaKeypair = options.getSolanaKeypair ?? (() => null);
|
|
437
|
+
}
|
|
438
|
+
async getEvmAddress() {
|
|
439
|
+
return this.evmWallet.address;
|
|
440
|
+
}
|
|
441
|
+
async getSolanaAddress() {
|
|
442
|
+
const kp = this.getSolanaKeypair();
|
|
443
|
+
return kp ? kp.publicKey.toBase58() : null;
|
|
444
|
+
}
|
|
445
|
+
async signTypedData(envelope) {
|
|
446
|
+
const mutableTypes = {};
|
|
447
|
+
for (const [key, fields] of Object.entries(envelope.types)) {
|
|
448
|
+
mutableTypes[key] = [...fields];
|
|
449
|
+
}
|
|
450
|
+
return this.evmWallet.signTypedData(
|
|
451
|
+
envelope.domain,
|
|
452
|
+
mutableTypes,
|
|
453
|
+
envelope.message
|
|
454
|
+
);
|
|
455
|
+
}
|
|
456
|
+
async sendEvmTransaction(args) {
|
|
457
|
+
const chain = findChainByChainId(args.chainId);
|
|
458
|
+
if (!chain) {
|
|
459
|
+
throw new Error(`sendEvmTransaction: unknown chainId ${args.chainId}`);
|
|
460
|
+
}
|
|
461
|
+
const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
|
|
462
|
+
const connected = this.evmWallet.connect(provider);
|
|
463
|
+
const tx = await connected.sendTransaction({
|
|
464
|
+
to: args.to,
|
|
465
|
+
data: args.data,
|
|
466
|
+
value: args.value ? BigInt(args.value) : 0n
|
|
467
|
+
});
|
|
468
|
+
return tx.hash;
|
|
469
|
+
}
|
|
470
|
+
async signSolanaTransaction(args) {
|
|
471
|
+
const kp = this.getSolanaKeypair();
|
|
472
|
+
if (!kp) {
|
|
473
|
+
throw new Error("signSolanaTransaction: no Solana wallet configured");
|
|
474
|
+
}
|
|
475
|
+
const tx = import_web34.Transaction.from(Buffer.from(args.transactionBase64, "base64"));
|
|
476
|
+
if (args.partialSign) {
|
|
477
|
+
tx.partialSign(kp);
|
|
478
|
+
} else {
|
|
479
|
+
tx.sign(kp);
|
|
480
|
+
}
|
|
481
|
+
return tx.serialize({ requireAllSignatures: false }).toString("base64");
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
function findChainByChainId(chainId) {
|
|
485
|
+
for (const cfg of Object.values(CHAINS)) {
|
|
486
|
+
if (cfg.chainId === chainId) {
|
|
487
|
+
return cfg;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return void 0;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// src/client/node/index.ts
|
|
331
494
|
var DEFAULT_CONFIG = {
|
|
332
495
|
chain: "base",
|
|
333
496
|
limits: {
|
|
@@ -340,6 +503,7 @@ var MoltsPayClient = class {
|
|
|
340
503
|
config;
|
|
341
504
|
walletData = null;
|
|
342
505
|
wallet = null;
|
|
506
|
+
signer = null;
|
|
343
507
|
todaySpending = 0;
|
|
344
508
|
lastSpendingReset = 0;
|
|
345
509
|
constructor(options = {}) {
|
|
@@ -348,7 +512,11 @@ var MoltsPayClient = class {
|
|
|
348
512
|
this.walletData = this.loadWallet();
|
|
349
513
|
this.loadSpending();
|
|
350
514
|
if (this.walletData) {
|
|
351
|
-
this.wallet = new
|
|
515
|
+
this.wallet = new import_ethers2.Wallet(this.walletData.privateKey);
|
|
516
|
+
const configDir = this.configDir;
|
|
517
|
+
this.signer = new NodeSigner(this.wallet, {
|
|
518
|
+
getSolanaKeypair: () => loadSolanaWallet(configDir)
|
|
519
|
+
});
|
|
352
520
|
}
|
|
353
521
|
}
|
|
354
522
|
/**
|
|
@@ -476,20 +644,6 @@ var MoltsPayClient = class {
|
|
|
476
644
|
} catch {
|
|
477
645
|
throw new Error("Invalid x-payment-required header");
|
|
478
646
|
}
|
|
479
|
-
const networkToChainName = (network2) => {
|
|
480
|
-
if (network2 === "solana:mainnet") return "solana";
|
|
481
|
-
if (network2 === "solana:devnet") return "solana_devnet";
|
|
482
|
-
const match = network2.match(/^eip155:(\d+)$/);
|
|
483
|
-
if (!match) return null;
|
|
484
|
-
const chainId = parseInt(match[1]);
|
|
485
|
-
if (chainId === 8453) return "base";
|
|
486
|
-
if (chainId === 137) return "polygon";
|
|
487
|
-
if (chainId === 84532) return "base_sepolia";
|
|
488
|
-
if (chainId === 42431) return "tempo_moderato";
|
|
489
|
-
if (chainId === 56) return "bnb";
|
|
490
|
-
if (chainId === 97) return "bnb_testnet";
|
|
491
|
-
return null;
|
|
492
|
-
};
|
|
493
647
|
const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
|
|
494
648
|
const userSpecifiedChain = options.chain;
|
|
495
649
|
let selectedChain;
|
|
@@ -718,14 +872,14 @@ Please specify: --chain <chain_name>`
|
|
|
718
872
|
async handleBNBPayment(executeUrl, service, params, paymentDetails, options = {}) {
|
|
719
873
|
const { to, amount, token, chainName, chain, spender } = paymentDetails;
|
|
720
874
|
const tokenConfig = chain.tokens[token];
|
|
721
|
-
const provider = new
|
|
875
|
+
const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
|
|
722
876
|
const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
|
|
723
877
|
const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
|
|
724
878
|
if (allowance < amountWeiCheck) {
|
|
725
879
|
const nativeBalance = await provider.getBalance(this.wallet.address);
|
|
726
|
-
const minGasBalance =
|
|
880
|
+
const minGasBalance = import_ethers2.ethers.parseEther("0.0005");
|
|
727
881
|
if (nativeBalance < minGasBalance) {
|
|
728
|
-
const nativeBNB = parseFloat(
|
|
882
|
+
const nativeBNB = parseFloat(import_ethers2.ethers.formatEther(nativeBalance)).toFixed(4);
|
|
729
883
|
const isTestnet = chainName === "bnb_testnet";
|
|
730
884
|
if (isTestnet) {
|
|
731
885
|
throw new Error(
|
|
@@ -759,35 +913,21 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
759
913
|
);
|
|
760
914
|
}
|
|
761
915
|
const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
|
|
762
|
-
const
|
|
916
|
+
const intentNonce = Date.now();
|
|
917
|
+
const intentDeadline = Date.now() + 36e5;
|
|
918
|
+
const envelope = buildBnbIntentTypedData({
|
|
763
919
|
from: this.wallet.address,
|
|
764
920
|
to,
|
|
765
921
|
amount: amountWei,
|
|
766
|
-
|
|
922
|
+
tokenAddress: tokenConfig.address,
|
|
767
923
|
service,
|
|
768
|
-
nonce:
|
|
769
|
-
|
|
770
|
-
deadline: Date.now() + 36e5
|
|
771
|
-
// 1 hour
|
|
772
|
-
};
|
|
773
|
-
const domain = {
|
|
774
|
-
name: "MoltsPay",
|
|
775
|
-
version: "1",
|
|
924
|
+
nonce: intentNonce,
|
|
925
|
+
deadline: intentDeadline,
|
|
776
926
|
chainId: chain.chainId
|
|
777
|
-
};
|
|
778
|
-
const types = {
|
|
779
|
-
PaymentIntent: [
|
|
780
|
-
{ name: "from", type: "address" },
|
|
781
|
-
{ name: "to", type: "address" },
|
|
782
|
-
{ name: "amount", type: "uint256" },
|
|
783
|
-
{ name: "token", type: "address" },
|
|
784
|
-
{ name: "service", type: "string" },
|
|
785
|
-
{ name: "nonce", type: "uint256" },
|
|
786
|
-
{ name: "deadline", type: "uint256" }
|
|
787
|
-
]
|
|
788
|
-
};
|
|
927
|
+
});
|
|
789
928
|
console.log(`[MoltsPay] Signing BNB payment intent...`);
|
|
790
|
-
const signature = await this.
|
|
929
|
+
const signature = await this.signer.signTypedData(envelope);
|
|
930
|
+
const intent = envelope.message;
|
|
791
931
|
const network = `eip155:${chain.chainId}`;
|
|
792
932
|
const payload = {
|
|
793
933
|
x402Version: 2,
|
|
@@ -848,11 +988,11 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
848
988
|
throw new Error("Missing payTo address in payment requirements");
|
|
849
989
|
}
|
|
850
990
|
const solanaFeePayer = requirements.extra?.solanaFeePayer;
|
|
851
|
-
const feePayerPubkey = solanaFeePayer ? new
|
|
991
|
+
const feePayerPubkey = solanaFeePayer ? new import_web35.PublicKey(solanaFeePayer) : void 0;
|
|
852
992
|
if (feePayerPubkey) {
|
|
853
993
|
console.log(`[MoltsPay] Gasless mode: server pays fees`);
|
|
854
994
|
}
|
|
855
|
-
const recipientPubkey = new
|
|
995
|
+
const recipientPubkey = new import_web35.PublicKey(requirements.payTo);
|
|
856
996
|
const transaction = await createSolanaPaymentTransaction(
|
|
857
997
|
solanaWallet.publicKey,
|
|
858
998
|
recipientPubkey,
|
|
@@ -861,12 +1001,11 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
861
1001
|
feePayerPubkey
|
|
862
1002
|
// Optional fee payer for gasless mode
|
|
863
1003
|
);
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
}
|
|
869
|
-
const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
|
|
1004
|
+
const unsignedBase64 = transaction.serialize({ requireAllSignatures: false, verifySignatures: false }).toString("base64");
|
|
1005
|
+
const signedTx = await this.signer.signSolanaTransaction({
|
|
1006
|
+
transactionBase64: unsignedBase64,
|
|
1007
|
+
partialSign: !!feePayerPubkey
|
|
1008
|
+
});
|
|
870
1009
|
console.log(`[MoltsPay] Transaction signed, sending to server...`);
|
|
871
1010
|
const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
|
|
872
1011
|
const payload = {
|
|
@@ -913,7 +1052,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
913
1052
|
* Check ERC20 allowance for a spender
|
|
914
1053
|
*/
|
|
915
1054
|
async checkAllowance(tokenAddress, spender, provider) {
|
|
916
|
-
const contract = new
|
|
1055
|
+
const contract = new import_ethers2.ethers.Contract(
|
|
917
1056
|
tokenAddress,
|
|
918
1057
|
["function allowance(address owner, address spender) view returns (uint256)"],
|
|
919
1058
|
provider
|
|
@@ -924,41 +1063,29 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
924
1063
|
* Sign EIP-3009 transferWithAuthorization (GASLESS)
|
|
925
1064
|
* This only signs - no on-chain transaction, no gas needed.
|
|
926
1065
|
* Supports both USDC and USDT.
|
|
1066
|
+
*
|
|
1067
|
+
* Delegates typed-data construction to `core/eip3009.ts` and the signature
|
|
1068
|
+
* itself to `this.signer`. That way Web Client (Phase 4) can reuse the same
|
|
1069
|
+
* flow with an EIP-1193 signer without duplicating typed-data layout.
|
|
927
1070
|
*/
|
|
928
1071
|
async signEIP3009(to, amount, chain, token = "USDC", domainOverride) {
|
|
929
|
-
const validAfter = 0;
|
|
930
|
-
const validBefore = Math.floor(Date.now() / 1e3) + 3600;
|
|
931
|
-
const nonce = import_ethers.ethers.hexlify(import_ethers.ethers.randomBytes(32));
|
|
932
1072
|
const tokenConfig = chain.tokens[token];
|
|
933
1073
|
const value = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
|
|
934
|
-
const
|
|
1074
|
+
const nonce = import_ethers2.ethers.hexlify(import_ethers2.ethers.randomBytes(32));
|
|
1075
|
+
const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
|
|
1076
|
+
const tokenVersion = domainOverride?.version || "2";
|
|
1077
|
+
const envelope = buildEIP3009TypedData({
|
|
935
1078
|
from: this.wallet.address,
|
|
936
1079
|
to,
|
|
937
1080
|
value,
|
|
938
|
-
|
|
939
|
-
validBefore: validBefore.toString(),
|
|
940
|
-
nonce
|
|
941
|
-
};
|
|
942
|
-
const tokenName = domainOverride?.name || tokenConfig.eip712Name || (token === "USDC" ? "USD Coin" : "Tether USD");
|
|
943
|
-
const tokenVersion = domainOverride?.version || "2";
|
|
944
|
-
const domain = {
|
|
945
|
-
name: tokenName,
|
|
946
|
-
version: tokenVersion,
|
|
1081
|
+
nonce,
|
|
947
1082
|
chainId: chain.chainId,
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
{ name: "value", type: "uint256" },
|
|
955
|
-
{ name: "validAfter", type: "uint256" },
|
|
956
|
-
{ name: "validBefore", type: "uint256" },
|
|
957
|
-
{ name: "nonce", type: "bytes32" }
|
|
958
|
-
]
|
|
959
|
-
};
|
|
960
|
-
const signature = await this.wallet.signTypedData(domain, types, authorization);
|
|
961
|
-
return { authorization, signature };
|
|
1083
|
+
tokenAddress: tokenConfig.address,
|
|
1084
|
+
tokenName,
|
|
1085
|
+
tokenVersion
|
|
1086
|
+
});
|
|
1087
|
+
const signature = await this.signer.signTypedData(envelope);
|
|
1088
|
+
return { authorization: envelope.message, signature };
|
|
962
1089
|
}
|
|
963
1090
|
/**
|
|
964
1091
|
* Check spending limits
|
|
@@ -1040,15 +1167,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1040
1167
|
loadWallet() {
|
|
1041
1168
|
const walletPath = (0, import_path2.join)(this.configDir, "wallet.json");
|
|
1042
1169
|
if ((0, import_fs2.existsSync)(walletPath)) {
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1170
|
+
if (process.platform !== "win32") {
|
|
1171
|
+
try {
|
|
1172
|
+
const stats = (0, import_fs2.statSync)(walletPath);
|
|
1173
|
+
const mode = stats.mode & 511;
|
|
1174
|
+
if (mode !== 384) {
|
|
1175
|
+
console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
|
|
1176
|
+
console.warn(`[MoltsPay] Fixing permissions to 0600...`);
|
|
1177
|
+
(0, import_fs2.chmodSync)(walletPath, 384);
|
|
1178
|
+
}
|
|
1179
|
+
} catch {
|
|
1050
1180
|
}
|
|
1051
|
-
} catch (err) {
|
|
1052
1181
|
}
|
|
1053
1182
|
const content = (0, import_fs2.readFileSync)(walletPath, "utf-8");
|
|
1054
1183
|
return JSON.parse(content);
|
|
@@ -1060,7 +1189,7 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1060
1189
|
*/
|
|
1061
1190
|
static init(configDir, options) {
|
|
1062
1191
|
(0, import_fs2.mkdirSync)(configDir, { recursive: true });
|
|
1063
|
-
const wallet =
|
|
1192
|
+
const wallet = import_ethers2.Wallet.createRandom();
|
|
1064
1193
|
const walletData = {
|
|
1065
1194
|
address: wallet.address,
|
|
1066
1195
|
privateKey: wallet.privateKey,
|
|
@@ -1092,17 +1221,17 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1092
1221
|
} catch {
|
|
1093
1222
|
throw new Error(`Unknown chain: ${this.config.chain}`);
|
|
1094
1223
|
}
|
|
1095
|
-
const provider = new
|
|
1224
|
+
const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
|
|
1096
1225
|
const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
|
|
1097
1226
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
1098
1227
|
provider.getBalance(this.wallet.address),
|
|
1099
|
-
new
|
|
1100
|
-
new
|
|
1228
|
+
new import_ethers2.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
1229
|
+
new import_ethers2.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
1101
1230
|
]);
|
|
1102
1231
|
return {
|
|
1103
|
-
usdc: parseFloat(
|
|
1104
|
-
usdt: parseFloat(
|
|
1105
|
-
native: parseFloat(
|
|
1232
|
+
usdc: parseFloat(import_ethers2.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
1233
|
+
usdt: parseFloat(import_ethers2.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
1234
|
+
native: parseFloat(import_ethers2.ethers.formatEther(nativeBalance))
|
|
1106
1235
|
};
|
|
1107
1236
|
}
|
|
1108
1237
|
/**
|
|
@@ -1125,38 +1254,38 @@ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
|
|
|
1125
1254
|
supportedChains.map(async (chainName) => {
|
|
1126
1255
|
try {
|
|
1127
1256
|
const chain = getChain(chainName);
|
|
1128
|
-
const provider = new
|
|
1257
|
+
const provider = new import_ethers2.ethers.JsonRpcProvider(chain.rpc);
|
|
1129
1258
|
if (chainName === "tempo_moderato") {
|
|
1130
1259
|
const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
|
|
1131
1260
|
provider.getBalance(this.wallet.address),
|
|
1132
|
-
new
|
|
1133
|
-
new
|
|
1134
|
-
new
|
|
1135
|
-
new
|
|
1261
|
+
new import_ethers2.ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
1262
|
+
new import_ethers2.ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
1263
|
+
new import_ethers2.ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
1264
|
+
new import_ethers2.ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
1136
1265
|
]);
|
|
1137
1266
|
results[chainName] = {
|
|
1138
|
-
usdc: parseFloat(
|
|
1267
|
+
usdc: parseFloat(import_ethers2.ethers.formatUnits(pathUSD, 6)),
|
|
1139
1268
|
// pathUSD as default USDC
|
|
1140
|
-
usdt: parseFloat(
|
|
1269
|
+
usdt: parseFloat(import_ethers2.ethers.formatUnits(alphaUSD, 6)),
|
|
1141
1270
|
// alphaUSD as default USDT
|
|
1142
|
-
native: parseFloat(
|
|
1271
|
+
native: parseFloat(import_ethers2.ethers.formatEther(nativeBalance)),
|
|
1143
1272
|
tempo: {
|
|
1144
|
-
pathUSD: parseFloat(
|
|
1145
|
-
alphaUSD: parseFloat(
|
|
1146
|
-
betaUSD: parseFloat(
|
|
1147
|
-
thetaUSD: parseFloat(
|
|
1273
|
+
pathUSD: parseFloat(import_ethers2.ethers.formatUnits(pathUSD, 6)),
|
|
1274
|
+
alphaUSD: parseFloat(import_ethers2.ethers.formatUnits(alphaUSD, 6)),
|
|
1275
|
+
betaUSD: parseFloat(import_ethers2.ethers.formatUnits(betaUSD, 6)),
|
|
1276
|
+
thetaUSD: parseFloat(import_ethers2.ethers.formatUnits(thetaUSD, 6))
|
|
1148
1277
|
}
|
|
1149
1278
|
};
|
|
1150
1279
|
} else {
|
|
1151
1280
|
const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
|
|
1152
1281
|
provider.getBalance(this.wallet.address),
|
|
1153
|
-
new
|
|
1154
|
-
new
|
|
1282
|
+
new import_ethers2.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
|
|
1283
|
+
new import_ethers2.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
|
|
1155
1284
|
]);
|
|
1156
1285
|
results[chainName] = {
|
|
1157
|
-
usdc: parseFloat(
|
|
1158
|
-
usdt: parseFloat(
|
|
1159
|
-
native: parseFloat(
|
|
1286
|
+
usdc: parseFloat(import_ethers2.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
|
|
1287
|
+
usdt: parseFloat(import_ethers2.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
|
|
1288
|
+
native: parseFloat(import_ethers2.ethers.formatEther(nativeBalance))
|
|
1160
1289
|
};
|
|
1161
1290
|
}
|
|
1162
1291
|
} catch (err) {
|