zoa-wallet 0.2.4 → 0.2.6

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.
Files changed (2) hide show
  1. package/dist/index.mjs +619 -102
  2. package/package.json +2 -2
package/dist/index.mjs CHANGED
@@ -22224,6 +22224,67 @@ async function deleteConfigValue(key) {
22224
22224
  function getConfigFilePath() {
22225
22225
  return CONFIG_FILE;
22226
22226
  }
22227
+ function generateWalletId() {
22228
+ const hex = Array.from(crypto.getRandomValues(new Uint8Array(4))).map((b) => b.toString(16).padStart(2, "0")).join("");
22229
+ return `w_${hex}`;
22230
+ }
22231
+ async function getWallets() {
22232
+ const config = await loadConfig();
22233
+ return config.wallets ?? [];
22234
+ }
22235
+ async function getActiveWallet() {
22236
+ const config = await loadConfig();
22237
+ if (!config.activeWalletId || !config.wallets?.length)
22238
+ return null;
22239
+ return config.wallets.find((w) => w.id === config.activeWalletId) ?? null;
22240
+ }
22241
+ async function getActiveAccountIndex() {
22242
+ const config = await loadConfig();
22243
+ return config.activeAccountIndex ?? 0;
22244
+ }
22245
+ async function addWallet(entry) {
22246
+ const config = await loadConfig();
22247
+ config.wallets = config.wallets ?? [];
22248
+ config.wallets.push(entry);
22249
+ if (!config.activeWalletId) {
22250
+ config.activeWalletId = entry.id;
22251
+ }
22252
+ await saveConfig(config);
22253
+ }
22254
+ async function removeWallet(id) {
22255
+ const config = await loadConfig();
22256
+ config.wallets = (config.wallets ?? []).filter((w) => w.id !== id);
22257
+ if (config.activeWalletId === id) {
22258
+ config.activeWalletId = config.wallets[0]?.id;
22259
+ config.activeAccountIndex = 0;
22260
+ }
22261
+ await saveConfig(config);
22262
+ }
22263
+ async function setActiveWallet(id) {
22264
+ const config = await loadConfig();
22265
+ const wallet = (config.wallets ?? []).find((w) => w.id === id);
22266
+ if (!wallet)
22267
+ throw new Error(`Wallet ${id} not found`);
22268
+ config.activeWalletId = id;
22269
+ config.activeAccountIndex = 0;
22270
+ wallet.lastUsedAt = Date.now();
22271
+ await saveConfig(config);
22272
+ }
22273
+ async function updateWallet(id, updates) {
22274
+ const config = await loadConfig();
22275
+ const wallet = (config.wallets ?? []).find((w) => w.id === id);
22276
+ if (!wallet)
22277
+ throw new Error(`Wallet ${id} not found`);
22278
+ if (updates.label !== void 0)
22279
+ wallet.label = updates.label;
22280
+ if (updates.color !== void 0)
22281
+ wallet.color = updates.color;
22282
+ if (updates.evmAddress !== void 0)
22283
+ wallet.evmAddress = updates.evmAddress;
22284
+ if (updates.solanaAddress !== void 0)
22285
+ wallet.solanaAddress = updates.solanaAddress;
22286
+ await saveConfig(config);
22287
+ }
22227
22288
 
22228
22289
  // dist/utils/output.js
22229
22290
  var _outputMode = "pretty";
@@ -44074,6 +44135,49 @@ var ChainAdapterRegistry = class {
44074
44135
  };
44075
44136
  var chainRegistry = new ChainAdapterRegistry();
44076
44137
 
44138
+ // dist/commands/balance.js
44139
+ import inquirer from "inquirer";
44140
+
44141
+ // dist/utils/format.js
44142
+ function truncateAddress(address, chars = 6) {
44143
+ if (address.length <= chars * 2 + 3)
44144
+ return address;
44145
+ return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
44146
+ }
44147
+ function formatBalance(balance, decimals = 4) {
44148
+ const num2 = typeof balance === "string" ? Number.parseFloat(balance) : balance;
44149
+ if (num2 === 0)
44150
+ return "0";
44151
+ if (num2 < 1e-4)
44152
+ return "< 0.0001";
44153
+ return num2.toFixed(decimals).replace(/\.?0+$/, "");
44154
+ }
44155
+ function formatUSD(value) {
44156
+ if (value < 0.01)
44157
+ return "< $0.01";
44158
+ return `$${value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
44159
+ }
44160
+ function timeAgo(timestamp) {
44161
+ const seconds = Math.floor((Date.now() - timestamp * 1e3) / 1e3);
44162
+ if (seconds < 60)
44163
+ return `${seconds}s ago`;
44164
+ const minutes = Math.floor(seconds / 60);
44165
+ if (minutes < 60)
44166
+ return `${minutes}m ago`;
44167
+ const hours = Math.floor(minutes / 60);
44168
+ if (hours < 24)
44169
+ return `${hours}h ago`;
44170
+ const days = Math.floor(hours / 24);
44171
+ if (days < 30)
44172
+ return `${days}d ago`;
44173
+ const months = Math.floor(days / 30);
44174
+ return `${months}mo ago`;
44175
+ }
44176
+ function formatGas(wei, decimals = 18) {
44177
+ const value = Number(wei) / 10 ** decimals;
44178
+ return formatBalance(value, 6);
44179
+ }
44180
+
44077
44181
  // ../../packages/wallet-core/dist/storage.js
44078
44182
  function uint8ToBase64(bytes) {
44079
44183
  let binary = "";
@@ -47399,17 +47503,17 @@ async function decryptVault(vault, password) {
47399
47503
  throw new Error("Incorrect password or corrupted vault");
47400
47504
  }
47401
47505
  }
47402
- async function saveVault(vault, storage) {
47506
+ async function saveVault(vault, storage, key) {
47403
47507
  const backend = storage ?? getDefaultStorage();
47404
- await backend.save(VAULT_KEY, vault);
47508
+ await backend.save(key ?? VAULT_KEY, vault);
47405
47509
  }
47406
- async function loadVault(storage) {
47510
+ async function loadVault(storage, key) {
47407
47511
  const backend = storage ?? getDefaultStorage();
47408
- return backend.load(VAULT_KEY);
47512
+ return backend.load(key ?? VAULT_KEY);
47409
47513
  }
47410
- async function hasVault(storage) {
47514
+ async function hasVault(storage, key) {
47411
47515
  const backend = storage ?? getDefaultStorage();
47412
- return backend.has(VAULT_KEY);
47516
+ return backend.has(key ?? VAULT_KEY);
47413
47517
  }
47414
47518
  async function deriveEncryptionKey(password, salt, iterations) {
47415
47519
  const encoder5 = new TextEncoder();
@@ -47449,8 +47553,10 @@ var KeyringManager = class {
47449
47553
  _seed = null;
47450
47554
  _accounts = [];
47451
47555
  _storage;
47452
- constructor(storage) {
47556
+ _vaultKey;
47557
+ constructor(storage, vaultKey) {
47453
47558
  this._storage = storage;
47559
+ this._vaultKey = vaultKey;
47454
47560
  }
47455
47561
  get state() {
47456
47562
  return this._state;
@@ -47464,12 +47570,12 @@ var KeyringManager = class {
47464
47570
  const account = await this.deriveAccount(0, "Account 1");
47465
47571
  this._accounts = [account];
47466
47572
  const vault = await encryptVault(this._mnemonic, password, [{ index: 0, label: "Account 1" }]);
47467
- await saveVault(vault, this._storage);
47573
+ await saveVault(vault, this._storage, this._vaultKey);
47468
47574
  this.persistSession();
47469
47575
  this._state = "unlocked";
47470
47576
  }
47471
47577
  async unlock(password) {
47472
- const vault = await loadVault(this._storage);
47578
+ const vault = await loadVault(this._storage, this._vaultKey);
47473
47579
  if (!vault) {
47474
47580
  throw new Error("No vault found. Initialize a wallet first.");
47475
47581
  }
@@ -47491,7 +47597,7 @@ var KeyringManager = class {
47491
47597
  if (!sessionMnemonic)
47492
47598
  return false;
47493
47599
  try {
47494
- const vault = await loadVault(this._storage);
47600
+ const vault = await loadVault(this._storage, this._vaultKey);
47495
47601
  if (!vault)
47496
47602
  return false;
47497
47603
  this._mnemonic = sessionMnemonic;
@@ -47581,7 +47687,7 @@ var KeyringManager = class {
47581
47687
  return `0x${bytesToHex7(account.keys.evm.privateKey)}`;
47582
47688
  }
47583
47689
  async hasVault() {
47584
- return hasVault(this._storage);
47690
+ return hasVault(this._storage, this._vaultKey);
47585
47691
  }
47586
47692
  requireUnlocked() {
47587
47693
  if (this._state !== "unlocked") {
@@ -47606,13 +47712,13 @@ var KeyringManager = class {
47606
47712
  async persistAccountMetadata() {
47607
47713
  if (!this._mnemonic)
47608
47714
  return;
47609
- const vault = await loadVault(this._storage);
47715
+ const vault = await loadVault(this._storage, this._vaultKey);
47610
47716
  if (vault) {
47611
47717
  vault.accounts = this._accounts.map((a) => ({
47612
47718
  index: a.index,
47613
47719
  label: a.label
47614
47720
  }));
47615
- await saveVault(vault, this._storage);
47721
+ await saveVault(vault, this._storage, this._vaultKey);
47616
47722
  }
47617
47723
  }
47618
47724
  persistSession() {
@@ -47621,8 +47727,8 @@ var KeyringManager = class {
47621
47727
  }
47622
47728
  }
47623
47729
  };
47624
- function createKeyring(storage) {
47625
- return new KeyringManager(storage);
47730
+ function createKeyring(storage, vaultKey) {
47731
+ return new KeyringManager(storage, vaultKey);
47626
47732
  }
47627
47733
  var keyring = new KeyringManager();
47628
47734
 
@@ -47678,47 +47784,50 @@ var FileStorage = class {
47678
47784
  }
47679
47785
  };
47680
47786
 
47681
- // dist/commands/balance.js
47682
- import inquirer from "inquirer";
47683
-
47684
- // dist/utils/format.js
47685
- function truncateAddress(address, chars = 6) {
47686
- if (address.length <= chars * 2 + 3)
47687
- return address;
47688
- return `${address.slice(0, chars + 2)}...${address.slice(-chars)}`;
47689
- }
47690
- function formatBalance(balance, decimals = 4) {
47691
- const num2 = typeof balance === "string" ? Number.parseFloat(balance) : balance;
47692
- if (num2 === 0)
47693
- return "0";
47694
- if (num2 < 1e-4)
47695
- return "< 0.0001";
47696
- return num2.toFixed(decimals).replace(/\.?0+$/, "");
47697
- }
47698
- function formatUSD(value) {
47699
- if (value < 0.01)
47700
- return "< $0.01";
47701
- return `$${value.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
47702
- }
47703
- function timeAgo(timestamp) {
47704
- const seconds = Math.floor((Date.now() - timestamp * 1e3) / 1e3);
47705
- if (seconds < 60)
47706
- return `${seconds}s ago`;
47707
- const minutes = Math.floor(seconds / 60);
47708
- if (minutes < 60)
47709
- return `${minutes}m ago`;
47710
- const hours = Math.floor(minutes / 60);
47711
- if (hours < 24)
47712
- return `${hours}h ago`;
47713
- const days = Math.floor(hours / 24);
47714
- if (days < 30)
47715
- return `${days}d ago`;
47716
- const months = Math.floor(days / 30);
47717
- return `${months}mo ago`;
47787
+ // dist/utils/wallet.js
47788
+ var LEGACY_VAULT_KEY = "zoa-vault";
47789
+ async function loadActiveWallet(password) {
47790
+ await migrateLegacyVaultIfNeeded();
47791
+ const wallet = await getActiveWallet();
47792
+ if (!wallet) {
47793
+ throw new Error("No wallet configured. Run 'zoa wallet create' or 'zoa init' first.");
47794
+ }
47795
+ const storage = new FileStorage();
47796
+ const kr = createKeyring(storage, wallet.vaultKey);
47797
+ await kr.unlock(password);
47798
+ const accounts = kr.getAccounts();
47799
+ const accountIndex = await getActiveAccountIndex();
47800
+ const account = accounts[accountIndex] ?? accounts[0];
47801
+ if (!account)
47802
+ throw new Error("No accounts found in wallet.");
47803
+ const config = await loadConfig();
47804
+ const entry = (config.wallets ?? []).find((w) => w.id === wallet.id);
47805
+ if (entry) {
47806
+ entry.lastUsedAt = Date.now();
47807
+ if (!entry.evmAddress || !entry.solanaAddress) {
47808
+ entry.evmAddress = account.evmAddress;
47809
+ entry.solanaAddress = account.solanaAddress;
47810
+ }
47811
+ await saveConfig(config);
47812
+ }
47813
+ return { keyring: kr, wallet, account };
47718
47814
  }
47719
- function formatGas(wei, decimals = 18) {
47720
- const value = Number(wei) / 10 ** decimals;
47721
- return formatBalance(value, 6);
47815
+ async function migrateLegacyVaultIfNeeded() {
47816
+ const wallets = await getWallets();
47817
+ if (wallets.length > 0)
47818
+ return;
47819
+ const storage = new FileStorage();
47820
+ const hasLegacy = await storage.has(LEGACY_VAULT_KEY);
47821
+ if (!hasLegacy)
47822
+ return;
47823
+ const id = generateWalletId();
47824
+ await addWallet({
47825
+ id,
47826
+ label: "Default Wallet",
47827
+ color: "#00bbf9",
47828
+ vaultKey: LEGACY_VAULT_KEY,
47829
+ createdAt: Date.now()
47830
+ });
47722
47831
  }
47723
47832
 
47724
47833
  // dist/commands/balance.js
@@ -47750,13 +47859,7 @@ function registerBalanceCommand(program2) {
47750
47859
  const spinner = createSpinner("Unlocking wallet...");
47751
47860
  if (!isJsonMode())
47752
47861
  spinner.start();
47753
- const storage = new FileStorage();
47754
- const kr = createKeyring(storage);
47755
- await kr.unlock(password);
47756
- const accounts = kr.getAccounts();
47757
- const account = accounts[0];
47758
- if (!account)
47759
- throw new Error("No accounts found.");
47862
+ const { account } = await loadActiveWallet(password);
47760
47863
  if (!isJsonMode())
47761
47864
  spinner.text = " Fetching balances across networks...";
47762
47865
  let chainsToFetch = Object.entries(CHAIN_MAP);
@@ -47939,9 +48042,7 @@ function registerExportCommand(program2) {
47939
48042
  const spinner = createSpinner("Unlocking wallet...");
47940
48043
  if (!isJsonMode())
47941
48044
  spinner.start();
47942
- const storage = new FileStorage();
47943
- const kr = createKeyring(storage);
47944
- await kr.unlock(password);
48045
+ const { keyring: kr } = await loadActiveWallet(password);
47945
48046
  if (!isJsonMode())
47946
48047
  spinner.stop();
47947
48048
  let exportType = opts.type;
@@ -48030,13 +48131,7 @@ function registerHistoryCommand(program2) {
48030
48131
  const spinner = createSpinner("Unlocking wallet...");
48031
48132
  if (!isJsonMode())
48032
48133
  spinner.start();
48033
- const storage = new FileStorage();
48034
- const kr = createKeyring(storage);
48035
- await kr.unlock(password);
48036
- const accounts = kr.getAccounts();
48037
- const account = accounts[0];
48038
- if (!account)
48039
- throw new Error("No accounts found.");
48134
+ const { account } = await loadActiveWallet(password);
48040
48135
  const chainMap = {
48041
48136
  ethereum: ChainId.Ethereum,
48042
48137
  base: ChainId.Base,
@@ -48164,13 +48259,25 @@ function registerInitCommand(program2) {
48164
48259
  const spinner = createSpinner("Deriving keys and encrypting vault...");
48165
48260
  if (!isJsonMode())
48166
48261
  spinner.start();
48262
+ const walletId = generateWalletId();
48263
+ const vaultKey = `wallet_${walletId}`;
48167
48264
  const storage = new FileStorage();
48168
- const kr = createKeyring(storage);
48265
+ const kr = createKeyring(storage, vaultKey);
48169
48266
  await kr.initialize(mnemonic, password);
48170
- if (!isJsonMode())
48171
- spinner.succeed(" Wallet initialized successfully!");
48172
48267
  const accounts = kr.getAccounts();
48173
48268
  const firstAccount = accounts[0];
48269
+ await addWallet({
48270
+ id: walletId,
48271
+ label: "Default Wallet",
48272
+ color: "#00bbf9",
48273
+ vaultKey,
48274
+ createdAt: Date.now(),
48275
+ evmAddress: firstAccount?.evmAddress,
48276
+ solanaAddress: firstAccount?.solanaAddress
48277
+ });
48278
+ await setActiveWallet(walletId);
48279
+ if (!isJsonMode())
48280
+ spinner.succeed(" Wallet initialized successfully!");
48174
48281
  if (firstAccount) {
48175
48282
  output({
48176
48283
  success: true,
@@ -48289,13 +48396,7 @@ function registerReceiveCommand(program2) {
48289
48396
  const spinner = createSpinner("Unlocking wallet...");
48290
48397
  if (!isJsonMode())
48291
48398
  spinner.start();
48292
- const storage = new FileStorage();
48293
- const kr = createKeyring(storage);
48294
- await kr.unlock(password);
48295
- const accounts = kr.getAccounts();
48296
- const account = accounts[0];
48297
- if (!account)
48298
- throw new Error("No accounts found.");
48399
+ const { account } = await loadActiveWallet(password);
48299
48400
  if (!isJsonMode())
48300
48401
  spinner.stop();
48301
48402
  const showEvm = !opts.chain || opts.chain === "evm";
@@ -48393,13 +48494,7 @@ function registerSendCommand(program2) {
48393
48494
  const spinner = createSpinner("Unlocking wallet...");
48394
48495
  if (!isJsonMode())
48395
48496
  spinner.start();
48396
- const storage = new FileStorage();
48397
- const kr = createKeyring(storage);
48398
- await kr.unlock(password);
48399
- const accounts = kr.getAccounts();
48400
- const account = accounts[0];
48401
- if (!account)
48402
- throw new Error("No accounts found.");
48497
+ const { account } = await loadActiveWallet(password);
48403
48498
  if (!isJsonMode())
48404
48499
  spinner.text = " Estimating gas...";
48405
48500
  const chainMap = {
@@ -48480,8 +48575,421 @@ function registerSendCommand(program2) {
48480
48575
  });
48481
48576
  }
48482
48577
 
48483
- // dist/ui/banner.js
48578
+ // dist/commands/wallet.js
48484
48579
  import chalk3 from "chalk";
48580
+ import inquirer7 from "inquirer";
48581
+ var COLOR_PRESETS = [
48582
+ { name: "Cyan", hex: "#00bbf9" },
48583
+ { name: "Orange", hex: "#ff6b35" },
48584
+ { name: "Purple", hex: "#7b2ff7" },
48585
+ { name: "Green", hex: "#10b981" },
48586
+ { name: "Pink", hex: "#ec4899" },
48587
+ { name: "Yellow", hex: "#f59e0b" },
48588
+ { name: "Red", hex: "#ef4444" },
48589
+ { name: "Teal", hex: "#14b8a6" }
48590
+ ];
48591
+ function registerWalletCommand(program2) {
48592
+ const wallet = program2.command("wallet").description("Manage multiple wallets");
48593
+ wallet.command("list").description("List all wallets").action(async () => {
48594
+ try {
48595
+ await migrateLegacyVaultIfNeeded();
48596
+ const wallets = await getWallets();
48597
+ const active = await getActiveWallet();
48598
+ if (wallets.length === 0) {
48599
+ output({ wallets: [] }, () => {
48600
+ console.log(colors.muted("\n No wallets configured. Run 'zoa wallet create' to get started.\n"));
48601
+ });
48602
+ return;
48603
+ }
48604
+ output({
48605
+ wallets: wallets.map((w) => ({
48606
+ ...w,
48607
+ active: w.id === active?.id
48608
+ })),
48609
+ activeWalletId: active?.id
48610
+ }, () => {
48611
+ console.log(sectionHeader("Wallets"));
48612
+ const shortenAddr = (addr) => addr ? `${addr.slice(0, 4)}...${addr.slice(-4)}` : "\u2014";
48613
+ const table = createTable(["", "Label", "ID", "EVM Address", "Solana Address", "Created", "Last Used"], [4, 22, 14, 14, 14, 14, 14]);
48614
+ for (const w of wallets) {
48615
+ const isActive = w.id === active?.id;
48616
+ const dot = chalk3.hex(w.color)("\u25CF");
48617
+ const indicator = isActive ? ` ${chalk3.hex("#f59e0b")("\u2605")}` : "";
48618
+ const label = isActive ? chalk3.bold(w.label) : w.label;
48619
+ const created = new Date(w.createdAt).toLocaleDateString();
48620
+ const lastUsed = w.lastUsedAt ? new Date(w.lastUsedAt).toLocaleDateString() : colors.muted("never");
48621
+ table.push([
48622
+ `${dot}${indicator}`,
48623
+ label,
48624
+ colors.muted(w.id),
48625
+ colors.address(shortenAddr(w.evmAddress)),
48626
+ colors.address(shortenAddr(w.solanaAddress)),
48627
+ colors.muted(created),
48628
+ typeof lastUsed === "string" ? lastUsed : lastUsed
48629
+ ]);
48630
+ }
48631
+ console.log(table.toString());
48632
+ console.log();
48633
+ });
48634
+ } catch (error) {
48635
+ const msg = error instanceof Error ? error.message : String(error);
48636
+ outputError(msg);
48637
+ if (!isJsonMode())
48638
+ console.log(errorMsg(msg));
48639
+ process.exitCode = 1;
48640
+ }
48641
+ });
48642
+ wallet.command("create").description("Create a new wallet").option("--label <label>", "Wallet label").option("--color <hex>", "Wallet color (hex)").option("--mnemonic <phrase>", "Import mnemonic phrase").option("--password <password>", "Set wallet password").option("--word-count <count>", "Word count for new mnemonic: 12 or 24", "12").action(async (opts) => {
48643
+ try {
48644
+ let label;
48645
+ let color;
48646
+ let mnemonic;
48647
+ let password;
48648
+ let isNew = false;
48649
+ if (opts.label && opts.password) {
48650
+ label = opts.label;
48651
+ color = opts.color || "#00bbf9";
48652
+ if (opts.mnemonic) {
48653
+ mnemonic = opts.mnemonic;
48654
+ } else {
48655
+ const wordCount = Number(opts.wordCount) === 24 ? 24 : 12;
48656
+ const result = createMnemonic(wordCount);
48657
+ mnemonic = result.phrase;
48658
+ isNew = true;
48659
+ }
48660
+ password = opts.password;
48661
+ } else {
48662
+ if (!isJsonMode()) {
48663
+ console.log(colors.brandBold(" Create New Wallet"));
48664
+ console.log();
48665
+ }
48666
+ const { walletLabel } = await inquirer7.prompt([{
48667
+ type: "input",
48668
+ name: "walletLabel",
48669
+ message: "Wallet label:",
48670
+ default: "My Wallet",
48671
+ validate: (input) => input.trim().length > 0 || "Label is required"
48672
+ }]);
48673
+ label = walletLabel;
48674
+ const colorChoices = COLOR_PRESETS.map((c) => ({
48675
+ name: `${chalk3.hex(c.hex)("\u25CF")} ${c.name}`,
48676
+ value: c.hex
48677
+ }));
48678
+ colorChoices.push({ name: "Custom hex...", value: "custom" });
48679
+ const { selectedColor } = await inquirer7.prompt([{
48680
+ type: "list",
48681
+ name: "selectedColor",
48682
+ message: "Choose a color:",
48683
+ choices: colorChoices
48684
+ }]);
48685
+ if (selectedColor === "custom") {
48686
+ const { customHex } = await inquirer7.prompt([{
48687
+ type: "input",
48688
+ name: "customHex",
48689
+ message: "Enter hex color (e.g. #ff6b35):",
48690
+ validate: (input) => /^#[0-9a-fA-F]{6}$/.test(input) || "Enter a valid hex color"
48691
+ }]);
48692
+ color = customHex;
48693
+ } else {
48694
+ color = selectedColor;
48695
+ }
48696
+ const { action } = await inquirer7.prompt([{
48697
+ type: "list",
48698
+ name: "action",
48699
+ message: "What would you like to do?",
48700
+ choices: [
48701
+ { name: "Create a new wallet", value: "create" },
48702
+ { name: "Import from recovery phrase", value: "import" }
48703
+ ]
48704
+ }]);
48705
+ if (action === "create") {
48706
+ isNew = true;
48707
+ const wordCount = Number(opts.wordCount) === 24 ? 24 : 12;
48708
+ const result = createMnemonic(wordCount);
48709
+ mnemonic = result.phrase;
48710
+ if (!isJsonMode()) {
48711
+ console.log();
48712
+ console.log(warningBox("Recovery Phrase \u2014 Save This!", [
48713
+ colors.warning("Write down these words and store them safely."),
48714
+ colors.warning("Anyone with this phrase can access your funds."),
48715
+ "",
48716
+ colors.highlight(mnemonic),
48717
+ "",
48718
+ colors.muted(`${wordCount} words`)
48719
+ ]));
48720
+ console.log();
48721
+ }
48722
+ } else {
48723
+ const { phrase } = await inquirer7.prompt([{
48724
+ type: "input",
48725
+ name: "phrase",
48726
+ message: "Enter your recovery phrase:"
48727
+ }]);
48728
+ mnemonic = phrase.trim();
48729
+ }
48730
+ const { pwd } = await inquirer7.prompt([{
48731
+ type: "password",
48732
+ name: "pwd",
48733
+ message: "Set a secure password:",
48734
+ mask: "*",
48735
+ validate: (input) => input.length >= 8 || "Password must be at least 8 characters"
48736
+ }]);
48737
+ await inquirer7.prompt([{
48738
+ type: "password",
48739
+ name: "confirmPwd",
48740
+ message: "Confirm password:",
48741
+ mask: "*",
48742
+ validate: (input) => input === pwd || "Passwords do not match"
48743
+ }]);
48744
+ password = pwd;
48745
+ }
48746
+ const spinner = createSpinner("Creating wallet...");
48747
+ if (!isJsonMode())
48748
+ spinner.start();
48749
+ const walletId = generateWalletId();
48750
+ const vaultKey = `wallet_${walletId}`;
48751
+ const storage = new FileStorage();
48752
+ const kr = createKeyring(storage, vaultKey);
48753
+ await kr.initialize(mnemonic, password);
48754
+ const accounts = kr.getAccounts();
48755
+ const firstAccount = accounts[0];
48756
+ await addWallet({
48757
+ id: walletId,
48758
+ label,
48759
+ color,
48760
+ vaultKey,
48761
+ createdAt: Date.now(),
48762
+ evmAddress: firstAccount?.evmAddress,
48763
+ solanaAddress: firstAccount?.solanaAddress
48764
+ });
48765
+ await setActiveWallet(walletId);
48766
+ if (!isJsonMode())
48767
+ spinner.succeed(" Wallet created successfully!");
48768
+ if (firstAccount) {
48769
+ output({
48770
+ success: true,
48771
+ isNew,
48772
+ walletId,
48773
+ label,
48774
+ color,
48775
+ account: {
48776
+ index: firstAccount.index,
48777
+ label: firstAccount.label,
48778
+ evmAddress: firstAccount.evmAddress,
48779
+ solanaAddress: firstAccount.solanaAddress
48780
+ },
48781
+ ...isNew ? { mnemonic } : {}
48782
+ }, () => {
48783
+ console.log();
48784
+ console.log(successBox(`Wallet: ${label}`, [
48785
+ kvLine("Wallet ID", walletId),
48786
+ kvLine("EVM Address", firstAccount.evmAddress),
48787
+ kvLine("Solana", firstAccount.solanaAddress),
48788
+ "",
48789
+ colors.muted("Vault encrypted and saved to ~/.zoa/")
48790
+ ]));
48791
+ console.log();
48792
+ });
48793
+ }
48794
+ } catch (error) {
48795
+ const msg = error instanceof Error ? error.message : String(error);
48796
+ outputError(msg);
48797
+ if (!isJsonMode())
48798
+ console.log(errorMsg(msg));
48799
+ process.exitCode = 1;
48800
+ }
48801
+ });
48802
+ wallet.command("switch [id-or-label]").description("Switch active wallet").action(async (idOrLabel) => {
48803
+ try {
48804
+ await migrateLegacyVaultIfNeeded();
48805
+ const wallets = await getWallets();
48806
+ if (wallets.length === 0) {
48807
+ throw new Error("No wallets configured. Run 'zoa wallet create' first.");
48808
+ }
48809
+ let targetId;
48810
+ if (idOrLabel) {
48811
+ const match = wallets.find((w) => w.id === idOrLabel || w.label.toLowerCase() === idOrLabel.toLowerCase() || w.id.includes(idOrLabel) || w.label.toLowerCase().includes(idOrLabel.toLowerCase()));
48812
+ if (!match)
48813
+ throw new Error(`No wallet found matching '${idOrLabel}'`);
48814
+ targetId = match.id;
48815
+ } else {
48816
+ const active = await getActiveWallet();
48817
+ const { selected } = await inquirer7.prompt([{
48818
+ type: "list",
48819
+ name: "selected",
48820
+ message: "Select wallet:",
48821
+ choices: wallets.map((w) => ({
48822
+ name: `${chalk3.hex(w.color)("\u25CF")} ${w.label}${w.id === active?.id ? chalk3.hex("#f59e0b")(" \u2605 active") : ""} ${colors.muted(`(${w.id})`)}`,
48823
+ value: w.id
48824
+ }))
48825
+ }]);
48826
+ targetId = selected;
48827
+ }
48828
+ await setActiveWallet(targetId);
48829
+ const switched = wallets.find((w) => w.id === targetId);
48830
+ output({ success: true, activeWalletId: targetId, label: switched.label }, () => {
48831
+ console.log(`
48832
+ ${chalk3.hex(switched.color)("\u25CF")} Switched to ${chalk3.bold(switched.label)} ${colors.muted(`(${switched.id})`)}
48833
+ `);
48834
+ });
48835
+ } catch (error) {
48836
+ const msg = error instanceof Error ? error.message : String(error);
48837
+ outputError(msg);
48838
+ if (!isJsonMode())
48839
+ console.log(errorMsg(msg));
48840
+ process.exitCode = 1;
48841
+ }
48842
+ });
48843
+ wallet.command("rename <id> <new-label>").description("Rename a wallet").action(async (id, newLabel) => {
48844
+ try {
48845
+ await migrateLegacyVaultIfNeeded();
48846
+ const wallets = await getWallets();
48847
+ const match = wallets.find((w) => w.id === id || w.label.toLowerCase() === id.toLowerCase());
48848
+ if (!match)
48849
+ throw new Error(`No wallet found matching '${id}'`);
48850
+ await updateWallet(match.id, { label: newLabel });
48851
+ output({ success: true, id: match.id, label: newLabel }, () => {
48852
+ console.log(`
48853
+ ${chalk3.hex(match.color)("\u25CF")} Renamed to ${chalk3.bold(newLabel)} ${colors.muted(`(${match.id})`)}
48854
+ `);
48855
+ });
48856
+ } catch (error) {
48857
+ const msg = error instanceof Error ? error.message : String(error);
48858
+ outputError(msg);
48859
+ if (!isJsonMode())
48860
+ console.log(errorMsg(msg));
48861
+ process.exitCode = 1;
48862
+ }
48863
+ });
48864
+ wallet.command("color <id> <hex>").description("Change wallet color").action(async (id, hex) => {
48865
+ try {
48866
+ if (!/^#[0-9a-fA-F]{6}$/.test(hex)) {
48867
+ throw new Error("Invalid hex color. Use format: #ff6b35");
48868
+ }
48869
+ await migrateLegacyVaultIfNeeded();
48870
+ const wallets = await getWallets();
48871
+ const match = wallets.find((w) => w.id === id || w.label.toLowerCase() === id.toLowerCase());
48872
+ if (!match)
48873
+ throw new Error(`No wallet found matching '${id}'`);
48874
+ await updateWallet(match.id, { color: hex });
48875
+ output({ success: true, id: match.id, color: hex }, () => {
48876
+ console.log(`
48877
+ ${chalk3.hex(hex)("\u25CF")} Updated color for ${chalk3.bold(match.label)}
48878
+ `);
48879
+ });
48880
+ } catch (error) {
48881
+ const msg = error instanceof Error ? error.message : String(error);
48882
+ outputError(msg);
48883
+ if (!isJsonMode())
48884
+ console.log(errorMsg(msg));
48885
+ process.exitCode = 1;
48886
+ }
48887
+ });
48888
+ wallet.command("remove <id>").description("Remove a wallet").option("--yes", "Skip confirmation").action(async (id, opts) => {
48889
+ try {
48890
+ await migrateLegacyVaultIfNeeded();
48891
+ const wallets = await getWallets();
48892
+ const match = wallets.find((w) => w.id === id || w.label.toLowerCase() === id.toLowerCase());
48893
+ if (!match)
48894
+ throw new Error(`No wallet found matching '${id}'`);
48895
+ if (!opts.yes && !isJsonMode()) {
48896
+ const { confirm } = await inquirer7.prompt([{
48897
+ type: "confirm",
48898
+ name: "confirm",
48899
+ message: `Remove wallet '${match.label}' (${match.id})? This will delete the vault file.`,
48900
+ default: false
48901
+ }]);
48902
+ if (!confirm) {
48903
+ console.log(colors.muted(" Cancelled."));
48904
+ return;
48905
+ }
48906
+ }
48907
+ const storage = new FileStorage();
48908
+ await storage.delete(match.vaultKey);
48909
+ await removeWallet(match.id);
48910
+ output({ success: true, removed: match.id }, () => {
48911
+ console.log(`
48912
+ ${colors.error("\u2716")} Removed wallet ${chalk3.bold(match.label)} ${colors.muted(`(${match.id})`)}
48913
+ `);
48914
+ });
48915
+ } catch (error) {
48916
+ const msg = error instanceof Error ? error.message : String(error);
48917
+ outputError(msg);
48918
+ if (!isJsonMode())
48919
+ console.log(errorMsg(msg));
48920
+ process.exitCode = 1;
48921
+ }
48922
+ });
48923
+ wallet.command("info").description("Show active wallet details").option("--password <password>", "Wallet password (non-interactive)").action(async (opts) => {
48924
+ try {
48925
+ await migrateLegacyVaultIfNeeded();
48926
+ const active = await getActiveWallet();
48927
+ if (!active) {
48928
+ throw new Error("No active wallet. Run 'zoa wallet create' first.");
48929
+ }
48930
+ let password;
48931
+ if (opts.password) {
48932
+ password = opts.password;
48933
+ } else {
48934
+ const { pwd } = await inquirer7.prompt([{
48935
+ type: "password",
48936
+ name: "pwd",
48937
+ message: "Enter your wallet password:",
48938
+ mask: "*"
48939
+ }]);
48940
+ password = pwd;
48941
+ }
48942
+ const spinner = createSpinner("Unlocking wallet...");
48943
+ if (!isJsonMode())
48944
+ spinner.start();
48945
+ const storage = new FileStorage();
48946
+ const kr = createKeyring(storage, active.vaultKey);
48947
+ await kr.unlock(password);
48948
+ const accounts = kr.getAccounts();
48949
+ if (!isJsonMode())
48950
+ spinner.stop();
48951
+ output({
48952
+ wallet: {
48953
+ id: active.id,
48954
+ label: active.label,
48955
+ color: active.color,
48956
+ createdAt: active.createdAt,
48957
+ lastUsedAt: active.lastUsedAt
48958
+ },
48959
+ accounts: accounts.map((a) => ({
48960
+ index: a.index,
48961
+ label: a.label,
48962
+ evmAddress: a.evmAddress,
48963
+ solanaAddress: a.solanaAddress
48964
+ }))
48965
+ }, () => {
48966
+ console.log(sectionHeader(`${chalk3.hex(active.color)("\u25CF")} ${active.label}`));
48967
+ console.log(kvLine("ID", active.id));
48968
+ console.log(kvLine("Color", chalk3.hex(active.color)(active.color)));
48969
+ console.log(kvLine("Created", new Date(active.createdAt).toLocaleString()));
48970
+ if (active.lastUsedAt) {
48971
+ console.log(kvLine("Last Used", new Date(active.lastUsedAt).toLocaleString()));
48972
+ }
48973
+ console.log();
48974
+ for (const account of accounts) {
48975
+ console.log(colors.brandBold(` Account ${account.index + 1}: ${account.label}`));
48976
+ console.log(kvLine("EVM", account.evmAddress));
48977
+ console.log(kvLine("Solana", account.solanaAddress));
48978
+ console.log();
48979
+ }
48980
+ });
48981
+ } catch (error) {
48982
+ const msg = error instanceof Error ? error.message : String(error);
48983
+ outputError(msg);
48984
+ if (!isJsonMode())
48985
+ console.log(errorMsg(msg));
48986
+ process.exitCode = 1;
48987
+ }
48988
+ });
48989
+ }
48990
+
48991
+ // dist/ui/banner.js
48992
+ import chalk4 from "chalk";
48485
48993
  import figlet from "figlet";
48486
48994
  import gradient2 from "gradient-string";
48487
48995
  var zoaGradient2 = gradient2([
@@ -48492,37 +49000,45 @@ var zoaGradient2 = gradient2([
48492
49000
  "#c77dff"
48493
49001
  ]);
48494
49002
  var subtleGradient = gradient2(["#6b7280", "#9ca3af", "#6b7280"]);
48495
- var VERSION = "0.2.4";
48496
- function displayBanner() {
49003
+ var VERSION = "0.2.6";
49004
+ async function displayBanner() {
48497
49005
  const banner = figlet.textSync("ZOA-wallet", { font: "ANSI Shadow" });
48498
49006
  console.log();
48499
49007
  console.log(zoaGradient2.multiline(banner));
48500
- const tagline = " API-First Crypto Wallet for Developers & AI Agents";
49008
+ const tagline = " API-First Crypto Wallet for Power-traders, Developers, & AI Agents";
48501
49009
  console.log(subtleGradient(tagline));
48502
49010
  console.log();
48503
- const versionBadge = chalk3.bgHex("#5e60ce").white.bold(` v${VERSION} `);
48504
- const githubLink = chalk3.hex("#6b7280")("github.com/airxtech/zoa-wallet");
48505
- const npmBadge = chalk3.hex("#cb3837")("npm") + chalk3.hex("#6b7280")(" zoa-wallet");
48506
- console.log(` ${versionBadge} ${chalk3.hex("#3b3b3b")("\u2502")} ${githubLink} ${chalk3.hex("#3b3b3b")("\u2502")} ${npmBadge}`);
49011
+ const versionBadge = chalk4.bgHex("#5e60ce").white.bold(` v${VERSION} `);
49012
+ const siteLink = chalk4.white("https://") + chalk4.hex("#ff6b35")("wallet") + chalk4.hex("#00bbf9")(".zoa.fun");
49013
+ const npmBadge = chalk4.hex("#cb3837")("npm") + chalk4.hex("#6b7280")(" zoa-wallet");
49014
+ let walletIndicator = "";
49015
+ try {
49016
+ const active = await getActiveWallet();
49017
+ if (active) {
49018
+ walletIndicator = ` ${chalk4.hex("#3b3b3b")("\u2502")} ${chalk4.hex(active.color)("\u25CF")} ${chalk4.bold(active.label)}`;
49019
+ }
49020
+ } catch {
49021
+ }
49022
+ console.log(` ${versionBadge} ${chalk4.hex("#3b3b3b")("\u2502")} ${siteLink} ${chalk4.hex("#3b3b3b")("\u2502")} ${npmBadge}${walletIndicator}`);
48507
49023
  console.log();
48508
- console.log(chalk3.hex("#2d2d2d")(" " + "\u2500".repeat(58)));
49024
+ console.log(chalk4.hex("#2d2d2d")(" " + "\u2500".repeat(58)));
48509
49025
  console.log();
48510
49026
  }
48511
49027
 
48512
49028
  // dist/index.js
48513
49029
  program.option("--json", "Output results as JSON (for scripts and AI agents)");
48514
- program.hook("preAction", (_thisCommand, actionCommand) => {
49030
+ program.hook("preAction", async (_thisCommand, actionCommand) => {
48515
49031
  const rootOpts = program.opts();
48516
49032
  if (rootOpts.json) {
48517
49033
  setOutputMode("json");
48518
49034
  } else {
48519
49035
  const cmdName = actionCommand.name();
48520
49036
  if (cmdName !== "help") {
48521
- displayBanner();
49037
+ await displayBanner();
48522
49038
  }
48523
49039
  }
48524
49040
  });
48525
- program.name("zoa").description("ZOA Wallet \u2014 API-First Crypto Wallet for Developers & AI Agents").version("0.2.4");
49041
+ program.name("zoa").description("ZOA Wallet \u2014 API-First Crypto Wallet for Power-traders, Developers, & AI Agents").version("0.2.6");
48526
49042
  registerInitCommand(program);
48527
49043
  registerBalanceCommand(program);
48528
49044
  registerSendCommand(program);
@@ -48533,8 +49049,9 @@ registerChainsCommand(program);
48533
49049
  registerPricesCommand(program);
48534
49050
  registerConfigCommand(program);
48535
49051
  registerApiCommand(program);
49052
+ registerWalletCommand(program);
48536
49053
  if (!process.argv.slice(2).length) {
48537
- displayBanner();
49054
+ await displayBanner();
48538
49055
  }
48539
49056
  program.parse(process.argv);
48540
49057
  if (!process.argv.slice(2).length) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zoa-wallet",
3
- "version": "0.2.4",
4
- "description": "API-First Crypto Wallet CLI for Developers & AI Agents. Manage multi-chain wallets from your terminal.",
3
+ "version": "0.2.6",
4
+ "description": "API-First Crypto Wallet CLI for Power-traders, Developers, & AI Agents. Manage multi-chain wallets from your terminal.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "keywords": [