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/cli.js +13 -3
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +13 -3
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +289 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +287 -9
- package/dist/index.mjs.map +1 -1
- package/dist/wallet/index.d.mts +130 -1
- package/dist/wallet/index.d.ts +130 -1
- package/dist/wallet/index.js +280 -0
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +278 -0
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1211,8 +1211,284 @@ var PermitSigner = class {
|
|
|
1211
1211
|
}
|
|
1212
1212
|
};
|
|
1213
1213
|
|
|
1214
|
-
// src/
|
|
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
|
|
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
|
|
1507
|
+
this.wallet = new ethers7.Wallet(privateKey, this.provider);
|
|
1232
1508
|
this.spenderAddress = this.wallet.address;
|
|
1233
1509
|
}
|
|
1234
|
-
this.usdcContract = new
|
|
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
|
|
1518
|
-
var TRANSFER_EVENT_TOPIC =
|
|
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
|
|
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
|
|
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
|
|
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,
|