@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 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();
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src"
6
+ },
7
+ "include": ["src"],
8
+ "references": [
9
+ { "path": "../sdk" }
10
+ ]
11
+ }