OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-wallet / deprecated / basic-cli / hyper-wallet-cli.cjs

796 lines · javascript ref:
#!/usr/bin/env bare

// hyper-wallet-bare.cjs - Bare Runtime CLI (Phase 4)
// CRITICAL: Load bare-node-runtime FIRST before any other requires
require('bare-node-runtime/global')
// WORKAROUND: Pear doesn't support import attributes with ESM yet
// Manually load imports.json into bundle for Bare 1.24/1.26 compatibility
require('bare-node-runtime/imports')

const fs = require('bare-fs')
const path = require('bare-path')
const os = require('bare-os')
const IdentityKey = require('keet-identity-key')
const WDK = require('@tetherto/wdk').default
const WalletManagerEvm = require('@tetherto/wdk-wallet-evm').default
const WalletManagerBtc = require('@tetherto/wdk-wallet-btc').default
const { initializeStorage, storeTx, getTxHistory } = require('./lib/wallet-storage.cjs')

// Storage configuration
const STORAGE_DIR = path.join(os.homedir(), '.hyper-wallet')
const MNEMONIC_FILE = path.join(STORAGE_DIR, '.mnemonic')
const WALLET_MNEMONIC_FILE = path.join(STORAGE_DIR, '.wallet-mnemonic')

// Network configuration
const CONFIG = {
  ethereum: {
    provider: 'https://ethereum-sepolia-rpc.publicnode.com',
    network: 'sepolia'
  },
  bitcoin: {
    network: 'testnet',
    host: 'electrum.blockstream.info',
    port: 60001
  }
}

// ============================================================================
// STORAGE FUNCTIONS
// ============================================================================

function ensureStorageDir() {
  if (!fs.existsSync(STORAGE_DIR)) {
    fs.mkdirSync(STORAGE_DIR, { recursive: true, mode: 0o700 })
  }
}

// ============================================================================
// FORMATTING HELPERS
// ============================================================================

function formatBTC(satoshis) {
  const btc = Number(satoshis) / 1e8
  return btc.toFixed(8) + ' BTC'
}

function formatETH(wei) {
  const eth = Number(wei) / 1e18
  return eth.toFixed(18).replace(/\.?0+$/, '') + ' ETH'
}

function formatSatoshis(satoshis) {
  return Number(satoshis).toLocaleString() + ' satoshis'
}

function formatWei(wei) {
  return wei.toString() + ' wei'
}

// ============================================================================
// AMOUNT PARSING HELPERS
// ============================================================================

function parseBTC(btcString) {
  // Parse BTC amount and convert to satoshis (BigInt)
  const btc = parseFloat(btcString)
  if (isNaN(btc) || btc <= 0) {
    throw new Error('Invalid BTC amount')
  }
  const satoshis = Math.floor(btc * 1e8)
  return BigInt(satoshis)
}

function parseETH(ethString) {
  // Parse ETH amount and convert to wei (BigInt)
  const eth = parseFloat(ethString)
  if (isNaN(eth) || eth <= 0) {
    throw new Error('Invalid ETH amount')
  }
  // Use string multiplication to avoid floating point issues
  const weiString = (eth * 1e18).toFixed(0)
  return BigInt(weiString)
}

// ============================================================================
// USER CONFIRMATION HELPER
// ============================================================================

async function confirm(message) {
  // Simple confirmation prompt for Bare runtime
  process.stdout.write(message + ' (yes/no): ')

  return new Promise((resolve) => {
    process.stdin.once('data', (data) => {
      const answer = data.toString().trim().toLowerCase()
      resolve(answer === 'yes' || answer === 'y')
    })
  })
}

// ============================================================================
// WALLET INITIALIZATION HELPER
// ============================================================================

async function initializeWallet() {
  const identityMnemonic = loadMnemonic()
  const walletMnemonic = loadWalletMnemonic()

  if (!identityMnemonic || !walletMnemonic) {
    console.error('❌ Wallet not initialized. Run: bare hyper-wallet-bare.cjs init')
    process.exit(1)
  }

  // Initialize WDK wallet
  const wdk = new WDK(walletMnemonic)
    .registerWallet('ethereum', WalletManagerEvm, CONFIG.ethereum)
    .registerWallet('bitcoin', WalletManagerBtc, CONFIG.bitcoin)

  return { identityMnemonic, walletMnemonic, wdk }
}

