moltspay 1.2.1 → 1.4.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.
Files changed (59) hide show
  1. package/README.md +292 -34
  2. package/dist/cdp/index.d.mts +4 -4
  3. package/dist/cdp/index.d.ts +4 -4
  4. package/dist/cdp/index.js +110 -30368
  5. package/dist/cdp/index.js.map +1 -1
  6. package/dist/cdp/index.mjs +94 -30360
  7. package/dist/cdp/index.mjs.map +1 -1
  8. package/dist/cdp-DeohBe1o.d.ts +66 -0
  9. package/dist/cdp-p_eHuQpb.d.mts +66 -0
  10. package/dist/chains/index.d.mts +9 -8
  11. package/dist/chains/index.d.ts +9 -8
  12. package/dist/chains/index.js +86 -0
  13. package/dist/chains/index.js.map +1 -1
  14. package/dist/chains/index.mjs +86 -0
  15. package/dist/chains/index.mjs.map +1 -1
  16. package/dist/cli/index.js +2746 -290
  17. package/dist/cli/index.js.map +1 -1
  18. package/dist/cli/index.mjs +2752 -282
  19. package/dist/cli/index.mjs.map +1 -1
  20. package/dist/client/index.d.mts +60 -4
  21. package/dist/client/index.d.ts +60 -4
  22. package/dist/client/index.js +734 -43
  23. package/dist/client/index.js.map +1 -1
  24. package/dist/client/index.mjs +732 -41
  25. package/dist/client/index.mjs.map +1 -1
  26. package/dist/facilitators/index.d.mts +220 -39
  27. package/dist/facilitators/index.d.ts +220 -39
  28. package/dist/facilitators/index.js +897 -1
  29. package/dist/facilitators/index.js.map +1 -1
  30. package/dist/facilitators/index.mjs +902 -1
  31. package/dist/facilitators/index.mjs.map +1 -1
  32. package/dist/{index-DgJPZMBG.d.mts → index-D_2FkLwV.d.mts} +6 -2
  33. package/dist/{index-DgJPZMBG.d.ts → index-D_2FkLwV.d.ts} +6 -2
  34. package/dist/index.d.mts +3 -2
  35. package/dist/index.d.ts +3 -2
  36. package/dist/index.js +2238 -30837
  37. package/dist/index.js.map +1 -1
  38. package/dist/index.mjs +2167 -30766
  39. package/dist/index.mjs.map +1 -1
  40. package/dist/server/index.d.mts +30 -3
  41. package/dist/server/index.d.ts +30 -3
  42. package/dist/server/index.js +1345 -54
  43. package/dist/server/index.js.map +1 -1
  44. package/dist/server/index.mjs +1355 -54
  45. package/dist/server/index.mjs.map +1 -1
  46. package/dist/verify/index.d.mts +1 -1
  47. package/dist/verify/index.d.ts +1 -1
  48. package/dist/verify/index.js +86 -0
  49. package/dist/verify/index.js.map +1 -1
  50. package/dist/verify/index.mjs +86 -0
  51. package/dist/verify/index.mjs.map +1 -1
  52. package/dist/wallet/index.d.mts +3 -3
  53. package/dist/wallet/index.d.ts +3 -3
  54. package/dist/wallet/index.js +86 -0
  55. package/dist/wallet/index.js.map +1 -1
  56. package/dist/wallet/index.mjs +86 -0
  57. package/dist/wallet/index.mjs.map +1 -1
  58. package/package.json +8 -2
  59. package/schemas/moltspay.services.schema.json +27 -132
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/client/index.ts
@@ -23,9 +33,9 @@ __export(client_exports, {
23
33
  MoltsPayClient: () => MoltsPayClient
24
34
  });
25
35
  module.exports = __toCommonJS(client_exports);
26
- var import_fs = require("fs");
27
- var import_os = require("os");
28
- var import_path = require("path");
36
+ var import_fs2 = require("fs");
37
+ var import_os2 = require("os");
38
+ var import_path2 = require("path");
29
39
  var import_ethers = require("ethers");
30
40
 
31
41
  // src/chains/index.ts
@@ -106,6 +116,92 @@ var CHAINS = {
106
116
  explorer: "https://sepolia.basescan.org/address/",
107
117
  explorerTx: "https://sepolia.basescan.org/tx/",
108
118
  avgBlockTime: 2
119
+ },
120
+ // ============ Tempo Testnet (Moderato) ============
121
+ tempo_moderato: {
122
+ name: "Tempo Moderato",
123
+ chainId: 42431,
124
+ rpc: "https://rpc.moderato.tempo.xyz",
125
+ tokens: {
126
+ // TIP-20 stablecoins on Tempo testnet (from mppx SDK)
127
+ // Note: Tempo uses USD as native gas token, not ETH
128
+ USDC: {
129
+ address: "0x20c0000000000000000000000000000000000000",
130
+ // pathUSD - primary testnet stablecoin
131
+ decimals: 6,
132
+ symbol: "USDC",
133
+ eip712Name: "pathUSD"
134
+ },
135
+ USDT: {
136
+ address: "0x20c0000000000000000000000000000000000001",
137
+ // alphaUSD
138
+ decimals: 6,
139
+ symbol: "USDT",
140
+ eip712Name: "alphaUSD"
141
+ }
142
+ },
143
+ usdc: "0x20c0000000000000000000000000000000000000",
144
+ explorer: "https://explore.testnet.tempo.xyz/address/",
145
+ explorerTx: "https://explore.testnet.tempo.xyz/tx/",
146
+ avgBlockTime: 0.5
147
+ // ~500ms finality
148
+ },
149
+ // ============ BNB Chain Testnet ============
150
+ bnb_testnet: {
151
+ name: "BNB Testnet",
152
+ chainId: 97,
153
+ rpc: "https://data-seed-prebsc-1-s1.binance.org:8545",
154
+ tokens: {
155
+ // Note: BNB uses 18 decimals for stablecoins (unlike Base/Polygon which use 6)
156
+ // Using official Binance-Peg testnet tokens
157
+ USDC: {
158
+ address: "0x64544969ed7EBf5f083679233325356EbE738930",
159
+ // Testnet USDC
160
+ decimals: 18,
161
+ symbol: "USDC",
162
+ eip712Name: "USD Coin"
163
+ },
164
+ USDT: {
165
+ address: "0x337610d27c682E347C9cD60BD4b3b107C9d34dDd",
166
+ // Testnet USDT
167
+ decimals: 18,
168
+ symbol: "USDT",
169
+ eip712Name: "Tether USD"
170
+ }
171
+ },
172
+ usdc: "0x64544969ed7EBf5f083679233325356EbE738930",
173
+ explorer: "https://testnet.bscscan.com/address/",
174
+ explorerTx: "https://testnet.bscscan.com/tx/",
175
+ avgBlockTime: 3,
176
+ // BNB-specific: requires approval for pay-for-success flow
177
+ requiresApproval: true
178
+ },
179
+ // ============ BNB Chain Mainnet ============
180
+ bnb: {
181
+ name: "BNB Smart Chain",
182
+ chainId: 56,
183
+ rpc: "https://bsc-dataseed.binance.org",
184
+ tokens: {
185
+ // Note: BNB uses 18 decimals for stablecoins
186
+ USDC: {
187
+ address: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
188
+ decimals: 18,
189
+ symbol: "USDC",
190
+ eip712Name: "USD Coin"
191
+ },
192
+ USDT: {
193
+ address: "0x55d398326f99059fF775485246999027B3197955",
194
+ decimals: 18,
195
+ symbol: "USDT",
196
+ eip712Name: "Tether USD"
197
+ }
198
+ },
199
+ usdc: "0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d",
200
+ explorer: "https://bscscan.com/address/",
201
+ explorerTx: "https://bscscan.com/tx/",
202
+ avgBlockTime: 3,
203
+ // BNB-specific: requires approval for pay-for-success flow
204
+ requiresApproval: true
109
205
  }
