{"version":3,"file":"wallet-BrTTnf7w.mjs","names":["nodeErr"],"sources":["../src/wallet/index.ts"],"sourcesContent":["import crypto from 'node:crypto'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { readFile, writeFile, mkdir, stat } from 'node:fs/promises'\nimport type { Address, Hex } from 'viem'\nimport { privateKeyToAccount } from 'viem/accounts'\n\n// Path derived at call time so tests can override HOME.\nexport function walletPath(): string {\n  return path.join(os.homedir(), '.chain-insights', 'wallet.json')\n}\n\n// Derive a 32-byte key from the machine identity (hostname + username) and a\n// random per-wallet salt. The salt prevents precomputation attacks across wallets.\nfunction deriveKey(salt: Buffer): Buffer {\n  return crypto.scryptSync(\n    `${os.hostname()}:${os.userInfo().username}`,\n    salt,\n    32,\n  )\n}\n\ninterface WalletData {\n  salt: string\n  iv: string\n  tag: string\n  data: string\n}\n\nexport function normalizeWalletPrivateKey(value: string): Hex {\n  if (!/^0x[0-9a-fA-F]{64}$/.test(value)) {\n    throw new Error('Stored wallet private key is not a valid 0x-prefixed EVM private key')\n  }\n  return value as Hex\n}\n\nexport function walletAddressFromPrivateKey(privateKey: string): Address {\n  return privateKeyToAccount(normalizeWalletPrivateKey(privateKey)).address\n}\n\n/**\n * Encrypts a private key and writes it to ~/.chain-insights/wallet.json.\n * Uses AES-256-GCM with a machine-identity-derived key and a random per-wallet salt.\n * File is written with 0o600 permissions (owner read/write only).\n *\n * @param privateKey - The EVM private key to encrypt (0x-prefixed)\n */\nexport async function encryptKey(privateKey: string): Promise<void> {\n  const normalizedPrivateKey = normalizeWalletPrivateKey(privateKey)\n  const salt = crypto.randomBytes(16)\n  const key = deriveKey(salt)\n  const iv = crypto.randomBytes(12)\n  const cipher = crypto.createCipheriv('aes-256-gcm', key, iv)\n\n  const encrypted = Buffer.concat([\n    cipher.update(normalizedPrivateKey, 'utf8'),\n    cipher.final(),\n  ])\n\n  // getAuthTag() MUST be called after final()\n  const tag = cipher.getAuthTag()\n\n  const walletData: WalletData = {\n    salt: salt.toString('hex'),\n    iv: iv.toString('hex'),\n    tag: tag.toString('hex'),\n    data: encrypted.toString('hex'),\n  }\n\n  const p = walletPath()\n  await mkdir(path.dirname(p), { recursive: true })\n  await writeFile(p, JSON.stringify(walletData, null, 2) + '\\n', { mode: 0o600 })\n}\n\nexport interface SetWalletPrivateKeyOptions {\n  /** Overwrite an existing wallet. The previous ciphertext is backed up first. */\n  force?: boolean\n}\n\n/**\n * Best-effort address of the currently stored wallet, for overwrite messaging.\n * Returns null when the wallet can't be decrypted (e.g. hostname/username\n * changed) — the overwrite is still refused, just without naming the address.\n */\nasync function existingWalletAddress(): Promise<Address | null> {\n  try {\n    return walletAddressFromPrivateKey(await decryptKey())\n  } catch {\n    return null\n  }\n}\n\n/**\n * Copies the existing wallet.json to a timestamped `.bak-*` sibling before it\n * is overwritten, preserving the 0o600 permission. No-op when absent.\n */\nasync function backupExistingWallet(): Promise<void> {\n  const p = walletPath()\n  let raw: string\n  try {\n    raw = await readFile(p, 'utf8')\n  } catch (err: unknown) {\n    if ((err as NodeJS.ErrnoException).code === 'ENOENT') return\n    throw err\n  }\n  const stamp = new Date().toISOString().replace(/[:.]/g, '-')\n  await writeFile(`${p}.bak-${stamp}`, raw, { mode: 0o600 })\n}\n\n/**\n * Encrypts and stores a private key, refusing to overwrite an existing wallet\n * unless `force` is set. Importing a new key over a funded wallet discards the\n * only local copy of the old key, so the overwrite is guarded and the previous\n * ciphertext is backed up.\n */\nexport async function setWalletPrivateKey(\n  privateKey: string,\n  options: SetWalletPrivateKeyOptions = {},\n): Promise<Address> {\n  const normalizedPrivateKey = normalizeWalletPrivateKey(privateKey)\n  const address = walletAddressFromPrivateKey(normalizedPrivateKey)\n\n  if (await isWalletConfigured()) {\n    if (!options.force) {\n      const existing = await existingWalletAddress()\n      const which = existing ? ` (${existing})` : ''\n      throw new Error(\n        `A payment wallet already exists${which}. Importing a new key overwrites it and permanently ` +\n        `discards the old key. Re-run \\`chain-insights wallet import <key> --force\\` to replace it; ` +\n        `the previous encrypted key is backed up next to wallet.json first.`,\n      )\n    }\n    await backupExistingWallet()\n  }\n\n  await encryptKey(normalizedPrivateKey)\n  return address\n}\n\n/**\n * Reads and decrypts the private key from ~/.chain-insights/wallet.json.\n * Throws a human-readable error if wallet is absent or decryption fails.\n *\n * @returns The decrypted EVM private key string\n */\nexport async function decryptKey(): Promise<string> {\n  let raw: string\n  try {\n    raw = await readFile(walletPath(), 'utf8')\n  } catch (err: unknown) {\n    const nodeErr = err as NodeJS.ErrnoException\n    if (nodeErr.code === 'ENOENT') {\n      throw new Error(\n        'Wallet not configured. Run `chain-insights wallet import <private-key>`, then `chain-insights wallet ready`.',\n      )\n    }\n    throw err\n  }\n\n  try {\n    const stored = JSON.parse(raw) as WalletData\n    const salt = Buffer.from(stored.salt, 'hex')\n    const key = deriveKey(salt)\n    const iv = Buffer.from(stored.iv, 'hex')\n    const tag = Buffer.from(stored.tag, 'hex')\n    const encrypted = Buffer.from(stored.data, 'hex')\n\n    const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv)\n    // setAuthTag() MUST be called before update()\n    decipher.setAuthTag(tag)\n\n    const decrypted = Buffer.concat([\n      decipher.update(encrypted),\n      decipher.final(),\n    ])\n\n    return decrypted.toString('utf8')\n  } catch {\n    throw new Error(\n      'Wallet decryption failed. If you changed your hostname or username, re-import it with `chain-insights wallet import <private-key>`.',\n    )\n  }\n}\n\n/**\n * Returns true if wallet.json exists, false if absent.\n * Does not validate the wallet contents.\n */\nexport async function isWalletConfigured(): Promise<boolean> {\n  try {\n    await stat(walletPath())\n    return true\n  } catch (err: unknown) {\n    const nodeErr = err as NodeJS.ErrnoException\n    if (nodeErr.code === 'ENOENT') return false\n    throw err\n  }\n}\n"],"mappings":"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"}