naracli 0.1.0

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.
@@ -0,0 +1,719 @@
1
+ /**
2
+ * Wallet commands
3
+ */
4
+
5
+ import { Command } from "commander";
6
+ import {
7
+ Keypair,
8
+ LAMPORTS_PER_SOL,
9
+ PublicKey,
10
+ SystemProgram,
11
+ Transaction,
12
+ } from "@solana/web3.js";
13
+ import {
14
+ getAssociatedTokenAddress,
15
+ createTransferInstruction,
16
+ TOKEN_PROGRAM_ID,
17
+ } from "@solana/spl-token";
18
+ import * as bip39 from "bip39";
19
+ import { derivePath } from "ed25519-hd-key";
20
+ import { join } from "node:path";
21
+ import { homedir } from "node:os";
22
+ import { mkdir } from "node:fs/promises";
23
+ import bs58 from "bs58";
24
+ import { NaraSDK } from "../../client";
25
+ import { DEFAULT_WALLET_PATH as _DEFAULT_WALLET_PATH } from "../../constants";
26
+ import { loadWallet, getRpcUrl } from "../utils/wallet";
27
+ import { validatePublicKey, validatePositiveNumber } from "../utils/validation";
28
+ import {
29
+ handleTransaction,
30
+ printTransactionResult,
31
+ } from "../utils/transaction";
32
+ import { formatOutput, printError, printInfo, printSuccess, printWarning } from "../utils/output";
33
+ import type {
34
+ GlobalOptions,
35
+ WalletBalanceOptions,
36
+ TokenBalanceOptions,
37
+ TxStatusOptions,
38
+ TransferSolOptions,
39
+ TransferTokenOptions,
40
+ } from "../types";
41
+
42
+ /**
43
+ * Resolve wallet path (expand ~ to home directory)
44
+ */
45
+ const DEFAULT_WALLET_PATH = _DEFAULT_WALLET_PATH.startsWith("~")
46
+ ? join(homedir(), _DEFAULT_WALLET_PATH.slice(1))
47
+ : _DEFAULT_WALLET_PATH;
48
+
49
+ /**
50
+ * Register wallet commands
51
+ * @param program Commander program
52
+ */
53
+ export function registerWalletCommands(program: Command): void {
54
+ const wallet = program
55
+ .command("wallet")
56
+ .description("Wallet management commands");
57
+
58
+ // wallet create
59
+ wallet
60
+ .command("create")
61
+ .description("Create a new wallet")
62
+ .option("-o, --output <path>", "Output path for wallet file (default: ~/.config/nara/id.json)")
63
+ .action(async (options: { output?: string }) => {
64
+ try {
65
+ await handleWalletCreate(options);
66
+ } catch (error: any) {
67
+ printError(error.message);
68
+ process.exit(1);
69
+ }
70
+ });
71
+
72
+ // wallet import
73
+ wallet
74
+ .command("import")
75
+ .description("Import a wallet from mnemonic or private key")
76
+ .option("-m, --mnemonic <phrase>", "Mnemonic phrase (12 or 24 words)")
77
+ .option("-k, --private-key <key>", "Private key (base58 or JSON array)")
78
+ .option("-o, --output <path>", "Output path for wallet file (default: ~/.config/nara/id.json)")
79
+ .action(async (options: { mnemonic?: string; privateKey?: string; output?: string }) => {
80
+ try {
81
+ await handleWalletImport(options);
82
+ } catch (error: any) {
83
+ printError(error.message);
84
+ process.exit(1);
85
+ }
86
+ });
87
+
88
+ // wallet address
89
+ wallet
90
+ .command("address")
91
+ .description("Show wallet address")
92
+ .action(async (options: GlobalOptions) => {
93
+ try {
94
+ await handleWalletAddress(options);
95
+ } catch (error: any) {
96
+ printError(error.message);
97
+ process.exit(1);
98
+ }
99
+ });
100
+
101
+ // wallet balance
102
+ wallet
103
+ .command("balance")
104
+ .description("Check NSO balance")
105
+ .argument("[address]", "Wallet address (optional, defaults to current wallet)")
106
+ .action(async (address: string | undefined, options: WalletBalanceOptions) => {
107
+ try {
108
+ await handleWalletBalance(address, options);
109
+ } catch (error: any) {
110
+ printError(error.message);
111
+ process.exit(1);
112
+ }
113
+ });
114
+
115
+ // wallet token-balance
116
+ wallet
117
+ .command("token-balance <token-address>")
118
+ .description("Check token balance")
119
+ .option("--owner <address>", "Owner address (optional, defaults to current wallet)")
120
+ .action(
121
+ async (
122
+ tokenAddress: string,
123
+ options: Omit<TokenBalanceOptions, "tokenAddress">
124
+ ) => {
125
+ try {
126
+ await handleTokenBalance(tokenAddress, options);
127
+ } catch (error: any) {
128
+ printError(error.message);
129
+ process.exit(1);
130
+ }
131
+ }
132
+ );
133
+
134
+ // wallet tx-status
135
+ wallet
136
+ .command("tx-status <signature>")
137
+ .description("Check transaction status")
138
+ .action(
139
+ async (signature: string, options: Omit<TxStatusOptions, "signature">) => {
140
+ try {
141
+ await handleTxStatus(signature, options);
142
+ } catch (error: any) {
143
+ printError(error.message);
144
+ process.exit(1);
145
+ }
146
+ }
147
+ );
148
+
149
+ // wallet transfer
150
+ wallet
151
+ .command("transfer <to> <amount>")
152
+ .description("Transfer NSO to another wallet")
153
+ .option("-e, --export-tx", "Export unsigned transaction", false)
154
+ .action(
155
+ async (
156
+ to: string,
157
+ amount: string,
158
+ options: Omit<TransferSolOptions, "to" | "amount">
159
+ ) => {
160
+ try {
161
+ await handleTransferSol(to, amount, options);
162
+ } catch (error: any) {
163
+ printError(error.message);
164
+ process.exit(1);
165
+ }
166
+ }
167
+ );
168
+
169
+ // wallet transfer-token
170
+ wallet
171
+ .command("transfer-token <token-address> <to> <amount>")
172
+ .description("Transfer tokens to another wallet")
173
+ .option("--decimals <number>", "Token decimals", "6")
174
+ .option("-e, --export-tx", "Export unsigned transaction", false)
175
+ .action(
176
+ async (
177
+ tokenAddress: string,
178
+ to: string,
179
+ amount: string,
180
+ options: Omit<TransferTokenOptions, "tokenAddress" | "to" | "amount">
181
+ ) => {
182
+ try {
183
+ await handleTransferToken(tokenAddress, to, amount, options);
184
+ } catch (error: any) {
185
+ printError(error.message);
186
+ process.exit(1);
187
+ }
188
+ }
189
+ );
190
+ }
191
+
192
+ /**
193
+ * Handle wallet balance command
194
+ * @param address Wallet address
195
+ * @param options Command options
196
+ */
197
+ async function handleWalletBalance(
198
+ address: string | undefined,
199
+ options: WalletBalanceOptions
200
+ ): Promise<void> {
201
+ const rpcUrl = getRpcUrl(options.rpcUrl);
202
+
203
+ printInfo(`Using RPC: ${rpcUrl}`);
204
+
205
+ // Initialize SDK
206
+ const sdk = new NaraSDK({
207
+ rpcUrl,
208
+ commitment: "confirmed",
209
+ });
210
+
211
+ const connection = sdk.getConnection();
212
+
213
+ // Determine which address to query
214
+ let pubkey: PublicKey;
215
+ if (address) {
216
+ pubkey = validatePublicKey(address);
217
+ } else {
218
+ // Load wallet to get address
219
+ const wallet = await loadWallet(options.wallet);
220
+ pubkey = wallet.publicKey;
221
+ }
222
+
223
+ printInfo(`Checking balance for: ${pubkey.toBase58()}`);
224
+
225
+ // Get balance
226
+ const balance = await connection.getBalance(pubkey);
227
+ const balanceSOL = balance / LAMPORTS_PER_SOL;
228
+
229
+ // Output result
230
+ if (options.json) {
231
+ const output = {
232
+ address: pubkey.toBase58(),
233
+ balance: balanceSOL,
234
+ lamports: balance,
235
+ };
236
+ console.log(JSON.stringify(output, null, 2));
237
+ } else {
238
+ console.log(`\nWallet: ${pubkey.toBase58()}`);
239
+ console.log(`Balance: ${balanceSOL.toFixed(4)} NSO (${balance.toLocaleString()} lamports)`);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Handle token balance command
245
+ * @param tokenAddress Token address
246
+ * @param options Command options
247
+ */
248
+ async function handleTokenBalance(
249
+ tokenAddress: string,
250
+ options: Omit<TokenBalanceOptions, "tokenAddress">
251
+ ): Promise<void> {
252
+ const rpcUrl = getRpcUrl(options.rpcUrl);
253
+
254
+ printInfo(`Using RPC: ${rpcUrl}`);
255
+
256
+ // Validate token address
257
+ const tokenMint = validatePublicKey(tokenAddress);
258
+
259
+ // Initialize SDK
260
+ const sdk = new NaraSDK({
261
+ rpcUrl,
262
+ commitment: "confirmed",
263
+ });
264
+
265
+ const connection = sdk.getConnection();
266
+
267
+ // Determine owner address
268
+ let owner: PublicKey;
269
+ if (options.owner) {
270
+ owner = validatePublicKey(options.owner);
271
+ } else {
272
+ // Load wallet to get owner
273
+ const wallet = await loadWallet(options.wallet);
274
+ owner = wallet.publicKey;
275
+ }
276
+
277
+ printInfo(`Owner: ${owner.toBase58()}`);
278
+ printInfo(`Token: ${tokenAddress}`);
279
+
280
+ // Get associated token account
281
+ const tokenAccount = await getAssociatedTokenAddress(tokenMint, owner);
282
+
283
+ // Get token account balance
284
+ try {
285
+ const accountInfo = await connection.getTokenAccountBalance(tokenAccount);
286
+ const balance = accountInfo.value;
287
+
288
+ // Output result
289
+ if (options.json) {
290
+ const output = {
291
+ owner: owner.toBase58(),
292
+ tokenAddress,
293
+ tokenAccount: tokenAccount.toBase58(),
294
+ balance: balance.uiAmountString,
295
+ amount: balance.amount,
296
+ decimals: balance.decimals,
297
+ };
298
+ console.log(JSON.stringify(output, null, 2));
299
+ } else {
300
+ console.log(`\nToken Account: ${tokenAccount.toBase58()}`);
301
+ console.log(`Balance: ${balance.uiAmountString || "0"} tokens`);
302
+ console.log(`Amount: ${balance.amount} (smallest unit)`);
303
+ console.log(`Decimals: ${balance.decimals}`);
304
+ }
305
+ } catch (error: any) {
306
+ if (error.message?.includes("could not find account")) {
307
+ if (options.json) {
308
+ const output = {
309
+ owner: owner.toBase58(),
310
+ tokenAddress,
311
+ tokenAccount: tokenAccount.toBase58(),
312
+ balance: "0",
313
+ amount: "0",
314
+ };
315
+ console.log(JSON.stringify(output, null, 2));
316
+ } else {
317
+ printInfo("\nToken account does not exist yet.");
318
+ console.log(`Balance: 0 tokens`);
319
+ console.log(`Token Account (will be created on first transfer): ${tokenAccount.toBase58()}`);
320
+ }
321
+ } else {
322
+ throw error;
323
+ }
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Handle transaction status command
329
+ * @param signature Transaction signature
330
+ * @param options Command options
331
+ */
332
+ async function handleTxStatus(
333
+ signature: string,
334
+ options: Omit<TxStatusOptions, "signature">
335
+ ): Promise<void> {
336
+ const rpcUrl = getRpcUrl(options.rpcUrl);
337
+
338
+ printInfo(`Using RPC: ${rpcUrl}`);
339
+ printInfo(`Checking transaction: ${signature}`);
340
+
341
+ // Initialize SDK
342
+ const sdk = new NaraSDK({
343
+ rpcUrl,
344
+ commitment: "confirmed",
345
+ });
346
+
347
+ const connection = sdk.getConnection();
348
+
349
+ // Get transaction status
350
+ const status = await connection.getSignatureStatus(signature);
351
+
352
+ if (!status || !status.value) {
353
+ if (options.json) {
354
+ console.log(JSON.stringify({ signature, status: "not_found" }, null, 2));
355
+ } else {
356
+ printError("Transaction not found");
357
+ }
358
+ return;
359
+ }
360
+
361
+ // Get transaction details
362
+ const transaction = await connection.getTransaction(signature, {
363
+ maxSupportedTransactionVersion: 0,
364
+ });
365
+
366
+ // Build output
367
+ const output: any = {
368
+ signature,
369
+ status: status.value.confirmationStatus || "unknown",
370
+ slot: status.value.slot,
371
+ confirmations: status.value.confirmations,
372
+ };
373
+
374
+ if (status.value.err) {
375
+ output.error = status.value.err;
376
+ output.success = false;
377
+ } else {
378
+ output.success = true;
379
+ }
380
+
381
+ if (transaction) {
382
+ output.blockTime = transaction.blockTime;
383
+ output.fee = transaction.meta?.fee;
384
+ }
385
+
386
+ // Output result
387
+ if (options.json) {
388
+ console.log(JSON.stringify(output, null, 2));
389
+ } else {
390
+ console.log(`\nTransaction: ${signature}`);
391
+ console.log(`Status: ${output.status}`);
392
+ console.log(`Success: ${output.success ? "Yes" : "No"}`);
393
+ console.log(`Slot: ${output.slot}`);
394
+ if (output.confirmations !== null) {
395
+ console.log(`Confirmations: ${output.confirmations}`);
396
+ }
397
+ if (output.blockTime) {
398
+ const date = new Date(output.blockTime * 1000);
399
+ console.log(`Time: ${date.toISOString()}`);
400
+ }
401
+ if (output.fee) {
402
+ console.log(`Fee: ${output.fee / LAMPORTS_PER_SOL} NSO`);
403
+ }
404
+ if (output.error) {
405
+ console.log(`Error: ${JSON.stringify(output.error)}`);
406
+ }
407
+ console.log(
408
+ `\nView on Solscan: https://solscan.io/tx/${signature}?cluster=devnet`
409
+ );
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Handle transfer SOL command
415
+ * @param to Recipient address
416
+ * @param amount Amount in SOL
417
+ * @param options Command options
418
+ */
419
+ async function handleTransferSol(
420
+ to: string,
421
+ amount: string,
422
+ options: Omit<TransferSolOptions, "to" | "amount">
423
+ ): Promise<void> {
424
+ // Load wallet
425
+ const wallet = await loadWallet(options.wallet);
426
+ const rpcUrl = getRpcUrl(options.rpcUrl);
427
+
428
+ printInfo(`Using RPC: ${rpcUrl}`);
429
+ printInfo(`From: ${wallet.publicKey.toBase58()}`);
430
+
431
+ // Validate inputs
432
+ const recipient = validatePublicKey(to);
433
+ const amountSOL = validatePositiveNumber(amount, "amount");
434
+ const lamports = Math.floor(amountSOL * LAMPORTS_PER_SOL);
435
+
436
+ printInfo(`To: ${recipient.toBase58()}`);
437
+ printInfo(`Amount: ${amountSOL} NSO`);
438
+
439
+ // Initialize SDK
440
+ const sdk = new NaraSDK({
441
+ rpcUrl,
442
+ commitment: "confirmed",
443
+ });
444
+
445
+ const connection = sdk.getConnection();
446
+
447
+ // Create transfer instruction
448
+ const transferInstruction = SystemProgram.transfer({
449
+ fromPubkey: wallet.publicKey,
450
+ toPubkey: recipient,
451
+ lamports,
452
+ });
453
+
454
+ // Create transaction
455
+ const transaction = new Transaction().add(transferInstruction);
456
+
457
+ // Get latest blockhash
458
+ const { blockhash } = await connection.getLatestBlockhash("confirmed");
459
+ transaction.recentBlockhash = blockhash;
460
+ transaction.feePayer = wallet.publicKey;
461
+
462
+ // Handle transaction
463
+ const txResult = await handleTransaction(
464
+ sdk,
465
+ transaction,
466
+ [wallet],
467
+ options.exportTx || false
468
+ );
469
+
470
+ // Output result
471
+ if (options.json) {
472
+ const output = {
473
+ from: wallet.publicKey.toBase58(),
474
+ to: recipient.toBase58(),
475
+ amount: amountSOL,
476
+ lamports,
477
+ ...(txResult.signature && { signature: txResult.signature }),
478
+ ...(txResult.base64 && { transaction: txResult.base64 }),
479
+ };
480
+ console.log(JSON.stringify(output, null, 2));
481
+ } else {
482
+ console.log(`\nTransfer Details:`);
483
+ console.log(` From: ${wallet.publicKey.toBase58()}`);
484
+ console.log(` To: ${recipient.toBase58()}`);
485
+ console.log(` Amount: ${amountSOL} NSO`);
486
+ printTransactionResult(txResult, false);
487
+ }
488
+ }
489
+
490
+ /**
491
+ * Handle transfer token command
492
+ * @param tokenAddress Token address
493
+ * @param to Recipient address
494
+ * @param amount Amount in tokens
495
+ * @param options Command options
496
+ */
497
+ async function handleTransferToken(
498
+ tokenAddress: string,
499
+ to: string,
500
+ amount: string,
501
+ options: Omit<TransferTokenOptions, "tokenAddress" | "to" | "amount">
502
+ ): Promise<void> {
503
+ // Load wallet
504
+ const wallet = await loadWallet(options.wallet);
505
+ const rpcUrl = getRpcUrl(options.rpcUrl);
506
+
507
+ printInfo(`Using RPC: ${rpcUrl}`);
508
+ printInfo(`From: ${wallet.publicKey.toBase58()}`);
509
+
510
+ // Validate inputs
511
+ const tokenMint = validatePublicKey(tokenAddress);
512
+ const recipient = validatePublicKey(to);
513
+ const amountInToken = validatePositiveNumber(amount, "amount");
514
+ const decimals = parseInt(String(options.decimals || "6"));
515
+ const amountInSmallestUnit = Math.floor(amountInToken * 10 ** decimals);
516
+
517
+ printInfo(`To: ${recipient.toBase58()}`);
518
+ printInfo(`Token: ${tokenAddress}`);
519
+ printInfo(`Amount: ${amountInToken} tokens`);
520
+
521
+ // Initialize SDK
522
+ const sdk = new NaraSDK({
523
+ rpcUrl,
524
+ commitment: "confirmed",
525
+ });
526
+
527
+ const connection = sdk.getConnection();
528
+
529
+ // Get source and destination token accounts
530
+ const sourceAccount = await getAssociatedTokenAddress(
531
+ tokenMint,
532
+ wallet.publicKey
533
+ );
534
+ const destinationAccount = await getAssociatedTokenAddress(
535
+ tokenMint,
536
+ recipient
537
+ );
538
+
539
+ // Create transfer instruction
540
+ const transferInstruction = createTransferInstruction(
541
+ sourceAccount,
542
+ destinationAccount,
543
+ wallet.publicKey,
544
+ amountInSmallestUnit,
545
+ [],
546
+ TOKEN_PROGRAM_ID
547
+ );
548
+
549
+ // Create transaction
550
+ const transaction = new Transaction().add(transferInstruction);
551
+
552
+ // Get latest blockhash
553
+ const { blockhash } = await connection.getLatestBlockhash("confirmed");
554
+ transaction.recentBlockhash = blockhash;
555
+ transaction.feePayer = wallet.publicKey;
556
+
557
+ // Handle transaction
558
+ const txResult = await handleTransaction(
559
+ sdk,
560
+ transaction,
561
+ [wallet],
562
+ options.exportTx || false
563
+ );
564
+
565
+ // Output result
566
+ if (options.json) {
567
+ const output = {
568
+ from: wallet.publicKey.toBase58(),
569
+ to: recipient.toBase58(),
570
+ tokenAddress,
571
+ amount: amountInToken,
572
+ amountSmallestUnit: amountInSmallestUnit.toString(),
573
+ decimals,
574
+ ...(txResult.signature && { signature: txResult.signature }),
575
+ ...(txResult.base64 && { transaction: txResult.base64 }),
576
+ };
577
+ console.log(JSON.stringify(output, null, 2));
578
+ } else {
579
+ console.log(`\nToken Transfer Details:`);
580
+ console.log(` From: ${wallet.publicKey.toBase58()}`);
581
+ console.log(` To: ${recipient.toBase58()}`);
582
+ console.log(` Token: ${tokenAddress}`);
583
+ console.log(` Amount: ${amountInToken} tokens`);
584
+ printTransactionResult(txResult, false);
585
+ }
586
+ }
587
+
588
+ /**
589
+ * Handle wallet create command
590
+ * @param options Command options
591
+ */
592
+ async function handleWalletCreate(options: { output?: string }): Promise<void> {
593
+ const outputPath = options.output || DEFAULT_WALLET_PATH;
594
+
595
+ // Check if wallet file already exists
596
+ if (await Bun.file(outputPath).exists()) {
597
+ throw new Error(
598
+ `Wallet file already exists at ${outputPath}. Please use a different path or remove the existing file first.`
599
+ );
600
+ }
601
+
602
+ // Generate mnemonic (12 words by default, can be changed to 24)
603
+ const mnemonic = bip39.generateMnemonic(128); // 128 bits = 12 words, 256 bits = 24 words
604
+
605
+ // Derive keypair from mnemonic using Solana's derivation path
606
+ const seed = await bip39.mnemonicToSeed(mnemonic);
607
+ const derivedSeed = derivePath("m/44'/501'/0'/0'", seed.toString("hex")).key;
608
+ const keypair = Keypair.fromSeed(derivedSeed);
609
+
610
+ // Ensure directory exists
611
+ const dir = outputPath.substring(0, outputPath.lastIndexOf("/"));
612
+ await mkdir(dir, { recursive: true });
613
+
614
+ // Save wallet to file
615
+ const walletData = Array.from(keypair.secretKey);
616
+ await Bun.write(outputPath, JSON.stringify(walletData, null, 2));
617
+
618
+ // Display results
619
+ console.log("\nāœ… Wallet created successfully!");
620
+ console.log(`\nšŸ“ Wallet saved to: ${outputPath}`);
621
+ console.log(`šŸ”‘ Public Key: ${keypair.publicKey.toBase58()}`);
622
+
623
+ printWarning("\nāš ļø IMPORTANT: Save your mnemonic phrase securely!");
624
+ printWarning("āš ļø You will need it to recover your wallet.");
625
+ console.log("\nšŸ“ Mnemonic phrase (12 words):");
626
+ console.log(`\n${mnemonic}\n`);
627
+
628
+ printWarning("āš ļø Never share your mnemonic phrase with anyone!");
629
+ printWarning("āš ļø Anyone with your mnemonic can access your funds.\n");
630
+ }
631
+
632
+ /**
633
+ * Handle wallet import command
634
+ * @param options Command options
635
+ */
636
+ async function handleWalletImport(options: {
637
+ mnemonic?: string;
638
+ privateKey?: string;
639
+ output?: string;
640
+ }): Promise<void> {
641
+ const outputPath = options.output || DEFAULT_WALLET_PATH;
642
+
643
+ // Check if wallet file already exists
644
+ if (await Bun.file(outputPath).exists()) {
645
+ throw new Error(
646
+ `Wallet file already exists at ${outputPath}. Please use a different path or remove the existing file first.`
647
+ );
648
+ }
649
+
650
+ let keypair: Keypair;
651
+
652
+ if (options.mnemonic) {
653
+ // Import from mnemonic
654
+ const mnemonic = options.mnemonic.trim();
655
+
656
+ // Validate mnemonic
657
+ if (!bip39.validateMnemonic(mnemonic)) {
658
+ throw new Error("Invalid mnemonic phrase. Please check your words and try again.");
659
+ }
660
+
661
+ // Derive keypair from mnemonic
662
+ const seed = await bip39.mnemonicToSeed(mnemonic);
663
+ const derivedSeed = derivePath("m/44'/501'/0'/0'", seed.toString("hex")).key;
664
+ keypair = Keypair.fromSeed(derivedSeed);
665
+
666
+ printInfo("Importing wallet from mnemonic...");
667
+ } else if (options.privateKey) {
668
+ // Import from private key
669
+ const privateKey = options.privateKey.trim();
670
+
671
+ try {
672
+ if (privateKey.startsWith("[")) {
673
+ // JSON array format
674
+ const data = JSON.parse(privateKey);
675
+ keypair = Keypair.fromSecretKey(new Uint8Array(data));
676
+ } else {
677
+ // Base58 format
678
+ keypair = Keypair.fromSecretKey(bs58.decode(privateKey));
679
+ }
680
+ printInfo("Importing wallet from private key...");
681
+ } catch (error: any) {
682
+ throw new Error(`Invalid private key format: ${error.message}`);
683
+ }
684
+ } else {
685
+ throw new Error("Please provide either --mnemonic or --private-key option.");
686
+ }
687
+
688
+ // Ensure directory exists
689
+ const dir = outputPath.substring(0, outputPath.lastIndexOf("/"));
690
+ await mkdir(dir, { recursive: true });
691
+
692
+ // Save wallet to file
693
+ const walletData = Array.from(keypair.secretKey);
694
+ await Bun.write(outputPath, JSON.stringify(walletData, null, 2));
695
+
696
+ // Display results
697
+ console.log("\nāœ… Wallet imported successfully!");
698
+ console.log(`\nšŸ“ Wallet saved to: ${outputPath}`);
699
+ console.log(`šŸ”‘ Public Key: ${keypair.publicKey.toBase58()}\n`);
700
+ }
701
+
702
+ /**
703
+ * Handle wallet address command
704
+ * @param options Command options
705
+ */
706
+ async function handleWalletAddress(options: GlobalOptions): Promise<void> {
707
+ // Load wallet
708
+ const wallet = await loadWallet(options.wallet);
709
+
710
+ // Output result
711
+ if (options.json) {
712
+ const output = {
713
+ address: wallet.publicKey.toBase58(),
714
+ };
715
+ console.log(JSON.stringify(output, null, 2));
716
+ } else {
717
+ console.log(`\nšŸ”‘ Wallet Address: ${wallet.publicKey.toBase58()}\n`);
718
+ }
719
+ }