moltspay 0.5.4 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -124
- package/dist/cdp/index.d.mts +111 -0
- package/dist/cdp/index.d.ts +111 -0
- package/dist/cdp/index.js +30655 -0
- package/dist/cdp/index.js.map +1 -0
- package/dist/cdp/index.mjs +30631 -0
- package/dist/cdp/index.mjs.map +1 -0
- package/dist/chains/index.d.mts +1 -1
- package/dist/chains/index.d.ts +1 -1
- package/dist/cli/index.js +990 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/index.mjs +967 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/client/index.d.mts +134 -0
- package/dist/client/index.d.ts +134 -0
- package/dist/client/index.js +331 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/index.mjs +296 -0
- package/dist/client/index.mjs.map +1 -0
- package/dist/createWallet-D53qu7ie.d.mts +77 -0
- package/dist/createWallet-D53qu7ie.d.ts +77 -0
- package/dist/index-Dg8n6wdW.d.mts +32 -0
- package/dist/index-Dg8n6wdW.d.ts +32 -0
- package/dist/index.d.mts +6 -1483
- package/dist/index.d.ts +6 -1483
- package/dist/index.js +31039 -4254
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +31042 -4203
- package/dist/index.mjs.map +1 -1
- package/dist/server/index.d.mts +120 -0
- package/dist/server/index.d.ts +120 -0
- package/dist/server/index.js +418 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/index.mjs +393 -0
- package/dist/server/index.mjs.map +1 -0
- package/dist/wallet/index.d.mts +3 -451
- package/dist/wallet/index.d.ts +3 -451
- package/dist/wallet/index.js +5 -1021
- package/dist/wallet/index.js.map +1 -1
- package/dist/wallet/index.mjs +16 -1015
- package/dist/wallet/index.mjs.map +1 -1
- package/package.json +19 -19
- package/dist/cli.js +0 -1984
- package/dist/cli.js.map +0 -1
- package/dist/cli.mjs +0 -1969
- package/dist/cli.mjs.map +0 -1
- package/dist/guide/index.d.mts +0 -39
- package/dist/guide/index.d.ts +0 -39
- package/dist/guide/index.js +0 -181
- package/dist/guide/index.js.map +0 -1
- package/dist/guide/index.mjs +0 -152
- package/dist/guide/index.mjs.map +0 -1
- package/dist/index-CyFg9s2m.d.mts +0 -161
- package/dist/index-CyFg9s2m.d.ts +0 -161
- package/dist/orders/index.d.mts +0 -97
- package/dist/orders/index.d.ts +0 -97
- package/dist/orders/index.js +0 -162
- package/dist/orders/index.js.map +0 -1
- package/dist/orders/index.mjs +0 -136
- package/dist/orders/index.mjs.map +0 -1
- package/dist/permit/index.d.mts +0 -49
- package/dist/permit/index.d.ts +0 -49
- package/dist/permit/index.js +0 -273
- package/dist/permit/index.js.map +0 -1
- package/dist/permit/index.mjs +0 -246
- package/dist/permit/index.mjs.map +0 -1
- /package/dist/{cli.d.mts → cli/index.d.mts} +0 -0
- /package/dist/{cli.d.ts → cli/index.d.ts} +0 -0
package/dist/cli.mjs
DELETED
|
@@ -1,1969 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
5
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
6
|
-
}) : x)(function(x) {
|
|
7
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
8
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
9
|
-
});
|
|
10
|
-
var __esm = (fn, res) => function __init() {
|
|
11
|
-
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
12
|
-
};
|
|
13
|
-
var __export = (target, all) => {
|
|
14
|
-
for (var name in all)
|
|
15
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// src/chains/index.ts
|
|
19
|
-
var chains_exports = {};
|
|
20
|
-
__export(chains_exports, {
|
|
21
|
-
CHAINS: () => CHAINS,
|
|
22
|
-
ERC20_ABI: () => ERC20_ABI,
|
|
23
|
-
getChain: () => getChain,
|
|
24
|
-
getChainById: () => getChainById,
|
|
25
|
-
listChains: () => listChains
|
|
26
|
-
});
|
|
27
|
-
function getChain(name) {
|
|
28
|
-
const config = CHAINS[name];
|
|
29
|
-
if (!config) {
|
|
30
|
-
throw new Error(`Unsupported chain: ${name}. Supported: ${Object.keys(CHAINS).join(", ")}`);
|
|
31
|
-
}
|
|
32
|
-
return config;
|
|
33
|
-
}
|
|
34
|
-
function listChains() {
|
|
35
|
-
return Object.keys(CHAINS);
|
|
36
|
-
}
|
|
37
|
-
function getChainById(chainId) {
|
|
38
|
-
return Object.values(CHAINS).find((c) => c.chainId === chainId);
|
|
39
|
-
}
|
|
40
|
-
var CHAINS, ERC20_ABI;
|
|
41
|
-
var init_chains = __esm({
|
|
42
|
-
"src/chains/index.ts"() {
|
|
43
|
-
"use strict";
|
|
44
|
-
CHAINS = {
|
|
45
|
-
// ============ Mainnet ============
|
|
46
|
-
base: {
|
|
47
|
-
name: "Base",
|
|
48
|
-
chainId: 8453,
|
|
49
|
-
rpc: "https://mainnet.base.org",
|
|
50
|
-
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
51
|
-
explorer: "https://basescan.org/address/",
|
|
52
|
-
explorerTx: "https://basescan.org/tx/",
|
|
53
|
-
avgBlockTime: 2
|
|
54
|
-
},
|
|
55
|
-
polygon: {
|
|
56
|
-
name: "Polygon",
|
|
57
|
-
chainId: 137,
|
|
58
|
-
rpc: "https://polygon-rpc.com",
|
|
59
|
-
usdc: "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359",
|
|
60
|
-
explorer: "https://polygonscan.com/address/",
|
|
61
|
-
explorerTx: "https://polygonscan.com/tx/",
|
|
62
|
-
avgBlockTime: 2
|
|
63
|
-
},
|
|
64
|
-
ethereum: {
|
|
65
|
-
name: "Ethereum",
|
|
66
|
-
chainId: 1,
|
|
67
|
-
rpc: "https://eth.llamarpc.com",
|
|
68
|
-
usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
|
|
69
|
-
explorer: "https://etherscan.io/address/",
|
|
70
|
-
explorerTx: "https://etherscan.io/tx/",
|
|
71
|
-
avgBlockTime: 12
|
|
72
|
-
},
|
|
73
|
-
// ============ Testnet ============
|
|
74
|
-
base_sepolia: {
|
|
75
|
-
name: "Base Sepolia",
|
|
76
|
-
chainId: 84532,
|
|
77
|
-
rpc: "https://sepolia.base.org",
|
|
78
|
-
usdc: "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
|
|
79
|
-
explorer: "https://sepolia.basescan.org/address/",
|
|
80
|
-
explorerTx: "https://sepolia.basescan.org/tx/",
|
|
81
|
-
avgBlockTime: 2
|
|
82
|
-
},
|
|
83
|
-
sepolia: {
|
|
84
|
-
name: "Sepolia",
|
|
85
|
-
chainId: 11155111,
|
|
86
|
-
rpc: "https://rpc.sepolia.org",
|
|
87
|
-
usdc: "0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238",
|
|
88
|
-
explorer: "https://sepolia.etherscan.io/address/",
|
|
89
|
-
explorerTx: "https://sepolia.etherscan.io/tx/",
|
|
90
|
-
avgBlockTime: 12
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
ERC20_ABI = [
|
|
94
|
-
"function balanceOf(address owner) view returns (uint256)",
|
|
95
|
-
"function transfer(address to, uint256 amount) returns (bool)",
|
|
96
|
-
"function approve(address spender, uint256 amount) returns (bool)",
|
|
97
|
-
"function allowance(address owner, address spender) view returns (uint256)",
|
|
98
|
-
"function decimals() view returns (uint8)",
|
|
99
|
-
"function symbol() view returns (string)",
|
|
100
|
-
"function name() view returns (string)",
|
|
101
|
-
"function nonces(address owner) view returns (uint256)",
|
|
102
|
-
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
103
|
-
"event Transfer(address indexed from, address indexed to, uint256 value)",
|
|
104
|
-
"event Approval(address indexed owner, address indexed spender, uint256 value)"
|
|
105
|
-
];
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
// src/agent/AgentWallet.ts
|
|
110
|
-
var AgentWallet_exports = {};
|
|
111
|
-
__export(AgentWallet_exports, {
|
|
112
|
-
AgentWallet: () => AgentWallet,
|
|
113
|
-
getAgentAddress: () => getAgentAddress
|
|
114
|
-
});
|
|
115
|
-
import { ethers as ethers2 } from "ethers";
|
|
116
|
-
import * as fs from "fs";
|
|
117
|
-
import * as path from "path";
|
|
118
|
-
function getAgentAddress(config) {
|
|
119
|
-
const wallet = new AgentWallet(config);
|
|
120
|
-
return wallet.address;
|
|
121
|
-
}
|
|
122
|
-
var PERMIT_ABI, AgentWallet;
|
|
123
|
-
var init_AgentWallet = __esm({
|
|
124
|
-
"src/agent/AgentWallet.ts"() {
|
|
125
|
-
"use strict";
|
|
126
|
-
init_chains();
|
|
127
|
-
PERMIT_ABI = [
|
|
128
|
-
...ERC20_ABI,
|
|
129
|
-
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
130
|
-
"function transferFrom(address from, address to, uint256 amount) returns (bool)",
|
|
131
|
-
"function allowance(address owner, address spender) view returns (uint256)",
|
|
132
|
-
"function nonces(address owner) view returns (uint256)"
|
|
133
|
-
];
|
|
134
|
-
AgentWallet = class {
|
|
135
|
-
chain;
|
|
136
|
-
chainConfig;
|
|
137
|
-
storageDir;
|
|
138
|
-
_address = null;
|
|
139
|
-
_privateKey = null;
|
|
140
|
-
_wallet = null;
|
|
141
|
-
_provider = null;
|
|
142
|
-
_permits = /* @__PURE__ */ new Map();
|
|
143
|
-
constructor(config = {}) {
|
|
144
|
-
this.chain = config.chain || "base";
|
|
145
|
-
this.chainConfig = getChain(this.chain);
|
|
146
|
-
this.storageDir = config.storageDir || this.getDefaultStorageDir();
|
|
147
|
-
this.ensureInitialized();
|
|
148
|
-
}
|
|
149
|
-
getDefaultStorageDir() {
|
|
150
|
-
const home = process.env.HOME || process.env.USERPROFILE || ".";
|
|
151
|
-
return path.join(home, ".moltspay");
|
|
152
|
-
}
|
|
153
|
-
getWalletPath() {
|
|
154
|
-
return path.join(this.storageDir, "wallet.json");
|
|
155
|
-
}
|
|
156
|
-
getPermitsPath() {
|
|
157
|
-
return path.join(this.storageDir, "permits.json");
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* Auto-initialize: create wallet if not exists
|
|
161
|
-
* This is called automatically in constructor
|
|
162
|
-
*/
|
|
163
|
-
ensureInitialized() {
|
|
164
|
-
if (!fs.existsSync(this.storageDir)) {
|
|
165
|
-
fs.mkdirSync(this.storageDir, { recursive: true });
|
|
166
|
-
}
|
|
167
|
-
const walletPath = this.getWalletPath();
|
|
168
|
-
if (fs.existsSync(walletPath)) {
|
|
169
|
-
const data = JSON.parse(fs.readFileSync(walletPath, "utf-8"));
|
|
170
|
-
this._address = data.address;
|
|
171
|
-
this._privateKey = data.privateKey;
|
|
172
|
-
} else {
|
|
173
|
-
const wallet = ethers2.Wallet.createRandom();
|
|
174
|
-
this._address = wallet.address;
|
|
175
|
-
this._privateKey = wallet.privateKey;
|
|
176
|
-
fs.writeFileSync(walletPath, JSON.stringify({
|
|
177
|
-
address: this._address,
|
|
178
|
-
privateKey: this._privateKey,
|
|
179
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
180
|
-
chain: this.chain
|
|
181
|
-
}, null, 2), { mode: 384 });
|
|
182
|
-
}
|
|
183
|
-
const permitsPath = this.getPermitsPath();
|
|
184
|
-
if (fs.existsSync(permitsPath)) {
|
|
185
|
-
const permits = JSON.parse(fs.readFileSync(permitsPath, "utf-8"));
|
|
186
|
-
for (const [owner, permit] of Object.entries(permits)) {
|
|
187
|
-
this._permits.set(owner.toLowerCase(), permit);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
/** Agent's address (auto-generated on first use) */
|
|
192
|
-
get address() {
|
|
193
|
-
return this._address;
|
|
194
|
-
}
|
|
195
|
-
get wallet() {
|
|
196
|
-
if (!this._wallet) {
|
|
197
|
-
this._wallet = new ethers2.Wallet(this._privateKey, this.provider);
|
|
198
|
-
}
|
|
199
|
-
return this._wallet;
|
|
200
|
-
}
|
|
201
|
-
get provider() {
|
|
202
|
-
if (!this._provider) {
|
|
203
|
-
this._provider = new ethers2.JsonRpcProvider(this.chainConfig.rpc);
|
|
204
|
-
}
|
|
205
|
-
return this._provider;
|
|
206
|
-
}
|
|
207
|
-
/**
|
|
208
|
-
* Store a Permit from Owner
|
|
209
|
-
*/
|
|
210
|
-
storePermit(permit) {
|
|
211
|
-
const ownerLower = permit.owner.toLowerCase();
|
|
212
|
-
this._permits.set(ownerLower, permit);
|
|
213
|
-
const permitsPath = this.getPermitsPath();
|
|
214
|
-
const permits = {};
|
|
215
|
-
for (const [owner, p] of this._permits) {
|
|
216
|
-
permits[owner] = p;
|
|
217
|
-
}
|
|
218
|
-
fs.writeFileSync(permitsPath, JSON.stringify(permits, null, 2));
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* Get stored permit for an owner
|
|
222
|
-
*/
|
|
223
|
-
getPermit(owner) {
|
|
224
|
-
return this._permits.get(owner.toLowerCase());
|
|
225
|
-
}
|
|
226
|
-
/**
|
|
227
|
-
* Check allowance from an owner
|
|
228
|
-
*/
|
|
229
|
-
async checkAllowance(owner) {
|
|
230
|
-
const usdcContract = new ethers2.Contract(
|
|
231
|
-
this.chainConfig.usdc,
|
|
232
|
-
PERMIT_ABI,
|
|
233
|
-
this.provider
|
|
234
|
-
);
|
|
235
|
-
const ownerAddress = ethers2.getAddress(owner);
|
|
236
|
-
const [allowance, balance] = await Promise.all([
|
|
237
|
-
usdcContract.allowance(ownerAddress, this.address),
|
|
238
|
-
usdcContract.balanceOf(ownerAddress)
|
|
239
|
-
]);
|
|
240
|
-
return {
|
|
241
|
-
allowance: (Number(allowance) / 1e6).toFixed(2),
|
|
242
|
-
ownerBalance: (Number(balance) / 1e6).toFixed(2),
|
|
243
|
-
canSpend: Number(allowance) > 0
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
/**
|
|
247
|
-
* Spend USDC from Owner's wallet
|
|
248
|
-
*
|
|
249
|
-
* @param to - Recipient (service provider)
|
|
250
|
-
* @param amount - Amount in USDC
|
|
251
|
-
* @param permit - Optional, uses stored permit if not provided
|
|
252
|
-
*/
|
|
253
|
-
async spend(to, amount, permit) {
|
|
254
|
-
const toAddress = ethers2.getAddress(to);
|
|
255
|
-
const amountWei = BigInt(Math.floor(amount * 1e6));
|
|
256
|
-
let usePermit = permit;
|
|
257
|
-
let ownerAddress;
|
|
258
|
-
if (usePermit) {
|
|
259
|
-
ownerAddress = ethers2.getAddress(usePermit.owner);
|
|
260
|
-
this.storePermit(usePermit);
|
|
261
|
-
} else {
|
|
262
|
-
const usdcContract = new ethers2.Contract(
|
|
263
|
-
this.chainConfig.usdc,
|
|
264
|
-
PERMIT_ABI,
|
|
265
|
-
this.provider
|
|
266
|
-
);
|
|
267
|
-
for (const [owner, p] of this._permits) {
|
|
268
|
-
const allowance = await usdcContract.allowance(owner, this.address);
|
|
269
|
-
if (BigInt(allowance) >= amountWei) {
|
|
270
|
-
ownerAddress = ethers2.getAddress(owner);
|
|
271
|
-
usePermit = p;
|
|
272
|
-
break;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
if (!usePermit) {
|
|
276
|
-
return {
|
|
277
|
-
success: false,
|
|
278
|
-
error: "No valid permit. Ask Owner to authorize spending first.",
|
|
279
|
-
from: "",
|
|
280
|
-
to: toAddress,
|
|
281
|
-
amount
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
try {
|
|
286
|
-
const usdcContract = new ethers2.Contract(
|
|
287
|
-
this.chainConfig.usdc,
|
|
288
|
-
PERMIT_ABI,
|
|
289
|
-
this.wallet
|
|
290
|
-
);
|
|
291
|
-
const currentAllowance = await usdcContract.allowance(ownerAddress, this.address);
|
|
292
|
-
if (BigInt(currentAllowance) < amountWei) {
|
|
293
|
-
const now = Math.floor(Date.now() / 1e3);
|
|
294
|
-
if (usePermit.deadline < now) {
|
|
295
|
-
return {
|
|
296
|
-
success: false,
|
|
297
|
-
error: "Permit expired. Ask Owner for a new authorization.",
|
|
298
|
-
from: ownerAddress,
|
|
299
|
-
to: toAddress,
|
|
300
|
-
amount
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
const permitTx = await usdcContract.permit(
|
|
304
|
-
ownerAddress,
|
|
305
|
-
this.address,
|
|
306
|
-
usePermit.value,
|
|
307
|
-
usePermit.deadline,
|
|
308
|
-
usePermit.v,
|
|
309
|
-
usePermit.r,
|
|
310
|
-
usePermit.s
|
|
311
|
-
);
|
|
312
|
-
await permitTx.wait();
|
|
313
|
-
}
|
|
314
|
-
const tx = await usdcContract.transferFrom(ownerAddress, toAddress, amountWei);
|
|
315
|
-
await tx.wait();
|
|
316
|
-
const newAllowance = await usdcContract.allowance(ownerAddress, this.address);
|
|
317
|
-
return {
|
|
318
|
-
success: true,
|
|
319
|
-
txHash: tx.hash,
|
|
320
|
-
from: ownerAddress,
|
|
321
|
-
to: toAddress,
|
|
322
|
-
amount,
|
|
323
|
-
remainingAllowance: (Number(newAllowance) / 1e6).toFixed(2),
|
|
324
|
-
explorerUrl: `${this.chainConfig.explorerTx}${tx.hash}`
|
|
325
|
-
};
|
|
326
|
-
} catch (error) {
|
|
327
|
-
return {
|
|
328
|
-
success: false,
|
|
329
|
-
error: error.message,
|
|
330
|
-
from: ownerAddress,
|
|
331
|
-
to: toAddress,
|
|
332
|
-
amount
|
|
333
|
-
};
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
* Get USDC balance
|
|
338
|
-
*/
|
|
339
|
-
async getBalance() {
|
|
340
|
-
const usdcContract = new ethers2.Contract(
|
|
341
|
-
this.chainConfig.usdc,
|
|
342
|
-
PERMIT_ABI,
|
|
343
|
-
this.provider
|
|
344
|
-
);
|
|
345
|
-
const [usdcBalance, ethBalance] = await Promise.all([
|
|
346
|
-
usdcContract.balanceOf(this.address),
|
|
347
|
-
this.provider.getBalance(this.address)
|
|
348
|
-
]);
|
|
349
|
-
return {
|
|
350
|
-
usdc: (Number(usdcBalance) / 1e6).toFixed(2),
|
|
351
|
-
eth: ethers2.formatEther(ethBalance)
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
|
-
* Transfer USDC to a recipient (direct payment)
|
|
356
|
-
*
|
|
357
|
-
* This is the simplest payment method - Agent pays directly from its wallet.
|
|
358
|
-
* Requires Agent wallet to have USDC (funded by Owner).
|
|
359
|
-
*
|
|
360
|
-
* @example
|
|
361
|
-
* ```typescript
|
|
362
|
-
* const wallet = new AgentWallet({ chain: 'base' });
|
|
363
|
-
*
|
|
364
|
-
* // Check balance
|
|
365
|
-
* const balance = await wallet.getBalance();
|
|
366
|
-
* console.log('USDC:', balance.usdc);
|
|
367
|
-
*
|
|
368
|
-
* // Pay for service
|
|
369
|
-
* const result = await wallet.transfer({
|
|
370
|
-
* to: '0xServiceProvider...',
|
|
371
|
-
* amount: 0.99
|
|
372
|
-
* });
|
|
373
|
-
* console.log('Tx:', result.txHash);
|
|
374
|
-
* ```
|
|
375
|
-
*/
|
|
376
|
-
async transfer(params) {
|
|
377
|
-
const { to, amount } = params;
|
|
378
|
-
try {
|
|
379
|
-
const toAddress = ethers2.getAddress(to);
|
|
380
|
-
const amountWei = BigInt(Math.floor(amount * 1e6));
|
|
381
|
-
const usdcContract = new ethers2.Contract(
|
|
382
|
-
this.chainConfig.usdc,
|
|
383
|
-
PERMIT_ABI,
|
|
384
|
-
this.wallet
|
|
385
|
-
);
|
|
386
|
-
const balance = await usdcContract.balanceOf(this.address);
|
|
387
|
-
if (BigInt(balance) < amountWei) {
|
|
388
|
-
return {
|
|
389
|
-
success: false,
|
|
390
|
-
error: `Insufficient USDC: have ${(Number(balance) / 1e6).toFixed(2)}, need ${amount}`
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
if (!await this.hasGas()) {
|
|
394
|
-
return {
|
|
395
|
-
success: false,
|
|
396
|
-
error: "Insufficient ETH for gas. Need at least 0.0005 ETH."
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
const tx = await usdcContract.transfer(toAddress, amountWei);
|
|
400
|
-
const receipt = await tx.wait();
|
|
401
|
-
if (receipt.status === 1) {
|
|
402
|
-
return {
|
|
403
|
-
success: true,
|
|
404
|
-
txHash: tx.hash,
|
|
405
|
-
from: this.address,
|
|
406
|
-
to: toAddress,
|
|
407
|
-
amount,
|
|
408
|
-
explorerUrl: `${this.chainConfig.explorerTx}${tx.hash}`
|
|
409
|
-
};
|
|
410
|
-
} else {
|
|
411
|
-
return {
|
|
412
|
-
success: false,
|
|
413
|
-
txHash: tx.hash,
|
|
414
|
-
error: "Transaction reverted"
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
} catch (error) {
|
|
418
|
-
return {
|
|
419
|
-
success: false,
|
|
420
|
-
error: error.message
|
|
421
|
-
};
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Pay for a service (transfer USDC to service provider)
|
|
426
|
-
*
|
|
427
|
-
* This is the main method for Agent-to-Agent payments.
|
|
428
|
-
* Returns payment data ready to submit to service API.
|
|
429
|
-
*
|
|
430
|
-
* @example
|
|
431
|
-
* ```typescript
|
|
432
|
-
* const wallet = new AgentWallet({ chain: 'base' });
|
|
433
|
-
*
|
|
434
|
-
* // Pay for Zen7 video service
|
|
435
|
-
* const payment = await wallet.payService({
|
|
436
|
-
* provider: '0xb8d6f2441e8f8dfB6288A74Cf73804cDd0484E0C',
|
|
437
|
-
* amount: 0.99,
|
|
438
|
-
* service: 'video_generation'
|
|
439
|
-
* });
|
|
440
|
-
*
|
|
441
|
-
* if (payment.success) {
|
|
442
|
-
* // Submit to service API
|
|
443
|
-
* await fetch('/v1/video/generate', {
|
|
444
|
-
* body: JSON.stringify({
|
|
445
|
-
* prompt: "...",
|
|
446
|
-
* payment: payment.paymentData // Ready to use!
|
|
447
|
-
* })
|
|
448
|
-
* });
|
|
449
|
-
* }
|
|
450
|
-
* ```
|
|
451
|
-
*/
|
|
452
|
-
async payService(params) {
|
|
453
|
-
const result = await this.transfer({
|
|
454
|
-
to: params.provider,
|
|
455
|
-
amount: params.amount
|
|
456
|
-
});
|
|
457
|
-
if (result.success && result.txHash) {
|
|
458
|
-
return {
|
|
459
|
-
success: true,
|
|
460
|
-
txHash: result.txHash,
|
|
461
|
-
explorerUrl: result.explorerUrl,
|
|
462
|
-
paymentData: {
|
|
463
|
-
method: "transfer",
|
|
464
|
-
chain: this.chain,
|
|
465
|
-
tx_hash: result.txHash
|
|
466
|
-
}
|
|
467
|
-
};
|
|
468
|
-
} else {
|
|
469
|
-
return {
|
|
470
|
-
success: false,
|
|
471
|
-
error: result.error
|
|
472
|
-
};
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
/**
|
|
476
|
-
* Get gas balance (ETH needed for transactions)
|
|
477
|
-
*/
|
|
478
|
-
async getGasBalance() {
|
|
479
|
-
const balance = await this.provider.getBalance(this.address);
|
|
480
|
-
return ethers2.formatEther(balance);
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Check if agent has enough gas
|
|
484
|
-
*/
|
|
485
|
-
async hasGas(minEth = 5e-4) {
|
|
486
|
-
const balance = await this.getGasBalance();
|
|
487
|
-
return parseFloat(balance) >= minEth;
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* Generate authorization request for Owner
|
|
491
|
-
* Owner can sign this with CLI (ethers) or MetaMask
|
|
492
|
-
*/
|
|
493
|
-
async generateAuthRequest(params) {
|
|
494
|
-
const { ownerAddress, amount, expiresInHours = 168 } = params;
|
|
495
|
-
const deadline = Math.floor(Date.now() / 1e3) + expiresInHours * 3600;
|
|
496
|
-
const value = BigInt(Math.floor(amount * 1e6)).toString();
|
|
497
|
-
const usdcContract = new ethers2.Contract(
|
|
498
|
-
this.chainConfig.usdc,
|
|
499
|
-
PERMIT_ABI,
|
|
500
|
-
this.provider
|
|
501
|
-
);
|
|
502
|
-
const nonce = Number(await usdcContract.nonces(ownerAddress));
|
|
503
|
-
const domain = {
|
|
504
|
-
name: "USD Coin",
|
|
505
|
-
version: "2",
|
|
506
|
-
chainId: this.chainConfig.chainId,
|
|
507
|
-
verifyingContract: this.chainConfig.usdc
|
|
508
|
-
};
|
|
509
|
-
const types = {
|
|
510
|
-
Permit: [
|
|
511
|
-
{ name: "owner", type: "address" },
|
|
512
|
-
{ name: "spender", type: "address" },
|
|
513
|
-
{ name: "value", type: "uint256" },
|
|
514
|
-
{ name: "nonce", type: "uint256" },
|
|
515
|
-
{ name: "deadline", type: "uint256" }
|
|
516
|
-
]
|
|
517
|
-
};
|
|
518
|
-
const permitMessage = {
|
|
519
|
-
owner: ownerAddress,
|
|
520
|
-
spender: this.address,
|
|
521
|
-
value,
|
|
522
|
-
nonce,
|
|
523
|
-
deadline
|
|
524
|
-
};
|
|
525
|
-
const typedData = {
|
|
526
|
-
types: { ...types, EIP712Domain: [
|
|
527
|
-
{ name: "name", type: "string" },
|
|
528
|
-
{ name: "version", type: "string" },
|
|
529
|
-
{ name: "chainId", type: "uint256" },
|
|
530
|
-
{ name: "verifyingContract", type: "address" }
|
|
531
|
-
] },
|
|
532
|
-
primaryType: "Permit",
|
|
533
|
-
domain,
|
|
534
|
-
message: permitMessage
|
|
535
|
-
};
|
|
536
|
-
const cliCommand = `npx moltspay sign-permit \\
|
|
537
|
-
--owner ${ownerAddress} \\
|
|
538
|
-
--spender ${this.address} \\
|
|
539
|
-
--amount ${amount} \\
|
|
540
|
-
--deadline ${deadline} \\
|
|
541
|
-
--nonce ${nonce} \\
|
|
542
|
-
--chain ${this.chain}`;
|
|
543
|
-
const message = `\u{1F510} Authorization Request
|
|
544
|
-
|
|
545
|
-
I need permission to spend up to ${amount} USDC from your wallet.
|
|
546
|
-
|
|
547
|
-
**Details:**
|
|
548
|
-
- Your wallet: ${ownerAddress}
|
|
549
|
-
- My address: ${this.address}
|
|
550
|
-
- Amount: ${amount} USDC
|
|
551
|
-
- Expires: ${new Date(deadline * 1e3).toISOString()}
|
|
552
|
-
- Chain: ${this.chainConfig.name}
|
|
553
|
-
|
|
554
|
-
**Option 1: Sign with CLI** (if you have the private key)
|
|
555
|
-
\`\`\`
|
|
556
|
-
${cliCommand}
|
|
557
|
-
\`\`\`
|
|
558
|
-
|
|
559
|
-
**Option 2: Sign with MetaMask**
|
|
560
|
-
Visit: https://moltspay.vercel.app/permit?data=${encodeURIComponent(JSON.stringify(typedData))}
|
|
561
|
-
|
|
562
|
-
After signing, send me the signature (v, r, s).`;
|
|
563
|
-
return { message, typedData, cliCommand };
|
|
564
|
-
}
|
|
565
|
-
};
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
|
|
569
|
-
// src/cdp/index.ts
|
|
570
|
-
var cdp_exports = {};
|
|
571
|
-
__export(cdp_exports, {
|
|
572
|
-
CDPWallet: () => CDPWallet,
|
|
573
|
-
getCDPWalletAddress: () => getCDPWalletAddress,
|
|
574
|
-
initCDPWallet: () => initCDPWallet,
|
|
575
|
-
isCDPAvailable: () => isCDPAvailable,
|
|
576
|
-
loadCDPWallet: () => loadCDPWallet
|
|
577
|
-
});
|
|
578
|
-
import * as fs3 from "fs";
|
|
579
|
-
import * as path3 from "path";
|
|
580
|
-
function isCDPAvailable() {
|
|
581
|
-
try {
|
|
582
|
-
__require.resolve("@coinbase/cdp-sdk");
|
|
583
|
-
return true;
|
|
584
|
-
} catch {
|
|
585
|
-
return false;
|
|
586
|
-
}
|
|
587
|
-
}
|
|
588
|
-
function getCDPCredentials(config) {
|
|
589
|
-
const apiKeyId = config.apiKeyId || process.env.CDP_API_KEY_ID;
|
|
590
|
-
const apiKeySecret = config.apiKeySecret || process.env.CDP_API_KEY_SECRET;
|
|
591
|
-
const walletSecret = config.walletSecret || process.env.CDP_WALLET_SECRET;
|
|
592
|
-
if (!apiKeyId || !apiKeySecret) {
|
|
593
|
-
return null;
|
|
594
|
-
}
|
|
595
|
-
return { apiKeyId, apiKeySecret, walletSecret };
|
|
596
|
-
}
|
|
597
|
-
async function initCDPWallet(config = {}) {
|
|
598
|
-
const storageDir = config.storageDir || DEFAULT_STORAGE_DIR2;
|
|
599
|
-
const chain = config.chain || "base";
|
|
600
|
-
const storagePath = path3.join(storageDir, CDP_CONFIG_FILE);
|
|
601
|
-
if (fs3.existsSync(storagePath)) {
|
|
602
|
-
try {
|
|
603
|
-
const data = JSON.parse(fs3.readFileSync(storagePath, "utf-8"));
|
|
604
|
-
return {
|
|
605
|
-
success: true,
|
|
606
|
-
address: data.address,
|
|
607
|
-
walletId: data.walletId,
|
|
608
|
-
isNew: false,
|
|
609
|
-
storagePath
|
|
610
|
-
};
|
|
611
|
-
} catch (error) {
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
if (!isCDPAvailable()) {
|
|
615
|
-
return {
|
|
616
|
-
success: false,
|
|
617
|
-
error: "CDP SDK not installed. Run: npm install @coinbase/cdp-sdk"
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
const creds = getCDPCredentials(config);
|
|
621
|
-
if (!creds) {
|
|
622
|
-
return {
|
|
623
|
-
success: false,
|
|
624
|
-
error: "CDP credentials not found. Set CDP_API_KEY_ID and CDP_API_KEY_SECRET environment variables."
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
try {
|
|
628
|
-
const { CdpClient } = await import("@coinbase/cdp-sdk");
|
|
629
|
-
const cdp = new CdpClient({
|
|
630
|
-
apiKeyId: creds.apiKeyId,
|
|
631
|
-
apiKeySecret: creds.apiKeySecret,
|
|
632
|
-
walletSecret: creds.walletSecret
|
|
633
|
-
});
|
|
634
|
-
const account = await cdp.evm.createAccount();
|
|
635
|
-
if (!fs3.existsSync(storageDir)) {
|
|
636
|
-
fs3.mkdirSync(storageDir, { recursive: true });
|
|
637
|
-
}
|
|
638
|
-
const walletData = {
|
|
639
|
-
address: account.address,
|
|
640
|
-
walletId: account.address,
|
|
641
|
-
// CDP uses address as ID for EVM
|
|
642
|
-
chain,
|
|
643
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
644
|
-
};
|
|
645
|
-
fs3.writeFileSync(storagePath, JSON.stringify(walletData, null, 2), { mode: 384 });
|
|
646
|
-
return {
|
|
647
|
-
success: true,
|
|
648
|
-
address: account.address,
|
|
649
|
-
walletId: account.address,
|
|
650
|
-
isNew: true,
|
|
651
|
-
storagePath
|
|
652
|
-
};
|
|
653
|
-
} catch (error) {
|
|
654
|
-
return {
|
|
655
|
-
success: false,
|
|
656
|
-
error: error.message
|
|
657
|
-
};
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
function loadCDPWallet(config = {}) {
|
|
661
|
-
const storageDir = config.storageDir || DEFAULT_STORAGE_DIR2;
|
|
662
|
-
const storagePath = path3.join(storageDir, CDP_CONFIG_FILE);
|
|
663
|
-
if (!fs3.existsSync(storagePath)) {
|
|
664
|
-
return null;
|
|
665
|
-
}
|
|
666
|
-
try {
|
|
667
|
-
return JSON.parse(fs3.readFileSync(storagePath, "utf-8"));
|
|
668
|
-
} catch {
|
|
669
|
-
return null;
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
function getCDPWalletAddress(storageDir) {
|
|
673
|
-
const data = loadCDPWallet({ storageDir });
|
|
674
|
-
return data?.address || null;
|
|
675
|
-
}
|
|
676
|
-
var DEFAULT_STORAGE_DIR2, CDP_CONFIG_FILE, CDPWallet;
|
|
677
|
-
var init_cdp = __esm({
|
|
678
|
-
"src/cdp/index.ts"() {
|
|
679
|
-
"use strict";
|
|
680
|
-
init_chains();
|
|
681
|
-
DEFAULT_STORAGE_DIR2 = path3.join(process.env.HOME || ".", ".moltspay");
|
|
682
|
-
CDP_CONFIG_FILE = "cdp-wallet.json";
|
|
683
|
-
CDPWallet = class {
|
|
684
|
-
address;
|
|
685
|
-
chain;
|
|
686
|
-
chainConfig;
|
|
687
|
-
storageDir;
|
|
688
|
-
constructor(config = {}) {
|
|
689
|
-
this.storageDir = config.storageDir || DEFAULT_STORAGE_DIR2;
|
|
690
|
-
this.chain = config.chain || "base";
|
|
691
|
-
this.chainConfig = getChain(this.chain);
|
|
692
|
-
const data = loadCDPWallet(config);
|
|
693
|
-
if (!data) {
|
|
694
|
-
throw new Error("CDP wallet not initialized. Run: npx moltspay init --cdp");
|
|
695
|
-
}
|
|
696
|
-
this.address = data.address;
|
|
697
|
-
}
|
|
698
|
-
/**
|
|
699
|
-
* Get USDC balance
|
|
700
|
-
*/
|
|
701
|
-
async getBalance() {
|
|
702
|
-
const { ethers: ethers11 } = await import("ethers");
|
|
703
|
-
const provider = new ethers11.JsonRpcProvider(this.chainConfig.rpc);
|
|
704
|
-
const usdcContract = new ethers11.Contract(
|
|
705
|
-
this.chainConfig.usdc,
|
|
706
|
-
["function balanceOf(address) view returns (uint256)"],
|
|
707
|
-
provider
|
|
708
|
-
);
|
|
709
|
-
const [usdcBalance, ethBalance] = await Promise.all([
|
|
710
|
-
usdcContract.balanceOf(this.address),
|
|
711
|
-
provider.getBalance(this.address)
|
|
712
|
-
]);
|
|
713
|
-
return {
|
|
714
|
-
usdc: (Number(usdcBalance) / 1e6).toFixed(2),
|
|
715
|
-
eth: ethers11.formatEther(ethBalance)
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
/**
|
|
719
|
-
* Transfer USDC to a recipient
|
|
720
|
-
*
|
|
721
|
-
* Requires CDP SDK and credentials to sign transactions.
|
|
722
|
-
*/
|
|
723
|
-
async transfer(params) {
|
|
724
|
-
if (!isCDPAvailable()) {
|
|
725
|
-
return { success: false, error: "CDP SDK not installed" };
|
|
726
|
-
}
|
|
727
|
-
const creds = getCDPCredentials({});
|
|
728
|
-
if (!creds) {
|
|
729
|
-
return { success: false, error: "CDP credentials not found" };
|
|
730
|
-
}
|
|
731
|
-
try {
|
|
732
|
-
const { CdpClient } = await import("@coinbase/cdp-sdk");
|
|
733
|
-
const { ethers: ethers11 } = await import("ethers");
|
|
734
|
-
const cdp = new CdpClient({
|
|
735
|
-
apiKeyId: creds.apiKeyId,
|
|
736
|
-
apiKeySecret: creds.apiKeySecret,
|
|
737
|
-
walletSecret: creds.walletSecret
|
|
738
|
-
});
|
|
739
|
-
const account = await cdp.evm.getAccount({ address: this.address });
|
|
740
|
-
const amountWei = BigInt(Math.floor(params.amount * 1e6));
|
|
741
|
-
const iface = new ethers11.Interface([
|
|
742
|
-
"function transfer(address to, uint256 amount) returns (bool)"
|
|
743
|
-
]);
|
|
744
|
-
const callData = iface.encodeFunctionData("transfer", [params.to, amountWei]);
|
|
745
|
-
const txOptions = {
|
|
746
|
-
to: this.chainConfig.usdc,
|
|
747
|
-
data: callData
|
|
748
|
-
};
|
|
749
|
-
const tx = await account.sendTransaction(txOptions);
|
|
750
|
-
return {
|
|
751
|
-
success: true,
|
|
752
|
-
txHash: tx.transactionHash,
|
|
753
|
-
explorerUrl: `${this.chainConfig.explorerTx}${tx.transactionHash}`
|
|
754
|
-
};
|
|
755
|
-
} catch (error) {
|
|
756
|
-
return {
|
|
757
|
-
success: false,
|
|
758
|
-
error: error.message
|
|
759
|
-
};
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
/**
|
|
763
|
-
* Create viem-compatible signer for x402
|
|
764
|
-
*
|
|
765
|
-
* This allows using CDP wallet with x402 protocol.
|
|
766
|
-
*/
|
|
767
|
-
async getViemAccount() {
|
|
768
|
-
if (!isCDPAvailable()) {
|
|
769
|
-
throw new Error("CDP SDK not installed");
|
|
770
|
-
}
|
|
771
|
-
const creds = getCDPCredentials({});
|
|
772
|
-
if (!creds) {
|
|
773
|
-
throw new Error("CDP credentials not found");
|
|
774
|
-
}
|
|
775
|
-
const { CdpClient } = await import("@coinbase/cdp-sdk");
|
|
776
|
-
const { toAccount } = await import("viem/accounts");
|
|
777
|
-
const cdp = new CdpClient({
|
|
778
|
-
apiKeyId: creds.apiKeyId,
|
|
779
|
-
apiKeySecret: creds.apiKeySecret,
|
|
780
|
-
walletSecret: creds.walletSecret
|
|
781
|
-
});
|
|
782
|
-
const account = await cdp.evm.getAccount({ address: this.address });
|
|
783
|
-
return toAccount(account);
|
|
784
|
-
}
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
});
|
|
788
|
-
|
|
789
|
-
// src/x402/client.ts
|
|
790
|
-
var client_exports = {};
|
|
791
|
-
__export(client_exports, {
|
|
792
|
-
createX402Client: () => createX402Client,
|
|
793
|
-
isX402Available: () => isX402Available,
|
|
794
|
-
x402Fetch: () => x402Fetch
|
|
795
|
-
});
|
|
796
|
-
function isX402Available() {
|
|
797
|
-
try {
|
|
798
|
-
__require.resolve("@x402/fetch");
|
|
799
|
-
__require.resolve("@x402/evm");
|
|
800
|
-
return true;
|
|
801
|
-
} catch {
|
|
802
|
-
return false;
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
async function createLocalX402Client(config) {
|
|
806
|
-
const { AgentWallet: AgentWallet2 } = await Promise.resolve().then(() => (init_AgentWallet(), AgentWallet_exports));
|
|
807
|
-
const { ethers: ethers11 } = await import("ethers");
|
|
808
|
-
const wallet = new AgentWallet2({
|
|
809
|
-
chain: config.chain,
|
|
810
|
-
storageDir: config.storageDir
|
|
811
|
-
});
|
|
812
|
-
const fs4 = await import("fs");
|
|
813
|
-
const path4 = await import("path");
|
|
814
|
-
const storageDir = config.storageDir || path4.join(process.env.HOME || ".", ".moltspay");
|
|
815
|
-
const walletPath = path4.join(storageDir, "wallet.json");
|
|
816
|
-
const walletData = JSON.parse(fs4.readFileSync(walletPath, "utf-8"));
|
|
817
|
-
const privateKey = config.privateKey || walletData.privateKey;
|
|
818
|
-
const { privateKeyToAccount } = await import("viem/accounts");
|
|
819
|
-
const signer = privateKeyToAccount(privateKey);
|
|
820
|
-
const { x402Client, wrapFetchWithPayment } = await import("@x402/fetch");
|
|
821
|
-
const { registerExactEvmScheme } = await import("@x402/evm/exact/client");
|
|
822
|
-
const client = new x402Client();
|
|
823
|
-
registerExactEvmScheme(client, { signer });
|
|
824
|
-
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
|
|
825
|
-
return {
|
|
826
|
-
fetch: fetchWithPayment,
|
|
827
|
-
address: wallet.address,
|
|
828
|
-
chain: config.chain || "base"
|
|
829
|
-
};
|
|
830
|
-
}
|
|
831
|
-
async function createCDPX402Client(config) {
|
|
832
|
-
const { CDPWallet: CDPWallet2 } = await Promise.resolve().then(() => (init_cdp(), cdp_exports));
|
|
833
|
-
const wallet = new CDPWallet2({
|
|
834
|
-
chain: config.chain,
|
|
835
|
-
storageDir: config.storageDir
|
|
836
|
-
});
|
|
837
|
-
const signer = await wallet.getViemAccount();
|
|
838
|
-
const { x402Client, wrapFetchWithPayment } = await import("@x402/fetch");
|
|
839
|
-
const { registerExactEvmScheme } = await import("@x402/evm/exact/client");
|
|
840
|
-
const client = new x402Client();
|
|
841
|
-
registerExactEvmScheme(client, { signer });
|
|
842
|
-
const fetchWithPayment = wrapFetchWithPayment(fetch, client);
|
|
843
|
-
return {
|
|
844
|
-
fetch: fetchWithPayment,
|
|
845
|
-
address: wallet.address,
|
|
846
|
-
chain: config.chain || "base"
|
|
847
|
-
};
|
|
848
|
-
}
|
|
849
|
-
async function createX402Client(config = {}) {
|
|
850
|
-
if (!isX402Available()) {
|
|
851
|
-
throw new Error("x402 packages not installed. Run: npm install @x402/fetch @x402/evm");
|
|
852
|
-
}
|
|
853
|
-
if (config.useCDP) {
|
|
854
|
-
return createCDPX402Client(config);
|
|
855
|
-
} else {
|
|
856
|
-
return createLocalX402Client(config);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
async function x402Fetch(input, init, config) {
|
|
860
|
-
const client = await createX402Client(config);
|
|
861
|
-
return client.fetch(input, init);
|
|
862
|
-
}
|
|
863
|
-
var init_client = __esm({
|
|
864
|
-
"src/x402/client.ts"() {
|
|
865
|
-
"use strict";
|
|
866
|
-
}
|
|
867
|
-
});
|
|
868
|
-
|
|
869
|
-
// src/cli.ts
|
|
870
|
-
import { Command } from "commander";
|
|
871
|
-
|
|
872
|
-
// src/agent/PaymentAgent.ts
|
|
873
|
-
init_chains();
|
|
874
|
-
import { ethers } from "ethers";
|
|
875
|
-
var PaymentAgent = class _PaymentAgent {
|
|
876
|
-
chain;
|
|
877
|
-
chainConfig;
|
|
878
|
-
walletAddress;
|
|
879
|
-
provider;
|
|
880
|
-
usdcContract;
|
|
881
|
-
static PROTOCOL_VERSION = "1.0";
|
|
882
|
-
constructor(config = {}) {
|
|
883
|
-
this.chain = config.chain || "base_sepolia";
|
|
884
|
-
this.chainConfig = getChain(this.chain);
|
|
885
|
-
this.walletAddress = config.walletAddress || process.env.PAYMENT_AGENT_WALLET || "";
|
|
886
|
-
if (!this.walletAddress) {
|
|
887
|
-
throw new Error("walletAddress is required. Set via config or PAYMENT_AGENT_WALLET env var.");
|
|
888
|
-
}
|
|
889
|
-
const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
|
|
890
|
-
this.provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
891
|
-
this.usdcContract = new ethers.Contract(
|
|
892
|
-
this.chainConfig.usdc,
|
|
893
|
-
ERC20_ABI,
|
|
894
|
-
this.provider
|
|
895
|
-
);
|
|
896
|
-
}
|
|
897
|
-
/**
|
|
898
|
-
* Generate payment request(Invoice)
|
|
899
|
-
*/
|
|
900
|
-
createInvoice(params) {
|
|
901
|
-
const expiresMinutes = params.expiresMinutes || 30;
|
|
902
|
-
const expiresAt = new Date(Date.now() + expiresMinutes * 60 * 1e3).toISOString();
|
|
903
|
-
const invoice = {
|
|
904
|
-
type: "payment_request",
|
|
905
|
-
version: _PaymentAgent.PROTOCOL_VERSION,
|
|
906
|
-
order_id: params.orderId,
|
|
907
|
-
service: params.service,
|
|
908
|
-
description: params.description || `${params.service} service`,
|
|
909
|
-
amount: params.amount.toFixed(2),
|
|
910
|
-
token: "USDC",
|
|
911
|
-
chain: this.chain,
|
|
912
|
-
chain_id: this.chainConfig.chainId,
|
|
913
|
-
recipient: this.walletAddress,
|
|
914
|
-
memo: params.orderId,
|
|
915
|
-
expires_at: expiresAt,
|
|
916
|
-
deep_link: this.generateDeepLink(params.amount, params.orderId),
|
|
917
|
-
explorer_url: `${this.chainConfig.explorer}${this.walletAddress}`
|
|
918
|
-
};
|
|
919
|
-
if (params.metadata) {
|
|
920
|
-
invoice.metadata = params.metadata;
|
|
921
|
-
}
|
|
922
|
-
return invoice;
|
|
923
|
-
}
|
|
924
|
-
/**
|
|
925
|
-
* Generate wallet deep link(supports MetaMask etc)
|
|
926
|
-
*/
|
|
927
|
-
generateDeepLink(amount, memo) {
|
|
928
|
-
const amountWei = Math.floor(amount * 1e6);
|
|
929
|
-
return `https://metamask.app.link/send/${this.chainConfig.usdc}@${this.chainConfig.chainId}/transfer?address=${this.walletAddress}&uint256=${amountWei}`;
|
|
930
|
-
}
|
|
931
|
-
/**
|
|
932
|
-
* Verify on-chain payment
|
|
933
|
-
*/
|
|
934
|
-
async verifyPayment(txHash, options = {}) {
|
|
935
|
-
try {
|
|
936
|
-
if (!txHash.startsWith("0x")) {
|
|
937
|
-
txHash = "0x" + txHash;
|
|
938
|
-
}
|
|
939
|
-
const receipt = await this.provider.getTransactionReceipt(txHash);
|
|
940
|
-
if (!receipt) {
|
|
941
|
-
return { verified: false, error: "Transaction not found", pending: true };
|
|
942
|
-
}
|
|
943
|
-
if (receipt.status !== 1) {
|
|
944
|
-
return { verified: false, error: "Transaction failed" };
|
|
945
|
-
}
|
|
946
|
-
const transferTopic = ethers.id("Transfer(address,address,uint256)");
|
|
947
|
-
const usdcAddress = this.chainConfig.usdc.toLowerCase();
|
|
948
|
-
for (const log of receipt.logs) {
|
|
949
|
-
if (log.address.toLowerCase() === usdcAddress && log.topics.length >= 3 && log.topics[0] === transferTopic) {
|
|
950
|
-
const from = ethers.getAddress("0x" + log.topics[1].slice(-40));
|
|
951
|
-
const to = ethers.getAddress("0x" + log.topics[2].slice(-40));
|
|
952
|
-
const amountWei = BigInt(log.data);
|
|
953
|
-
const amount = Number(amountWei) / 1e6;
|
|
954
|
-
if (to.toLowerCase() !== this.walletAddress.toLowerCase()) {
|
|
955
|
-
continue;
|
|
956
|
-
}
|
|
957
|
-
const tolerance = options.tolerance ?? 0.01;
|
|
958
|
-
if (options.expectedAmount) {
|
|
959
|
-
const diff = Math.abs(amount - options.expectedAmount);
|
|
960
|
-
if (diff > options.expectedAmount * tolerance) {
|
|
961
|
-
return {
|
|
962
|
-
verified: false,
|
|
963
|
-
error: `Amount mismatch: expected ${options.expectedAmount}, got ${amount}`
|
|
964
|
-
};
|
|
965
|
-
}
|
|
966
|
-
}
|
|
967
|
-
const currentBlock = await this.provider.getBlockNumber();
|
|
968
|
-
return {
|
|
969
|
-
verified: true,
|
|
970
|
-
tx_hash: txHash,
|
|
971
|
-
amount: amount.toFixed(2),
|
|
972
|
-
token: "USDC",
|
|
973
|
-
from,
|
|
974
|
-
to,
|
|
975
|
-
block_number: receipt.blockNumber,
|
|
976
|
-
confirmations: currentBlock - receipt.blockNumber,
|
|
977
|
-
explorer_url: `${this.chainConfig.explorerTx}${txHash}`
|
|
978
|
-
};
|
|
979
|
-
}
|
|
980
|
-
}
|
|
981
|
-
return { verified: false, error: "No USDC transfer to recipient found in transaction" };
|
|
982
|
-
} catch (error) {
|
|
983
|
-
return { verified: false, error: error.message };
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
/**
|
|
987
|
-
* Scan recent transfers (match by amount)
|
|
988
|
-
*/
|
|
989
|
-
async scanRecentTransfers(expectedAmount, timeoutMinutes = 30) {
|
|
990
|
-
try {
|
|
991
|
-
const currentBlock = await this.provider.getBlockNumber();
|
|
992
|
-
const blocksPerMinute = Math.ceil(60 / this.chainConfig.avgBlockTime);
|
|
993
|
-
const fromBlock = currentBlock - timeoutMinutes * blocksPerMinute;
|
|
994
|
-
const transferTopic = ethers.id("Transfer(address,address,uint256)");
|
|
995
|
-
const recipientTopic = ethers.zeroPadValue(this.walletAddress, 32);
|
|
996
|
-
const logs = await this.provider.getLogs({
|
|
997
|
-
address: this.chainConfig.usdc,
|
|
998
|
-
topics: [transferTopic, null, recipientTopic],
|
|
999
|
-
fromBlock,
|
|
1000
|
-
toBlock: "latest"
|
|
1001
|
-
});
|
|
1002
|
-
for (const log of logs) {
|
|
1003
|
-
const amountWei = BigInt(log.data);
|
|
1004
|
-
const amount = Number(amountWei) / 1e6;
|
|
1005
|
-
if (Math.abs(amount - expectedAmount) < 0.01) {
|
|
1006
|
-
const from = ethers.getAddress("0x" + log.topics[1].slice(-40));
|
|
1007
|
-
return {
|
|
1008
|
-
verified: true,
|
|
1009
|
-
tx_hash: log.transactionHash,
|
|
1010
|
-
amount: amount.toFixed(2),
|
|
1011
|
-
token: "USDC",
|
|
1012
|
-
from,
|
|
1013
|
-
to: this.walletAddress,
|
|
1014
|
-
block_number: log.blockNumber,
|
|
1015
|
-
explorer_url: `${this.chainConfig.explorerTx}${log.transactionHash}`
|
|
1016
|
-
};
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
return { verified: false, error: "No matching payment found" };
|
|
1020
|
-
} catch (error) {
|
|
1021
|
-
return { verified: false, error: error.message };
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
/**
|
|
1025
|
-
* Get wallet balance
|
|
1026
|
-
*/
|
|
1027
|
-
async getBalance(address) {
|
|
1028
|
-
const addr = address || this.walletAddress;
|
|
1029
|
-
const [ethBalance, usdcBalance] = await Promise.all([
|
|
1030
|
-
this.provider.getBalance(addr),
|
|
1031
|
-
this.usdcContract.balanceOf(addr)
|
|
1032
|
-
]);
|
|
1033
|
-
return {
|
|
1034
|
-
address: addr,
|
|
1035
|
-
eth: ethers.formatEther(ethBalance),
|
|
1036
|
-
usdc: (Number(usdcBalance) / 1e6).toFixed(2),
|
|
1037
|
-
chain: this.chain
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
/**
|
|
1041
|
-
* Format Invoice as human-readable message
|
|
1042
|
-
*/
|
|
1043
|
-
formatInvoiceMessage(invoice, includeJson = true) {
|
|
1044
|
-
let msg = `\u{1F3AC} **Payment Request**
|
|
1045
|
-
|
|
1046
|
-
**Service:** ${invoice.service}
|
|
1047
|
-
**Price:** ${invoice.amount} USDC (${this.chainConfig.name})
|
|
1048
|
-
|
|
1049
|
-
**\u{1F4B3} Payment Options:**
|
|
1050
|
-
|
|
1051
|
-
1\uFE0F\u20E3 **Direct Transfer:**
|
|
1052
|
-
Send exactly \`${invoice.amount} USDC\` to:
|
|
1053
|
-
\`${invoice.recipient}\`
|
|
1054
|
-
(Network: ${this.chainConfig.name})
|
|
1055
|
-
|
|
1056
|
-
2\uFE0F\u20E3 **One-Click Pay (MetaMask):**
|
|
1057
|
-
${invoice.deep_link}
|
|
1058
|
-
|
|
1059
|
-
\u23F1\uFE0F Expires: ${invoice.expires_at}`;
|
|
1060
|
-
if (includeJson) {
|
|
1061
|
-
msg += `
|
|
1062
|
-
|
|
1063
|
-
3\uFE0F\u20E3 **For AI Agents:**
|
|
1064
|
-
\`\`\`json
|
|
1065
|
-
${JSON.stringify(invoice, null, 2)}
|
|
1066
|
-
\`\`\``;
|
|
1067
|
-
}
|
|
1068
|
-
msg += `
|
|
1069
|
-
|
|
1070
|
-
After payment, reply with your tx hash:
|
|
1071
|
-
\`paid: 0x...\``;
|
|
1072
|
-
return msg;
|
|
1073
|
-
}
|
|
1074
|
-
};
|
|
1075
|
-
|
|
1076
|
-
// src/index.ts
|
|
1077
|
-
init_AgentWallet();
|
|
1078
|
-
|
|
1079
|
-
// src/wallet/Wallet.ts
|
|
1080
|
-
init_chains();
|
|
1081
|
-
import { ethers as ethers3 } from "ethers";
|
|
1082
|
-
var Wallet = class {
|
|
1083
|
-
chain;
|
|
1084
|
-
chainConfig;
|
|
1085
|
-
address;
|
|
1086
|
-
wallet;
|
|
1087
|
-
provider;
|
|
1088
|
-
usdcContract;
|
|
1089
|
-
constructor(config = {}) {
|
|
1090
|
-
this.chain = config.chain || "base_sepolia";
|
|
1091
|
-
this.chainConfig = getChain(this.chain);
|
|
1092
|
-
const privateKey = config.privateKey || process.env.PAYMENT_AGENT_PRIVATE_KEY;
|
|
1093
|
-
if (!privateKey) {
|
|
1094
|
-
throw new Error("privateKey is required. Set via config or PAYMENT_AGENT_PRIVATE_KEY env var.");
|
|
1095
|
-
}
|
|
1096
|
-
const rpcUrl = config.rpcUrl || this.chainConfig.rpc;
|
|
1097
|
-
this.provider = new ethers3.JsonRpcProvider(rpcUrl);
|
|
1098
|
-
this.wallet = new ethers3.Wallet(privateKey, this.provider);
|
|
1099
|
-
this.address = this.wallet.address;
|
|
1100
|
-
this.usdcContract = new ethers3.Contract(
|
|
1101
|
-
this.chainConfig.usdc,
|
|
1102
|
-
ERC20_ABI,
|
|
1103
|
-
this.wallet
|
|
1104
|
-
);
|
|
1105
|
-
}
|
|
1106
|
-
/**
|
|
1107
|
-
* Get wallet balance
|
|
1108
|
-
*/
|
|
1109
|
-
async getBalance() {
|
|
1110
|
-
const [ethBalance, usdcBalance] = await Promise.all([
|
|
1111
|
-
this.provider.getBalance(this.address),
|
|
1112
|
-
this.usdcContract.balanceOf(this.address)
|
|
1113
|
-
]);
|
|
1114
|
-
return {
|
|
1115
|
-
address: this.address,
|
|
1116
|
-
eth: ethers3.formatEther(ethBalance),
|
|
1117
|
-
usdc: (Number(usdcBalance) / 1e6).toFixed(2),
|
|
1118
|
-
chain: this.chain
|
|
1119
|
-
};
|
|
1120
|
-
}
|
|
1121
|
-
/**
|
|
1122
|
-
* Send USDC transfer
|
|
1123
|
-
*/
|
|
1124
|
-
async transfer(to, amount) {
|
|
1125
|
-
try {
|
|
1126
|
-
to = ethers3.getAddress(to);
|
|
1127
|
-
const amountWei = BigInt(Math.floor(amount * 1e6));
|
|
1128
|
-
const balance = await this.usdcContract.balanceOf(this.address);
|
|
1129
|
-
if (BigInt(balance) < amountWei) {
|
|
1130
|
-
return {
|
|
1131
|
-
success: false,
|
|
1132
|
-
error: `Insufficient USDC balance: ${Number(balance) / 1e6} < ${amount}`
|
|
1133
|
-
};
|
|
1134
|
-
}
|
|
1135
|
-
const tx = await this.usdcContract.transfer(to, amountWei);
|
|
1136
|
-
const receipt = await tx.wait();
|
|
1137
|
-
if (receipt.status === 1) {
|
|
1138
|
-
return {
|
|
1139
|
-
success: true,
|
|
1140
|
-
tx_hash: tx.hash,
|
|
1141
|
-
from: this.address,
|
|
1142
|
-
to,
|
|
1143
|
-
amount,
|
|
1144
|
-
gas_used: Number(receipt.gasUsed),
|
|
1145
|
-
block_number: receipt.blockNumber,
|
|
1146
|
-
explorer_url: `${this.chainConfig.explorerTx}${tx.hash}`
|
|
1147
|
-
};
|
|
1148
|
-
} else {
|
|
1149
|
-
return {
|
|
1150
|
-
success: false,
|
|
1151
|
-
tx_hash: tx.hash,
|
|
1152
|
-
error: "Transaction reverted"
|
|
1153
|
-
};
|
|
1154
|
-
}
|
|
1155
|
-
} catch (error) {
|
|
1156
|
-
return {
|
|
1157
|
-
success: false,
|
|
1158
|
-
error: error.message
|
|
1159
|
-
};
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
/**
|
|
1163
|
-
* Get ETH balance
|
|
1164
|
-
*/
|
|
1165
|
-
async getEthBalance() {
|
|
1166
|
-
const balance = await this.provider.getBalance(this.address);
|
|
1167
|
-
return ethers3.formatEther(balance);
|
|
1168
|
-
}
|
|
1169
|
-
/**
|
|
1170
|
-
* Get USDC balance
|
|
1171
|
-
*/
|
|
1172
|
-
async getUsdcBalance() {
|
|
1173
|
-
const balance = await this.usdcContract.balanceOf(this.address);
|
|
1174
|
-
return (Number(balance) / 1e6).toFixed(2);
|
|
1175
|
-
}
|
|
1176
|
-
};
|
|
1177
|
-
|
|
1178
|
-
// src/audit/AuditLog.ts
|
|
1179
|
-
import * as fs2 from "fs";
|
|
1180
|
-
import * as path2 from "path";
|
|
1181
|
-
import * as crypto from "crypto";
|
|
1182
|
-
var AuditLog = class {
|
|
1183
|
-
basePath;
|
|
1184
|
-
lastHash = "0000000000000000";
|
|
1185
|
-
constructor(basePath) {
|
|
1186
|
-
this.basePath = basePath || path2.join(process.cwd(), "data", "audit");
|
|
1187
|
-
this.ensureDir();
|
|
1188
|
-
this.loadLastHash();
|
|
1189
|
-
}
|
|
1190
|
-
/**
|
|
1191
|
-
* Record audit log
|
|
1192
|
-
*/
|
|
1193
|
-
async log(params) {
|
|
1194
|
-
const now = /* @__PURE__ */ new Date();
|
|
1195
|
-
const entry = {
|
|
1196
|
-
timestamp: now.getTime() / 1e3,
|
|
1197
|
-
datetime: now.toISOString(),
|
|
1198
|
-
action: params.action,
|
|
1199
|
-
request_id: params.request_id,
|
|
1200
|
-
from: params.from,
|
|
1201
|
-
to: params.to,
|
|
1202
|
-
amount: params.amount,
|
|
1203
|
-
tx_hash: params.tx_hash,
|
|
1204
|
-
reason: params.reason,
|
|
1205
|
-
requester: params.requester,
|
|
1206
|
-
prev_hash: this.lastHash,
|
|
1207
|
-
hash: "",
|
|
1208
|
-
// Filled after calculation
|
|
1209
|
-
metadata: params.metadata
|
|
1210
|
-
};
|
|
1211
|
-
entry.hash = this.calculateHash(entry);
|
|
1212
|
-
this.lastHash = entry.hash;
|
|
1213
|
-
const filePath = this.getFilePath(now);
|
|
1214
|
-
const line = JSON.stringify(entry) + "\n";
|
|
1215
|
-
fs2.appendFileSync(filePath, line, "utf-8");
|
|
1216
|
-
return entry;
|
|
1217
|
-
}
|
|
1218
|
-
/**
|
|
1219
|
-
* Read logs for specified date
|
|
1220
|
-
*/
|
|
1221
|
-
read(date) {
|
|
1222
|
-
const filePath = this.getFilePath(date || /* @__PURE__ */ new Date());
|
|
1223
|
-
if (!fs2.existsSync(filePath)) {
|
|
1224
|
-
return [];
|
|
1225
|
-
}
|
|
1226
|
-
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1227
|
-
const lines = content.trim().split("\n").filter(Boolean);
|
|
1228
|
-
return lines.map((line) => JSON.parse(line));
|
|
1229
|
-
}
|
|
1230
|
-
/**
|
|
1231
|
-
* Verify log integrity
|
|
1232
|
-
*/
|
|
1233
|
-
verify(date) {
|
|
1234
|
-
const entries = this.read(date);
|
|
1235
|
-
const errors = [];
|
|
1236
|
-
for (let i = 0; i < entries.length; i++) {
|
|
1237
|
-
const entry = entries[i];
|
|
1238
|
-
const expectedHash = this.calculateHash(entry);
|
|
1239
|
-
if (entry.hash !== expectedHash) {
|
|
1240
|
-
errors.push(`Entry ${i}: hash mismatch (expected ${expectedHash}, got ${entry.hash})`);
|
|
1241
|
-
}
|
|
1242
|
-
if (i > 0 && entry.prev_hash !== entries[i - 1].hash) {
|
|
1243
|
-
errors.push(`Entry ${i}: prev_hash mismatch`);
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
return { valid: errors.length === 0, errors };
|
|
1247
|
-
}
|
|
1248
|
-
/**
|
|
1249
|
-
* Search logs
|
|
1250
|
-
*/
|
|
1251
|
-
search(filter) {
|
|
1252
|
-
const results = [];
|
|
1253
|
-
const startDate = filter.startDate || new Date(Date.now() - 30 * 24 * 60 * 60 * 1e3);
|
|
1254
|
-
const endDate = filter.endDate || /* @__PURE__ */ new Date();
|
|
1255
|
-
const current = new Date(startDate);
|
|
1256
|
-
while (current <= endDate) {
|
|
1257
|
-
const entries = this.read(current);
|
|
1258
|
-
for (const entry of entries) {
|
|
1259
|
-
let match = true;
|
|
1260
|
-
if (filter.action && entry.action !== filter.action) match = false;
|
|
1261
|
-
if (filter.request_id && entry.request_id !== filter.request_id) match = false;
|
|
1262
|
-
if (filter.from && entry.from?.toLowerCase() !== filter.from.toLowerCase()) match = false;
|
|
1263
|
-
if (filter.to && entry.to?.toLowerCase() !== filter.to.toLowerCase()) match = false;
|
|
1264
|
-
if (match) {
|
|
1265
|
-
results.push(entry);
|
|
1266
|
-
}
|
|
1267
|
-
}
|
|
1268
|
-
current.setDate(current.getDate() + 1);
|
|
1269
|
-
}
|
|
1270
|
-
return results;
|
|
1271
|
-
}
|
|
1272
|
-
/**
|
|
1273
|
-
* Get log file path
|
|
1274
|
-
*/
|
|
1275
|
-
getFilePath(date) {
|
|
1276
|
-
const dateStr = date.toISOString().slice(0, 10);
|
|
1277
|
-
return path2.join(this.basePath, `audit_${dateStr}.jsonl`);
|
|
1278
|
-
}
|
|
1279
|
-
/**
|
|
1280
|
-
* Calculate entry hash
|
|
1281
|
-
*/
|
|
1282
|
-
calculateHash(entry) {
|
|
1283
|
-
const data = {
|
|
1284
|
-
timestamp: entry.timestamp,
|
|
1285
|
-
action: entry.action,
|
|
1286
|
-
request_id: entry.request_id,
|
|
1287
|
-
from: entry.from,
|
|
1288
|
-
to: entry.to,
|
|
1289
|
-
amount: entry.amount,
|
|
1290
|
-
tx_hash: entry.tx_hash,
|
|
1291
|
-
prev_hash: entry.prev_hash
|
|
1292
|
-
};
|
|
1293
|
-
const str = JSON.stringify(data);
|
|
1294
|
-
return crypto.createHash("sha256").update(str).digest("hex").slice(0, 16);
|
|
1295
|
-
}
|
|
1296
|
-
/**
|
|
1297
|
-
* Load last log entry hash
|
|
1298
|
-
*/
|
|
1299
|
-
loadLastHash() {
|
|
1300
|
-
const today = /* @__PURE__ */ new Date();
|
|
1301
|
-
for (let i = 0; i < 2; i++) {
|
|
1302
|
-
const date = new Date(today);
|
|
1303
|
-
date.setDate(date.getDate() - i);
|
|
1304
|
-
const entries = this.read(date);
|
|
1305
|
-
if (entries.length > 0) {
|
|
1306
|
-
this.lastHash = entries[entries.length - 1].hash;
|
|
1307
|
-
return;
|
|
1308
|
-
}
|
|
1309
|
-
}
|
|
1310
|
-
}
|
|
1311
|
-
/**
|
|
1312
|
-
* Ensure directory exists
|
|
1313
|
-
*/
|
|
1314
|
-
ensureDir() {
|
|
1315
|
-
if (!fs2.existsSync(this.basePath)) {
|
|
1316
|
-
fs2.mkdirSync(this.basePath, { recursive: true });
|
|
1317
|
-
}
|
|
1318
|
-
}
|
|
1319
|
-
};
|
|
1320
|
-
|
|
1321
|
-
// src/wallet/SecureWallet.ts
|
|
1322
|
-
var DEFAULT_LIMITS = {
|
|
1323
|
-
singleMax: 100,
|
|
1324
|
-
// Single max $100
|
|
1325
|
-
dailyMax: 1e3,
|
|
1326
|
-
// Daily max $1000
|
|
1327
|
-
requireWhitelist: true
|
|
1328
|
-
};
|
|
1329
|
-
var SecureWallet = class {
|
|
1330
|
-
wallet;
|
|
1331
|
-
limits;
|
|
1332
|
-
whitelist;
|
|
1333
|
-
auditLog;
|
|
1334
|
-
dailyTotal = 0;
|
|
1335
|
-
dailyDate = "";
|
|
1336
|
-
pendingTransfers = /* @__PURE__ */ new Map();
|
|
1337
|
-
constructor(config = {}) {
|
|
1338
|
-
this.wallet = new Wallet({
|
|
1339
|
-
chain: config.chain,
|
|
1340
|
-
privateKey: config.privateKey
|
|
1341
|
-
});
|
|
1342
|
-
this.limits = { ...DEFAULT_LIMITS, ...config.limits };
|
|
1343
|
-
this.whitelist = new Set((config.whitelist || []).map((a) => a.toLowerCase()));
|
|
1344
|
-
this.auditLog = new AuditLog(config.auditPath);
|
|
1345
|
-
}
|
|
1346
|
-
/**
|
|
1347
|
-
* Get wallet address
|
|
1348
|
-
*/
|
|
1349
|
-
get address() {
|
|
1350
|
-
return this.wallet.address;
|
|
1351
|
-
}
|
|
1352
|
-
/**
|
|
1353
|
-
* Get balance
|
|
1354
|
-
*/
|
|
1355
|
-
async getBalance() {
|
|
1356
|
-
return this.wallet.getBalance();
|
|
1357
|
-
}
|
|
1358
|
-
/**
|
|
1359
|
-
* Secure transfer (with limit and whitelist checks)
|
|
1360
|
-
*
|
|
1361
|
-
* Supports two calling methods:
|
|
1362
|
-
* - transfer({ to, amount, reason?, requester? })
|
|
1363
|
-
* - transfer(to, amount)
|
|
1364
|
-
*/
|
|
1365
|
-
async transfer(paramsOrTo, amountArg) {
|
|
1366
|
-
let params;
|
|
1367
|
-
if (typeof paramsOrTo === "string") {
|
|
1368
|
-
params = {
|
|
1369
|
-
to: paramsOrTo,
|
|
1370
|
-
amount: typeof amountArg === "string" ? parseFloat(amountArg) : amountArg || 0
|
|
1371
|
-
};
|
|
1372
|
-
} else {
|
|
1373
|
-
params = paramsOrTo;
|
|
1374
|
-
}
|
|
1375
|
-
const { to, amount, reason, requester } = params;
|
|
1376
|
-
const toAddress = to.toLowerCase();
|
|
1377
|
-
const requestId = `tr_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
|
|
1378
|
-
await this.auditLog.log({
|
|
1379
|
-
action: "transfer_request",
|
|
1380
|
-
request_id: requestId,
|
|
1381
|
-
from: this.wallet.address,
|
|
1382
|
-
to,
|
|
1383
|
-
amount,
|
|
1384
|
-
reason,
|
|
1385
|
-
requester
|
|
1386
|
-
});
|
|
1387
|
-
if (this.limits.requireWhitelist && !this.whitelist.has(toAddress)) {
|
|
1388
|
-
await this.auditLog.log({
|
|
1389
|
-
action: "transfer_failed",
|
|
1390
|
-
request_id: requestId,
|
|
1391
|
-
metadata: { error: "Address not in whitelist" }
|
|
1392
|
-
});
|
|
1393
|
-
return { success: false, error: `Address not in whitelist: ${to}` };
|
|
1394
|
-
}
|
|
1395
|
-
if (amount > this.limits.singleMax) {
|
|
1396
|
-
const pending = {
|
|
1397
|
-
id: requestId,
|
|
1398
|
-
to,
|
|
1399
|
-
amount,
|
|
1400
|
-
reason,
|
|
1401
|
-
requester,
|
|
1402
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1403
|
-
status: "pending"
|
|
1404
|
-
};
|
|
1405
|
-
this.pendingTransfers.set(requestId, pending);
|
|
1406
|
-
await this.auditLog.log({
|
|
1407
|
-
action: "transfer_request",
|
|
1408
|
-
request_id: requestId,
|
|
1409
|
-
metadata: { pending: true, reason: "Exceeds single limit" }
|
|
1410
|
-
});
|
|
1411
|
-
return {
|
|
1412
|
-
success: false,
|
|
1413
|
-
error: `Amount ${amount} exceeds single limit ${this.limits.singleMax}. Pending approval: ${requestId}`
|
|
1414
|
-
};
|
|
1415
|
-
}
|
|
1416
|
-
this.updateDailyTotal();
|
|
1417
|
-
if (this.dailyTotal + amount > this.limits.dailyMax) {
|
|
1418
|
-
const pending = {
|
|
1419
|
-
id: requestId,
|
|
1420
|
-
to,
|
|
1421
|
-
amount,
|
|
1422
|
-
reason,
|
|
1423
|
-
requester,
|
|
1424
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1425
|
-
status: "pending"
|
|
1426
|
-
};
|
|
1427
|
-
this.pendingTransfers.set(requestId, pending);
|
|
1428
|
-
await this.auditLog.log({
|
|
1429
|
-
action: "transfer_request",
|
|
1430
|
-
request_id: requestId,
|
|
1431
|
-
metadata: { pending: true, reason: "Exceeds daily limit" }
|
|
1432
|
-
});
|
|
1433
|
-
return {
|
|
1434
|
-
success: false,
|
|
1435
|
-
error: `Daily limit would be exceeded (${this.dailyTotal} + ${amount} > ${this.limits.dailyMax}). Pending approval: ${requestId}`
|
|
1436
|
-
};
|
|
1437
|
-
}
|
|
1438
|
-
const result = await this.wallet.transfer(to, amount);
|
|
1439
|
-
if (result.success) {
|
|
1440
|
-
this.dailyTotal += amount;
|
|
1441
|
-
await this.auditLog.log({
|
|
1442
|
-
action: "transfer_executed",
|
|
1443
|
-
request_id: requestId,
|
|
1444
|
-
from: this.wallet.address,
|
|
1445
|
-
to,
|
|
1446
|
-
amount,
|
|
1447
|
-
tx_hash: result.tx_hash,
|
|
1448
|
-
reason,
|
|
1449
|
-
requester
|
|
1450
|
-
});
|
|
1451
|
-
} else {
|
|
1452
|
-
await this.auditLog.log({
|
|
1453
|
-
action: "transfer_failed",
|
|
1454
|
-
request_id: requestId,
|
|
1455
|
-
metadata: { error: result.error }
|
|
1456
|
-
});
|
|
1457
|
-
}
|
|
1458
|
-
return result;
|
|
1459
|
-
}
|
|
1460
|
-
/**
|
|
1461
|
-
* Approve pending transfer
|
|
1462
|
-
*/
|
|
1463
|
-
async approve(requestId, approver) {
|
|
1464
|
-
const pending = this.pendingTransfers.get(requestId);
|
|
1465
|
-
if (!pending) {
|
|
1466
|
-
return { success: false, error: `Pending transfer not found: ${requestId}` };
|
|
1467
|
-
}
|
|
1468
|
-
if (pending.status !== "pending") {
|
|
1469
|
-
return { success: false, error: `Transfer already ${pending.status}` };
|
|
1470
|
-
}
|
|
1471
|
-
await this.auditLog.log({
|
|
1472
|
-
action: "transfer_approved",
|
|
1473
|
-
request_id: requestId,
|
|
1474
|
-
metadata: { approver }
|
|
1475
|
-
});
|
|
1476
|
-
pending.status = "approved";
|
|
1477
|
-
const result = await this.wallet.transfer(pending.to, pending.amount);
|
|
1478
|
-
if (result.success) {
|
|
1479
|
-
pending.status = "executed";
|
|
1480
|
-
this.dailyTotal += pending.amount;
|
|
1481
|
-
await this.auditLog.log({
|
|
1482
|
-
action: "transfer_executed",
|
|
1483
|
-
request_id: requestId,
|
|
1484
|
-
from: this.wallet.address,
|
|
1485
|
-
to: pending.to,
|
|
1486
|
-
amount: pending.amount,
|
|
1487
|
-
tx_hash: result.tx_hash,
|
|
1488
|
-
reason: pending.reason,
|
|
1489
|
-
requester: pending.requester,
|
|
1490
|
-
metadata: { approved_by: approver }
|
|
1491
|
-
});
|
|
1492
|
-
} else {
|
|
1493
|
-
await this.auditLog.log({
|
|
1494
|
-
action: "transfer_failed",
|
|
1495
|
-
request_id: requestId,
|
|
1496
|
-
metadata: { error: result.error }
|
|
1497
|
-
});
|
|
1498
|
-
}
|
|
1499
|
-
return result;
|
|
1500
|
-
}
|
|
1501
|
-
/**
|
|
1502
|
-
* Reject pending transfer
|
|
1503
|
-
*/
|
|
1504
|
-
async reject(requestId, rejecter, reason) {
|
|
1505
|
-
const pending = this.pendingTransfers.get(requestId);
|
|
1506
|
-
if (!pending) {
|
|
1507
|
-
throw new Error(`Pending transfer not found: ${requestId}`);
|
|
1508
|
-
}
|
|
1509
|
-
pending.status = "rejected";
|
|
1510
|
-
await this.auditLog.log({
|
|
1511
|
-
action: "transfer_rejected",
|
|
1512
|
-
request_id: requestId,
|
|
1513
|
-
metadata: { rejected_by: rejecter, reason }
|
|
1514
|
-
});
|
|
1515
|
-
}
|
|
1516
|
-
/**
|
|
1517
|
-
* Add to whitelist
|
|
1518
|
-
*/
|
|
1519
|
-
async addToWhitelist(address, addedBy) {
|
|
1520
|
-
const addr = address.toLowerCase();
|
|
1521
|
-
this.whitelist.add(addr);
|
|
1522
|
-
await this.auditLog.log({
|
|
1523
|
-
action: "whitelist_add",
|
|
1524
|
-
request_id: `wl_${Date.now()}`,
|
|
1525
|
-
to: address,
|
|
1526
|
-
metadata: { added_by: addedBy }
|
|
1527
|
-
});
|
|
1528
|
-
}
|
|
1529
|
-
/**
|
|
1530
|
-
* Remove from whitelist
|
|
1531
|
-
*/
|
|
1532
|
-
async removeFromWhitelist(address, removedBy) {
|
|
1533
|
-
const addr = address.toLowerCase();
|
|
1534
|
-
this.whitelist.delete(addr);
|
|
1535
|
-
await this.auditLog.log({
|
|
1536
|
-
action: "whitelist_remove",
|
|
1537
|
-
request_id: `wl_${Date.now()}`,
|
|
1538
|
-
to: address,
|
|
1539
|
-
metadata: { removed_by: removedBy }
|
|
1540
|
-
});
|
|
1541
|
-
}
|
|
1542
|
-
/**
|
|
1543
|
-
* Check if address is whitelisted
|
|
1544
|
-
*/
|
|
1545
|
-
isWhitelisted(address) {
|
|
1546
|
-
return this.whitelist.has(address.toLowerCase());
|
|
1547
|
-
}
|
|
1548
|
-
/**
|
|
1549
|
-
* Get pending transfers list
|
|
1550
|
-
*/
|
|
1551
|
-
getPendingTransfers() {
|
|
1552
|
-
return Array.from(this.pendingTransfers.values()).filter((p) => p.status === "pending");
|
|
1553
|
-
}
|
|
1554
|
-
/**
|
|
1555
|
-
* Get current limit config
|
|
1556
|
-
*/
|
|
1557
|
-
getLimits() {
|
|
1558
|
-
return { ...this.limits };
|
|
1559
|
-
}
|
|
1560
|
-
/**
|
|
1561
|
-
* Get daily used amount
|
|
1562
|
-
*/
|
|
1563
|
-
getDailyUsed() {
|
|
1564
|
-
this.updateDailyTotal();
|
|
1565
|
-
return this.dailyTotal;
|
|
1566
|
-
}
|
|
1567
|
-
/**
|
|
1568
|
-
* Update daily limit counter
|
|
1569
|
-
*/
|
|
1570
|
-
updateDailyTotal() {
|
|
1571
|
-
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
1572
|
-
if (this.dailyDate !== today) {
|
|
1573
|
-
this.dailyDate = today;
|
|
1574
|
-
this.dailyTotal = 0;
|
|
1575
|
-
}
|
|
1576
|
-
}
|
|
1577
|
-
};
|
|
1578
|
-
|
|
1579
|
-
// src/wallet/createWallet.ts
|
|
1580
|
-
import { ethers as ethers4 } from "ethers";
|
|
1581
|
-
import { join as join3, dirname } from "path";
|
|
1582
|
-
var DEFAULT_STORAGE_DIR = join3(process.env.HOME || "~", ".moltspay");
|
|
1583
|
-
|
|
1584
|
-
// src/wallet/PermitWallet.ts
|
|
1585
|
-
init_chains();
|
|
1586
|
-
import { ethers as ethers5 } from "ethers";
|
|
1587
|
-
var PERMIT_ABI2 = [
|
|
1588
|
-
...ERC20_ABI,
|
|
1589
|
-
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
1590
|
-
"function transferFrom(address from, address to, uint256 amount) returns (bool)",
|
|
1591
|
-
"function allowance(address owner, address spender) view returns (uint256)"
|
|
1592
|
-
];
|
|
1593
|
-
|
|
1594
|
-
// src/wallet/signPermit.ts
|
|
1595
|
-
init_chains();
|
|
1596
|
-
import { ethers as ethers6 } from "ethers";
|
|
1597
|
-
|
|
1598
|
-
// src/wallet/AllowanceWallet.ts
|
|
1599
|
-
init_chains();
|
|
1600
|
-
import { ethers as ethers7 } from "ethers";
|
|
1601
|
-
var PERMIT_ABI3 = [
|
|
1602
|
-
...ERC20_ABI,
|
|
1603
|
-
"function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s)",
|
|
1604
|
-
"function transferFrom(address from, address to, uint256 amount) returns (bool)",
|
|
1605
|
-
"function allowance(address owner, address spender) view returns (uint256)",
|
|
1606
|
-
"function nonces(address owner) view returns (uint256)"
|
|
1607
|
-
];
|
|
1608
|
-
|
|
1609
|
-
// src/permit/Permit.ts
|
|
1610
|
-
init_chains();
|
|
1611
|
-
import { ethers as ethers8 } from "ethers";
|
|
1612
|
-
|
|
1613
|
-
// src/verify/index.ts
|
|
1614
|
-
init_chains();
|
|
1615
|
-
import { ethers as ethers9 } from "ethers";
|
|
1616
|
-
var TRANSFER_EVENT_TOPIC = ethers9.id("Transfer(address,address,uint256)");
|
|
1617
|
-
|
|
1618
|
-
// src/receipt/index.ts
|
|
1619
|
-
init_chains();
|
|
1620
|
-
|
|
1621
|
-
// src/templates/index.ts
|
|
1622
|
-
init_chains();
|
|
1623
|
-
|
|
1624
|
-
// src/index.ts
|
|
1625
|
-
init_chains();
|
|
1626
|
-
|
|
1627
|
-
// src/x402/index.ts
|
|
1628
|
-
init_client();
|
|
1629
|
-
init_chains();
|
|
1630
|
-
import { ethers as ethers10 } from "ethers";
|
|
1631
|
-
|
|
1632
|
-
// src/index.ts
|
|
1633
|
-
init_cdp();
|
|
1634
|
-
|
|
1635
|
-
// src/cli.ts
|
|
1636
|
-
var program = new Command();
|
|
1637
|
-
program.name("payment-agent").description("Blockchain payment infrastructure for AI Agents").version("0.1.0");
|
|
1638
|
-
program.command("balance").description("Get wallet balance").option("-c, --chain <chain>", "Chain name", "base_sepolia").option("-a, --address <address>", "Wallet address").action(async (options) => {
|
|
1639
|
-
try {
|
|
1640
|
-
const agent = new PaymentAgent({
|
|
1641
|
-
chain: options.chain,
|
|
1642
|
-
walletAddress: options.address || process.env.PAYMENT_AGENT_WALLET
|
|
1643
|
-
});
|
|
1644
|
-
const balance = await agent.getBalance();
|
|
1645
|
-
console.log(JSON.stringify(balance, null, 2));
|
|
1646
|
-
} catch (error) {
|
|
1647
|
-
console.error("Error:", error.message);
|
|
1648
|
-
process.exit(1);
|
|
1649
|
-
}
|
|
1650
|
-
});
|
|
1651
|
-
program.command("invoice").description("Generate payment invoice").requiredOption("-o, --order <orderId>", "Order ID").requiredOption("-a, --amount <amount>", "Amount in USDC").option("-s, --service <service>", "Service type", "payment").option("-c, --chain <chain>", "Chain name", "base_sepolia").option("--json", "Output raw JSON").action(async (options) => {
|
|
1652
|
-
try {
|
|
1653
|
-
const agent = new PaymentAgent({
|
|
1654
|
-
chain: options.chain,
|
|
1655
|
-
walletAddress: process.env.PAYMENT_AGENT_WALLET
|
|
1656
|
-
});
|
|
1657
|
-
const invoice = agent.createInvoice({
|
|
1658
|
-
orderId: options.order,
|
|
1659
|
-
amount: parseFloat(options.amount),
|
|
1660
|
-
service: options.service
|
|
1661
|
-
});
|
|
1662
|
-
if (options.json) {
|
|
1663
|
-
console.log(JSON.stringify(invoice, null, 2));
|
|
1664
|
-
} else {
|
|
1665
|
-
console.log(agent.formatInvoiceMessage(invoice));
|
|
1666
|
-
}
|
|
1667
|
-
} catch (error) {
|
|
1668
|
-
console.error("Error:", error.message);
|
|
1669
|
-
process.exit(1);
|
|
1670
|
-
}
|
|
1671
|
-
});
|
|
1672
|
-
program.command("verify").description("Verify a payment transaction").requiredOption("-t, --tx <txHash>", "Transaction hash").option("-a, --amount <amount>", "Expected amount").option("-c, --chain <chain>", "Chain name", "base_sepolia").action(async (options) => {
|
|
1673
|
-
try {
|
|
1674
|
-
const agent = new PaymentAgent({
|
|
1675
|
-
chain: options.chain,
|
|
1676
|
-
walletAddress: process.env.PAYMENT_AGENT_WALLET
|
|
1677
|
-
});
|
|
1678
|
-
const result = await agent.verifyPayment(options.tx, {
|
|
1679
|
-
expectedAmount: options.amount ? parseFloat(options.amount) : void 0
|
|
1680
|
-
});
|
|
1681
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1682
|
-
process.exit(result.verified ? 0 : 1);
|
|
1683
|
-
} catch (error) {
|
|
1684
|
-
console.error("Error:", error.message);
|
|
1685
|
-
process.exit(1);
|
|
1686
|
-
}
|
|
1687
|
-
});
|
|
1688
|
-
program.command("transfer").description("Transfer USDC (requires private key)").requiredOption("--to <address>", "Recipient address").requiredOption("-a, --amount <amount>", "Amount in USDC").option("-r, --reason <reason>", "Transfer reason").option("-c, --chain <chain>", "Chain name", "base_sepolia").option("--secure", "Use secure wallet with limits").action(async (options) => {
|
|
1689
|
-
try {
|
|
1690
|
-
if (options.secure) {
|
|
1691
|
-
const wallet = new SecureWallet({ chain: options.chain });
|
|
1692
|
-
const result = await wallet.transfer({
|
|
1693
|
-
to: options.to,
|
|
1694
|
-
amount: parseFloat(options.amount),
|
|
1695
|
-
reason: options.reason
|
|
1696
|
-
});
|
|
1697
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1698
|
-
process.exit(result.success ? 0 : 1);
|
|
1699
|
-
} else {
|
|
1700
|
-
const wallet = new Wallet({ chain: options.chain });
|
|
1701
|
-
const result = await wallet.transfer(options.to, parseFloat(options.amount));
|
|
1702
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1703
|
-
process.exit(result.success ? 0 : 1);
|
|
1704
|
-
}
|
|
1705
|
-
} catch (error) {
|
|
1706
|
-
console.error("Error:", error.message);
|
|
1707
|
-
process.exit(1);
|
|
1708
|
-
}
|
|
1709
|
-
});
|
|
1710
|
-
program.command("chains").description("List supported chains").action(() => {
|
|
1711
|
-
const chains = listChains();
|
|
1712
|
-
console.log("Supported chains:");
|
|
1713
|
-
for (const name of chains) {
|
|
1714
|
-
const config = CHAINS[name];
|
|
1715
|
-
console.log(` ${name}: ${config.name} (chainId: ${config.chainId})`);
|
|
1716
|
-
}
|
|
1717
|
-
});
|
|
1718
|
-
program.command("init").description("Initialize agent wallet").option("-c, --chain <chain>", "Chain name", "base").option("-d, --dir <directory>", "Storage directory", "~/.moltspay").option("--cdp", "Use CDP (Coinbase Developer Platform) wallet").option("--local", "Use local wallet (default)").action(async (options) => {
|
|
1719
|
-
const storageDir = options.dir.replace("~", process.env.HOME || ".");
|
|
1720
|
-
if (options.cdp) {
|
|
1721
|
-
const { initCDPWallet: initCDPWallet2 } = await Promise.resolve().then(() => (init_cdp(), cdp_exports));
|
|
1722
|
-
console.log("\u{1F504} Initializing CDP wallet...");
|
|
1723
|
-
const result = await initCDPWallet2({
|
|
1724
|
-
chain: options.chain,
|
|
1725
|
-
storageDir
|
|
1726
|
-
});
|
|
1727
|
-
if (!result.success) {
|
|
1728
|
-
console.error("\u274C Failed:", result.error);
|
|
1729
|
-
console.log("");
|
|
1730
|
-
console.log("To use CDP wallet, set these environment variables:");
|
|
1731
|
-
console.log(" export CDP_API_KEY_ID=your-key-id");
|
|
1732
|
-
console.log(" export CDP_API_KEY_SECRET=your-key-secret");
|
|
1733
|
-
console.log(" export CDP_WALLET_SECRET=your-wallet-secret # optional");
|
|
1734
|
-
console.log("");
|
|
1735
|
-
console.log("Get credentials at: https://cdp.coinbase.com/");
|
|
1736
|
-
process.exit(1);
|
|
1737
|
-
}
|
|
1738
|
-
console.log("\u2705 CDP wallet initialized");
|
|
1739
|
-
console.log(` Address: ${result.address}`);
|
|
1740
|
-
console.log(` Chain: ${options.chain}`);
|
|
1741
|
-
console.log(` Storage: ${result.storagePath}`);
|
|
1742
|
-
console.log(` New wallet: ${result.isNew ? "Yes" : "No (loaded existing)"}`);
|
|
1743
|
-
console.log("");
|
|
1744
|
-
console.log("Next steps:");
|
|
1745
|
-
console.log(` 1. Fund your wallet: Send USDC to ${result.address}`);
|
|
1746
|
-
console.log(" 2. Use x402 to pay for services automatically");
|
|
1747
|
-
console.log("");
|
|
1748
|
-
console.log("Example:");
|
|
1749
|
-
console.log(' import { createX402Client } from "moltspay/x402";');
|
|
1750
|
-
console.log(' const client = await createX402Client({ chain: "base", useCDP: true });');
|
|
1751
|
-
console.log(' const response = await client.fetch("https://api.example.com/paid-resource");');
|
|
1752
|
-
} else {
|
|
1753
|
-
const { AgentWallet: AgentWallet2 } = await Promise.resolve().then(() => (init_AgentWallet(), AgentWallet_exports));
|
|
1754
|
-
const wallet = new AgentWallet2({
|
|
1755
|
-
chain: options.chain,
|
|
1756
|
-
storageDir
|
|
1757
|
-
});
|
|
1758
|
-
console.log("\u2705 Local wallet initialized");
|
|
1759
|
-
console.log(` Address: ${wallet.address}`);
|
|
1760
|
-
console.log(` Chain: ${options.chain}`);
|
|
1761
|
-
console.log(` Storage: ${storageDir}`);
|
|
1762
|
-
console.log("");
|
|
1763
|
-
console.log("Next steps:");
|
|
1764
|
-
console.log(` 1. Fund your wallet: Send USDC to ${wallet.address}`);
|
|
1765
|
-
console.log(` 2. Send a small amount of ETH for gas (~0.001 ETH)`);
|
|
1766
|
-
console.log("");
|
|
1767
|
-
console.log("Or use Permit (no gas needed):");
|
|
1768
|
-
console.log(` npx moltspay auth-request --owner <OWNER_ADDRESS> --amount <USDC_AMOUNT>`);
|
|
1769
|
-
}
|
|
1770
|
-
});
|
|
1771
|
-
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) => {
|
|
1772
|
-
const { AgentWallet: AgentWallet2 } = await Promise.resolve().then(() => (init_AgentWallet(), AgentWallet_exports));
|
|
1773
|
-
const wallet = new AgentWallet2({ chain: options.chain });
|
|
1774
|
-
const request = await wallet.generateAuthRequest({
|
|
1775
|
-
ownerAddress: options.owner,
|
|
1776
|
-
amount: parseFloat(options.amount),
|
|
1777
|
-
expiresInHours: parseInt(options.expires)
|
|
1778
|
-
});
|
|
1779
|
-
if (options.json) {
|
|
1780
|
-
console.log(JSON.stringify({
|
|
1781
|
-
agentAddress: wallet.address,
|
|
1782
|
-
typedData: request.typedData,
|
|
1783
|
-
cliCommand: request.cliCommand
|
|
1784
|
-
}, null, 2));
|
|
1785
|
-
} else {
|
|
1786
|
-
console.log(request.message);
|
|
1787
|
-
}
|
|
1788
|
-
});
|
|
1789
|
-
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) => {
|
|
1790
|
-
const { ethers: ethers11 } = await import("ethers");
|
|
1791
|
-
const { getChain: getChain2 } = await Promise.resolve().then(() => (init_chains(), chains_exports));
|
|
1792
|
-
const privateKey = options.privateKey || process.env.OWNER_PRIVATE_KEY;
|
|
1793
|
-
if (!privateKey) {
|
|
1794
|
-
console.error("Error: Private key required. Use --private-key or set OWNER_PRIVATE_KEY env");
|
|
1795
|
-
process.exit(1);
|
|
1796
|
-
}
|
|
1797
|
-
const chainConfig = getChain2(options.chain);
|
|
1798
|
-
const wallet = new ethers11.Wallet(privateKey);
|
|
1799
|
-
if (wallet.address.toLowerCase() !== options.owner.toLowerCase()) {
|
|
1800
|
-
console.error(`Error: Private key doesn't match owner address`);
|
|
1801
|
-
console.error(` Expected: ${options.owner}`);
|
|
1802
|
-
console.error(` Got: ${wallet.address}`);
|
|
1803
|
-
process.exit(1);
|
|
1804
|
-
}
|
|
1805
|
-
const value = BigInt(Math.floor(parseFloat(options.amount) * 1e6)).toString();
|
|
1806
|
-
const domain = {
|
|
1807
|
-
name: "USD Coin",
|
|
1808
|
-
version: "2",
|
|
1809
|
-
chainId: chainConfig.chainId,
|
|
1810
|
-
verifyingContract: chainConfig.usdc
|
|
1811
|
-
};
|
|
1812
|
-
const types = {
|
|
1813
|
-
Permit: [
|
|
1814
|
-
{ name: "owner", type: "address" },
|
|
1815
|
-
{ name: "spender", type: "address" },
|
|
1816
|
-
{ name: "value", type: "uint256" },
|
|
1817
|
-
{ name: "nonce", type: "uint256" },
|
|
1818
|
-
{ name: "deadline", type: "uint256" }
|
|
1819
|
-
]
|
|
1820
|
-
};
|
|
1821
|
-
const message = {
|
|
1822
|
-
owner: options.owner,
|
|
1823
|
-
spender: options.spender,
|
|
1824
|
-
value,
|
|
1825
|
-
nonce: parseInt(options.nonce),
|
|
1826
|
-
deadline: parseInt(options.deadline)
|
|
1827
|
-
};
|
|
1828
|
-
const signature = await wallet.signTypedData(domain, types, message);
|
|
1829
|
-
const sig = ethers11.Signature.from(signature);
|
|
1830
|
-
const permit = {
|
|
1831
|
-
owner: options.owner,
|
|
1832
|
-
value,
|
|
1833
|
-
deadline: parseInt(options.deadline),
|
|
1834
|
-
nonce: parseInt(options.nonce),
|
|
1835
|
-
v: sig.v,
|
|
1836
|
-
r: sig.r,
|
|
1837
|
-
s: sig.s
|
|
1838
|
-
};
|
|
1839
|
-
console.log("\u2705 Permit signed successfully!");
|
|
1840
|
-
console.log("");
|
|
1841
|
-
console.log("Send this to your Agent:");
|
|
1842
|
-
console.log(JSON.stringify(permit, null, 2));
|
|
1843
|
-
});
|
|
1844
|
-
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) => {
|
|
1845
|
-
const { AgentWallet: AgentWallet2 } = await Promise.resolve().then(() => (init_AgentWallet(), AgentWallet_exports));
|
|
1846
|
-
const wallet = new AgentWallet2({ chain: options.chain });
|
|
1847
|
-
let permit;
|
|
1848
|
-
if (options.permit) {
|
|
1849
|
-
permit = JSON.parse(options.permit);
|
|
1850
|
-
}
|
|
1851
|
-
console.log(`Spending ${options.amount} USDC to ${options.to}...`);
|
|
1852
|
-
const result = await wallet.spend(options.to, parseFloat(options.amount), permit);
|
|
1853
|
-
console.log(JSON.stringify(result, null, 2));
|
|
1854
|
-
process.exit(result.success ? 0 : 1);
|
|
1855
|
-
});
|
|
1856
|
-
program.command("x402").description("Make HTTP request with automatic x402 payment").argument("<url>", "URL to request").option("-X, --method <method>", "HTTP method", "GET").option("-d, --data <json>", "Request body (JSON)").option("-i, --image <path>", "Image file to include (reads file and sends as base64)").option("-H, --header <header...>", "Additional headers (key:value)").option("-c, --chain <chain>", "Chain name", "base").option("--cdp", "Use CDP wallet").option("-o, --output <file>", "Save response to file").option("-v, --verbose", "Show payment details").action(async (url, options) => {
|
|
1857
|
-
const { createX402Client: createX402Client2, isX402Available: isX402Available2 } = await Promise.resolve().then(() => (init_client(), client_exports));
|
|
1858
|
-
if (!isX402Available2()) {
|
|
1859
|
-
console.error("\u274C x402 packages not installed.");
|
|
1860
|
-
console.error("");
|
|
1861
|
-
console.error("Install them with:");
|
|
1862
|
-
console.error(" npm install @x402/fetch @x402/evm viem");
|
|
1863
|
-
process.exit(1);
|
|
1864
|
-
}
|
|
1865
|
-
try {
|
|
1866
|
-
if (options.verbose) {
|
|
1867
|
-
console.error(`\u{1F504} Initializing x402 client (chain: ${options.chain})...`);
|
|
1868
|
-
}
|
|
1869
|
-
const client = await createX402Client2({
|
|
1870
|
-
chain: options.chain,
|
|
1871
|
-
useCDP: options.cdp
|
|
1872
|
-
});
|
|
1873
|
-
if (options.verbose) {
|
|
1874
|
-
console.error(` Wallet: ${client.address}`);
|
|
1875
|
-
console.error(` Chain: ${client.chain}`);
|
|
1876
|
-
console.error("");
|
|
1877
|
-
}
|
|
1878
|
-
const headers = {
|
|
1879
|
-
"Content-Type": "application/json"
|
|
1880
|
-
};
|
|
1881
|
-
if (options.header) {
|
|
1882
|
-
for (const h of options.header) {
|
|
1883
|
-
const [key, ...valueParts] = h.split(":");
|
|
1884
|
-
headers[key.trim()] = valueParts.join(":").trim();
|
|
1885
|
-
}
|
|
1886
|
-
}
|
|
1887
|
-
const init = {
|
|
1888
|
-
method: options.method.toUpperCase(),
|
|
1889
|
-
headers
|
|
1890
|
-
};
|
|
1891
|
-
let bodyData = {};
|
|
1892
|
-
if (options.data) {
|
|
1893
|
-
try {
|
|
1894
|
-
bodyData = JSON.parse(options.data);
|
|
1895
|
-
} catch {
|
|
1896
|
-
bodyData = { data: options.data };
|
|
1897
|
-
}
|
|
1898
|
-
}
|
|
1899
|
-
if (options.image) {
|
|
1900
|
-
const fs4 = await import("fs");
|
|
1901
|
-
const path4 = await import("path");
|
|
1902
|
-
const imagePath = path4.resolve(options.image);
|
|
1903
|
-
if (!fs4.existsSync(imagePath)) {
|
|
1904
|
-
console.error(`\u274C Image file not found: ${imagePath}`);
|
|
1905
|
-
process.exit(1);
|
|
1906
|
-
}
|
|
1907
|
-
const imageBuffer = fs4.readFileSync(imagePath);
|
|
1908
|
-
const imageBase64 = imageBuffer.toString("base64");
|
|
1909
|
-
if (options.verbose) {
|
|
1910
|
-
console.error(`\u{1F4F7} Image: ${imagePath} (${Math.round(imageBuffer.length / 1024)}KB)`);
|
|
1911
|
-
}
|
|
1912
|
-
bodyData.image_base64 = imageBase64;
|
|
1913
|
-
}
|
|
1914
|
-
if (["POST", "PUT", "PATCH"].includes(init.method) && Object.keys(bodyData).length > 0) {
|
|
1915
|
-
init.body = JSON.stringify(bodyData);
|
|
1916
|
-
}
|
|
1917
|
-
if (options.verbose) {
|
|
1918
|
-
console.error(`\u{1F4E4} ${init.method} ${url}`);
|
|
1919
|
-
if (options.data) {
|
|
1920
|
-
console.error(` Body: ${options.data.substring(0, 100)}${options.data.length > 100 ? "..." : ""}`);
|
|
1921
|
-
}
|
|
1922
|
-
console.error("");
|
|
1923
|
-
}
|
|
1924
|
-
const response = await client.fetch(url, init);
|
|
1925
|
-
if (options.verbose) {
|
|
1926
|
-
console.error(`\u{1F4E5} Response: ${response.status} ${response.statusText}`);
|
|
1927
|
-
console.error("");
|
|
1928
|
-
}
|
|
1929
|
-
const contentType = response.headers.get("content-type") || "";
|
|
1930
|
-
let body;
|
|
1931
|
-
if (contentType.includes("application/json")) {
|
|
1932
|
-
const json = await response.json();
|
|
1933
|
-
body = JSON.stringify(json, null, 2);
|
|
1934
|
-
} else {
|
|
1935
|
-
body = await response.text();
|
|
1936
|
-
}
|
|
1937
|
-
if (options.output) {
|
|
1938
|
-
const fs4 = await import("fs");
|
|
1939
|
-
fs4.writeFileSync(options.output, body);
|
|
1940
|
-
console.error(`\u2705 Saved to ${options.output}`);
|
|
1941
|
-
} else {
|
|
1942
|
-
console.log(body);
|
|
1943
|
-
}
|
|
1944
|
-
process.exit(response.ok ? 0 : 1);
|
|
1945
|
-
} catch (error) {
|
|
1946
|
-
console.error("\u274C Error:", error.message);
|
|
1947
|
-
process.exit(1);
|
|
1948
|
-
}
|
|
1949
|
-
});
|
|
1950
|
-
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) => {
|
|
1951
|
-
const { AgentWallet: AgentWallet2 } = await Promise.resolve().then(() => (init_AgentWallet(), AgentWallet_exports));
|
|
1952
|
-
const wallet = new AgentWallet2({ chain: options.chain });
|
|
1953
|
-
console.log("Agent Wallet Status");
|
|
1954
|
-
console.log("==================");
|
|
1955
|
-
console.log(`Address: ${wallet.address}`);
|
|
1956
|
-
console.log(`Chain: ${options.chain}`);
|
|
1957
|
-
console.log(`Gas balance: ${await wallet.getGasBalance()} ETH`);
|
|
1958
|
-
console.log(`Has gas: ${await wallet.hasGas() ? "Yes \u2705" : "No \u274C (need ~0.0005 ETH)"}`);
|
|
1959
|
-
if (options.owner) {
|
|
1960
|
-
const allowance = await wallet.checkAllowance(options.owner);
|
|
1961
|
-
console.log("");
|
|
1962
|
-
console.log(`Allowance from ${options.owner}:`);
|
|
1963
|
-
console.log(` Allowance: ${allowance.allowance} USDC`);
|
|
1964
|
-
console.log(` Owner balance: ${allowance.ownerBalance} USDC`);
|
|
1965
|
-
console.log(` Can spend: ${allowance.canSpend ? "Yes \u2705" : "No \u274C"}`);
|
|
1966
|
-
}
|
|
1967
|
-
});
|
|
1968
|
-
program.parse();
|
|
1969
|
-
//# sourceMappingURL=cli.mjs.map
|