zoa-wallet 0.2.3 → 0.2.5

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