function saveMnemonic(identityMnemonic, walletMnemonic) {
  ensureStorageDir()
  fs.writeFileSync(MNEMONIC_FILE, identityMnemonic, { mode: 0o600 })
  fs.writeFileSync(WALLET_MNEMONIC_FILE, walletMnemonic, { mode: 0o600 })
}

function loadMnemonic() {
  if (!fs.existsSync(MNEMONIC_FILE)) {
    return null
  }
  return fs.readFileSync(MNEMONIC_FILE, 'utf8').trim()
}

function loadWalletMnemonic() {
  if (!fs.existsSync(WALLET_MNEMONIC_FILE)) {
    return null
  }
  return fs.readFileSync(WALLET_MNEMONIC_FILE, 'utf8').trim()
}

function isInitialized() {
  return fs.existsSync(MNEMONIC_FILE) && fs.existsSync(WALLET_MNEMONIC_FILE)
}

// ============================================================================
// COMMANDS
// ============================================================================

async function cmdInit(args) {
  console.log('🔐 Initializing hyper-wallet...')
  console.log('')

  // Check if already initialized
  if (isInitialized()) {
    console.error('❌ Wallet already initialized!')
    console.error('   Mnemonics stored at:')
    console.error(`     ${MNEMONIC_FILE}`)
    console.error(`     ${WALLET_MNEMONIC_FILE}`)
    console.error('')
    console.error('   To create a new wallet, first delete both files.')
    process.exit(1)
  }

  // Check for import flag
  let identityMnemonic, walletMnemonic
  const importIndex = args.indexOf('--import')

  if (importIndex !== -1 && args[importIndex + 1] && args[importIndex + 2]) {
    // Import existing mnemonics (both identity and wallet)
    identityMnemonic = args[importIndex + 1]
    walletMnemonic = args[importIndex + 2]
    console.log('📥 Importing existing wallet...')

    // Validate identity mnemonic
    try {
      await IdentityKey.from({ mnemonic: identityMnemonic })
      console.log('✅ Valid identity mnemonic (24 words)')
    } catch (err) {
      console.error('❌ Invalid identity mnemonic:', err.message)
      process.exit(1)
    }

    // Validate wallet mnemonic
    if (!WDK.isValidSeedPhrase(walletMnemonic)) {
      console.error('❌ Invalid wallet mnemonic (must be 12 words)')
      process.exit(1)
    }
    console.log('✅ Valid wallet mnemonic (12 words)')
  } else {
    // Generate new mnemonics
    console.log('🎲 Generating new identity...')
    identityMnemonic = IdentityKey.generateMnemonic()
    console.log('✅ Generated identity mnemonic (24 words)')

    console.log('🎲 Generating new wallet...')
    walletMnemonic = WDK.getRandomSeedPhrase()
    console.log('✅ Generated wallet mnemonic (12 words)')
  }

  // Save mnemonics
  saveMnemonic(identityMnemonic, walletMnemonic)

  console.log('')
  console.log('✅ Wallet initialized!')
  console.log('')
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
  console.log('⚠️  SAVE YOUR MNEMONICS')
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
  console.log('')
  console.log('Identity Mnemonic (24 words):')
  console.log(identityMnemonic)
  console.log('')
  console.log('Wallet Mnemonic (12 words):')
  console.log(walletMnemonic)
  console.log('')
  console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
  console.log('')
  console.log('⚠️  CRITICAL: Write BOTH mnemonics down on paper!')
  console.log('   These are the ONLY way to recover your wallet.')
  console.log('   Store them safely offline.')
  console.log('')
  console.log('   Mnemonics saved to:')
  console.log(`     ${MNEMONIC_FILE}`)
  console.log(`     ${WALLET_MNEMONIC_FILE}`)
  console.log('')
  console.log('Next steps:')
  console.log('  bare hyper-wallet-bare.cjs whoami   # Show addresses')
  console.log('')
}