110
206
  };
111
207
  function getChain(name) {
@@ -116,7 +212,119 @@ function getChain(name) {
116
212
  return config;
117
213
  }
118
214
 
215
+ // src/wallet/solana.ts
216
+ var import_web32 = require("@solana/web3.js");
217
+ var import_spl_token = require("@solana/spl-token");
218
+ var import_fs = require("fs");
219
+ var import_path = require("path");
220
+ var import_os = require("os");
221
+ var import_bs58 = __toESM(require("bs58"));
222
+
223
+ // src/chains/solana.ts
224
+ var import_web3 = require("@solana/web3.js");
225
+ var SOLANA_CHAINS = {
226
+ solana: {
227
+ name: "Solana Mainnet",
228
+ cluster: "mainnet-beta",
229
+ rpc: "https://api.mainnet-beta.solana.com",
230
+ explorer: "https://solscan.io/account/",
231
+ explorerTx: "https://solscan.io/tx/",
232
+ tokens: {
233
+ USDC: {
234
+ mint: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
235
+ // Circle official USDC
236
+ decimals: 6
237
+ }
238
+ }
239
+ },
240
+ solana_devnet: {
241
+ name: "Solana Devnet",
242
+ cluster: "devnet",
243
+ rpc: "https://api.devnet.solana.com",
244
+ explorer: "https://solscan.io/account/",
245
+ explorerTx: "https://solscan.io/tx/",
246
+ tokens: {
247
+ USDC: {
248
+ // Circle's devnet USDC (if not available, we'll deploy our own test token)
249
+ mint: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
250
+ decimals: 6
251
+ }
252
+ }
253
+ }
254
+ };
255
+
256
+ // src/wallet/solana.ts
257
+ var DEFAULT_CONFIG_DIR = (0, import_path.join)((0, import_os.homedir)(), ".moltspay");
258
+ var SOLANA_WALLET_FILE = "wallet-solana.json";
259
+ function getSolanaWalletPath(configDir = DEFAULT_CONFIG_DIR) {
260
+ return (0, import_path.join)(configDir, SOLANA_WALLET_FILE);
261
+ }
262
+ function loadSolanaWallet(configDir = DEFAULT_CONFIG_DIR) {
263
+ const walletPath = getSolanaWalletPath(configDir);
264
+ if (!(0, import_fs.existsSync)(walletPath)) {
265
+ return null;
266
+ }
267
+ try {
268
+ const data = JSON.parse((0, import_fs.readFileSync)(walletPath, "utf-8"));
269
+ const secretKey = import_bs58.default.decode(data.secretKey);
270
+ return import_web32.Keypair.fromSecretKey(secretKey);
271
+ } catch (error) {
272
+ console.error("Failed to load Solana wallet:", error);
273
+ return null;
274
+ }
275
+ }
276
+
277
+ // src/facilitators/solana.ts
278
+ var import_web33 = require("@solana/web3.js");
279
+ var import_spl_token2 = require("@solana/spl-token");
280
+ async function createSolanaPaymentTransaction(senderPubkey, recipientPubkey, amount, chain, feePayerPubkey) {
281
+ const chainConfig = SOLANA_CHAINS[chain];
282
+ const connection = new import_web33.Connection(chainConfig.rpc, "confirmed");
283
+ const mint = new import_web33.PublicKey(chainConfig.tokens.USDC.mint);
284
+ const actualFeePayer = feePayerPubkey || senderPubkey;
285
+ const senderATA = await (0, import_spl_token2.getAssociatedTokenAddress)(mint, senderPubkey);
286
+ const recipientATA = await (0, import_spl_token2.getAssociatedTokenAddress)(mint, recipientPubkey);
287
+ const transaction = new import_web33.Transaction();
288
+ try {
289
+ await (0, import_spl_token2.getAccount)(connection, recipientATA);
290
+ } catch {
291
+ transaction.add(
292
+ (0, import_spl_token2.createAssociatedTokenAccountInstruction)(
293
+ actualFeePayer,
294
+ // payer (fee payer in gasless mode)
295
+ recipientATA,
296
+ // ata to create
297
+ recipientPubkey,
298
+ // owner
299
+ mint
300
+ // mint
301
+ )
302
+ );
303
+ }
304
+ transaction.add(
305
+ (0, import_spl_token2.createTransferCheckedInstruction)(
306
+ senderATA,
307
+ // source
308
+ mint,
309
+ // mint
310
+ recipientATA,
311
+ // destination
312
+ senderPubkey,
313
+ // owner (sender still authorizes the transfer)
314
+ amount,
315
+ // amount
316
+ chainConfig.tokens.USDC.decimals
317
+ // decimals
318
+ )
319
+ );
320
+ const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
321
+ transaction.recentBlockhash = blockhash;
322
+ transaction.feePayer = actualFeePayer;
323
+ return transaction;
324
+ }
325
+
119
326
  // src/client/index.ts
327
+ var import_web34 = require("@solana/web3.js");
120
328
  var X402_VERSION = 2;
121
329
  var PAYMENT_REQUIRED_HEADER = "x-payment-required";
122
330
  var PAYMENT_HEADER = "x-payment";
@@ -135,7 +343,7 @@ var MoltsPayClient = class {
135
343
  todaySpending = 0;
136
344
  lastSpendingReset = 0;
137
345
  constructor(options = {}) {
138
- this.configDir = options.configDir || (0, import_path.join)((0, import_os.homedir)(), ".moltspay");
346
+ this.configDir = options.configDir || (0, import_path2.join)((0, import_os2.homedir)(), ".moltspay");
139
347
  this.config = this.loadConfig();
140
348
  this.walletData = this.loadWallet();
141
349
  this.loadSpending();
@@ -155,6 +363,12 @@ var MoltsPayClient = class {
155
363
  get address() {
156
364
  return this.wallet?.address || null;
157
365
  }
366
+ /**
367
+ * Get wallet instance (for direct operations like approvals)
368
+ */
369
+ getWallet() {
370
+ return this.wallet;
371
+ }
158
372
  /**
159
373
  * Get current config
160
374
  */
@@ -224,9 +438,14 @@ var MoltsPayClient = class {
224
438
  }
225
439
  throw new Error(data.error || "Unexpected response");
226
440
  }
441
+ const wwwAuthHeader = initialRes.headers.get("www-authenticate");
227
442
  const paymentRequiredHeader = initialRes.headers.get(PAYMENT_REQUIRED_HEADER);
443
+ if (wwwAuthHeader && wwwAuthHeader.toLowerCase().includes("payment")) {
444
+ console.log("[MoltsPay] Detected MPP protocol, using Tempo flow...");
445
+ return await this.handleMPPPayment(serverUrl, service, params, wwwAuthHeader);
446
+ }
228
447
  if (!paymentRequiredHeader) {
229
- throw new Error("Missing x-payment-required header");
448
+ throw new Error("Missing payment header (x-payment-required or www-authenticate)");
230
449
  }
231
450
  let requirements;
232
451
  try {
@@ -243,17 +462,22 @@ var MoltsPayClient = class {
243
462
  throw new Error("Invalid x-payment-required header");
244
463
  }
245
464
  const networkToChainName = (network2) => {
465
+ if (network2 === "solana:mainnet") return "solana";
466
+ if (network2 === "solana:devnet") return "solana_devnet";
246
467
  const match = network2.match(/^eip155:(\d+)$/);
247
468
  if (!match) return null;
248
469
  const chainId = parseInt(match[1]);
249
470
  if (chainId === 8453) return "base";
250
471
  if (chainId === 137) return "polygon";
251
472
  if (chainId === 84532) return "base_sepolia";
473
+ if (chainId === 42431) return "tempo_moderato";
474
+ if (chainId === 56) return "bnb";
475
+ if (chainId === 97) return "bnb_testnet";
252
476
  return null;
253
477
  };
254
478
  const serverChains = requirements.map((r) => networkToChainName(r.network)).filter((c) => c !== null);
255
- let chainName;
256
479
  const userSpecifiedChain = options.chain;
480
+ let selectedChain;
257
481
  if (userSpecifiedChain) {
258
482
  if (!serverChains.includes(userSpecifiedChain)) {
259
483
  throw new Error(
@@ -261,17 +485,27 @@ var MoltsPayClient = class {
261
485
  Server accepts: ${serverChains.join(", ")}`
262
486
  );
263
487
  }
264
- chainName = userSpecifiedChain;
488
+ selectedChain = userSpecifiedChain;
265
489
  } else {
266
490
  if (serverChains.length === 1 && serverChains[0] === "base") {
267
- chainName = "base";
491
+ selectedChain = "base";
268
492
  } else {
269
493
  throw new Error(
270
494
  `Server accepts: ${serverChains.join(", ")}
271
- Please specify: --chain base, --chain polygon, or --chain base_sepolia`
495
+ Please specify: --chain <chain_name>`
272
496
  );
273
497
  }
274
498
  }
499
+ if (selectedChain === "solana" || selectedChain === "solana_devnet") {
500
+ const solanaChain = selectedChain;
501
+ const network2 = solanaChain === "solana" ? "solana:mainnet" : "solana:devnet";
502
+ const req2 = requirements.find((r) => r.network === network2);
503
+ if (!req2) {
504
+ throw new Error(`Failed to find payment requirement for ${selectedChain}`);
505
+ }
506
+ return await this.handleSolanaPayment(serverUrl, service, params, req2, solanaChain);
507
+ }
508
+ const chainName = selectedChain;
275
509
  const chain = getChain(chainName);
276
510
  const network = `eip155:${chain.chainId}`;
277
511
  const req = requirements.find((r) => r.scheme === "exact" && r.network === network);
@@ -306,6 +540,25 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
306
540
  } else {
307
541
  console.log(`[MoltsPay] Signing payment: $${amount} ${token} (gasless)`);
308
542
  }
543
+ if (chainName === "bnb" || chainName === "bnb_testnet") {
544
+ console.log(`[MoltsPay] Using BNB intent-based payment flow...`);
545
+ const payTo2 = req.payTo || req.resource;
546
+ if (!payTo2) {
547
+ throw new Error("Missing payTo address in payment requirements");
548
+ }
549
+ const bnbSpender = req.extra?.bnbSpender;
550
+ if (!bnbSpender) {
551
+ throw new Error("Server did not provide bnbSpender address. Server may not support BNB payments.");
552
+ }
553
+ return await this.handleBNBPayment(serverUrl, service, params, {
554
+ to: payTo2,
555
+ amount,
556
+ token,
557
+ chainName,
558
+ chain,
559
+ spender: bnbSpender
560
+ });
561
+ }
309
562
  const payTo = req.payTo || req.resource;
310
563
  if (!payTo) {
311
564
  throw new Error("Missing payTo address in payment requirements");
@@ -355,6 +608,300 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
355
608
  console.log(`[MoltsPay] Success! Payment: ${result.payment?.status || "claimed"}`);
356
609
  return result.result;
357
610
  }
611
+ /**
612
+ * Handle MPP (Machine Payments Protocol) payment flow
613
+ * Called when pay() detects WWW-Authenticate header in 402 response
614
+ */
615
+ async handleMPPPayment(serverUrl, service, params, wwwAuthHeader) {
616
+ const { privateKeyToAccount } = await import("viem/accounts");
617
+ const { createWalletClient, createPublicClient, http } = await import("viem");
618
+ const { tempoModerato } = await import("viem/chains");
619
+ const { Actions } = await import("viem/tempo");
620
+ const privateKey = this.walletData.privateKey;
621
+ const account = privateKeyToAccount(privateKey);
622
+ console.log(`[MoltsPay] Using MPP protocol on Tempo`);
623
+ console.log(`[MoltsPay] Account: ${account.address}`);
624
+ const parseAuthParam = (header, key) => {
625
+ const match = header.match(new RegExp(`${key}="([^"]+)"`, "i"));
626
+ return match ? match[1] : null;
627
+ };
628
+ const challengeId = parseAuthParam(wwwAuthHeader, "id");
629
+ const method = parseAuthParam(wwwAuthHeader, "method");
630
+ const realm = parseAuthParam(wwwAuthHeader, "realm");
631
+ const requestB64 = parseAuthParam(wwwAuthHeader, "request");
632
+ if (method !== "tempo") {
633
+ throw new Error(`Unsupported payment method: ${method}`);
634
+ }
635
+ if (!requestB64) {
636
+ throw new Error("Missing request in WWW-Authenticate");
637
+ }
638
+ const requestJson = Buffer.from(requestB64, "base64").toString("utf-8");
639
+ const paymentRequest = JSON.parse(requestJson);
640
+ const { amount, currency, recipient, methodDetails } = paymentRequest;
641
+ const chainId = methodDetails?.chainId || 42431;
642
+ const amountDisplay = Number(amount) / 1e6;
643
+ console.log(`[MoltsPay] Payment: $${amountDisplay} to ${recipient}`);
644
+ this.checkLimits(amountDisplay);
645
+ console.log(`[MoltsPay] Sending transaction on Tempo...`);
646
+ const tempoChain = { ...tempoModerato, feeToken: currency };
647
+ const publicClient = createPublicClient({
648
+ chain: tempoChain,
649
+ transport: http("https://rpc.moderato.tempo.xyz")
650
+ });
651
+ const walletClient = createWalletClient({
652
+ account,
653
+ chain: tempoChain,
654
+ transport: http("https://rpc.moderato.tempo.xyz")
655
+ });
656
+ const txHash = await Actions.token.transfer(walletClient, {
657
+ to: recipient,
658
+ amount: BigInt(amount),
659
+ token: currency
660
+ });
661
+ console.log(`[MoltsPay] Transaction: ${txHash}`);
662
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
663
+ console.log(`[MoltsPay] Confirmed! Retrying with credential...`);
664
+ const credential = {
665
+ challenge: {
666
+ id: challengeId,
667
+ realm,
668
+ method: "tempo",
669
+ intent: "charge",
670
+ request: paymentRequest
671
+ },
672
+ payload: { hash: txHash, type: "hash" },
673
+ source: `did:pkh:eip155:${chainId}:${account.address}`
674
+ };
675
+ const credentialB64 = Buffer.from(JSON.stringify(credential)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
676
+ const paidRes = await fetch(`${serverUrl}/execute`, {
677
+ method: "POST",
678
+ headers: {
679
+ "Content-Type": "application/json",
680
+ "Authorization": `Payment ${credentialB64}`
681
+ },
682
+ body: JSON.stringify({ service, params, chain: "tempo_moderato" })
683
+ });
684
+ const result = await paidRes.json();
685
+ if (!paidRes.ok) {
686
+ throw new Error(result.error || "Payment verification failed");
687
+ }
688
+ this.recordSpending(amountDisplay);
689
+ console.log(`[MoltsPay] Success!`);
690
+ return result.result || result;
691
+ }
692
+ /**
693
+ * Handle BNB Chain payment flow (pre-approval + intent signature)
694
+ *
695
+ * Flow:
696
+ * 1. Check client has approved server wallet (done via `moltspay init`)
697
+ * 2. Sign EIP-712 payment intent (no gas, just signature)
698
+ * 3. Send intent to server
699
+ * 4. Server executes service
700
+ * 5. Server calls transferFrom if successful (pay-for-success)
701
+ */
702
+ async handleBNBPayment(serverUrl, service, params, paymentDetails) {
703
+ const { to, amount, token, chainName, chain, spender } = paymentDetails;
704
+ const tokenConfig = chain.tokens[token];
705
+ const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
706
+ const allowance = await this.checkAllowance(tokenConfig.address, spender, provider);
707
+ const amountWeiCheck = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals));
708
+ if (allowance < amountWeiCheck) {
709
+ const nativeBalance = await provider.getBalance(this.wallet.address);
710
+ const minGasBalance = import_ethers.ethers.parseEther("0.0005");
711
+ if (nativeBalance < minGasBalance) {
712
+ const nativeBNB = parseFloat(import_ethers.ethers.formatEther(nativeBalance)).toFixed(4);
713
+ const isTestnet = chainName === "bnb_testnet";
714
+ if (isTestnet) {
715
+ throw new Error(
716
+ `\u274C Insufficient tBNB for approval transaction
717
+
718
+ Current tBNB: ${nativeBNB}
719
+ Required: ~0.001 tBNB
720
+
721
+ Get testnet tokens: npx moltspay faucet --chain bnb_testnet
722
+ (Gives USDC + tBNB for gas)`
723
+ );
724
+ } else {
725
+ throw new Error(
726
+ `\u274C Insufficient BNB for approval transaction
727
+
728
+ Current BNB: ${nativeBNB}
729
+ Required: ~0.001 BNB (~$0.60)
730
+
731
+ To get BNB:
732
+ \u2022 Withdraw from Binance/exchange to your wallet
733
+ \u2022 Most exchanges include BNB dust with withdrawals
734
+
735
+ After funding, run:
736
+ npx moltspay approve --chain ${chainName} --spender ${spender}`
737
+ );
738
+ }
739
+ }
740
+ throw new Error(
741
+ `Insufficient allowance for ${spender.slice(0, 10)}...
742
+ Run: npx moltspay approve --chain ${chainName} --spender ${spender}`
743
+ );
744
+ }
745
+ const amountWei = BigInt(Math.floor(amount * 10 ** tokenConfig.decimals)).toString();
746
+ const intent = {
747
+ from: this.wallet.address,
748
+ to,
749
+ amount: amountWei,
750
+ token: tokenConfig.address,
751
+ service,
752
+ nonce: Date.now(),
753
+ // Use timestamp as nonce for simplicity
754
+ deadline: Date.now() + 36e5
755
+ // 1 hour
756
+ };
757
+ const domain = {
758
+ name: "MoltsPay",
759
+ version: "1",
760
+ chainId: chain.chainId
761
+ };
762
+ const types = {
763
+ PaymentIntent: [
764
+ { name: "from", type: "address" },
765
+ { name: "to", type: "address" },
766
+ { name: "amount", type: "uint256" },
767
+ { name: "token", type: "address" },
768
+ { name: "service", type: "string" },
769
+ { name: "nonce", type: "uint256" },
770
+ { name: "deadline", type: "uint256" }
771
+ ]
772
+ };
773
+ console.log(`[MoltsPay] Signing BNB payment intent...`);
774
+ const signature = await this.wallet.signTypedData(domain, types, intent);
775
+ const network = `eip155:${chain.chainId}`;
776
+ const payload = {
777
+ x402Version: 2,
778
+ scheme: "exact",
779
+ network,
780
+ payload: {
781
+ intent: {
782
+ ...intent,
783
+ signature
784
+ },
785
+ chainId: chain.chainId
786
+ },
787
+ accepted: {
788
+ scheme: "exact",
789
+ network,
790
+ asset: tokenConfig.address,
791
+ amount: amountWei,
792
+ payTo: to,
793
+ maxTimeoutSeconds: 300
794
+ }
795
+ };
796
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
797
+ console.log(`[MoltsPay] Sending BNB payment request...`);
798
+ const paidRes = await fetch(`${serverUrl}/execute`, {
799
+ method: "POST",
800
+ headers: {
801
+ "Content-Type": "application/json",
802
+ "X-Payment": paymentHeader
803
+ },
804
+ body: JSON.stringify({ service, params, chain: chainName })
805
+ });
806
+ const result = await paidRes.json();
807
+ if (!paidRes.ok) {
808
+ throw new Error(result.error || "BNB payment failed");
809
+ }
810
+ this.recordSpending(amount);
811
+ console.log(`[MoltsPay] Success! BNB payment settled.`);
812
+ return result.result || result;
813
+ }
814
+ /**
815
+ * Handle Solana payment flow
816
+ *
817
+ * Solana uses SPL token transfers with pay-for-success model:
818
+ * 1. Client creates and signs a transfer transaction
819
+ * 2. Server submits the transaction after service completes
820
+ */
821
+ async handleSolanaPayment(serverUrl, service, params, requirements, chain) {
822
+ const solanaWallet = loadSolanaWallet(this.configDir);
823
+ if (!solanaWallet) {
824
+ throw new Error("No Solana wallet found. Run: npx moltspay init --chain solana_devnet");
825
+ }
826
+ const amount = Number(requirements.amount);
827
+ const amountUSDC = amount / 1e6;
828
+ this.checkLimits(amountUSDC);
829
+ console.log(`[MoltsPay] Creating Solana payment: $${amountUSDC} USDC`);
830
+ if (!requirements.payTo) {
831
+ throw new Error("Missing payTo address in payment requirements");
832
+ }
833
+ const solanaFeePayer = requirements.extra?.solanaFeePayer;
834
+ const feePayerPubkey = solanaFeePayer ? new import_web34.PublicKey(solanaFeePayer) : void 0;
835
+ if (feePayerPubkey) {
836
+ console.log(`[MoltsPay] Gasless mode: server pays fees`);
837
+ }
838
+ const recipientPubkey = new import_web34.PublicKey(requirements.payTo);
839
+ const transaction = await createSolanaPaymentTransaction(
840
+ solanaWallet.publicKey,
841
+ recipientPubkey,
842
+ BigInt(amount),
843
+ chain,
844
+ feePayerPubkey
845
+ // Optional fee payer for gasless mode
846
+ );
847
+ if (feePayerPubkey) {
848
+ transaction.partialSign(solanaWallet);
849
+ } else {
850
+ transaction.sign(solanaWallet);
851
+ }
852
+ const signedTx = transaction.serialize({ requireAllSignatures: false }).toString("base64");
853
+ console.log(`[MoltsPay] Transaction signed, sending to server...`);
854
+ const network = chain === "solana" ? "solana:mainnet" : "solana:devnet";
855
+ const payload = {
856
+ x402Version: 2,
857
+ scheme: "exact",
858
+ network,
859
+ payload: {
860
+ signedTransaction: signedTx,
861
+ sender: solanaWallet.publicKey.toBase58(),
862
+ chain
863
+ },
864
+ accepted: {
865
+ scheme: "exact",
866
+ network,
867
+ asset: requirements.asset,
868
+ amount: requirements.amount,
869
+ payTo: requirements.payTo,
870
+ maxTimeoutSeconds: 300
871
+ }
872
+ };
873
+ const paymentHeader = Buffer.from(JSON.stringify(payload)).toString("base64");
874
+ const paidRes = await fetch(`${serverUrl}/execute`, {
875
+ method: "POST",
876
+ headers: {
877
+ "Content-Type": "application/json",
878
+ "X-Payment": paymentHeader
879
+ },
880
+ body: JSON.stringify({ service, params, chain })
881
+ });
882
+ const result = await paidRes.json();
883
+ if (!paidRes.ok) {
884
+ throw new Error(result.error || "Solana payment failed");
885
+ }
886
+ this.recordSpending(amountUSDC);
887
+ console.log(`[MoltsPay] Success! Solana payment settled.`);
888
+ if (result.payment?.transaction) {
889
+ const explorerUrl = chain === "solana" ? `https://solscan.io/tx/${result.payment.transaction}` : `https://solscan.io/tx/${result.payment.transaction}?cluster=devnet`;
890
+ console.log(`[MoltsPay] Transaction: ${explorerUrl}`);
891
+ }
892
+ return result.result || result;
893
+ }
894
+ /**
895
+ * Check ERC20 allowance for a spender
896
+ */
897
+ async checkAllowance(tokenAddress, spender, provider) {
898
+ const contract = new import_ethers.ethers.Contract(
899
+ tokenAddress,
900
+ ["function allowance(address owner, address spender) view returns (uint256)"],
901
+ provider
902
+ );
903
+ return await contract.allowance(this.wallet.address, spender);
904
+ }
358
905
  /**
359
906
  * Sign EIP-3009 transferWithAuthorization (GASLESS)
360
907
  * This only signs - no on-chain transaction, no gas needed.
@@ -425,26 +972,26 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
425
972
  }
426
973
  // --- Config & Wallet Management ---
427
974
  loadConfig() {
428
- const configPath = (0, import_path.join)(this.configDir, "config.json");
429
- if ((0, import_fs.existsSync)(configPath)) {
430
- const content = (0, import_fs.readFileSync)(configPath, "utf-8");
975
+ const configPath = (0, import_path2.join)(this.configDir, "config.json");
976
+ if ((0, import_fs2.existsSync)(configPath)) {
977
+ const content = (0, import_fs2.readFileSync)(configPath, "utf-8");
431
978
  return { ...DEFAULT_CONFIG, ...JSON.parse(content) };
432
979
  }
433
980
  return { ...DEFAULT_CONFIG };
434
981
  }
435
982
  saveConfig() {
436
- (0, import_fs.mkdirSync)(this.configDir, { recursive: true });
437
- const configPath = (0, import_path.join)(this.configDir, "config.json");
438
- (0, import_fs.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
983
+ (0, import_fs2.mkdirSync)(this.configDir, { recursive: true });
984
+ const configPath = (0, import_path2.join)(this.configDir, "config.json");
985
+ (0, import_fs2.writeFileSync)(configPath, JSON.stringify(this.config, null, 2));
439
986
  }
440
987
  /**
441
988
  * Load spending data from disk
442
989
  */
443
990
  loadSpending() {
444
- const spendingPath = (0, import_path.join)(this.configDir, "spending.json");
445
- if ((0, import_fs.existsSync)(spendingPath)) {
991
+ const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
992
+ if ((0, import_fs2.existsSync)(spendingPath)) {
446
993
  try {
447
- const data = JSON.parse((0, import_fs.readFileSync)(spendingPath, "utf-8"));
994
+ const data = JSON.parse((0, import_fs2.readFileSync)(spendingPath, "utf-8"));
448
995
  const today = (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0);
449
996
  if (data.date && data.date === today) {
450
997
  this.todaySpending = data.amount || 0;
@@ -463,29 +1010,29 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
463
1010
  * Save spending data to disk
464
1011
  */
465
1012
  saveSpending() {
466
- (0, import_fs.mkdirSync)(this.configDir, { recursive: true });
467
- const spendingPath = (0, import_path.join)(this.configDir, "spending.json");
1013
+ (0, import_fs2.mkdirSync)(this.configDir, { recursive: true });
1014
+ const spendingPath = (0, import_path2.join)(this.configDir, "spending.json");
468
1015
  const data = {
469
1016
  date: this.lastSpendingReset || (/* @__PURE__ */ new Date()).setHours(0, 0, 0, 0),
470
1017
  amount: this.todaySpending,
471
1018
  updatedAt: Date.now()
472
1019
  };
473
- (0, import_fs.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
1020
+ (0, import_fs2.writeFileSync)(spendingPath, JSON.stringify(data, null, 2));
474
1021
  }
475
1022
  loadWallet() {
476
- const walletPath = (0, import_path.join)(this.configDir, "wallet.json");
477
- if ((0, import_fs.existsSync)(walletPath)) {
1023
+ const walletPath = (0, import_path2.join)(this.configDir, "wallet.json");
1024
+ if ((0, import_fs2.existsSync)(walletPath)) {
478
1025
  try {
479
- const stats = (0, import_fs.statSync)(walletPath);
1026
+ const stats = (0, import_fs2.statSync)(walletPath);
480
1027
  const mode = stats.mode & 511;
481
1028
  if (mode !== 384) {
482
1029
  console.warn(`[MoltsPay] WARNING: wallet.json has insecure permissions (${mode.toString(8)})`);
483
1030
  console.warn(`[MoltsPay] Fixing permissions to 0600...`);
484
- (0, import_fs.chmodSync)(walletPath, 384);
1031
+ (0, import_fs2.chmodSync)(walletPath, 384);
485
1032
  }
486
1033
  } catch (err) {
487
1034
  }
488
- const content = (0, import_fs.readFileSync)(walletPath, "utf-8");
1035
+ const content = (0, import_fs2.readFileSync)(walletPath, "utf-8");
489
1036
  return JSON.parse(content);
490
1037
  }
491
1038
  return null;
@@ -494,15 +1041,15 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
494
1041
  * Initialize a new wallet (called by CLI)
495
1042
  */
496
1043
  static init(configDir, options) {
497
- (0, import_fs.mkdirSync)(configDir, { recursive: true });
1044
+ (0, import_fs2.mkdirSync)(configDir, { recursive: true });
498
1045
  const wallet = import_ethers.Wallet.createRandom();
499
1046
  const walletData = {
500
1047
  address: wallet.address,
501
1048
  privateKey: wallet.privateKey,
502
1049
  createdAt: Date.now()
503
1050
  };
504
- const walletPath = (0, import_path.join)(configDir, "wallet.json");
505
- (0, import_fs.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
1051
+ const walletPath = (0, import_path2.join)(configDir, "wallet.json");
1052
+ (0, import_fs2.writeFileSync)(walletPath, JSON.stringify(walletData, null, 2), { mode: 384 });
506
1053
  const config = {
507
1054
  chain: options.chain,
508
1055
  limits: {
@@ -510,8 +1057,8 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
510
1057
  maxPerDay: options.maxPerDay
511
1058
  }
512
1059
  };
513
- const configPath = (0, import_path.join)(configDir, "config.json");
514
- (0, import_fs.writeFileSync)(configPath, JSON.stringify(config, null, 2));
1060
+ const configPath = (0, import_path2.join)(configDir, "config.json");
1061
+ (0, import_fs2.writeFileSync)(configPath, JSON.stringify(config, null, 2));
515
1062
  return { address: wallet.address, configDir };
516
1063
  }
517
1064
  /**
@@ -541,30 +1088,59 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
541
1088
  };
542
1089
  }
543
1090
  /**
544
- * Get wallet balances on all supported chains (Base + Polygon)
1091
+ * Get wallet balances on all supported chains (Base + Polygon + Tempo)
545
1092
  */
546
1093
  async getAllBalances() {
547
1094
  if (!this.wallet) {
548
1095
  throw new Error("Client not initialized");
549
1096
  }
550
- const supportedChains = ["base", "polygon", "base_sepolia"];
1097
+ const supportedChains = ["base", "polygon", "base_sepolia", "tempo_moderato", "bnb", "bnb_testnet"];
551
1098
  const tokenAbi = ["function balanceOf(address) view returns (uint256)"];
552
1099
  const results = {};
1100
+ const tempoTokens = {
1101
+ pathUSD: "0x20c0000000000000000000000000000000000000",
1102
+ alphaUSD: "0x20c0000000000000000000000000000000000001",
1103
+ betaUSD: "0x20c0000000000000000000000000000000000002",
1104
+ thetaUSD: "0x20c0000000000000000000000000000000000003"
1105
+ };
553
1106
  await Promise.all(
554
1107
  supportedChains.map(async (chainName) => {
555
1108
  try {
556
1109
  const chain = getChain(chainName);
557
1110
  const provider = new import_ethers.ethers.JsonRpcProvider(chain.rpc);
558
- const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
559
- provider.getBalance(this.wallet.address),
560
- new import_ethers.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
561
- new import_ethers.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
562
- ]);
563
- results[chainName] = {
564
- usdc: parseFloat(import_ethers.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
565
- usdt: parseFloat(import_ethers.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
566
- native: parseFloat(import_ethers.ethers.formatEther(nativeBalance))
567
- };
1111
+ if (chainName === "tempo_moderato") {
1112
+ const [nativeBalance, pathUSD, alphaUSD, betaUSD, thetaUSD] = await Promise.all([
1113
+ provider.getBalance(this.wallet.address),
1114
+ new import_ethers.ethers.Contract(tempoTokens.pathUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1115
+ new import_ethers.ethers.Contract(tempoTokens.alphaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1116
+ new import_ethers.ethers.Contract(tempoTokens.betaUSD, tokenAbi, provider).balanceOf(this.wallet.address),
1117
+ new import_ethers.ethers.Contract(tempoTokens.thetaUSD, tokenAbi, provider).balanceOf(this.wallet.address)
1118
+ ]);
1119
+ results[chainName] = {
1120
+ usdc: parseFloat(import_ethers.ethers.formatUnits(pathUSD, 6)),
1121
+ // pathUSD as default USDC
1122
+ usdt: parseFloat(import_ethers.ethers.formatUnits(alphaUSD, 6)),
1123
+ // alphaUSD as default USDT
1124
+ native: parseFloat(import_ethers.ethers.formatEther(nativeBalance)),
1125
+ tempo: {
1126
+ pathUSD: parseFloat(import_ethers.ethers.formatUnits(pathUSD, 6)),
1127
+ alphaUSD: parseFloat(import_ethers.ethers.formatUnits(alphaUSD, 6)),
1128
+ betaUSD: parseFloat(import_ethers.ethers.formatUnits(betaUSD, 6)),
1129
+ thetaUSD: parseFloat(import_ethers.ethers.formatUnits(thetaUSD, 6))
1130
+ }
1131
+ };
1132
+ } else {
1133
+ const [nativeBalance, usdcBalance, usdtBalance] = await Promise.all([
1134
+ provider.getBalance(this.wallet.address),
1135
+ new import_ethers.ethers.Contract(chain.tokens.USDC.address, tokenAbi, provider).balanceOf(this.wallet.address),
1136
+ new import_ethers.ethers.Contract(chain.tokens.USDT.address, tokenAbi, provider).balanceOf(this.wallet.address)
1137
+ ]);
1138
+ results[chainName] = {
1139
+ usdc: parseFloat(import_ethers.ethers.formatUnits(usdcBalance, chain.tokens.USDC.decimals)),
1140
+ usdt: parseFloat(import_ethers.ethers.formatUnits(usdtBalance, chain.tokens.USDT.decimals)),
1141
+ native: parseFloat(import_ethers.ethers.formatEther(nativeBalance))
1142
+ };
1143
+ }
568
1144
  } catch (err) {
569
1145
  results[chainName] = { usdc: 0, usdt: 0, native: 0 };
570
1146
  }
@@ -572,6 +1148,121 @@ Please specify: --chain base, --chain polygon, or --chain base_sepolia`
572
1148
  );
573
1149
  return results;
574
1150
  }
1151
+ /**
1152
+ * Pay for a service using MPP (Machine Payments Protocol)
1153
+ *
1154
+ * This implements the MPP flow manually for EOA wallets:
1155
+ * 1. Request service → get 402 with WWW-Authenticate
1156
+ * 2. Parse payment requirements
1157
+ * 3. Execute transfer on Tempo chain
1158
+ * 4. Retry with transaction hash as credential
1159
+ *
1160
+ * @param url - Full URL of the MPP-enabled endpoint
1161
+ * @param options - Request options (body, headers)
1162
+ * @returns Response from the service
1163
+ */
1164
+ async payWithMPP(url, options = {}) {
1165
+ if (!this.wallet || !this.walletData) {
1166
+ throw new Error("Client not initialized. Run: npx moltspay init");
1167
+ }
1168
+ const { privateKeyToAccount } = await import("viem/accounts");
1169
+ const { createWalletClient, createPublicClient, http } = await import("viem");
1170
+ const { tempoModerato } = await import("viem/chains");
1171
+ const { Actions } = await import("viem/tempo");
1172
+ const privateKey = this.walletData.privateKey;
1173
+ const account = privateKeyToAccount(privateKey);
1174
+ console.log(`[MoltsPay] Making MPP request to: ${url}`);
1175
+ console.log(`[MoltsPay] Using account: ${account.address}`);
1176
+ const initResponse = await fetch(url, {
1177
+ method: "POST",
1178
+ headers: {
1179
+ "Content-Type": "application/json",
1180
+ ...options.headers
1181
+ },
1182
+ body: options.body ? JSON.stringify(options.body) : void 0
1183
+ });
1184
+ if (initResponse.status !== 402) {
1185
+ if (initResponse.ok) {
1186
+ return initResponse.json();
1187
+ }
1188
+ const errorText = await initResponse.text();
1189
+ throw new Error(`Request failed (${initResponse.status}): ${errorText}`);
1190
+ }
1191
+ const wwwAuth = initResponse.headers.get("www-authenticate");
1192
+ if (!wwwAuth || !wwwAuth.toLowerCase().includes("payment")) {
1193
+ throw new Error("No WWW-Authenticate Payment challenge in 402 response");
1194
+ }
1195
+ console.log(`[MoltsPay] Got 402, parsing payment challenge...`);
1196
+ const parseAuthParam = (header, key) => {
1197
+ const match = header.match(new RegExp(`${key}="([^"]+)"`, "i"));
1198
+ return match ? match[1] : null;
1199
+ };
1200
+ const challengeId = parseAuthParam(wwwAuth, "id");
1201
+ const method = parseAuthParam(wwwAuth, "method");
1202
+ const realm = parseAuthParam(wwwAuth, "realm");
1203
+ const requestB64 = parseAuthParam(wwwAuth, "request");
1204
+ if (method !== "tempo") {
1205
+ throw new Error(`Unsupported payment method: ${method}`);
1206
+ }
1207
+ if (!requestB64) {
1208
+ throw new Error("Missing request in WWW-Authenticate");
1209
+ }
1210
+ const requestJson = Buffer.from(requestB64, "base64").toString("utf-8");
1211
+ const paymentRequest = JSON.parse(requestJson);
1212
+ console.log(`[MoltsPay] Payment request:`, paymentRequest);
1213
+ const { amount, currency, recipient, methodDetails } = paymentRequest;
1214
+ const chainId = methodDetails?.chainId || 42431;
1215
+ console.log(`[MoltsPay] Executing transfer on Tempo (chainId: ${chainId})...`);
1216
+ console.log(`[MoltsPay] Amount: ${amount}, To: ${recipient}`);
1217
+ const tempoChain = { ...tempoModerato, feeToken: currency };
1218
+ const publicClient = createPublicClient({
1219
+ chain: tempoChain,
1220
+ transport: http("https://rpc.moderato.tempo.xyz")
1221
+ });
1222
+ const walletClient = createWalletClient({
1223
+ account,
1224
+ chain: tempoChain,
1225
+ transport: http("https://rpc.moderato.tempo.xyz")
1226
+ });
1227
+ const txHash = await Actions.token.transfer(walletClient, {
1228
+ to: recipient,
1229
+ amount: BigInt(amount),
1230
+ token: currency
1231
+ });
1232
+ console.log(`[MoltsPay] Transaction sent: ${txHash}`);
1233
+ console.log(`[MoltsPay] Waiting for confirmation...`);
1234
+ await publicClient.waitForTransactionReceipt({ hash: txHash });
1235
+ console.log(`[MoltsPay] Transaction confirmed!`);
1236
+ const challenge = {
1237
+ id: challengeId,
1238
+ realm,
1239
+ method: "tempo",
1240
+ intent: "charge",
1241
+ request: paymentRequest
1242
+ };
1243
+ const credential = {
1244
+ challenge,
1245
+ payload: { hash: txHash, type: "hash" },
1246
+ source: `did:pkh:eip155:${chainId}:${account.address}`
1247
+ };
1248
+ const credentialB64 = Buffer.from(JSON.stringify(credential)).toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1249
+ console.log(`[MoltsPay] Retrying with payment credential...`);
1250
+ const paidResponse = await fetch(url, {
1251
+ method: "POST",
1252
+ headers: {
1253
+ "Content-Type": "application/json",
1254
+ "Authorization": `Payment ${credentialB64}`,
1255
+ ...options.headers
1256
+ },
1257
+ body: options.body ? JSON.stringify(options.body) : void 0
1258
+ });
1259
+ if (!paidResponse.ok) {
1260
+ const errorText = await paidResponse.text();
1261
+ throw new Error(`Payment verification failed (${paidResponse.status}): ${errorText}`);
1262
+ }
1263
+ console.log(`[MoltsPay] Payment verified! Service completed.`);
1264
+ return paidResponse.json();
1265
+ }
575
1266
  };
576
1267
  // Annotate the CommonJS export names for ESM import in node:
577
1268
  0 && (module.exports = {