@agentwallet/cli 0.0.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/dist/cli.js +982 -0
- package/package.json +19 -0
- package/src/cli.ts +305 -0
- package/tsconfig.json +11 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,982 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
var __create = Object.create;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
10
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
11
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
12
|
+
for (let key of __getOwnPropNames(mod))
|
|
13
|
+
if (!__hasOwnProp.call(to, key))
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: () => mod[key],
|
|
16
|
+
enumerable: true
|
|
17
|
+
});
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
21
|
+
|
|
22
|
+
// ../sdk/dist/index.js
|
|
23
|
+
import { readFile, writeFile, mkdir } from "node:fs/promises";
|
|
24
|
+
import { homedir, platform } from "node:os";
|
|
25
|
+
import { join, dirname } from "node:path";
|
|
26
|
+
var CHAIN_MAP = {
|
|
27
|
+
ethereum: {
|
|
28
|
+
name: "ethereum",
|
|
29
|
+
chainId: 1,
|
|
30
|
+
type: "evm",
|
|
31
|
+
isTestnet: false,
|
|
32
|
+
displayName: "Ethereum",
|
|
33
|
+
explorer: "https://etherscan.io",
|
|
34
|
+
nativeToken: "ETH",
|
|
35
|
+
supportedTokens: ["eth", "usdc"]
|
|
36
|
+
},
|
|
37
|
+
base: {
|
|
38
|
+
name: "base",
|
|
39
|
+
chainId: 8453,
|
|
40
|
+
type: "evm",
|
|
41
|
+
isTestnet: false,
|
|
42
|
+
displayName: "Base",
|
|
43
|
+
explorer: "https://basescan.org",
|
|
44
|
+
nativeToken: "ETH",
|
|
45
|
+
supportedTokens: ["eth", "usdc"]
|
|
46
|
+
},
|
|
47
|
+
solana: {
|
|
48
|
+
name: "solana",
|
|
49
|
+
chainId: null,
|
|
50
|
+
type: "solana",
|
|
51
|
+
isTestnet: false,
|
|
52
|
+
displayName: "Solana",
|
|
53
|
+
explorer: "https://solscan.io",
|
|
54
|
+
nativeToken: "SOL",
|
|
55
|
+
supportedTokens: ["sol", "usdc"]
|
|
56
|
+
},
|
|
57
|
+
sepolia: {
|
|
58
|
+
name: "sepolia",
|
|
59
|
+
chainId: 11155111,
|
|
60
|
+
type: "evm",
|
|
61
|
+
isTestnet: true,
|
|
62
|
+
displayName: "Sepolia",
|
|
63
|
+
explorer: "https://sepolia.etherscan.io",
|
|
64
|
+
nativeToken: "ETH",
|
|
65
|
+
supportedTokens: ["eth", "usdc"]
|
|
66
|
+
},
|
|
67
|
+
"base-sepolia": {
|
|
68
|
+
name: "base-sepolia",
|
|
69
|
+
chainId: 84532,
|
|
70
|
+
type: "evm",
|
|
71
|
+
isTestnet: true,
|
|
72
|
+
displayName: "Base Sepolia",
|
|
73
|
+
explorer: "https://sepolia.basescan.org",
|
|
74
|
+
nativeToken: "ETH",
|
|
75
|
+
supportedTokens: ["eth", "usdc"]
|
|
76
|
+
},
|
|
77
|
+
"solana-devnet": {
|
|
78
|
+
name: "solana-devnet",
|
|
79
|
+
chainId: null,
|
|
80
|
+
type: "solana",
|
|
81
|
+
isTestnet: true,
|
|
82
|
+
displayName: "Solana Devnet",
|
|
83
|
+
explorer: "https://solscan.io/?cluster=devnet",
|
|
84
|
+
nativeToken: "SOL",
|
|
85
|
+
supportedTokens: ["sol", "usdc"]
|
|
86
|
+
},
|
|
87
|
+
tempo: {
|
|
88
|
+
name: "tempo",
|
|
89
|
+
chainId: null,
|
|
90
|
+
type: "evm",
|
|
91
|
+
isTestnet: true,
|
|
92
|
+
displayName: "Tempo Testnet",
|
|
93
|
+
explorer: "",
|
|
94
|
+
nativeToken: "ETH",
|
|
95
|
+
supportedTokens: ["eth"]
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
function resolveChain(chain) {
|
|
99
|
+
const config = CHAIN_MAP[chain];
|
|
100
|
+
if (!config) {
|
|
101
|
+
throw new Error(`Unknown chain: ${chain}`);
|
|
102
|
+
}
|
|
103
|
+
return config;
|
|
104
|
+
}
|
|
105
|
+
function getSolanaNetwork(chain) {
|
|
106
|
+
if (chain === "solana")
|
|
107
|
+
return "mainnet";
|
|
108
|
+
if (chain === "solana-devnet")
|
|
109
|
+
return "devnet";
|
|
110
|
+
throw new Error(`${chain} is not a Solana chain`);
|
|
111
|
+
}
|
|
112
|
+
function isChainName(value) {
|
|
113
|
+
return value in CHAIN_MAP;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
class AgentWalletError extends Error {
|
|
117
|
+
code;
|
|
118
|
+
hint;
|
|
119
|
+
status;
|
|
120
|
+
constructor(message, options) {
|
|
121
|
+
super(message);
|
|
122
|
+
this.name = "AgentWalletError";
|
|
123
|
+
this.code = options?.code ?? "AGENT_WALLET_ERROR";
|
|
124
|
+
this.hint = options?.hint;
|
|
125
|
+
this.status = options?.status ?? 0;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
class NotAuthenticatedError extends AgentWalletError {
|
|
130
|
+
constructor(message = "Not authenticated. Run `agentwallet connect` or call AgentWallet.connect().") {
|
|
131
|
+
super(message, { code: "NOT_AUTHENTICATED", status: 401 });
|
|
132
|
+
this.name = "NotAuthenticatedError";
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
class UnauthorizedError extends AgentWalletError {
|
|
137
|
+
constructor(message = "Unauthorized", hint) {
|
|
138
|
+
super(message, { code: "UNAUTHORIZED", hint, status: 401 });
|
|
139
|
+
this.name = "UnauthorizedError";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
class InsufficientFundsError extends AgentWalletError {
|
|
144
|
+
constructor(message = "Insufficient funds", hint) {
|
|
145
|
+
super(message, { code: "INSUFFICIENT_FUNDS", hint, status: 400 });
|
|
146
|
+
this.name = "InsufficientFundsError";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
class InvalidAddressError extends AgentWalletError {
|
|
151
|
+
constructor(message = "Invalid address", hint) {
|
|
152
|
+
super(message, { code: "INVALID_ADDRESS", hint, status: 400 });
|
|
153
|
+
this.name = "InvalidAddressError";
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
class SpendingLimitError extends AgentWalletError {
|
|
157
|
+
constructor(message = "Spending limit exceeded", hint) {
|
|
158
|
+
super(message, { code: "SPENDING_LIMIT", hint, status: 403 });
|
|
159
|
+
this.name = "SpendingLimitError";
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
class AllowlistError extends AgentWalletError {
|
|
164
|
+
constructor(message = "Address or chain not in allowlist", hint) {
|
|
165
|
+
super(message, { code: "ALLOWLIST", hint, status: 403 });
|
|
166
|
+
this.name = "AllowlistError";
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
class NetworkError extends AgentWalletError {
|
|
171
|
+
constructor(message = "Network request failed", hint) {
|
|
172
|
+
super(message, { code: "NETWORK_ERROR", hint, status: 0 });
|
|
173
|
+
this.name = "NetworkError";
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function errorFromResponse(status, body) {
|
|
177
|
+
const msg = body.error ?? `Request failed with status ${status}`;
|
|
178
|
+
const hint = body.hint;
|
|
179
|
+
if (status === 401) {
|
|
180
|
+
return new UnauthorizedError(msg, hint);
|
|
181
|
+
}
|
|
182
|
+
const lower = msg.toLowerCase();
|
|
183
|
+
if (lower.includes("insufficient") || lower.includes("not enough")) {
|
|
184
|
+
return new InsufficientFundsError(msg, hint);
|
|
185
|
+
}
|
|
186
|
+
if (lower.includes("invalid address") || lower.includes("bad address")) {
|
|
187
|
+
return new InvalidAddressError(msg, hint);
|
|
188
|
+
}
|
|
189
|
+
if (lower.includes("spending limit") || lower.includes("limit exceeded")) {
|
|
190
|
+
return new SpendingLimitError(msg, hint);
|
|
191
|
+
}
|
|
192
|
+
if (lower.includes("allowlist") || lower.includes("not allowed")) {
|
|
193
|
+
return new AllowlistError(msg, hint);
|
|
194
|
+
}
|
|
195
|
+
return new AgentWalletError(msg, { hint, status, code: "API_ERROR" });
|
|
196
|
+
}
|
|
197
|
+
function getDefaultConfigDir() {
|
|
198
|
+
const home = homedir();
|
|
199
|
+
switch (platform()) {
|
|
200
|
+
case "darwin":
|
|
201
|
+
return join(home, "Library", "Application Support", "agentwallet");
|
|
202
|
+
case "win32":
|
|
203
|
+
return join(process.env.APPDATA ?? join(home, "AppData", "Roaming"), "agentwallet");
|
|
204
|
+
default:
|
|
205
|
+
return join(home, ".config", "agentwallet");
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
var LEGACY_CONFIG_PATH = join(homedir(), ".agentwallet", "config.json");
|
|
209
|
+
|
|
210
|
+
class FileCredentialStorage {
|
|
211
|
+
configPath;
|
|
212
|
+
constructor(configDir) {
|
|
213
|
+
const dir = configDir ?? getDefaultConfigDir();
|
|
214
|
+
this.configPath = join(dir, "credentials.json");
|
|
215
|
+
}
|
|
216
|
+
getPath() {
|
|
217
|
+
return this.configPath;
|
|
218
|
+
}
|
|
219
|
+
async get() {
|
|
220
|
+
const creds = await this.readFile(this.configPath);
|
|
221
|
+
if (creds)
|
|
222
|
+
return creds;
|
|
223
|
+
const legacy = await this.readLegacy();
|
|
224
|
+
if (legacy) {
|
|
225
|
+
await this.set(legacy);
|
|
226
|
+
return legacy;
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
async set(credentials) {
|
|
231
|
+
const dir = dirname(this.configPath);
|
|
232
|
+
await mkdir(dir, { recursive: true });
|
|
233
|
+
await writeFile(this.configPath, JSON.stringify(credentials, null, 2) + `
|
|
234
|
+
`, { mode: 384 });
|
|
235
|
+
}
|
|
236
|
+
async delete() {
|
|
237
|
+
try {
|
|
238
|
+
const { unlink } = await import("node:fs/promises");
|
|
239
|
+
await unlink(this.configPath);
|
|
240
|
+
} catch {}
|
|
241
|
+
}
|
|
242
|
+
async readFile(path) {
|
|
243
|
+
try {
|
|
244
|
+
const raw = await readFile(path, "utf-8");
|
|
245
|
+
const data = JSON.parse(raw);
|
|
246
|
+
if (data.username && data.apiToken) {
|
|
247
|
+
return {
|
|
248
|
+
username: data.username,
|
|
249
|
+
email: data.email ?? "",
|
|
250
|
+
evmAddress: data.evmAddress ?? "",
|
|
251
|
+
solanaAddress: data.solanaAddress ?? "",
|
|
252
|
+
apiToken: data.apiToken
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
return null;
|
|
256
|
+
} catch {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
async readLegacy() {
|
|
261
|
+
try {
|
|
262
|
+
const raw = await readFile(LEGACY_CONFIG_PATH, "utf-8");
|
|
263
|
+
const data = JSON.parse(raw);
|
|
264
|
+
if (data.username && data.apiToken) {
|
|
265
|
+
return {
|
|
266
|
+
username: data.username,
|
|
267
|
+
email: data.email ?? "",
|
|
268
|
+
evmAddress: data.evmAddress ?? "",
|
|
269
|
+
solanaAddress: data.solanaAddress ?? "",
|
|
270
|
+
apiToken: data.apiToken
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
} catch {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
var TOOL_DEFINITIONS = [
|
|
280
|
+
{
|
|
281
|
+
name: "get_balances",
|
|
282
|
+
description: "Get all token balances across chains for this wallet.",
|
|
283
|
+
input_schema: {
|
|
284
|
+
type: "object",
|
|
285
|
+
properties: {},
|
|
286
|
+
required: []
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
name: "get_addresses",
|
|
291
|
+
description: "Get the wallet addresses (EVM and Solana).",
|
|
292
|
+
input_schema: {
|
|
293
|
+
type: "object",
|
|
294
|
+
properties: {},
|
|
295
|
+
required: []
|
|
296
|
+
}
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
name: "transfer",
|
|
300
|
+
description: "Transfer tokens to an address on a specific chain.",
|
|
301
|
+
input_schema: {
|
|
302
|
+
type: "object",
|
|
303
|
+
properties: {
|
|
304
|
+
chain: {
|
|
305
|
+
type: "string",
|
|
306
|
+
description: "Chain name: ethereum, base, solana, sepolia, base-sepolia, solana-devnet",
|
|
307
|
+
enum: ["ethereum", "base", "solana", "sepolia", "base-sepolia", "solana-devnet"]
|
|
308
|
+
},
|
|
309
|
+
to: {
|
|
310
|
+
type: "string",
|
|
311
|
+
description: "Recipient address"
|
|
312
|
+
},
|
|
313
|
+
amount: {
|
|
314
|
+
type: "string",
|
|
315
|
+
description: "Amount to send (in smallest unit, e.g. wei or lamports)"
|
|
316
|
+
},
|
|
317
|
+
currency: {
|
|
318
|
+
type: "string",
|
|
319
|
+
description: "Token to send (e.g. eth, usdc, sol). Defaults to chain native token."
|
|
320
|
+
}
|
|
321
|
+
},
|
|
322
|
+
required: ["chain", "to", "amount"]
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
name: "get_transaction_history",
|
|
327
|
+
description: "Get recent transaction history.",
|
|
328
|
+
input_schema: {
|
|
329
|
+
type: "object",
|
|
330
|
+
properties: {
|
|
331
|
+
limit: {
|
|
332
|
+
type: "number",
|
|
333
|
+
description: "Maximum number of transactions to return (default 20)"
|
|
334
|
+
}
|
|
335
|
+
},
|
|
336
|
+
required: []
|
|
337
|
+
}
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
name: "x402_fetch",
|
|
341
|
+
description: "Make an HTTP request with automatic x402 payment handling.",
|
|
342
|
+
input_schema: {
|
|
343
|
+
type: "object",
|
|
344
|
+
properties: {
|
|
345
|
+
url: {
|
|
346
|
+
type: "string",
|
|
347
|
+
description: "URL to fetch"
|
|
348
|
+
},
|
|
349
|
+
method: {
|
|
350
|
+
type: "string",
|
|
351
|
+
enum: ["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
352
|
+
description: "HTTP method (default GET)"
|
|
353
|
+
},
|
|
354
|
+
body: {
|
|
355
|
+
type: "object",
|
|
356
|
+
description: "Request body (for POST/PUT/PATCH)"
|
|
357
|
+
},
|
|
358
|
+
dryRun: {
|
|
359
|
+
type: "boolean",
|
|
360
|
+
description: "If true, only preview payment without executing"
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
required: ["url"]
|
|
364
|
+
}
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
name: "request_faucet",
|
|
368
|
+
description: "Request testnet tokens from the faucet (Solana devnet).",
|
|
369
|
+
input_schema: {
|
|
370
|
+
type: "object",
|
|
371
|
+
properties: {
|
|
372
|
+
chain: {
|
|
373
|
+
type: "string",
|
|
374
|
+
description: "Chain to request faucet on (default: solana-devnet)",
|
|
375
|
+
enum: ["solana-devnet"]
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
required: []
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
];
|
|
382
|
+
function createTools(wallet) {
|
|
383
|
+
async function execute(name, input) {
|
|
384
|
+
switch (name) {
|
|
385
|
+
case "get_balances":
|
|
386
|
+
return wallet.getBalances();
|
|
387
|
+
case "get_addresses":
|
|
388
|
+
return wallet.getAddresses();
|
|
389
|
+
case "transfer":
|
|
390
|
+
return wallet.transfer({
|
|
391
|
+
chain: input.chain,
|
|
392
|
+
to: input.to,
|
|
393
|
+
amount: input.amount,
|
|
394
|
+
currency: input.currency
|
|
395
|
+
});
|
|
396
|
+
case "get_transaction_history":
|
|
397
|
+
return wallet.getTransactionHistory({
|
|
398
|
+
limit: input.limit ?? 20
|
|
399
|
+
});
|
|
400
|
+
case "x402_fetch":
|
|
401
|
+
return wallet.x402Fetch({
|
|
402
|
+
url: input.url,
|
|
403
|
+
method: input.method,
|
|
404
|
+
body: input.body,
|
|
405
|
+
dryRun: input.dryRun
|
|
406
|
+
});
|
|
407
|
+
case "request_faucet":
|
|
408
|
+
return wallet.requestFaucet({
|
|
409
|
+
chain: input.chain ?? "solana-devnet"
|
|
410
|
+
});
|
|
411
|
+
default:
|
|
412
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
definitions: TOOL_DEFINITIONS,
|
|
417
|
+
execute
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
var DEFAULT_BASE_URL = "https://frames.ag/api";
|
|
421
|
+
|
|
422
|
+
class AgentWallet {
|
|
423
|
+
baseUrl;
|
|
424
|
+
apiToken;
|
|
425
|
+
_username;
|
|
426
|
+
_addresses;
|
|
427
|
+
storage;
|
|
428
|
+
constructor(config) {
|
|
429
|
+
this.baseUrl = config.baseUrl.replace(/\/$/, "");
|
|
430
|
+
this.apiToken = config.apiToken;
|
|
431
|
+
this._username = config.username;
|
|
432
|
+
this._addresses = config.addresses;
|
|
433
|
+
this.storage = config.storage;
|
|
434
|
+
}
|
|
435
|
+
static async connect(options = {}) {
|
|
436
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
437
|
+
const storage = options.credentialStorage ?? new FileCredentialStorage;
|
|
438
|
+
const existing = await storage.get();
|
|
439
|
+
if (existing) {
|
|
440
|
+
return new AgentWallet({
|
|
441
|
+
baseUrl,
|
|
442
|
+
apiToken: existing.apiToken,
|
|
443
|
+
username: existing.username,
|
|
444
|
+
addresses: { evm: existing.evmAddress, solana: existing.solanaAddress },
|
|
445
|
+
storage
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
let email = options.email;
|
|
449
|
+
if (!email) {
|
|
450
|
+
if (!options.onEmailPrompt) {
|
|
451
|
+
throw new NotAuthenticatedError("No stored credentials and no email/onEmailPrompt provided.");
|
|
452
|
+
}
|
|
453
|
+
email = await options.onEmailPrompt();
|
|
454
|
+
}
|
|
455
|
+
const startRes = await fetch(`${baseUrl}/connect/start`, {
|
|
456
|
+
method: "POST",
|
|
457
|
+
headers: { "Content-Type": "application/json" },
|
|
458
|
+
body: JSON.stringify({ email, ref: options.ref })
|
|
459
|
+
});
|
|
460
|
+
const startJson = await startRes.json();
|
|
461
|
+
const username = startJson.username;
|
|
462
|
+
if (!options.onOtpPrompt) {
|
|
463
|
+
throw new NotAuthenticatedError("OTP sent but no onOtpPrompt callback provided.");
|
|
464
|
+
}
|
|
465
|
+
const otp = await options.onOtpPrompt(email);
|
|
466
|
+
const completeRes = await fetch(`${baseUrl}/connect/complete`, {
|
|
467
|
+
method: "POST",
|
|
468
|
+
headers: { "Content-Type": "application/json" },
|
|
469
|
+
body: JSON.stringify({ username, email, otp })
|
|
470
|
+
});
|
|
471
|
+
const completeJson = await completeRes.json();
|
|
472
|
+
const credentials = {
|
|
473
|
+
username,
|
|
474
|
+
email,
|
|
475
|
+
evmAddress: completeJson.evmAddress,
|
|
476
|
+
solanaAddress: completeJson.solanaAddress,
|
|
477
|
+
apiToken: completeJson.apiToken
|
|
478
|
+
};
|
|
479
|
+
await storage.set(credentials);
|
|
480
|
+
return new AgentWallet({
|
|
481
|
+
baseUrl,
|
|
482
|
+
apiToken: completeJson.apiToken,
|
|
483
|
+
username,
|
|
484
|
+
addresses: { evm: completeJson.evmAddress, solana: completeJson.solanaAddress },
|
|
485
|
+
storage
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
static async fromApiKey(apiToken, options = {}) {
|
|
489
|
+
const baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
490
|
+
const storage = options.credentialStorage ?? new FileCredentialStorage;
|
|
491
|
+
if (options.username) {
|
|
492
|
+
const res = await fetch(`${baseUrl}/wallets/${options.username}`, {
|
|
493
|
+
headers: { Authorization: `Bearer ${apiToken}` }
|
|
494
|
+
});
|
|
495
|
+
const info = await res.json();
|
|
496
|
+
return new AgentWallet({
|
|
497
|
+
baseUrl,
|
|
498
|
+
apiToken,
|
|
499
|
+
username: options.username,
|
|
500
|
+
addresses: { evm: info.evmAddress ?? "", solana: info.solanaAddress ?? "" },
|
|
501
|
+
storage
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
const stored = await storage.get();
|
|
505
|
+
if (stored?.username) {
|
|
506
|
+
return new AgentWallet({
|
|
507
|
+
baseUrl,
|
|
508
|
+
apiToken,
|
|
509
|
+
username: stored.username,
|
|
510
|
+
addresses: { evm: stored.evmAddress, solana: stored.solanaAddress },
|
|
511
|
+
storage
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
throw new NotAuthenticatedError("Username required: provide `username` option or have stored credentials.");
|
|
515
|
+
}
|
|
516
|
+
static async fromEnv(options = {}) {
|
|
517
|
+
const apiToken = process.env.AGENTWALLET_API_TOKEN;
|
|
518
|
+
const username = process.env.AGENTWALLET_USERNAME;
|
|
519
|
+
const baseUrl = options.baseUrl ?? process.env.AGENTWALLET_BASE_URL ?? DEFAULT_BASE_URL;
|
|
520
|
+
if (apiToken) {
|
|
521
|
+
return AgentWallet.fromApiKey(apiToken, { baseUrl, username });
|
|
522
|
+
}
|
|
523
|
+
const storage = new FileCredentialStorage;
|
|
524
|
+
const stored = await storage.get();
|
|
525
|
+
if (stored) {
|
|
526
|
+
return new AgentWallet({
|
|
527
|
+
baseUrl: baseUrl.replace(/\/$/, ""),
|
|
528
|
+
apiToken: stored.apiToken,
|
|
529
|
+
username: stored.username,
|
|
530
|
+
addresses: { evm: stored.evmAddress, solana: stored.solanaAddress },
|
|
531
|
+
storage
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
throw new NotAuthenticatedError;
|
|
535
|
+
}
|
|
536
|
+
get agentId() {
|
|
537
|
+
return this._username;
|
|
538
|
+
}
|
|
539
|
+
get username() {
|
|
540
|
+
return this._username;
|
|
541
|
+
}
|
|
542
|
+
isAuthenticated() {
|
|
543
|
+
return !!this.apiToken && !!this._username;
|
|
544
|
+
}
|
|
545
|
+
async disconnect() {
|
|
546
|
+
await this.storage.delete();
|
|
547
|
+
}
|
|
548
|
+
getAddresses() {
|
|
549
|
+
return { ...this._addresses };
|
|
550
|
+
}
|
|
551
|
+
getAddress(chain) {
|
|
552
|
+
const config = resolveChain(chain);
|
|
553
|
+
if (config.type === "solana")
|
|
554
|
+
return this._addresses.solana;
|
|
555
|
+
return this._addresses.evm;
|
|
556
|
+
}
|
|
557
|
+
async getBalances() {
|
|
558
|
+
return this.request("GET", `/wallets/${this._username}/balances`, { auth: true });
|
|
559
|
+
}
|
|
560
|
+
async getBalance(chain) {
|
|
561
|
+
const balances = await this.getBalances();
|
|
562
|
+
const chainConfig = resolveChain(chain);
|
|
563
|
+
const match = balances.find((b) => b.chain.toLowerCase() === chainConfig.displayName.toLowerCase());
|
|
564
|
+
if (!match) {
|
|
565
|
+
return { chain: chainConfig.displayName, token: chainConfig.nativeToken, amount: "0", formatted: "0" };
|
|
566
|
+
}
|
|
567
|
+
return match;
|
|
568
|
+
}
|
|
569
|
+
async transfer(req) {
|
|
570
|
+
const chainConfig = resolveChain(req.chain);
|
|
571
|
+
if (chainConfig.type === "solana") {
|
|
572
|
+
const network = getSolanaNetwork(req.chain);
|
|
573
|
+
const asset2 = req.currency ?? "sol";
|
|
574
|
+
const result2 = await this.request("POST", `/wallets/${this._username}/actions/transfer-solana`, {
|
|
575
|
+
body: { to: req.to, amount: req.amount, asset: asset2, network, idempotencyKey: req.idempotencyKey },
|
|
576
|
+
auth: true
|
|
577
|
+
});
|
|
578
|
+
return actionToTransaction(result2, req.chain);
|
|
579
|
+
}
|
|
580
|
+
const asset = req.currency ?? "usdc";
|
|
581
|
+
const chainId = chainConfig.chainId;
|
|
582
|
+
const result = await this.request("POST", `/wallets/${this._username}/actions/transfer`, {
|
|
583
|
+
body: { to: req.to, amount: req.amount, asset, chainId, idempotencyKey: req.idempotencyKey },
|
|
584
|
+
auth: true
|
|
585
|
+
});
|
|
586
|
+
return actionToTransaction(result, req.chain);
|
|
587
|
+
}
|
|
588
|
+
async getTransactionHistory(options = {}) {
|
|
589
|
+
const limit = options.limit ?? 50;
|
|
590
|
+
const events = await this.request("GET", `/wallets/${this._username}/activity?limit=${limit}`, { auth: true });
|
|
591
|
+
return events.map((e) => ({
|
|
592
|
+
id: e.id,
|
|
593
|
+
status: e.status ?? e.type,
|
|
594
|
+
hash: e.txHash,
|
|
595
|
+
chain: e.chain,
|
|
596
|
+
to: e.to,
|
|
597
|
+
amount: e.amount,
|
|
598
|
+
currency: e.token,
|
|
599
|
+
timestamp: e.timestamp
|
|
600
|
+
}));
|
|
601
|
+
}
|
|
602
|
+
async requestFaucet(req = {}) {
|
|
603
|
+
const result = await this.request("POST", `/wallets/${this._username}/actions/faucet-sol`, {
|
|
604
|
+
body: {},
|
|
605
|
+
auth: true
|
|
606
|
+
});
|
|
607
|
+
return {
|
|
608
|
+
amount: result.amount,
|
|
609
|
+
remaining: result.remaining,
|
|
610
|
+
transaction: actionToTransaction(result, req.chain ?? "solana-devnet")
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
async x402Fetch(req) {
|
|
614
|
+
return this.request("POST", `/wallets/${this._username}/actions/x402/fetch`, { body: req, auth: true });
|
|
615
|
+
}
|
|
616
|
+
async x402Pay(req) {
|
|
617
|
+
return this.request("POST", `/wallets/${this._username}/actions/x402/pay`, {
|
|
618
|
+
body: req,
|
|
619
|
+
auth: true
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
async getPolicy() {
|
|
623
|
+
return this.request("GET", `/wallets/${this._username}/policy`, { auth: true });
|
|
624
|
+
}
|
|
625
|
+
async updatePolicy(req) {
|
|
626
|
+
return this.request("PATCH", `/wallets/${this._username}/policy`, { body: req, auth: true });
|
|
627
|
+
}
|
|
628
|
+
async getReferrals() {
|
|
629
|
+
return this.request("GET", `/wallets/${this._username}/referrals`, { auth: true });
|
|
630
|
+
}
|
|
631
|
+
async getStats() {
|
|
632
|
+
return this.request("GET", `/wallets/${this._username}/stats`, { auth: true });
|
|
633
|
+
}
|
|
634
|
+
static async getNetworkPulse(baseUrl) {
|
|
635
|
+
const url = (baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
|
|
636
|
+
const res = await fetch(`${url}/network/pulse`);
|
|
637
|
+
return await res.json();
|
|
638
|
+
}
|
|
639
|
+
tools() {
|
|
640
|
+
return createTools(this);
|
|
641
|
+
}
|
|
642
|
+
mcp() {
|
|
643
|
+
const t = createTools(this);
|
|
644
|
+
return {
|
|
645
|
+
name: "agentwallet",
|
|
646
|
+
version: "0.0.1",
|
|
647
|
+
tools: t.definitions
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
getAvailableChains() {
|
|
651
|
+
return Object.values(CHAIN_MAP);
|
|
652
|
+
}
|
|
653
|
+
async signMessage(req) {
|
|
654
|
+
return this.request("POST", `/wallets/${this._username}/actions/sign-message`, {
|
|
655
|
+
body: req,
|
|
656
|
+
auth: true
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
async contractCall(req) {
|
|
660
|
+
const result = await this.request("POST", `/wallets/${this._username}/actions/contract-call`, {
|
|
661
|
+
body: req,
|
|
662
|
+
auth: true
|
|
663
|
+
});
|
|
664
|
+
return actionToTransaction(result);
|
|
665
|
+
}
|
|
666
|
+
async request(method, path, options) {
|
|
667
|
+
const headers = {};
|
|
668
|
+
if (options?.auth) {
|
|
669
|
+
headers["Authorization"] = `Bearer ${this.apiToken}`;
|
|
670
|
+
}
|
|
671
|
+
if (options?.body !== undefined) {
|
|
672
|
+
headers["Content-Type"] = "application/json";
|
|
673
|
+
}
|
|
674
|
+
let res;
|
|
675
|
+
try {
|
|
676
|
+
res = await fetch(`${this.baseUrl}${path}`, {
|
|
677
|
+
method,
|
|
678
|
+
headers,
|
|
679
|
+
body: options?.body !== undefined ? JSON.stringify(options.body) : undefined
|
|
680
|
+
});
|
|
681
|
+
} catch (err) {
|
|
682
|
+
throw new NetworkError(`Request failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
683
|
+
}
|
|
684
|
+
let json;
|
|
685
|
+
try {
|
|
686
|
+
json = await res.json();
|
|
687
|
+
} catch {
|
|
688
|
+
throw new NetworkError(`Failed to parse response from ${path} (status ${res.status})`);
|
|
689
|
+
}
|
|
690
|
+
if (json.success === false) {
|
|
691
|
+
throw errorFromResponse(res.status, { error: json.error, hint: json.hint });
|
|
692
|
+
}
|
|
693
|
+
if (json.success === true && "data" in json) {
|
|
694
|
+
return json.data;
|
|
695
|
+
}
|
|
696
|
+
if (!res.ok) {
|
|
697
|
+
throw errorFromResponse(res.status, { error: json.error ?? json.message ?? String(json) });
|
|
698
|
+
}
|
|
699
|
+
return json.data ?? json;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
function actionToTransaction(action, chain) {
|
|
703
|
+
return {
|
|
704
|
+
id: action.actionId,
|
|
705
|
+
hash: action.txHash,
|
|
706
|
+
status: action.status,
|
|
707
|
+
explorerUrl: action.explorer,
|
|
708
|
+
chain
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// src/cli.ts
|
|
713
|
+
var args = process.argv.slice(2);
|
|
714
|
+
var command = args[0];
|
|
715
|
+
var subcommand = args[1];
|
|
716
|
+
function usage() {
|
|
717
|
+
console.log(`agentwallet - AgentWallet CLI
|
|
718
|
+
|
|
719
|
+
Usage:
|
|
720
|
+
agentwallet connect Connect wallet via email OTP
|
|
721
|
+
agentwallet status Show connection status and addresses
|
|
722
|
+
agentwallet addresses Show wallet addresses
|
|
723
|
+
agentwallet balances [--chain <name>] Show wallet balances
|
|
724
|
+
agentwallet transfer --chain <name> --to <addr> --amount <n> [--currency <tok>]
|
|
725
|
+
agentwallet history [--limit N] [--chain <name>] Show transaction history
|
|
726
|
+
agentwallet chains List available chains
|
|
727
|
+
agentwallet faucet [--chain solana-devnet] Request testnet tokens
|
|
728
|
+
agentwallet sign <evm|solana> <message> Sign a message
|
|
729
|
+
agentwallet x402 <url> [options] Make x402 payment request
|
|
730
|
+
agentwallet policy Show current policy
|
|
731
|
+
agentwallet referrals Show referral info
|
|
732
|
+
agentwallet stats Show personal stats
|
|
733
|
+
agentwallet pulse Show network pulse
|
|
734
|
+
agentwallet disconnect Clear stored credentials
|
|
735
|
+
agentwallet config Show config file path
|
|
736
|
+
agentwallet help Show this help`);
|
|
737
|
+
}
|
|
738
|
+
function flag(name) {
|
|
739
|
+
const idx = args.indexOf(`--${name}`);
|
|
740
|
+
if (idx === -1 || idx + 1 >= args.length)
|
|
741
|
+
return;
|
|
742
|
+
return args[idx + 1];
|
|
743
|
+
}
|
|
744
|
+
function promptInput(prompt) {
|
|
745
|
+
process.stdout.write(prompt);
|
|
746
|
+
const buf = Buffer.alloc(1024);
|
|
747
|
+
const fd = __require("fs").openSync("/dev/tty", "r");
|
|
748
|
+
const n = __require("fs").readSync(fd, buf, 0, 1024, null);
|
|
749
|
+
__require("fs").closeSync(fd);
|
|
750
|
+
return Promise.resolve(buf.toString("utf-8", 0, n).trim());
|
|
751
|
+
}
|
|
752
|
+
async function getClient() {
|
|
753
|
+
try {
|
|
754
|
+
return await AgentWallet.fromEnv();
|
|
755
|
+
} catch {
|
|
756
|
+
console.error("Not connected. Run: agentwallet connect");
|
|
757
|
+
process.exit(1);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
async function connect() {
|
|
761
|
+
const storage = new FileCredentialStorage;
|
|
762
|
+
const existing = await storage.get();
|
|
763
|
+
if (existing) {
|
|
764
|
+
console.log(`Already connected as ${existing.username}`);
|
|
765
|
+
console.log(`EVM: ${existing.evmAddress}`);
|
|
766
|
+
console.log(`Solana: ${existing.solanaAddress}`);
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
const ref = flag("ref");
|
|
770
|
+
const wallet = await AgentWallet.connect({
|
|
771
|
+
email: flag("email") ?? undefined,
|
|
772
|
+
onEmailPrompt: () => promptInput("Email: "),
|
|
773
|
+
onOtpPrompt: (email) => {
|
|
774
|
+
console.log(`OTP sent to ${email}`);
|
|
775
|
+
return promptInput("Enter OTP: ");
|
|
776
|
+
},
|
|
777
|
+
ref: ref ?? undefined,
|
|
778
|
+
credentialStorage: storage
|
|
779
|
+
});
|
|
780
|
+
const addrs = wallet.getAddresses();
|
|
781
|
+
console.log(`Connected as ${wallet.username}`);
|
|
782
|
+
console.log(`EVM: ${addrs.evm}`);
|
|
783
|
+
console.log(`Solana: ${addrs.solana}`);
|
|
784
|
+
console.log(`Config saved to ${storage.getPath()}`);
|
|
785
|
+
}
|
|
786
|
+
function requireChainFlag() {
|
|
787
|
+
const chain = flag("chain");
|
|
788
|
+
if (!chain) {
|
|
789
|
+
console.error("Missing --chain flag. Use `agentwallet chains` to see available chains.");
|
|
790
|
+
process.exit(1);
|
|
791
|
+
}
|
|
792
|
+
if (!isChainName(chain)) {
|
|
793
|
+
console.error(`Unknown chain: ${chain}. Use \`agentwallet chains\` to see available chains.`);
|
|
794
|
+
process.exit(1);
|
|
795
|
+
}
|
|
796
|
+
return chain;
|
|
797
|
+
}
|
|
798
|
+
async function run() {
|
|
799
|
+
try {
|
|
800
|
+
switch (command) {
|
|
801
|
+
case "connect":
|
|
802
|
+
await connect();
|
|
803
|
+
break;
|
|
804
|
+
case "status": {
|
|
805
|
+
const storage = new FileCredentialStorage;
|
|
806
|
+
const creds = await storage.get();
|
|
807
|
+
if (!creds) {
|
|
808
|
+
console.log("Not connected");
|
|
809
|
+
break;
|
|
810
|
+
}
|
|
811
|
+
console.log(`Username: ${creds.username}`);
|
|
812
|
+
console.log(`EVM: ${creds.evmAddress}`);
|
|
813
|
+
console.log(`Solana: ${creds.solanaAddress}`);
|
|
814
|
+
console.log(`Connected: true`);
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
case "addresses": {
|
|
818
|
+
const client = await getClient();
|
|
819
|
+
const addrs = client.getAddresses();
|
|
820
|
+
console.log(`EVM: ${addrs.evm}`);
|
|
821
|
+
console.log(`Solana: ${addrs.solana}`);
|
|
822
|
+
break;
|
|
823
|
+
}
|
|
824
|
+
case "balances": {
|
|
825
|
+
const client = await getClient();
|
|
826
|
+
const chainName = flag("chain");
|
|
827
|
+
if (chainName) {
|
|
828
|
+
if (!isChainName(chainName)) {
|
|
829
|
+
console.error(`Unknown chain: ${chainName}. Use \`agentwallet chains\` to see available chains.`);
|
|
830
|
+
process.exit(1);
|
|
831
|
+
}
|
|
832
|
+
const b = await client.getBalance(chainName);
|
|
833
|
+
console.log(`${b.formatted} ${b.token} (${b.chain})`);
|
|
834
|
+
break;
|
|
835
|
+
}
|
|
836
|
+
const balances = await client.getBalances();
|
|
837
|
+
if (!balances.length) {
|
|
838
|
+
console.log("No balances found");
|
|
839
|
+
break;
|
|
840
|
+
}
|
|
841
|
+
for (const b of balances) {
|
|
842
|
+
console.log(`${b.formatted} ${b.token} (${b.chain})`);
|
|
843
|
+
}
|
|
844
|
+
break;
|
|
845
|
+
}
|
|
846
|
+
case "transfer": {
|
|
847
|
+
const client = await getClient();
|
|
848
|
+
const chain = requireChainFlag();
|
|
849
|
+
const to = flag("to");
|
|
850
|
+
const amount = flag("amount");
|
|
851
|
+
const currency = flag("currency") ?? undefined;
|
|
852
|
+
if (!to || !amount) {
|
|
853
|
+
console.error("Usage: agentwallet transfer --chain <name> --to <addr> --amount <n> [--currency <tok>]");
|
|
854
|
+
process.exit(1);
|
|
855
|
+
}
|
|
856
|
+
const tx = await client.transfer({ chain, to, amount, currency });
|
|
857
|
+
console.log(`TX: ${tx.hash ?? tx.id}`);
|
|
858
|
+
if (tx.explorerUrl)
|
|
859
|
+
console.log(tx.explorerUrl);
|
|
860
|
+
break;
|
|
861
|
+
}
|
|
862
|
+
case "history": {
|
|
863
|
+
const client = await getClient();
|
|
864
|
+
const limit = flag("limit") ? parseInt(flag("limit"), 10) : 20;
|
|
865
|
+
const chainOpt = flag("chain");
|
|
866
|
+
const chainFilter = chainOpt && isChainName(chainOpt) ? chainOpt : undefined;
|
|
867
|
+
const txs = await client.getTransactionHistory({ limit, chain: chainFilter });
|
|
868
|
+
if (!txs.length) {
|
|
869
|
+
console.log("No transactions found");
|
|
870
|
+
break;
|
|
871
|
+
}
|
|
872
|
+
for (const tx of txs) {
|
|
873
|
+
const ts = tx.timestamp ? `[${tx.timestamp}]` : "";
|
|
874
|
+
const hash = tx.hash ? ` ${tx.hash.slice(0, 10)}...` : "";
|
|
875
|
+
console.log(`${ts} ${tx.status}${hash} ${tx.id}`);
|
|
876
|
+
}
|
|
877
|
+
break;
|
|
878
|
+
}
|
|
879
|
+
case "chains": {
|
|
880
|
+
const chains = Object.values(CHAIN_MAP);
|
|
881
|
+
for (const c of chains) {
|
|
882
|
+
const testnet = c.isTestnet ? " (testnet)" : "";
|
|
883
|
+
console.log(` ${c.name.padEnd(16)} ${c.displayName}${testnet} [${c.type}] tokens: ${c.supportedTokens.join(", ")}`);
|
|
884
|
+
}
|
|
885
|
+
break;
|
|
886
|
+
}
|
|
887
|
+
case "faucet": {
|
|
888
|
+
const client = await getClient();
|
|
889
|
+
const chainName = flag("chain");
|
|
890
|
+
const chain = chainName && isChainName(chainName) ? chainName : undefined;
|
|
891
|
+
const result = await client.requestFaucet({ chain });
|
|
892
|
+
console.log(`${result.amount} sent. TX: ${result.transaction.hash ?? result.transaction.id}`);
|
|
893
|
+
console.log(`Remaining today: ${result.remaining}`);
|
|
894
|
+
break;
|
|
895
|
+
}
|
|
896
|
+
case "stats": {
|
|
897
|
+
const client = await getClient();
|
|
898
|
+
const stats = await client.getStats();
|
|
899
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
900
|
+
break;
|
|
901
|
+
}
|
|
902
|
+
case "sign": {
|
|
903
|
+
const client = await getClient();
|
|
904
|
+
const chain = subcommand;
|
|
905
|
+
const message = args[2];
|
|
906
|
+
if (!chain || !message) {
|
|
907
|
+
console.error("Usage: agentwallet sign <evm|solana> <message>");
|
|
908
|
+
process.exit(1);
|
|
909
|
+
}
|
|
910
|
+
const result = await client.signMessage({ chain, message });
|
|
911
|
+
console.log(JSON.stringify(result, null, 2));
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
case "x402": {
|
|
915
|
+
const client = await getClient();
|
|
916
|
+
const url = subcommand;
|
|
917
|
+
if (!url) {
|
|
918
|
+
console.error("Usage: agentwallet x402 <url> [--method POST] [--body '{...}'] [--chain auto] [--dry-run]");
|
|
919
|
+
process.exit(1);
|
|
920
|
+
}
|
|
921
|
+
const method = flag("method") ?? "GET";
|
|
922
|
+
const bodyRaw = flag("body");
|
|
923
|
+
const body = bodyRaw ? JSON.parse(bodyRaw) : undefined;
|
|
924
|
+
const preferredChain = flag("chain") ?? "auto";
|
|
925
|
+
const dryRun = args.includes("--dry-run");
|
|
926
|
+
const result = await client.x402Fetch({ url, method, body, preferredChain, dryRun });
|
|
927
|
+
console.log(JSON.stringify(result, null, 2));
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
case "policy": {
|
|
931
|
+
const client = await getClient();
|
|
932
|
+
const policy = await client.getPolicy();
|
|
933
|
+
console.log(JSON.stringify(policy, null, 2));
|
|
934
|
+
break;
|
|
935
|
+
}
|
|
936
|
+
case "referrals": {
|
|
937
|
+
const client = await getClient();
|
|
938
|
+
const info = await client.getReferrals();
|
|
939
|
+
console.log(JSON.stringify(info, null, 2));
|
|
940
|
+
break;
|
|
941
|
+
}
|
|
942
|
+
case "pulse": {
|
|
943
|
+
const pulse = await AgentWallet.getNetworkPulse();
|
|
944
|
+
console.log(JSON.stringify(pulse, null, 2));
|
|
945
|
+
break;
|
|
946
|
+
}
|
|
947
|
+
case "disconnect": {
|
|
948
|
+
const storage = new FileCredentialStorage;
|
|
949
|
+
await storage.delete();
|
|
950
|
+
console.log("Credentials cleared.");
|
|
951
|
+
break;
|
|
952
|
+
}
|
|
953
|
+
case "config": {
|
|
954
|
+
const storage = new FileCredentialStorage;
|
|
955
|
+
console.log(storage.getPath());
|
|
956
|
+
break;
|
|
957
|
+
}
|
|
958
|
+
case "help":
|
|
959
|
+
case undefined:
|
|
960
|
+
usage();
|
|
961
|
+
break;
|
|
962
|
+
default:
|
|
963
|
+
console.error(`Unknown command: ${command}`);
|
|
964
|
+
usage();
|
|
965
|
+
process.exit(1);
|
|
966
|
+
}
|
|
967
|
+
} catch (err) {
|
|
968
|
+
if (err instanceof AgentWalletError) {
|
|
969
|
+
console.error(`Error: ${err.message}`);
|
|
970
|
+
if (err.hint) {
|
|
971
|
+
console.error(`Hint: ${err.hint}`);
|
|
972
|
+
}
|
|
973
|
+
if (err.code && err.code !== "API_ERROR" && err.code !== "AGENT_WALLET_ERROR") {
|
|
974
|
+
console.error(`Code: ${err.code}`);
|
|
975
|
+
}
|
|
976
|
+
} else if (err instanceof Error) {
|
|
977
|
+
console.error(`Error: ${err.message}`);
|
|
978
|
+
}
|
|
979
|
+
process.exit(1);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
run();
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@agentwallet/cli",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"bin": {
|
|
6
|
+
"agentwallet": "./dist/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "bun build ./src/cli.ts --outdir ./dist --target node",
|
|
10
|
+
"typecheck": "tsc --noEmit"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@agentwallet/sdk": "workspace:*"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"typescript": "^5.7.0",
|
|
17
|
+
"bun-types": "^1.2.0"
|
|
18
|
+
}
|
|
19
|
+
}
|
package/src/cli.ts
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import {
|
|
3
|
+
AgentWallet,
|
|
4
|
+
AgentWalletError,
|
|
5
|
+
FileCredentialStorage,
|
|
6
|
+
CHAIN_MAP,
|
|
7
|
+
isChainName,
|
|
8
|
+
} from "@agentwallet/sdk";
|
|
9
|
+
import type { ChainName } from "@agentwallet/sdk";
|
|
10
|
+
|
|
11
|
+
const args = process.argv.slice(2);
|
|
12
|
+
const command = args[0];
|
|
13
|
+
const subcommand = args[1];
|
|
14
|
+
|
|
15
|
+
function usage(): void {
|
|
16
|
+
console.log(`agentwallet - AgentWallet CLI
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
agentwallet connect Connect wallet via email OTP
|
|
20
|
+
agentwallet status Show connection status and addresses
|
|
21
|
+
agentwallet addresses Show wallet addresses
|
|
22
|
+
agentwallet balances [--chain <name>] Show wallet balances
|
|
23
|
+
agentwallet transfer --chain <name> --to <addr> --amount <n> [--currency <tok>]
|
|
24
|
+
agentwallet history [--limit N] [--chain <name>] Show transaction history
|
|
25
|
+
agentwallet chains List available chains
|
|
26
|
+
agentwallet faucet [--chain solana-devnet] Request testnet tokens
|
|
27
|
+
agentwallet sign <evm|solana> <message> Sign a message
|
|
28
|
+
agentwallet x402 <url> [options] Make x402 payment request
|
|
29
|
+
agentwallet policy Show current policy
|
|
30
|
+
agentwallet referrals Show referral info
|
|
31
|
+
agentwallet stats Show personal stats
|
|
32
|
+
agentwallet pulse Show network pulse
|
|
33
|
+
agentwallet disconnect Clear stored credentials
|
|
34
|
+
agentwallet config Show config file path
|
|
35
|
+
agentwallet help Show this help`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function flag(name: string): string | undefined {
|
|
39
|
+
const idx = args.indexOf(`--${name}`);
|
|
40
|
+
if (idx === -1 || idx + 1 >= args.length) return undefined;
|
|
41
|
+
return args[idx + 1];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function promptInput(prompt: string): Promise<string> {
|
|
45
|
+
process.stdout.write(prompt);
|
|
46
|
+
const buf = Buffer.alloc(1024);
|
|
47
|
+
const fd = require("node:fs").openSync("/dev/tty", "r");
|
|
48
|
+
const n = require("node:fs").readSync(fd, buf, 0, 1024, null);
|
|
49
|
+
require("node:fs").closeSync(fd);
|
|
50
|
+
return Promise.resolve(buf.toString("utf-8", 0, n).trim());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function getClient(): Promise<AgentWallet> {
|
|
54
|
+
try {
|
|
55
|
+
return await AgentWallet.fromEnv();
|
|
56
|
+
} catch {
|
|
57
|
+
console.error("Not connected. Run: agentwallet connect");
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async function connect(): Promise<void> {
|
|
63
|
+
const storage = new FileCredentialStorage();
|
|
64
|
+
const existing = await storage.get();
|
|
65
|
+
if (existing) {
|
|
66
|
+
console.log(`Already connected as ${existing.username}`);
|
|
67
|
+
console.log(`EVM: ${existing.evmAddress}`);
|
|
68
|
+
console.log(`Solana: ${existing.solanaAddress}`);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const ref = flag("ref");
|
|
73
|
+
const wallet = await AgentWallet.connect({
|
|
74
|
+
email: flag("email") ?? undefined,
|
|
75
|
+
onEmailPrompt: () => promptInput("Email: "),
|
|
76
|
+
onOtpPrompt: (email) => {
|
|
77
|
+
console.log(`OTP sent to ${email}`);
|
|
78
|
+
return promptInput("Enter OTP: ");
|
|
79
|
+
},
|
|
80
|
+
ref: ref ?? undefined,
|
|
81
|
+
credentialStorage: storage,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const addrs = wallet.getAddresses();
|
|
85
|
+
console.log(`Connected as ${wallet.username}`);
|
|
86
|
+
console.log(`EVM: ${addrs.evm}`);
|
|
87
|
+
console.log(`Solana: ${addrs.solana}`);
|
|
88
|
+
console.log(`Config saved to ${storage.getPath()}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function requireChainFlag(): ChainName {
|
|
92
|
+
const chain = flag("chain");
|
|
93
|
+
if (!chain) {
|
|
94
|
+
console.error("Missing --chain flag. Use `agentwallet chains` to see available chains.");
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
if (!isChainName(chain)) {
|
|
98
|
+
console.error(`Unknown chain: ${chain}. Use \`agentwallet chains\` to see available chains.`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
return chain;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async function run(): Promise<void> {
|
|
105
|
+
try {
|
|
106
|
+
switch (command) {
|
|
107
|
+
case "connect":
|
|
108
|
+
await connect();
|
|
109
|
+
break;
|
|
110
|
+
|
|
111
|
+
case "status": {
|
|
112
|
+
const storage = new FileCredentialStorage();
|
|
113
|
+
const creds = await storage.get();
|
|
114
|
+
if (!creds) {
|
|
115
|
+
console.log("Not connected");
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
console.log(`Username: ${creds.username}`);
|
|
119
|
+
console.log(`EVM: ${creds.evmAddress}`);
|
|
120
|
+
console.log(`Solana: ${creds.solanaAddress}`);
|
|
121
|
+
console.log(`Connected: true`);
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
case "addresses": {
|
|
126
|
+
const client = await getClient();
|
|
127
|
+
const addrs = client.getAddresses();
|
|
128
|
+
console.log(`EVM: ${addrs.evm}`);
|
|
129
|
+
console.log(`Solana: ${addrs.solana}`);
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
case "balances": {
|
|
134
|
+
const client = await getClient();
|
|
135
|
+
const chainName = flag("chain");
|
|
136
|
+
if (chainName) {
|
|
137
|
+
if (!isChainName(chainName)) {
|
|
138
|
+
console.error(`Unknown chain: ${chainName}. Use \`agentwallet chains\` to see available chains.`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
}
|
|
141
|
+
const b = await client.getBalance(chainName);
|
|
142
|
+
console.log(`${b.formatted} ${b.token} (${b.chain})`);
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
const balances = await client.getBalances();
|
|
146
|
+
if (!balances.length) {
|
|
147
|
+
console.log("No balances found");
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
for (const b of balances) {
|
|
151
|
+
console.log(`${b.formatted} ${b.token} (${b.chain})`);
|
|
152
|
+
}
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
case "transfer": {
|
|
157
|
+
const client = await getClient();
|
|
158
|
+
const chain = requireChainFlag();
|
|
159
|
+
const to = flag("to");
|
|
160
|
+
const amount = flag("amount");
|
|
161
|
+
const currency = flag("currency") ?? undefined;
|
|
162
|
+
if (!to || !amount) {
|
|
163
|
+
console.error("Usage: agentwallet transfer --chain <name> --to <addr> --amount <n> [--currency <tok>]");
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
const tx = await client.transfer({ chain, to, amount, currency });
|
|
167
|
+
console.log(`TX: ${tx.hash ?? tx.id}`);
|
|
168
|
+
if (tx.explorerUrl) console.log(tx.explorerUrl);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
case "history": {
|
|
173
|
+
const client = await getClient();
|
|
174
|
+
const limit = flag("limit") ? parseInt(flag("limit")!, 10) : 20;
|
|
175
|
+
const chainOpt = flag("chain");
|
|
176
|
+
const chainFilter = chainOpt && isChainName(chainOpt) ? chainOpt : undefined;
|
|
177
|
+
const txs = await client.getTransactionHistory({ limit, chain: chainFilter });
|
|
178
|
+
if (!txs.length) {
|
|
179
|
+
console.log("No transactions found");
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
for (const tx of txs) {
|
|
183
|
+
const ts = tx.timestamp ? `[${tx.timestamp}]` : "";
|
|
184
|
+
const hash = tx.hash ? ` ${tx.hash.slice(0, 10)}...` : "";
|
|
185
|
+
console.log(`${ts} ${tx.status}${hash} ${tx.id}`);
|
|
186
|
+
}
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
case "chains": {
|
|
191
|
+
const chains = Object.values(CHAIN_MAP);
|
|
192
|
+
for (const c of chains) {
|
|
193
|
+
const testnet = c.isTestnet ? " (testnet)" : "";
|
|
194
|
+
console.log(` ${c.name.padEnd(16)} ${c.displayName}${testnet} [${c.type}] tokens: ${c.supportedTokens.join(", ")}`);
|
|
195
|
+
}
|
|
196
|
+
break;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
case "faucet": {
|
|
200
|
+
const client = await getClient();
|
|
201
|
+
const chainName = flag("chain");
|
|
202
|
+
const chain = chainName && isChainName(chainName) ? chainName : undefined;
|
|
203
|
+
const result = await client.requestFaucet({ chain });
|
|
204
|
+
console.log(`${result.amount} sent. TX: ${result.transaction.hash ?? result.transaction.id}`);
|
|
205
|
+
console.log(`Remaining today: ${result.remaining}`);
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
case "stats": {
|
|
210
|
+
const client = await getClient();
|
|
211
|
+
const stats = await client.getStats();
|
|
212
|
+
console.log(JSON.stringify(stats, null, 2));
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
case "sign": {
|
|
217
|
+
const client = await getClient();
|
|
218
|
+
const chain = subcommand as "evm" | "solana";
|
|
219
|
+
const message = args[2];
|
|
220
|
+
if (!chain || !message) {
|
|
221
|
+
console.error("Usage: agentwallet sign <evm|solana> <message>");
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
const result = await client.signMessage({ chain, message });
|
|
225
|
+
console.log(JSON.stringify(result, null, 2));
|
|
226
|
+
break;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
case "x402": {
|
|
230
|
+
const client = await getClient();
|
|
231
|
+
const url = subcommand;
|
|
232
|
+
if (!url) {
|
|
233
|
+
console.error("Usage: agentwallet x402 <url> [--method POST] [--body '{...}'] [--chain auto] [--dry-run]");
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
const method = (flag("method") ?? "GET") as "GET" | "POST" | "PUT" | "DELETE" | "PATCH";
|
|
237
|
+
const bodyRaw = flag("body");
|
|
238
|
+
const body = bodyRaw ? JSON.parse(bodyRaw) : undefined;
|
|
239
|
+
const preferredChain = (flag("chain") ?? "auto") as "auto" | "evm" | "solana";
|
|
240
|
+
const dryRun = args.includes("--dry-run");
|
|
241
|
+
const result = await client.x402Fetch({ url, method, body, preferredChain, dryRun });
|
|
242
|
+
console.log(JSON.stringify(result, null, 2));
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
case "policy": {
|
|
247
|
+
const client = await getClient();
|
|
248
|
+
const policy = await client.getPolicy();
|
|
249
|
+
console.log(JSON.stringify(policy, null, 2));
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
case "referrals": {
|
|
254
|
+
const client = await getClient();
|
|
255
|
+
const info = await client.getReferrals();
|
|
256
|
+
console.log(JSON.stringify(info, null, 2));
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
case "pulse": {
|
|
261
|
+
const pulse = await AgentWallet.getNetworkPulse();
|
|
262
|
+
console.log(JSON.stringify(pulse, null, 2));
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
case "disconnect": {
|
|
267
|
+
const storage = new FileCredentialStorage();
|
|
268
|
+
await storage.delete();
|
|
269
|
+
console.log("Credentials cleared.");
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
case "config": {
|
|
274
|
+
const storage = new FileCredentialStorage();
|
|
275
|
+
console.log(storage.getPath());
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
case "help":
|
|
280
|
+
case undefined:
|
|
281
|
+
usage();
|
|
282
|
+
break;
|
|
283
|
+
|
|
284
|
+
default:
|
|
285
|
+
console.error(`Unknown command: ${command}`);
|
|
286
|
+
usage();
|
|
287
|
+
process.exit(1);
|
|
288
|
+
}
|
|
289
|
+
} catch (err) {
|
|
290
|
+
if (err instanceof AgentWalletError) {
|
|
291
|
+
console.error(`Error: ${err.message}`);
|
|
292
|
+
if (err.hint) {
|
|
293
|
+
console.error(`Hint: ${err.hint}`);
|
|
294
|
+
}
|
|
295
|
+
if (err.code && err.code !== "API_ERROR" && err.code !== "AGENT_WALLET_ERROR") {
|
|
296
|
+
console.error(`Code: ${err.code}`);
|
|
297
|
+
}
|
|
298
|
+
} else if (err instanceof Error) {
|
|
299
|
+
console.error(`Error: ${err.message}`);
|
|
300
|
+
}
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
run();
|