async function cmdWhoami() {
  console.log('👤 Wallet Identity')
  console.log('')

  // Initialize wallet
  const { identityMnemonic, wdk } = await initializeWallet()

  // Initialize identity (keet-identity-key)
  console.log('🔑 Loading identity...')
  const identity = await IdentityKey.from({ mnemonic: identityMnemonic })
  console.log('✅ Identity Public Key:')
  console.log(`   ${identity.identityKeyPair.publicKey.toString('hex')}`)
  console.log('')

  // Get accounts
  console.log('💰 Loading wallet...')
  const ethAccount = await wdk.getAccount('ethereum', 0)
  const btcAccount = await wdk.getAccount('bitcoin', 0)

  // Get addresses
  const ethAddress = await ethAccount.getAddress()
  const btcAddress = await btcAccount.getAddress()

  console.log('✅ Wallet Addresses:')
  console.log('')
  console.log('  Bitcoin (testnet):')
  console.log(`    ${btcAddress}`)
  console.log('')
  console.log('  Ethereum (Sepolia):')
  console.log(`    ${ethAddress}`)
  console.log('')
  console.log('Network Info:')
  console.log(`  Bitcoin: ${CONFIG.bitcoin.network} via ${CONFIG.bitcoin.host}`)
  console.log(`  Ethereum: ${CONFIG.ethereum.network}`)
  console.log('')
}

async function cmdBalance(args) {
  const chain = args[1] // 'btc', 'eth', or undefined (all)

  console.log('💰 Wallet Balance')
  console.log('')

  // Initialize wallet
  const { wdk } = await initializeWallet()

  // Show Bitcoin balance
  if (!chain || chain === 'btc' || chain === 'bitcoin') {
    console.log('🔶 Bitcoin (testnet)')
    try {
      const btcAccount = await wdk.getAccount('bitcoin', 0)
      const balance = await btcAccount.getBalance()
      console.log(`   ${formatBTC(balance)}`)
      console.log(`   ${formatSatoshis(balance)}`)
    } catch (err) {
      console.error('   ❌ Error:', err.message)
    }
    console.log('')
  }

  // Show Ethereum balance
  if (!chain || chain === 'eth' || chain === 'ethereum') {
    console.log('🔷 Ethereum (Sepolia)')
    try {
      const ethAccount = await wdk.getAccount('ethereum', 0)
      const balance = await ethAccount.getBalance()
      console.log(`   ${formatETH(balance)}`)
      console.log(`   ${formatWei(balance)}`)
    } catch (err) {
      console.error('   ❌ Error:', err.message)
    }
    console.log('')
  }

  if (!chain) {
    console.log('💡 Tip: Use `balance btc` or `balance eth` to show one chain')
    console.log('')
  }
}

async function cmdReceive(args) {
  const chain = args[1] // 'btc', 'eth'
  const accountIndex = parseInt(args[2]) || 0

  if (!chain) {
    console.error('❌ Please specify chain: btc or eth')
    console.error('')
    console.error('Usage:')
    console.error('  bare hyper-wallet-bare.cjs receive btc')
    console.error('  bare hyper-wallet-bare.cjs receive eth')
    console.error('  bare hyper-wallet-bare.cjs receive btc 1   # Account index 1')
    process.exit(1)
  }

  console.log('📬 Receiving Address')
  console.log('')

  // Initialize wallet
  const { wdk } = await initializeWallet()

  if (chain === 'btc' || chain === 'bitcoin') {
    console.log('🔶 Bitcoin (testnet)')
    console.log(`   Account index: ${accountIndex}`)
    console.log('')
    try {
      const account = await wdk.getAccount('bitcoin', accountIndex)
      const address = await account.getAddress()
      console.log('   Address:')
      console.log(`   ${address}`)
      console.log('')
      console.log('   Send Bitcoin testnet funds to this address.')
      console.log('   Faucet: https://coinfaucet.eu/en/btc-testnet/')
    } catch (err) {
      console.error('   ❌ Error:', err.message)
    }
    console.log('')
  } else if (chain === 'eth' || chain === 'ethereum') {
    console.log('🔷 Ethereum (Sepolia)')
    console.log(`   Account index: ${accountIndex}`)
    console.log('')
    try {
      const account = await wdk.getAccount('ethereum', accountIndex)
      const address = await account.getAddress()
      console.log('   Address:')
      console.log(`   ${address}`)
      console.log('')
      console.log('   Send Sepolia ETH to this address.')
      console.log('   Faucet: https://sepoliafaucet.com/')
    } catch (err) {
      console.error('   ❌ Error:', err.message)
    }
    console.log('')
  } else {
    console.error(`❌ Unknown chain: ${chain}`)
    console.error('   Supported: btc, eth')
    process.exit(1)
  }
}

