moltspay 0.2.3 → 0.2.4

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/dist/index.mjs CHANGED
@@ -1211,8 +1211,284 @@ var PermitSigner = class {
1211
1211
  }
1212
1212
  };
1213
1213
 
1214
- // src/permit/Permit.ts
1214
+ // src/wallet/AllowanceWallet.ts
1215
1215
  import { ethers as ethers6 } from "ethers";
1216
+ var PERMIT_ABI2 = [
1217
+ ...ERC20_ABI,
1218
+ "function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
1219
+ "function transferFrom(address from, address to, uint256 amount) returns (bool)",
1220
+ "function allowance(address owner, address spender) view returns (uint256)",
1221
+ "function nonces(address owner) view returns (uint256)"
1222
+ ];
1223
+ var AllowanceWallet = class {
1224
+ chain;
1225
+ chainConfig;
1226
+ address;
1227
+ // Agent's address
1228
+ wallet;
1229
+ provider;
1230
+ usdcContract;
1231
+ /** Stored permits from owners */
1232
+ permits = /* @__PURE__ */ new Map();
1233
+ constructor(config) {
1234
+ this.chain = config.chain || "base_sepolia";
1235
+ this.chainConfig = getChain(this.chain);
1236
+ const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
1237
+ this.provider = new ethers6.JsonRpcProvider(rpcUrl);
1238
+ this.wallet = new ethers6.Wallet(config.privateKey, this.provider);
1239
+ this.address = this.wallet.address;
1240
+ this.usdcContract = new ethers6.Contract(
1241
+ this.chainConfig.usdc,
1242
+ PERMIT_ABI2,
1243
+ this.wallet
1244
+ );
1245
+ }
1246
+ /**
1247
+ * Store a Permit received from Owner
1248
+ * Call this when Owner sends you a signed Permit
1249
+ */
1250
+ storePermit(permit) {
1251
+ const ownerLower = permit.owner.toLowerCase();
1252
+ this.permits.set(ownerLower, permit);
1253
+ }
1254
+ /**
1255
+ * Get stored Permit for an owner
1256
+ */
1257
+ getPermit(owner) {
1258
+ return this.permits.get(owner.toLowerCase());
1259
+ }
1260
+ /**
1261
+ * Check allowance status with an owner
1262
+ */
1263
+ async checkAllowance(owner) {
1264
+ const ownerAddress = ethers6.getAddress(owner);
1265
+ const [allowance, ownerBalance, agentGasBalance] = await Promise.all([
1266
+ this.usdcContract.allowance(ownerAddress, this.address),
1267
+ this.usdcContract.balanceOf(ownerAddress),
1268
+ this.provider.getBalance(this.address)
1269
+ ]);
1270
+ const allowanceNum = Number(allowance) / 1e6;
1271
+ const hasGas = Number(ethers6.formatEther(agentGasBalance)) >= 1e-4;
1272
+ return {
1273
+ owner: ownerAddress,
1274
+ agent: this.address,
1275
+ allowance: allowanceNum.toFixed(2),
1276
+ ownerBalance: (Number(ownerBalance) / 1e6).toFixed(2),
1277
+ agentGasBalance: ethers6.formatEther(agentGasBalance),
1278
+ canSpend: allowanceNum > 0 && hasGas,
1279
+ chain: this.chainConfig.name
1280
+ };
1281
+ }
1282
+ /**
1283
+ * Spend from Owner's wallet using Permit allowance
1284
+ *
1285
+ * @example
1286
+ * ```typescript
1287
+ * const agent = new AllowanceWallet({
1288
+ * chain: 'base',
1289
+ * privateKey: process.env.AGENT_KEY // Only needs gas
1290
+ * });
1291
+ *
1292
+ * // Owner gave us a Permit
1293
+ * agent.storePermit(ownerPermit);
1294
+ *
1295
+ * // Spend to pay for a service
1296
+ * const result = await agent.spend({
1297
+ * to: '0xServiceProvider...',
1298
+ * amount: 2.99,
1299
+ * });
1300
+ * ```
1301
+ */
1302
+ async spend(params) {
1303
+ const { to, amount, permit } = params;
1304
+ try {
1305
+ const toAddress = ethers6.getAddress(to);
1306
+ const amountWei = BigInt(Math.floor(amount * 1e6));
1307
+ let ownerPermit = permit;
1308
+ let ownerAddress;
1309
+ if (ownerPermit) {
1310
+ ownerAddress = ethers6.getAddress(ownerPermit.owner);
1311
+ this.storePermit(ownerPermit);
1312
+ } else {
1313
+ for (const [owner, p] of this.permits) {
1314
+ const allowance = await this.usdcContract.allowance(owner, this.address);
1315
+ if (BigInt(allowance) >= amountWei) {
1316
+ ownerAddress = ethers6.getAddress(owner);
1317
+ ownerPermit = p;
1318
+ break;
1319
+ }
1320
+ }
1321
+ if (!ownerPermit) {
1322
+ return {
1323
+ success: false,
1324
+ error: "No valid permit found. Ask Owner to sign a Permit first.",
1325
+ from: "",
1326
+ to: toAddress,
1327
+ amount
1328
+ };
1329
+ }
1330
+ }
1331
+ const currentAllowance = await this.usdcContract.allowance(ownerAddress, this.address);
1332
+ if (BigInt(currentAllowance) < amountWei) {
1333
+ const now = Math.floor(Date.now() / 1e3);
1334
+ if (ownerPermit.deadline < now) {
1335
+ return {
1336
+ success: false,
1337
+ error: `Permit expired at ${new Date(ownerPermit.deadline * 1e3).toISOString()}. Ask Owner for a new Permit.`,
1338
+ from: ownerAddress,
1339
+ to: toAddress,
1340
+ amount
1341
+ };
1342
+ }
1343
+ const currentNonce = await this.usdcContract.nonces(ownerAddress);
1344
+ if (Number(currentNonce) !== ownerPermit.nonce) {
1345
+ return {
1346
+ success: false,
1347
+ error: `Permit nonce mismatch (expected ${ownerPermit.nonce}, got ${currentNonce}). Owner may have used this permit or signed a new one.`,
1348
+ from: ownerAddress,
1349
+ to: toAddress,
1350
+ amount
1351
+ };
1352
+ }
1353
+ console.log("[AllowanceWallet] Submitting permit on-chain...");
1354
+ const permitTx = await this.usdcContract.permit(
1355
+ ownerAddress,
1356
+ this.address,
1357
+ ownerPermit.value,
1358
+ ownerPermit.deadline,
1359
+ ownerPermit.v,
1360
+ ownerPermit.r,
1361
+ ownerPermit.s
1362
+ );
1363
+ await permitTx.wait();
1364
+ console.log("[AllowanceWallet] Permit submitted:", permitTx.hash);
1365
+ }
1366
+ console.log("[AllowanceWallet] Executing transferFrom...");
1367
+ const tx = await this.usdcContract.transferFrom(
1368
+ ownerAddress,
1369
+ toAddress,
1370
+ amountWei
1371
+ );
1372
+ const receipt = await tx.wait();
1373
+ const newAllowance = await this.usdcContract.allowance(ownerAddress, this.address);
1374
+ return {
1375
+ success: true,
1376
+ txHash: tx.hash,
1377
+ from: ownerAddress,
1378
+ to: toAddress,
1379
+ amount,
1380
+ remainingAllowance: (Number(newAllowance) / 1e6).toFixed(2),
1381
+ explorerUrl: `${this.chainConfig.explorerTx}${tx.hash}`
1382
+ };
1383
+ } catch (error) {
1384
+ const message = error.message;
1385
+ if (message.includes("ERC20InsufficientAllowance")) {
1386
+ return {
1387
+ success: false,
1388
+ error: "Insufficient allowance. Ask Owner to sign a new Permit with higher amount.",
1389
+ from: "",
1390
+ to,
1391
+ amount
1392
+ };
1393
+ }
1394
+ if (message.includes("ERC20InsufficientBalance")) {
1395
+ return {
1396
+ success: false,
1397
+ error: "Owner's wallet has insufficient USDC balance.",
1398
+ from: "",
1399
+ to,
1400
+ amount
1401
+ };
1402
+ }
1403
+ return {
1404
+ success: false,
1405
+ error: message,
1406
+ from: "",
1407
+ to,
1408
+ amount
1409
+ };
1410
+ }
1411
+ }
1412
+ /**
1413
+ * Get Agent's gas balance (ETH)
1414
+ */
1415
+ async getGasBalance() {
1416
+ const balance = await this.provider.getBalance(this.address);
1417
+ return ethers6.formatEther(balance);
1418
+ }
1419
+ };
1420
+ function generatePermitInstructions(params) {
1421
+ const { ownerAddress, agentAddress, amount, deadlineHours = 24, chain = "base" } = params;
1422
+ const chainConfig = getChain(chain);
1423
+ const deadline = Math.floor(Date.now() / 1e3) + deadlineHours * 3600;
1424
+ const value = BigInt(Math.floor(amount * 1e6)).toString();
1425
+ const eip712Domain = {
1426
+ name: "USD Coin",
1427
+ version: "2",
1428
+ chainId: chainConfig.chainId,
1429
+ verifyingContract: chainConfig.usdc
1430
+ };
1431
+ const typedData = {
1432
+ types: {
1433
+ EIP712Domain: [
1434
+ { name: "name", type: "string" },
1435
+ { name: "version", type: "string" },
1436
+ { name: "chainId", type: "uint256" },
1437
+ { name: "verifyingContract", type: "address" }
1438
+ ],
1439
+ Permit: [
1440
+ { name: "owner", type: "address" },
1441
+ { name: "spender", type: "address" },
1442
+ { name: "value", type: "uint256" },
1443
+ { name: "nonce", type: "uint256" },
1444
+ { name: "deadline", type: "uint256" }
1445
+ ]
1446
+ },
1447
+ primaryType: "Permit",
1448
+ domain: eip712Domain,
1449
+ message: {
1450
+ owner: ownerAddress,
1451
+ spender: agentAddress,
1452
+ value,
1453
+ nonce: "<GET_FROM_USDC_CONTRACT>",
1454
+ // Owner needs to query this
1455
+ deadline
1456
+ }
1457
+ };
1458
+ const instructions = `
1459
+ \u{1F510} **Grant USDC Spending Allowance to Your Agent**
1460
+
1461
+ Your Agent (${agentAddress}) is requesting permission to spend up to ${amount} USDC from your wallet.
1462
+
1463
+ **What this does:**
1464
+ - Allows your Agent to pay for services on your behalf
1465
+ - Your USDC stays in YOUR wallet until spent
1466
+ - Agent can only spend up to the authorized amount
1467
+ - Expires in ${deadlineHours} hours
1468
+ - You can revoke anytime by moving your USDC
1469
+
1470
+ **How to sign (MetaMask / any web3 wallet):**
1471
+
1472
+ 1. Go to https://etherscan.io/address/${chainConfig.usdc}#readContract
1473
+ 2. Query \`nonces(${ownerAddress})\` to get your current nonce
1474
+ 3. Use eth_signTypedData_v4 with the data below (replace nonce)
1475
+ 4. Send the signature {v, r, s, deadline, nonce} to your Agent
1476
+
1477
+ **Chain:** ${chainConfig.name}
1478
+ **USDC Contract:** ${chainConfig.usdc}
1479
+
1480
+ **EIP-712 Typed Data:**
1481
+ \`\`\`json
1482
+ ${JSON.stringify(typedData, null, 2)}
1483
+ \`\`\`
1484
+
1485
+ \u26A0\uFE0F Never share your private key. This signature only authorizes spending, not wallet access.
1486
+ `;
1487
+ return { instructions, typedData, eip712Domain };
1488
+ }
1489
+
1490
+ // src/permit/Permit.ts
1491
+ import { ethers as ethers7 } from "ethers";
1216
1492
  var PermitPayment = class {
1217
1493
  chain;
1218
1494
  chainConfig;
@@ -1225,13 +1501,13 @@ var PermitPayment = class {
1225
1501
  this.chainConfig = getChain(this.chain);
1226
1502
  this.spenderAddress = config.spenderAddress || process.env.PAYMENT_AGENT_WALLET || "";
1227
1503
  const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
1228
- this.provider = new ethers6.JsonRpcProvider(rpcUrl);
1504
+ this.provider = new ethers7.JsonRpcProvider(rpcUrl);
1229
1505
  const privateKey = config.privateKey || process.env.PAYMENT_AGENT_PRIVATE_KEY;
1230
1506
  if (privateKey) {
1231
- this.wallet = new ethers6.Wallet(privateKey, this.provider);
1507
+ this.wallet = new ethers7.Wallet(privateKey, this.provider);
1232
1508
  this.spenderAddress = this.wallet.address;
1233
1509
  }
1234
- this.usdcContract = new ethers6.Contract(
1510
+ this.usdcContract = new ethers7.Contract(
1235
1511
  this.chainConfig.usdc,
1236
1512
  ERC20_ABI,
1237
1513
  this.wallet || this.provider
@@ -1514,8 +1790,8 @@ var OrderManager = class {
1514
1790
  };
1515
1791
 
1516
1792
  // src/verify/index.ts
1517
- import { ethers as ethers7 } from "ethers";
1518
- var TRANSFER_EVENT_TOPIC = ethers7.id("Transfer(address,address,uint256)");
1793
+ import { ethers as ethers8 } from "ethers";
1794
+ var TRANSFER_EVENT_TOPIC = ethers8.id("Transfer(address,address,uint256)");
1519
1795
  async function verifyPayment(params) {
1520
1796
  const { txHash, expectedAmount, expectedTo } = params;
1521
1797
  let chain;
@@ -1532,7 +1808,7 @@ async function verifyPayment(params) {
1532
1808
  return { verified: false, error: `Unsupported chain: ${params.chain}` };
1533
1809
  }
1534
1810
  try {
1535
- const provider = new ethers7.JsonRpcProvider(chain.rpc);
1811
+ const provider = new ethers8.JsonRpcProvider(chain.rpc);
1536
1812
  const receipt = await provider.getTransactionReceipt(txHash);
1537
1813
  if (!receipt) {
1538
1814
  return { verified: false, error: "Transaction not found or not confirmed" };
@@ -1592,7 +1868,7 @@ async function getTransactionStatus(txHash, chain = "base") {
1592
1868
  return { status: "not_found" };
1593
1869
  }
1594
1870
  try {
1595
- const provider = new ethers7.JsonRpcProvider(chainConfig.rpc);
1871
+ const provider = new ethers8.JsonRpcProvider(chainConfig.rpc);
1596
1872
  const receipt = await provider.getTransactionReceipt(txHash);
1597
1873
  if (!receipt) {
1598
1874
  const tx = await provider.getTransaction(txHash);
@@ -1629,7 +1905,7 @@ async function waitForTransaction(txHash, chain = "base", confirmations = 1, tim
1629
1905
  } catch (e) {
1630
1906
  return { verified: false, confirmed: false, error: `Unsupported chain: ${chain}` };
1631
1907
  }
1632
- const provider = new ethers7.JsonRpcProvider(chainConfig.rpc);
1908
+ const provider = new ethers8.JsonRpcProvider(chainConfig.rpc);
1633
1909
  try {
1634
1910
  const receipt = await provider.waitForTransaction(txHash, confirmations, timeoutMs);
1635
1911
  if (!receipt) {
@@ -2316,6 +2592,7 @@ function parseStatusMarker(message) {
2316
2592
  return { type: "unknown", data: { raw: content } };
2317
2593
  }
2318
2594
  export {
2595
+ AllowanceWallet,
2319
2596
  AuditLog,
2320
2597
  BuyerTemplates,
2321
2598
  CHAINS,
@@ -2338,6 +2615,7 @@ export {
2338
2615
  formatReceiptText,
2339
2616
  generatePaymentGuide,
2340
2617
  generatePaymentReminder,
2618
+ generatePermitInstructions,
2341
2619
  generateReceipt,
2342
2620
  generateReceiptFromInvoice,
2343
2621
  generateWalletGuide,