moltspay 0.2.3 → 0.2.5
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 +581 -91
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +585 -93
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +112 -2
- package/dist/index.d.ts +112 -2
- package/dist/index.js +640 -47
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +648 -59
- 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/cli.js
CHANGED
|
@@ -6,6 +6,13 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __getProtoOf = Object.getPrototypeOf;
|
|
8
8
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
|
+
};
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
9
16
|
var __copyProps = (to, from, except, desc) => {
|
|
10
17
|
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
18
|
for (let key of __getOwnPropNames(from))
|
|
@@ -23,62 +30,15 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
23
30
|
mod
|
|
24
31
|
));
|
|
25
32
|
|
|
26
|
-
// src/cli.ts
|
|
27
|
-
var import_commander = require("commander");
|
|
28
|
-
|
|
29
|
-
// src/agent/PaymentAgent.ts
|
|
30
|
-
var import_ethers = require("ethers");
|
|
31
|
-
|
|
32
33
|
// src/chains/index.ts
|
|
33
|
-
var
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
explorerTx: "https://basescan.org/tx/",
|
|
42
|
-
avgBlockTime: 2
|
|
43
|
-
},
|
|
44
|
-
polygon: {
|
|
45
|
-
name: "Polygon",
|
|
46
|
-
chainId: 137,
|
|
47
|
-
rpc: "https://polygon-rpc.com",
|
|
48
|
-
usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
49
|
-
explorer: "https://polygonscan.com/address/",
|
|
50
|
-
explorerTx: "https://polygonscan.com/tx/",
|
|
51
|
-
avgBlockTime: 2
|
|
52
|
-
},
|
|
53
|
-
ethereum: {
|
|
54
|
-
name: "Ethereum",
|
|
55
|
-
chainId: 1,
|
|
56
|
-
rpc: "https://eth.llamarpc.com",
|
|
57
|
-
usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
58
|
-
explorer: "https://etherscan.io/address/",
|
|
59
|
-
explorerTx: "https://etherscan.io/tx/",
|
|
60
|
-
avgBlockTime: 12
|
|
61
|
-
},
|
|
62
|
-
// ============ Testnet ============
|
|
63
|
-
base_sepolia: {
|
|
64
|
-
name: "Base Sepolia",
|
|
65
|
-
chainId: 84532,
|
|
66
|
-
rpc: "https://sepolia.base.org",
|
|
67
|
-
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
68
|
-
explorer: "https://sepolia.basescan.org/address/",
|
|
69
|
-
explorerTx: "https://sepolia.basescan.org/tx/",
|
|
70
|
-
avgBlockTime: 2
|
|
71
|
-
},
|
|
72
|
-
sepolia: {
|
|
73
|
-
name: "Sepolia",
|
|
74
|
-
chainId: 11155111,
|
|
75
|
-
rpc: "https://rpc.sepolia.org",
|
|
76
|
-
usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
|
|
77
|
-
explorer: "https://sepolia.etherscan.io/address/",
|
|
78
|
-
explorerTx: "https://sepolia.etherscan.io/tx/",
|
|
79
|
-
avgBlockTime: 12
|
|
80
|
-
}
|
|
81
|
-
};
|
|
34
|
+
var chains_exports = {};
|
|
35
|
+
__export(chains_exports, {
|
|
36
|
+
CHAINS: () => CHAINS,
|
|
37
|
+
ERC20_ABI: () => ERC20_ABI,
|
|
38
|
+
getChain: () => getChain,
|
|
39
|
+
getChainById: () => getChainById,
|
|
40
|
+
listChains: () => listChains
|
|
41
|
+
});
|
|
82
42
|
function getChain(name) {
|
|
83
43
|
const config = CHAINS[name];
|
|
84
44
|
if (!config) {
|
|
@@ -89,21 +49,405 @@ function getChain(name) {
|
|
|
89
49
|
function listChains() {
|
|
90
50
|
return Object.keys(CHAINS);
|
|
91
51
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
"
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
52
|
+
function getChainById(chainId) {
|
|
53
|
+
return Object.values(CHAINS).find((c) => c.chainId === chainId);
|
|
54
|
+
}
|
|
55
|
+
var CHAINS, ERC20_ABI;
|
|
56
|
+
var init_chains = __esm({
|
|
57
|
+
"src/chains/index.ts"() {
|
|
58
|
+
"use strict";
|
|
59
|
+
CHAINS = {
|
|
60
|
+
// ============ Mainnet ============
|
|
61
|
+
base: {
|
|
62
|
+
name: "Base",
|
|
63
|
+
chainId: 8453,
|
|
64
|
+
rpc: "https://mainnet.base.org",
|
|
65
|
+
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
66
|
+
explorer: "https://basescan.org/address/",
|
|
67
|
+
explorerTx: "https://basescan.org/tx/",
|
|
68
|
+
avgBlockTime: 2
|
|
69
|
+
},
|
|
70
|
+
polygon: {
|
|
71
|
+
name: "Polygon",
|
|
72
|
+
chainId: 137,
|
|
73
|
+
rpc: "https://polygon-rpc.com",
|
|
74
|
+
usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
75
|
+
explorer: "https://polygonscan.com/address/",
|
|
76
|
+
explorerTx: "https://polygonscan.com/tx/",
|
|
77
|
+
avgBlockTime: 2
|
|
78
|
+
},
|
|
79
|
+
ethereum: {
|
|
80
|
+
name: "Ethereum",
|
|
81
|
+
chainId: 1,
|
|
82
|
+
rpc: "https://eth.llamarpc.com",
|
|
83
|
+
usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
84
|
+
explorer: "https://etherscan.io/address/",
|
|
85
|
+
explorerTx: "https://etherscan.io/tx/",
|
|
86
|
+
avgBlockTime: 12
|
|
87
|
+
},
|
|
88
|
+
// ============ Testnet ============
|
|
89
|
+
base_sepolia: {
|
|
90
|
+
name: "Base Sepolia",
|
|
91
|
+
chainId: 84532,
|
|
92
|
+
rpc: "https://sepolia.base.org",
|
|
93
|
+
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
94
|
+
explorer: "https://sepolia.basescan.org/address/",
|
|
95
|
+
explorerTx: "https://sepolia.basescan.org/tx/",
|
|
96
|
+
avgBlockTime: 2
|
|
97
|
+
},
|
|
98
|
+
sepolia: {
|
|
99
|
+
name: "Sepolia",
|
|
100
|
+
chainId: 11155111,
|
|
101
|
+
rpc: "https://rpc.sepolia.org",
|
|
102
|
+
usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
|
|
103
|
+
explorer: "https://sepolia.etherscan.io/address/",
|
|
104
|
+
explorerTx: "https://sepolia.etherscan.io/tx/",
|
|
105
|
+
avgBlockTime: 12
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
ERC20_ABI = [
|
|
109
|
+
"function balanceOf(address owner) view returns (uint256)",
|
|
110
|
+
"function transfer(address to, uint256 amount) returns (bool)",
|
|
111
|
+
"function approve(address spender, uint256 amount) returns (bool)",
|
|
112
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
113
|
+
"function decimals() view returns (uint8)",
|
|
114
|
+
"function symbol() view returns (string)",
|
|
115
|
+
"function name() view returns (string)",
|
|
116
|
+
"function nonces(address owner) view returns (uint256)",
|
|
117
|
+
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
118
|
+
"event Transfer(address indexed from, address indexed to, uint256 value)",
|
|
119
|
+
"event Approval(address indexed owner, address indexed spender, uint256 value)"
|
|
120
|
+
];
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// src/agent/AgentWallet.ts
|
|
125
|
+
var AgentWallet_exports = {};
|
|
126
|
+
__export(AgentWallet_exports, {
|
|
127
|
+
AgentWallet: () => AgentWallet,
|
|
128
|
+
getAgentAddress: () => getAgentAddress
|
|
129
|
+
});
|
|
130
|
+
function getAgentAddress(config) {
|
|
131
|
+
const wallet = new AgentWallet(config);
|
|
132
|
+
return wallet.address;
|
|
133
|
+
}
|
|
134
|
+
var import_ethers2, fs, path, PERMIT_ABI, AgentWallet;
|
|
135
|
+
var init_AgentWallet = __esm({
|
|
136
|
+
"src/agent/AgentWallet.ts"() {
|
|
137
|
+
"use strict";
|
|
138
|
+
import_ethers2 = require("ethers");
|
|
139
|
+
fs = __toESM(require("fs"));
|
|
140
|
+
path = __toESM(require("path"));
|
|
141
|
+
init_chains();
|
|
142
|
+
PERMIT_ABI = [
|
|
143
|
+
...ERC20_ABI,
|
|
144
|
+
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
145
|
+
"function transferFrom(address from, address to, uint256 amount) returns (bool)",
|
|
146
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
147
|
+
"function nonces(address owner) view returns (uint256)"
|
|
148
|
+
];
|
|
149
|
+
AgentWallet = class {
|
|
150
|
+
chain;
|
|
151
|
+
chainConfig;
|
|
152
|
+
storageDir;
|
|
153
|
+
_address = null;
|
|
154
|
+
_privateKey = null;
|
|
155
|
+
_wallet = null;
|
|
156
|
+
_provider = null;
|
|
157
|
+
_permits = /* @__PURE__ */ new Map();
|
|
158
|
+
constructor(config = {}) {
|
|
159
|
+
this.chain = config.chain || "base";
|
|
160
|
+
this.chainConfig = getChain(this.chain);
|
|
161
|
+
this.storageDir = config.storageDir || this.getDefaultStorageDir();
|
|
162
|
+
this.ensureInitialized();
|
|
163
|
+
}
|
|
164
|
+
getDefaultStorageDir() {
|
|
165
|
+
const home = process.env.HOME || process.env.USERPROFILE || ".";
|
|
166
|
+
return path.join(home, ".moltspay");
|
|
167
|
+
}
|
|
168
|
+
getWalletPath() {
|
|
169
|
+
return path.join(this.storageDir, "wallet.json");
|
|
170
|
+
}
|
|
171
|
+
getPermitsPath() {
|
|
172
|
+
return path.join(this.storageDir, "permits.json");
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Auto-initialize: create wallet if not exists
|
|
176
|
+
* This is called automatically in constructor
|
|
177
|
+
*/
|
|
178
|
+
ensureInitialized() {
|
|
179
|
+
if (!fs.existsSync(this.storageDir)) {
|
|
180
|
+
fs.mkdirSync(this.storageDir, { recursive: true });
|
|
181
|
+
}
|
|
182
|
+
const walletPath = this.getWalletPath();
|
|
183
|
+
if (fs.existsSync(walletPath)) {
|
|
184
|
+
const data = JSON.parse(fs.readFileSync(walletPath, "utf-8"));
|
|
185
|
+
this._address = data.address;
|
|
186
|
+
this._privateKey = data.privateKey;
|
|
187
|
+
} else {
|
|
188
|
+
const wallet = import_ethers2.ethers.Wallet.createRandom();
|
|
189
|
+
this._address = wallet.address;
|
|
190
|
+
this._privateKey = wallet.privateKey;
|
|
191
|
+
fs.writeFileSync(walletPath, JSON.stringify({
|
|
192
|
+
address: this._address,
|
|
193
|
+
privateKey: this._privateKey,
|
|
194
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
195
|
+
chain: this.chain
|
|
196
|
+
}, null, 2), { mode: 384 });
|
|
197
|
+
}
|
|
198
|
+
const permitsPath = this.getPermitsPath();
|
|
199
|
+
if (fs.existsSync(permitsPath)) {
|
|
200
|
+
const permits = JSON.parse(fs.readFileSync(permitsPath, "utf-8"));
|
|
201
|
+
for (const [owner, permit] of Object.entries(permits)) {
|
|
202
|
+
this._permits.set(owner.toLowerCase(), permit);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/** Agent's address (auto-generated on first use) */
|
|
207
|
+
get address() {
|
|
208
|
+
return this._address;
|
|
209
|
+
}
|
|
210
|
+
get wallet() {
|
|
211
|
+
if (!this._wallet) {
|
|
212
|
+
this._wallet = new import_ethers2.ethers.Wallet(this._privateKey, this.provider);
|
|
213
|
+
}
|
|
214
|
+
return this._wallet;
|
|
215
|
+
}
|
|
216
|
+
get provider() {
|
|
217
|
+
if (!this._provider) {
|
|
218
|
+
this._provider = new import_ethers2.ethers.JsonRpcProvider(this.chainConfig.rpc);
|
|
219
|
+
}
|
|
220
|
+
return this._provider;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Store a Permit from Owner
|
|
224
|
+
*/
|
|
225
|
+
storePermit(permit) {
|
|
226
|
+
const ownerLower = permit.owner.toLowerCase();
|
|
227
|
+
this._permits.set(ownerLower, permit);
|
|
228
|
+
const permitsPath = this.getPermitsPath();
|
|
229
|
+
const permits = {};
|
|
230
|
+
for (const [owner, p] of this._permits) {
|
|
231
|
+
permits[owner] = p;
|
|
232
|
+
}
|
|
233
|
+
fs.writeFileSync(permitsPath, JSON.stringify(permits, null, 2));
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Get stored permit for an owner
|
|
237
|
+
*/
|
|
238
|
+
getPermit(owner) {
|
|
239
|
+
return this._permits.get(owner.toLowerCase());
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Check allowance from an owner
|
|
243
|
+
*/
|
|
244
|
+
async checkAllowance(owner) {
|
|
245
|
+
const usdcContract = new import_ethers2.ethers.Contract(
|
|
246
|
+
this.chainConfig.usdc,
|
|
247
|
+
PERMIT_ABI,
|
|
248
|
+
this.provider
|
|
249
|
+
);
|
|
250
|
+
const ownerAddress = import_ethers2.ethers.getAddress(owner);
|
|
251
|
+
const [allowance, balance] = await Promise.all([
|
|
252
|
+
usdcContract.allowance(ownerAddress, this.address),
|
|
253
|
+
usdcContract.balanceOf(ownerAddress)
|
|
254
|
+
]);
|
|
255
|
+
return {
|
|
256
|
+
allowance: (Number(allowance) / 1e6).toFixed(2),
|
|
257
|
+
ownerBalance: (Number(balance) / 1e6).toFixed(2),
|
|
258
|
+
canSpend: Number(allowance) > 0
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Spend USDC from Owner's wallet
|
|
263
|
+
*
|
|
264
|
+
* @param to - Recipient (service provider)
|
|
265
|
+
* @param amount - Amount in USDC
|
|
266
|
+
* @param permit - Optional, uses stored permit if not provided
|
|
267
|
+
*/
|
|
268
|
+
async spend(to, amount, permit) {
|
|
269
|
+
const toAddress = import_ethers2.ethers.getAddress(to);
|
|
270
|
+
const amountWei = BigInt(Math.floor(amount * 1e6));
|
|
271
|
+
let usePermit = permit;
|
|
272
|
+
let ownerAddress;
|
|
273
|
+
if (usePermit) {
|
|
274
|
+
ownerAddress = import_ethers2.ethers.getAddress(usePermit.owner);
|
|
275
|
+
this.storePermit(usePermit);
|
|
276
|
+
} else {
|
|
277
|
+
const usdcContract = new import_ethers2.ethers.Contract(
|
|
278
|
+
this.chainConfig.usdc,
|
|
279
|
+
PERMIT_ABI,
|
|
280
|
+
this.provider
|
|
281
|
+
);
|
|
282
|
+
for (const [owner, p] of this._permits) {
|
|
283
|
+
const allowance = await usdcContract.allowance(owner, this.address);
|
|
284
|
+
if (BigInt(allowance) >= amountWei) {
|
|
285
|
+
ownerAddress = import_ethers2.ethers.getAddress(owner);
|
|
286
|
+
usePermit = p;
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
if (!usePermit) {
|
|
291
|
+
return {
|
|
292
|
+
success: false,
|
|
293
|
+
error: "No valid permit. Ask Owner to authorize spending first.",
|
|
294
|
+
from: "",
|
|
295
|
+
to: toAddress,
|
|
296
|
+
amount
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
try {
|
|
301
|
+
const usdcContract = new import_ethers2.ethers.Contract(
|
|
302
|
+
this.chainConfig.usdc,
|
|
303
|
+
PERMIT_ABI,
|
|
304
|
+
this.wallet
|
|
305
|
+
);
|
|
306
|
+
const currentAllowance = await usdcContract.allowance(ownerAddress, this.address);
|
|
307
|
+
if (BigInt(currentAllowance) < amountWei) {
|
|
308
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
309
|
+
if (usePermit.deadline < now) {
|
|
310
|
+
return {
|
|
311
|
+
success: false,
|
|
312
|
+
error: "Permit expired. Ask Owner for a new authorization.",
|
|
313
|
+
from: ownerAddress,
|
|
314
|
+
to: toAddress,
|
|
315
|
+
amount
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
const permitTx = await usdcContract.permit(
|
|
319
|
+
ownerAddress,
|
|
320
|
+
this.address,
|
|
321
|
+
usePermit.value,
|
|
322
|
+
usePermit.deadline,
|
|
323
|
+
usePermit.v,
|
|
324
|
+
usePermit.r,
|
|
325
|
+
usePermit.s
|
|
326
|
+
);
|
|
327
|
+
await permitTx.wait();
|
|
328
|
+
}
|
|
329
|
+
const tx = await usdcContract.transferFrom(ownerAddress, toAddress, amountWei);
|
|
330
|
+
await tx.wait();
|
|
331
|
+
const newAllowance = await usdcContract.allowance(ownerAddress, this.address);
|
|
332
|
+
return {
|
|
333
|
+
success: true,
|
|
334
|
+
txHash: tx.hash,
|
|
335
|
+
from: ownerAddress,
|
|
336
|
+
to: toAddress,
|
|
337
|
+
amount,
|
|
338
|
+
remainingAllowance: (Number(newAllowance) / 1e6).toFixed(2),
|
|
339
|
+
explorerUrl: `${this.chainConfig.explorerTx}${tx.hash}`
|
|
340
|
+
};
|
|
341
|
+
} catch (error) {
|
|
342
|
+
return {
|
|
343
|
+
success: false,
|
|
344
|
+
error: error.message,
|
|
345
|
+
from: ownerAddress,
|
|
346
|
+
to: toAddress,
|
|
347
|
+
amount
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Get gas balance (ETH needed for transactions)
|
|
353
|
+
*/
|
|
354
|
+
async getGasBalance() {
|
|
355
|
+
const balance = await this.provider.getBalance(this.address);
|
|
356
|
+
return import_ethers2.ethers.formatEther(balance);
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Check if agent has enough gas
|
|
360
|
+
*/
|
|
361
|
+
async hasGas(minEth = 5e-4) {
|
|
362
|
+
const balance = await this.getGasBalance();
|
|
363
|
+
return parseFloat(balance) >= minEth;
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Generate authorization request for Owner
|
|
367
|
+
* Owner can sign this with CLI (ethers) or MetaMask
|
|
368
|
+
*/
|
|
369
|
+
async generateAuthRequest(params) {
|
|
370
|
+
const { ownerAddress, amount, expiresInHours = 168 } = params;
|
|
371
|
+
const deadline = Math.floor(Date.now() / 1e3) + expiresInHours * 3600;
|
|
372
|
+
const value = BigInt(Math.floor(amount * 1e6)).toString();
|
|
373
|
+
const usdcContract = new import_ethers2.ethers.Contract(
|
|
374
|
+
this.chainConfig.usdc,
|
|
375
|
+
PERMIT_ABI,
|
|
376
|
+
this.provider
|
|
377
|
+
);
|
|
378
|
+
const nonce = Number(await usdcContract.nonces(ownerAddress));
|
|
379
|
+
const domain = {
|
|
380
|
+
name: "USD Coin",
|
|
381
|
+
version: "2",
|
|
382
|
+
chainId: this.chainConfig.chainId,
|
|
383
|
+
verifyingContract: this.chainConfig.usdc
|
|
384
|
+
};
|
|
385
|
+
const types = {
|
|
386
|
+
Permit: [
|
|
387
|
+
{ name: "owner", type: "address" },
|
|
388
|
+
{ name: "spender", type: "address" },
|
|
389
|
+
{ name: "value", type: "uint256" },
|
|
390
|
+
{ name: "nonce", type: "uint256" },
|
|
391
|
+
{ name: "deadline", type: "uint256" }
|
|
392
|
+
]
|
|
393
|
+
};
|
|
394
|
+
const permitMessage = {
|
|
395
|
+
owner: ownerAddress,
|
|
396
|
+
spender: this.address,
|
|
397
|
+
value,
|
|
398
|
+
nonce,
|
|
399
|
+
deadline
|
|
400
|
+
};
|
|
401
|
+
const typedData = {
|
|
402
|
+
types: { ...types, EIP712Domain: [
|
|
403
|
+
{ name: "name", type: "string" },
|
|
404
|
+
{ name: "version", type: "string" },
|
|
405
|
+
{ name: "chainId", type: "uint256" },
|
|
406
|
+
{ name: "verifyingContract", type: "address" }
|
|
407
|
+
] },
|
|
408
|
+
primaryType: "Permit",
|
|
409
|
+
domain,
|
|
410
|
+
message: permitMessage
|
|
411
|
+
};
|
|
412
|
+
const cliCommand = `npx moltspay sign-permit \\
|
|
413
|
+
--owner ${ownerAddress} \\
|
|
414
|
+
--spender ${this.address} \\
|
|
415
|
+
--amount ${amount} \\
|
|
416
|
+
--deadline ${deadline} \\
|
|
417
|
+
--nonce ${nonce} \\
|
|
418
|
+
--chain ${this.chain}`;
|
|
419
|
+
const message = `\u{1F510} Authorization Request
|
|
420
|
+
|
|
421
|
+
I need permission to spend up to ${amount} USDC from your wallet.
|
|
422
|
+
|
|
423
|
+
**Details:**
|
|
424
|
+
- Your wallet: ${ownerAddress}
|
|
425
|
+
- My address: ${this.address}
|
|
426
|
+
- Amount: ${amount} USDC
|
|
427
|
+
- Expires: ${new Date(deadline * 1e3).toISOString()}
|
|
428
|
+
- Chain: ${this.chainConfig.name}
|
|
429
|
+
|
|
430
|
+
**Option 1: Sign with CLI** (if you have the private key)
|
|
431
|
+
\`\`\`
|
|
432
|
+
${cliCommand}
|
|
433
|
+
\`\`\`
|
|
434
|
+
|
|
435
|
+
**Option 2: Sign with MetaMask**
|
|
436
|
+
Visit: https://moltspay.vercel.app/permit?data=${encodeURIComponent(JSON.stringify(typedData))}
|
|
437
|
+
|
|
438
|
+
After signing, send me the signature (v, r, s).`;
|
|
439
|
+
return { message, typedData, cliCommand };
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
// src/cli.ts
|
|
446
|
+
var import_commander = require("commander");
|
|
105
447
|
|
|
106
448
|
// src/agent/PaymentAgent.ts
|
|
449
|
+
var import_ethers = require("ethers");
|
|
450
|
+
init_chains();
|
|
107
451
|
var PaymentAgent = class _PaymentAgent {
|
|
108
452
|
chain;
|
|
109
453
|
chainConfig;
|
|
@@ -305,8 +649,12 @@ After payment, reply with your tx hash:
|
|
|
305
649
|
}
|
|
306
650
|
};
|
|
307
651
|
|
|
652
|
+
// src/index.ts
|
|
653
|
+
init_AgentWallet();
|
|
654
|
+
|
|
308
655
|
// src/wallet/Wallet.ts
|
|
309
|
-
var
|
|
656
|
+
var import_ethers3 = require("ethers");
|
|
657
|
+
init_chains();
|
|
310
658
|
var Wallet = class {
|
|
311
659
|
chain;
|
|
312
660
|
chainConfig;
|
|
@@ -322,10 +670,10 @@ var Wallet = class {
|
|
|
322
670
|
throw new Error("privateKey is required. Set via config or PAYMENT_AGENT_PRIVATE_KEY env var.");
|
|
323
671
|
}
|
|
324
672
|
const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
|
|
325
|
-
this.provider = new
|
|
326
|
-
this.wallet = new
|
|
673
|
+
this.provider = new import_ethers3.ethers.JsonRpcProvider(rpcUrl);
|
|
674
|
+
this.wallet = new import_ethers3.ethers.Wallet(privateKey, this.provider);
|
|
327
675
|
this.address = this.wallet.address;
|
|
328
|
-
this.usdcContract = new
|
|
676
|
+
this.usdcContract = new import_ethers3.ethers.Contract(
|
|
329
677
|
this.chainConfig.usdc,
|
|
330
678
|
ERC20_ABI,
|
|
331
679
|
this.wallet
|
|
@@ -341,7 +689,7 @@ var Wallet = class {
|
|
|
341
689
|
]);
|
|
342
690
|
return {
|
|
343
691
|
address: this.address,
|
|
344
|
-
eth:
|
|
692
|
+
eth: import_ethers3.ethers.formatEther(ethBalance),
|
|
345
693
|
usdc: (Number(usdcBalance) / 1e6).toFixed(2),
|
|
346
694
|
chain: this.chain
|
|
347
695
|
};
|
|
@@ -351,7 +699,7 @@ var Wallet = class {
|
|
|
351
699
|
*/
|
|
352
700
|
async transfer(to, amount) {
|
|
353
701
|
try {
|
|
354
|
-
to =
|
|
702
|
+
to = import_ethers3.ethers.getAddress(to);
|
|
355
703
|
const amountWei = BigInt(Math.floor(amount * 1e6));
|
|
356
704
|
const balance = await this.usdcContract.balanceOf(this.address);
|
|
357
705
|
if (BigInt(balance) < amountWei) {
|
|
@@ -392,7 +740,7 @@ var Wallet = class {
|
|
|
392
740
|
*/
|
|
393
741
|
async getEthBalance() {
|
|
394
742
|
const balance = await this.provider.getBalance(this.address);
|
|
395
|
-
return
|
|
743
|
+
return import_ethers3.ethers.formatEther(balance);
|
|
396
744
|
}
|
|
397
745
|
/**
|
|
398
746
|
* Get USDC balance
|
|
@@ -404,14 +752,14 @@ var Wallet = class {
|
|
|
404
752
|
};
|
|
405
753
|
|
|
406
754
|
// src/audit/AuditLog.ts
|
|
407
|
-
var
|
|
408
|
-
var
|
|
755
|
+
var fs2 = __toESM(require("fs"));
|
|
756
|
+
var path2 = __toESM(require("path"));
|
|
409
757
|
var crypto = __toESM(require("crypto"));
|
|
410
758
|
var AuditLog = class {
|
|
411
759
|
basePath;
|
|
412
760
|
lastHash = "0000000000000000";
|
|
413
761
|
constructor(basePath) {
|
|
414
|
-
this.basePath = basePath ||
|
|
762
|
+
this.basePath = basePath || path2.join(process.cwd(), "data", "audit");
|
|
415
763
|
this.ensureDir();
|
|
416
764
|
this.loadLastHash();
|
|
417
765
|
}
|
|
@@ -440,7 +788,7 @@ var AuditLog = class {
|
|
|
440
788
|
this.lastHash = entry.hash;
|
|
441
789
|
const filePath = this.getFilePath(now);
|
|
442
790
|
const line = JSON.stringify(entry) + "\n";
|
|
443
|
-
|
|
791
|
+
fs2.appendFileSync(filePath, line, "utf-8");
|
|
444
792
|
return entry;
|
|
445
793
|
}
|
|
446
794
|
/**
|
|
@@ -448,10 +796,10 @@ var AuditLog = class {
|
|
|
448
796
|
*/
|
|
449
797
|
read(date) {
|
|
450
798
|
const filePath = this.getFilePath(date || /* @__PURE__ */ new Date());
|
|
451
|
-
if (!
|
|
799
|
+
if (!fs2.existsSync(filePath)) {
|
|
452
800
|
return [];
|
|
453
801
|
}
|
|
454
|
-
const content =
|
|
802
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
455
803
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
456
804
|
return lines.map((line) => JSON.parse(line));
|
|
457
805
|
}
|
|
@@ -502,7 +850,7 @@ var AuditLog = class {
|
|
|
502
850
|
*/
|
|
503
851
|
getFilePath(date) {
|
|
504
852
|
const dateStr = date.toISOString().slice(0, 10);
|
|
505
|
-
return
|
|
853
|
+
return path2.join(this.basePath, `audit_${dateStr}.jsonl`);
|
|
506
854
|
}
|
|
507
855
|
/**
|
|
508
856
|
* Calculate entry hash
|
|
@@ -540,8 +888,8 @@ var AuditLog = class {
|
|
|
540
888
|
* Ensure directory exists
|
|
541
889
|
*/
|
|
542
890
|
ensureDir() {
|
|
543
|
-
if (!
|
|
544
|
-
|
|
891
|
+
if (!fs2.existsSync(this.basePath)) {
|
|
892
|
+
fs2.mkdirSync(this.basePath, { recursive: true });
|
|
545
893
|
}
|
|
546
894
|
}
|
|
547
895
|
};
|
|
@@ -805,13 +1153,14 @@ var SecureWallet = class {
|
|
|
805
1153
|
};
|
|
806
1154
|
|
|
807
1155
|
// src/wallet/createWallet.ts
|
|
808
|
-
var
|
|
1156
|
+
var import_ethers4 = require("ethers");
|
|
809
1157
|
var import_path = require("path");
|
|
810
1158
|
var DEFAULT_STORAGE_DIR = (0, import_path.join)(process.env.HOME || "~", ".moltspay");
|
|
811
1159
|
|
|
812
1160
|
// src/wallet/PermitWallet.ts
|
|
813
|
-
var
|
|
814
|
-
|
|
1161
|
+
var import_ethers5 = require("ethers");
|
|
1162
|
+
init_chains();
|
|
1163
|
+
var PERMIT_ABI2 = [
|
|
815
1164
|
...ERC20_ABI,
|
|
816
1165
|
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
817
1166
|
"function transferFrom(address from, address to, uint256 amount) returns (bool)",
|
|
@@ -819,14 +1168,37 @@ var PERMIT_ABI = [
|
|
|
819
1168
|
];
|
|
820
1169
|
|
|
821
1170
|
// src/wallet/signPermit.ts
|
|
822
|
-
var
|
|
1171
|
+
var import_ethers6 = require("ethers");
|
|
1172
|
+
init_chains();
|
|
1173
|
+
|
|
1174
|
+
// src/wallet/AllowanceWallet.ts
|
|
1175
|
+
var import_ethers7 = require("ethers");
|
|
1176
|
+
init_chains();
|
|
1177
|
+
var PERMIT_ABI3 = [
|
|
1178
|
+
...ERC20_ABI,
|
|
1179
|
+
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
1180
|
+
"function transferFrom(address from, address to, uint256 amount) returns (bool)",
|
|
1181
|
+
"function allowance(address owner, address spender) view returns (uint256)",
|
|
1182
|
+
"function nonces(address owner) view returns (uint256)"
|
|
1183
|
+
];
|
|
823
1184
|
|
|
824
1185
|
// src/permit/Permit.ts
|
|
825
|
-
var
|
|
1186
|
+
var import_ethers8 = require("ethers");
|
|
1187
|
+
init_chains();
|
|
826
1188
|
|
|
827
1189
|
// src/verify/index.ts
|
|
828
|
-
var
|
|
829
|
-
|
|
1190
|
+
var import_ethers9 = require("ethers");
|
|
1191
|
+
init_chains();
|
|
1192
|
+
var TRANSFER_EVENT_TOPIC = import_ethers9.ethers.id("Transfer(address,address,uint256)");
|
|
1193
|
+
|
|
1194
|
+
// src/receipt/index.ts
|
|
1195
|
+
init_chains();
|
|
1196
|
+
|
|
1197
|
+
// src/templates/index.ts
|
|
1198
|
+
init_chains();
|
|
1199
|
+
|
|
1200
|
+
// src/index.ts
|
|
1201
|
+
init_chains();
|
|
830
1202
|
|
|
831
1203
|
// src/cli.ts
|
|
832
1204
|
var program = new import_commander.Command();
|
|
@@ -911,5 +1283,123 @@ program.command("chains").description("List supported chains").action(() => {
|
|
|
911
1283
|
console.log(` ${name}: ${config.name} (chainId: ${config.chainId})`);
|
|
912
1284
|
}
|
|
913
1285
|
});
|
|
1286
|
+
program.command("init").description("Initialize agent wallet (auto-generates address, no gas needed)").option("-c, --chain <chain>", "Chain name", "base").option("-d, --dir <directory>", "Storage directory", "~/.moltspay").action(async (options) => {
|
|
1287
|
+
const { AgentWallet: AgentWallet2 } = await Promise.resolve().then(() => (init_AgentWallet(), AgentWallet_exports));
|
|
1288
|
+
const storageDir = options.dir.replace("~", process.env.HOME || ".");
|
|
1289
|
+
const wallet = new AgentWallet2({
|
|
1290
|
+
chain: options.chain,
|
|
1291
|
+
storageDir
|
|
1292
|
+
});
|
|
1293
|
+
console.log("\u2705 Agent wallet initialized");
|
|
1294
|
+
console.log(` Address: ${wallet.address}`);
|
|
1295
|
+
console.log(` Chain: ${options.chain}`);
|
|
1296
|
+
console.log(` Storage: ${storageDir}`);
|
|
1297
|
+
console.log("");
|
|
1298
|
+
console.log("Next: Ask your Owner to authorize spending with:");
|
|
1299
|
+
console.log(` npx moltspay auth-request --owner <OWNER_ADDRESS> --amount <USDC_AMOUNT>`);
|
|
1300
|
+
});
|
|
1301
|
+
program.command("auth-request").description("Generate authorization request for Owner").requiredOption("-o, --owner <address>", "Owner wallet address (e.g., MetaMask)").requiredOption("-a, --amount <amount>", "Amount in USDC to authorize").option("-c, --chain <chain>", "Chain name", "base").option("-e, --expires <hours>", "Expiration in hours", "168").option("--json", "Output as JSON").action(async (options) => {
|
|
1302
|
+
const { AgentWallet: AgentWallet2 } = await Promise.resolve().then(() => (init_AgentWallet(), AgentWallet_exports));
|
|
1303
|
+
const wallet = new AgentWallet2({ chain: options.chain });
|
|
1304
|
+
const request = await wallet.generateAuthRequest({
|
|
1305
|
+
ownerAddress: options.owner,
|
|
1306
|
+
amount: parseFloat(options.amount),
|
|
1307
|
+
expiresInHours: parseInt(options.expires)
|
|
1308
|
+
});
|
|
1309
|
+
if (options.json) {
|
|
1310
|
+
console.log(JSON.stringify({
|
|
1311
|
+
agentAddress: wallet.address,
|
|
1312
|
+
typedData: request.typedData,
|
|
1313
|
+
cliCommand: request.cliCommand
|
|
1314
|
+
}, null, 2));
|
|
1315
|
+
} else {
|
|
1316
|
+
console.log(request.message);
|
|
1317
|
+
}
|
|
1318
|
+
});
|
|
1319
|
+
program.command("sign-permit").description("Sign a permit (Owner uses this to authorize Agent)").requiredOption("-o, --owner <address>", "Owner address").requiredOption("-s, --spender <address>", "Spender address (Agent)").requiredOption("-a, --amount <amount>", "Amount in USDC").requiredOption("-d, --deadline <timestamp>", "Deadline timestamp").requiredOption("-n, --nonce <nonce>", "Nonce from contract").option("-c, --chain <chain>", "Chain name", "base").option("-k, --private-key <key>", "Private key (or set OWNER_PRIVATE_KEY env)").action(async (options) => {
|
|
1320
|
+
const { ethers: ethers10 } = await import("ethers");
|
|
1321
|
+
const { getChain: getChain2 } = await Promise.resolve().then(() => (init_chains(), chains_exports));
|
|
1322
|
+
const privateKey = options.privateKey || process.env.OWNER_PRIVATE_KEY;
|
|
1323
|
+
if (!privateKey) {
|
|
1324
|
+
console.error("Error: Private key required. Use --private-key or set OWNER_PRIVATE_KEY env");
|
|
1325
|
+
process.exit(1);
|
|
1326
|
+
}
|
|
1327
|
+
const chainConfig = getChain2(options.chain);
|
|
1328
|
+
const wallet = new ethers10.Wallet(privateKey);
|
|
1329
|
+
if (wallet.address.toLowerCase() !== options.owner.toLowerCase()) {
|
|
1330
|
+
console.error(`Error: Private key doesn't match owner address`);
|
|
1331
|
+
console.error(` Expected: ${options.owner}`);
|
|
1332
|
+
console.error(` Got: ${wallet.address}`);
|
|
1333
|
+
process.exit(1);
|
|
1334
|
+
}
|
|
1335
|
+
const value = BigInt(Math.floor(parseFloat(options.amount) * 1e6)).toString();
|
|
1336
|
+
const domain = {
|
|
1337
|
+
name: "USD Coin",
|
|
1338
|
+
version: "2",
|
|
1339
|
+
chainId: chainConfig.chainId,
|
|
1340
|
+
verifyingContract: chainConfig.usdc
|
|
1341
|
+
};
|
|
1342
|
+
const types = {
|
|
1343
|
+
Permit: [
|
|
1344
|
+
{ name: "owner", type: "address" },
|
|
1345
|
+
{ name: "spender", type: "address" },
|
|
1346
|
+
{ name: "value", type: "uint256" },
|
|
1347
|
+
{ name: "nonce", type: "uint256" },
|
|
1348
|
+
{ name: "deadline", type: "uint256" }
|
|
1349
|
+
]
|
|
1350
|
+
};
|
|
1351
|
+
const message = {
|
|
1352
|
+
owner: options.owner,
|
|
1353
|
+
spender: options.spender,
|
|
1354
|
+
value,
|
|
1355
|
+
nonce: parseInt(options.nonce),
|
|
1356
|
+
deadline: parseInt(options.deadline)
|
|
1357
|
+
};
|
|
1358
|
+
const signature = await wallet.signTypedData(domain, types, message);
|
|
1359
|
+
const sig = ethers10.Signature.from(signature);
|
|
1360
|
+
const permit = {
|
|
1361
|
+
owner: options.owner,
|
|
1362
|
+
value,
|
|
1363
|
+
deadline: parseInt(options.deadline),
|
|
1364
|
+
nonce: parseInt(options.nonce),
|
|
1365
|
+
v: sig.v,
|
|
1366
|
+
r: sig.r,
|
|
1367
|
+
s: sig.s
|
|
1368
|
+
};
|
|
1369
|
+
console.log("\u2705 Permit signed successfully!");
|
|
1370
|
+
console.log("");
|
|
1371
|
+
console.log("Send this to your Agent:");
|
|
1372
|
+
console.log(JSON.stringify(permit, null, 2));
|
|
1373
|
+
});
|
|
1374
|
+
program.command("spend").description("Spend USDC from Owner wallet (requires permit)").requiredOption("--to <address>", "Recipient address").requiredOption("-a, --amount <amount>", "Amount in USDC").option("-c, --chain <chain>", "Chain name", "base").option("-p, --permit <json>", "Permit JSON (or stored permit is used)").action(async (options) => {
|
|
1375
|
+
const { AgentWallet: AgentWallet2 } = await Promise.resolve().then(() => (init_AgentWallet(), AgentWallet_exports));
|
|
1376
|
+
const wallet = new AgentWallet2({ chain: options.chain });
|
|
1377
|
+
let permit;
|
|
1378
|
+
if (options.permit) {
|
|
1379
|
+
permit = JSON.parse(options.permit);
|
|
1380
|
+
}
|
|
1381
|
+
console.log(`Spending ${options.amount} USDC to ${options.to}...`);
|
|
1382
|
+
const result = await wallet.spend(options.to, parseFloat(options.amount), permit);
|
|
1383
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1384
|
+
process.exit(result.success ? 0 : 1);
|
|
1385
|
+
});
|
|
1386
|
+
program.command("status").description("Show agent wallet status").option("-c, --chain <chain>", "Chain name", "base").option("-o, --owner <address>", "Check allowance from specific owner").action(async (options) => {
|
|
1387
|
+
const { AgentWallet: AgentWallet2 } = await Promise.resolve().then(() => (init_AgentWallet(), AgentWallet_exports));
|
|
1388
|
+
const wallet = new AgentWallet2({ chain: options.chain });
|
|
1389
|
+
console.log("Agent Wallet Status");
|
|
1390
|
+
console.log("==================");
|
|
1391
|
+
console.log(`Address: ${wallet.address}`);
|
|
1392
|
+
console.log(`Chain: ${options.chain}`);
|
|
1393
|
+
console.log(`Gas balance: ${await wallet.getGasBalance()} ETH`);
|
|
1394
|
+
console.log(`Has gas: ${await wallet.hasGas() ? "Yes \u2705" : "No \u274C (need ~0.0005 ETH)"}`);
|
|
1395
|
+
if (options.owner) {
|
|
1396
|
+
const allowance = await wallet.checkAllowance(options.owner);
|
|
1397
|
+
console.log("");
|
|
1398
|
+
console.log(`Allowance from ${options.owner}:`);
|
|
1399
|
+
console.log(` Allowance: ${allowance.allowance} USDC`);
|
|
1400
|
+
console.log(` Owner balance: ${allowance.ownerBalance} USDC`);
|
|
1401
|
+
console.log(` Can spend: ${allowance.canSpend ? "Yes \u2705" : "No \u274C"}`);
|
|
1402
|
+
}
|
|
1403
|
+
});
|
|
914
1404
|
program.parse();
|
|
915
1405
|
//# sourceMappingURL=cli.js.map
|