async function cmdSend(args) {
  const chain = args[1] // 'btc', 'eth'
  const toAddress = args[2]
  const amountStr = args[3]

  // Validate arguments
  if (!chain || !toAddress || !amountStr) {
    console.error('❌ Missing arguments')
    console.error('')
    console.error('Usage:')
    console.error('  bare hyper-wallet-bare.cjs send btc <address> <amount>')
    console.error('  bare hyper-wallet-bare.cjs send eth <address> <amount>')
    console.error('')
    console.error('Examples:')
    console.error('  bare hyper-wallet-bare.cjs send btc tb1q... 0.001')
    console.error('  bare hyper-wallet-bare.cjs send eth 0x... 0.1')
    process.exit(1)
  }

  console.log('📤 Send Transaction')
  console.log('')

  // Initialize wallet
  const { identityMnemonic, wdk } = await initializeWallet()

  // Initialize storage (required for storeTx)
  const identity = await IdentityKey.from({ mnemonic: identityMnemonic })
  await initializeStorage(identity)

  if (chain === 'btc' || chain === 'bitcoin') {
    console.log('🔶 Bitcoin (testnet)')
    console.log('')

    try {
      // Parse amount
      const satoshis = parseBTC(amountStr)
      const account = await wdk.getAccount('bitcoin', 0)

      // Check balance
      const balance = await account.getBalance()
      if (BigInt(balance) < satoshis) {
        console.error('❌ Insufficient balance!')
        console.error(`   Balance: ${formatBTC(balance)}`)
        console.error(`   Requested: ${formatBTC(satoshis)}`)
        process.exit(1)
      }

      // Quote transaction (estimate fee)
      console.log('💭 Estimating fee...')
      const quote = await account.quoteSendTransaction({
        to: toAddress,
        value: satoshis
      })

      const total = satoshis + quote.fee

      // Show transaction preview
      console.log('')
      console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
      console.log('📋 Transaction Preview')
      console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
      console.log('')
      console.log(`To:        ${toAddress}`)
      console.log(`Amount:    ${formatBTC(satoshis)} (${formatSatoshis(satoshis)})`)
      console.log(`Fee:       ${formatBTC(quote.fee)} (${formatSatoshis(quote.fee)})`)
      console.log(`Total:     ${formatBTC(total)} (${formatSatoshis(total)})`)
      console.log('')
      console.log(`Balance:   ${formatBTC(balance)}`)
      console.log(`Remaining: ${formatBTC(BigInt(balance) - total)}`)
      console.log('')
      console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
      console.log('')

      // Ask for confirmation
      const confirmed = await confirm('⚠️  Send this transaction?')

      if (!confirmed) {
        console.log('')
        console.log('❌ Transaction cancelled')
        console.log('')
        process.exit(0)
      }

      // Send transaction
      console.log('')
      console.log('📡 Broadcasting transaction...')
      const result = await account.sendTransaction({
        to: toAddress,
        value: satoshis
      })

      console.log('')
      console.log('✅ Transaction sent!')
      console.log('')
      console.log(`TX Hash: ${result.hash}`)
      console.log(`Fee:     ${formatBTC(result.fee)}`)
      console.log('')
      console.log(`View on explorer:`)
      console.log(`https://blockstream.info/testnet/tx/${result.hash}`)
      console.log('')

      // Store transaction in history
      await storeTx({
        hash: result.hash,
        chain: 'bitcoin',
        to: toAddress,
        amount: satoshis.toString(),
        fee: result.fee.toString(),
        timestamp: Date.now(),
        status: 'pending'
      })
    } catch (err) {
      console.error('')
      console.error('❌ Transaction failed:', err.message)
      if (process.env.DEBUG) {
        console.error('')
        console.error(err.stack)
      }
      console.error('')
      process.exit(1)
    }
  } else if (chain === 'eth' || chain === 'ethereum') {
    console.log('🔷 Ethereum (Sepolia)')
    console.log('')

    try {
      // Parse amount
      const wei = parseETH(amountStr)
      const account = await wdk.getAccount('ethereum', 0)

      // Check balance
      const balance = await account.getBalance()
      if (BigInt(balance) < wei) {
        console.error('❌ Insufficient balance!')
        console.error(`   Balance: ${formatETH(balance)}`)
        console.error(`   Requested: ${formatETH(wei)}`)
        process.exit(1)
      }

      // Quote transaction (estimate fee)
      console.log('💭 Estimating fee...')
      const quote = await account.quoteSendTransaction({
        to: toAddress,
        value: wei
      })

      const total = wei + quote.fee

      // Show transaction preview
      console.log('')
      console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
      console.log('📋 Transaction Preview')
      console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
      console.log('')
      console.log(`To:        ${toAddress}`)
      console.log(`Amount:    ${formatETH(wei)}`)
      console.log(`Fee:       ${formatETH(quote.fee)}`)
      console.log(`Total:     ${formatETH(total)}`)
      console.log('')
      console.log(`Balance:   ${formatETH(balance)}`)
      console.log(`Remaining: ${formatETH(BigInt(balance) - total)}`)
      console.log('')
      console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
      console.log('')

      // Ask for confirmation
      const confirmed = await confirm('⚠️  Send this transaction?')

      if (!confirmed) {
        console.log('')
        console.log('❌ Transaction cancelled')
        console.log('')
        process.exit(0)
      }

      // Send transaction
      console.log('')
      console.log('📡 Broadcasting transaction...')
      const result = await account.sendTransaction({
        to: toAddress,
        value: wei
      })

      console.log('')
      console.log('✅ Transaction sent!')
      console.log('')
      console.log(`TX Hash: ${result.hash}`)
      console.log(`Fee:     ${formatETH(result.fee)}`)
      console.log('')
      console.log(`View on explorer:`)
      console.log(`https://sepolia.etherscan.io/tx/${result.hash}`)
      console.log('')

      // Store transaction in history
      await storeTx({
        hash: result.hash,
        chain: 'ethereum',
        to: toAddress,
        amount: wei.toString(),
        fee: result.fee.toString(),
        timestamp: Date.now(),
        status: 'pending'
      })
    } catch (err) {
      console.error('')
      console.error('❌ Transaction failed:', err.message)
      if (process.env.DEBUG) {
        console.error('')
        console.error(err.stack)
      }
      console.error('')
      process.exit(1)
    }
  } else {
    console.error(`❌ Unknown chain: ${chain}`)
    console.error('   Supported: btc, eth')
    process.exit(1)
  }
}

async function cmdHistory(args) {
  const chain = args[1] // 'btc', 'eth', or undefined (all)

  console.log('📜 Transaction History')
  console.log('')

  try {
    // Initialize storage (required for getTxHistory)
    const identityMnemonic = loadMnemonic()
    if (!identityMnemonic) {
      console.error('❌ Wallet not initialized')
      console.error('   Run: bare hyper-wallet-cli.cjs init')
      process.exit(1)
    }

    const identity = await IdentityKey.from({ mnemonic: identityMnemonic })
    await initializeStorage(identity)

    // Map chain aliases to full names
    let chainFilter = null
    if (chain === 'btc') chainFilter = 'bitcoin'
    else if (chain === 'eth') chainFilter = 'ethereum'
    else if (chain) {
      console.error(`❌ Unknown chain: ${chain}`)
      console.error('   Supported: btc, eth')
      process.exit(1)
    }

    // Get transactions from Hyperbee
    const transactions = await getTxHistory(chainFilter)

    if (transactions.length === 0) {
      console.log('   No transactions found.')
      console.log('')
      console.log('   Send your first transaction:')
      console.log('   bare hyper-wallet-bare.cjs send btc <address> <amount>')
      console.log('   bare hyper-wallet-bare.cjs send eth <address> <amount>')
      console.log('')
      return
    }

    // Display transactions
    for (const tx of transactions) {
      const date = new Date(tx.timestamp)
      const chainSymbol = tx.chain === 'bitcoin' ? '🔶' : '🔷'
      const chainName = tx.chain === 'bitcoin' ? 'Bitcoin' : 'Ethereum'

      console.log(`${chainSymbol} ${chainName}`)
      console.log(`   Date:   ${date.toLocaleString()}`)
      console.log(`   To:     ${tx.to}`)

      if (tx.chain === 'bitcoin') {
        console.log(`   Amount: ${formatBTC(BigInt(tx.amount))}`)
        console.log(`   Fee:    ${formatBTC(BigInt(tx.fee))}`)
      } else {
        console.log(`   Amount: ${formatETH(BigInt(tx.amount))}`)
        console.log(`   Fee:    ${formatETH(BigInt(tx.fee))}`)
      }

      console.log(`   Status: ${tx.status}`)
      console.log(`   TX:     ${tx.hash}`)
      console.log('')
    }

    console.log(`Total: ${transactions.length} transaction${transactions.length === 1 ? '' : 's'}`)
    console.log('')

    if (!chain) {
      console.log('💡 Tip: Use `history btc` or `history eth` to filter')
      console.log('')
    }
  } catch (err) {
    console.error('')
    console.error('❌ Error reading history:', err.message)
    if (process.env.DEBUG) {
      console.error('')
      console.error(err.stack)
    }
    console.error('')
    process.exit(1)
  }
}

// ============================================================================
// HELP
// ============================================================================

function showHelp() {
  console.log(`
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  hyper-wallet - Self-custodial P2P Cryptocurrency Wallet
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

USAGE:
  bare hyper-wallet-bare.cjs <command> [options]
  pear run pear://18x3en... <command> [options]

COMMANDS:
  init              Generate new wallet
  init --import     Import wallet from mnemonic
  whoami            Show identity and addresses
  balance [chain]   Check balances (all, btc, or eth)
  receive <chain>   Generate receiving address (btc or eth)
  send <chain> <address> <amount>   Send transaction
  history [chain]   View transaction history (all, btc, or eth)
  help              Show this help

EXAMPLES:
  bare hyper-wallet-bare.cjs init
  bare hyper-wallet-bare.cjs init --import "word1 word2 ... word24"
  bare hyper-wallet-bare.cjs whoami
  bare hyper-wallet-bare.cjs balance
  bare hyper-wallet-bare.cjs balance btc
  bare hyper-wallet-bare.cjs receive eth
  bare hyper-wallet-bare.cjs receive btc 1
  bare hyper-wallet-bare.cjs send btc tb1q... 0.001
  bare hyper-wallet-bare.cjs send eth 0x... 0.1
  bare hyper-wallet-bare.cjs history
  bare hyper-wallet-bare.cjs history btc

PHASE 4 STATUS: ✅ P2P Storage & History
  ✅ Generate/import wallet
  ✅ Show identity and addresses
  ✅ Check balances
  ✅ Generate receiving addresses
  ✅ Send transactions with fee estimation
  ✅ Transaction history (Hyperbee P2P storage)

COMING SOON (Phase 5):
  - device          Multi-device sync (Autobase)

POWERED BY:
  ✅ Bare Runtime v1.26.0
  ✅ keet-identity-key (multi-device identity)
  ✅ Tether WDK (Bitcoin + Ethereum wallets)

Storage: ~/.hyper-wallet/.mnemonic
`)
}

// ============================================================================
// MAIN
// ============================================================================

async function main() {
  // Support both Pear runtime and bare/node
  const args = globalThis.Pear ? Pear.config.args : process.argv.slice(2)
  const command = args[0]

  try {
    switch (command) {
      case 'init':
        await cmdInit(args)
        break

      case 'whoami':
        await cmdWhoami()
        break

      case 'balance':
        await cmdBalance(args)
        break

      case 'receive':
        await cmdReceive(args)
        break

      case 'send':
        await cmdSend(args)
        break

      case 'history':
        await cmdHistory(args)
        break

      case 'help':
      case '--help':
      case '-h':
      default:
        showHelp()
        break
    }
  } catch (err) {
    console.error('')
    console.error('❌ Error:', err.message)
    if (process.env.DEBUG) {
      console.error('')
      console.error('Stack trace:')
      console.error(err.stack)
    }
    console.error('')
    process.exit(1)
  }
}

main()