#!/usr/bin/env bare
// hyper-wallet-autobase.cjs - Multi-Device CLI with Autobase (Phase 5.2)
// CRITICAL: Load bare-node-runtime FIRST before any other requires
require('bare-node-runtime/global')
const fs = require('bare-fs')
const path = require('bare-path')
const os = require('bare-os')
const IdentityKey = require('keet-identity-key')
const b4a = require('b4a')
const fetch = require('bare-fetch')
const {
initializeStorage,
bootstrapDevice,
createInvite,
joinWallet,
getCurrentDevice,
listDevices,
removeDevice,
getConnectionStatus,
waitForNewDevice,
getPendingWriters,
approveWriter,
enableReplication,
closeStorage,
// UTXO functions (Phase 6.3)
storeUTXO,
getUTXOs,
updateUTXO,
deleteUTXO,
// Transaction storage (Phase 6.6)
storeTransaction,
getTransactions,
getTransaction,
deleteTransaction,
// Transaction status updates (Phase 6.7)
updateTransactionStatus
} = require('./lib/hyper-wallet-lib/wallet-storage.cjs')
const {
generateWalletMnemonic,
saveWalletMnemonic,
loadWalletMnemonic,
initializeWDK,
getFirstAddress,
walletMnemonicExists
} = require('./lib/hyper-wallet-lib/wdk-integration.cjs')
const {
generateNewAddress,
listAddresses: listAddressesFromManager,
getCurrentAddress: getCurrentAddressFromManager
} = require('./lib/hyper-wallet-lib/address-manager.cjs')
const { loadConfig } = require('./lib/hyper-wallet-lib/config-loader.cjs')
// UTXO manager functions (not needed for WDK-based balance queries)
// const {
// storeUTXO: storeUTXOHelper,
// markUTXOSpent,
// calculateBalance,
// getSpendableUTXOs,
// formatBalance
// } = require('./lib/hyper-wallet-lib/utxo-manager.cjs')
// const {
// getUTXOsForAddresses,
// getFeeRates,
// checkConnection
// } = require('./lib/hyper-wallet-lib/blockchain-api.cjs')
// Load configuration from file
const config = loadConfig()
// Storage configuration
const STORAGE_DIR = path.join(os.homedir(), '.hyper-wallet')
const MNEMONIC_FILE = path.join(STORAGE_DIR, '.mnemonic')
// ============================================================================
// STORAGE FUNCTIONS
// ============================================================================
function ensureStorageDir() {
if (!fs.existsSync(STORAGE_DIR)) {
fs.mkdirSync(STORAGE_DIR, { recursive: true, mode: 0o700 })
}
}
function saveMnemonic(mnemonic) {
ensureStorageDir()
fs.writeFileSync(MNEMONIC_FILE, mnemonic, { mode: 0o600 })
}
function loadMnemonic() {
if (!fs.existsSync(MNEMONIC_FILE)) {
return null
}
return fs.readFileSync(MNEMONIC_FILE, 'utf8').trim()
}
// ============================================================================
// IDENTITY MANAGEMENT
// ============================================================================
async function loadIdentity() {
const mnemonic = loadMnemonic()
if (!mnemonic) {
throw new Error('Identity not initialized. Run: bare hyper-wallet-autobase.cjs init')
}
const identity = await IdentityKey.from({ mnemonic })
return identity
}
// ============================================================================
// COMMANDS
// ============================================================================
async function cmdInit(args) {
console.log('🔐 Initializing Hyper Wallet with Multi-Device Support')
console.log('')
const existing = loadMnemonic()
if (existing) {
console.log('⚠️ Wallet already initialized!')
console.log('')
console.log('Your identity mnemonic is stored at:')
console.log(MNEMONIC_FILE)
console.log('')
console.log('To see your identity:')
console.log(' bare hyper-wallet-autobase.cjs whoami')
console.log('')
console.log('To add another device:')
console.log(' bare hyper-wallet-autobase.cjs device add')
console.log('')
return
}
// Generate new identity
const mnemonic = IdentityKey.generateMnemonic()
const identity = await IdentityKey.from({ mnemonic })
saveMnemonic(mnemonic)
console.log('✅ Identity created!')
console.log('')
console.log('Your 24-word IDENTITY mnemonic (KEEP SAFE):')
console.log('')
console.log(' ' + mnemonic)
console.log('')
console.log('⚠️ IMPORTANT: Write this down and store it safely!')
console.log(' This is for DEVICE ATTESTATION only.')
console.log('')
console.log('Public Key:', b4a.toString(identity.identityKeyPair.publicKey, 'hex').slice(0, 16) + '...')
console.log('')
// Generate wallet mnemonic (separate from identity)
console.log('Generating wallet mnemonic (for cryptocurrency keys)...')
const walletMnemonic = generateWalletMnemonic()
saveWalletMnemonic(walletMnemonic)
console.log('✅ Wallet mnemonic created!')
console.log('')
console.log('Your 12-word WALLET mnemonic (KEEP SAFE):')
console.log('')
console.log(' ' + walletMnemonic)
console.log('')
console.log('⚠️ CRITICAL: This is SEPARATE from your identity mnemonic!')
console.log(' Identity (24 words) = Device access')
console.log(' Wallet (12 words) = Cryptocurrency funds')
console.log('')
console.log('⚠️ You must backup BOTH mnemonics separately!')
console.log('')
// Initialize storage
await initializeStorage(identity)
console.log('Next steps:')
console.log(' 1. Bootstrap this device:')
console.log(' bare hyper-wallet-autobase.cjs device bootstrap "My Laptop"')
console.log('')
console.log(' 2. Initialize WDK (test address generation):')
console.log(' bare hyper-wallet-autobase.cjs wallet init')
console.log('')
}
async function cmdWhoami() {
const identity = await loadIdentity()
console.log('👤 Identity Information')
console.log('')
console.log('Public Key:', b4a.toString(identity.identityKeyPair.publicKey, 'hex'))
console.log('')
// Initialize storage to get device info
await initializeStorage(identity)
const currentDevice = getCurrentDevice()
if (currentDevice) {
console.log('Current Device:')
console.log(` Name: ${currentDevice.name}`)
console.log(` Added: ${new Date(currentDevice.addedAt).toLocaleString()}`)
console.log(` Writer: ${b4a.toString(currentDevice.key, 'hex').slice(0, 16)}...`)
console.log('')
} else {
console.log('⚠️ Device not bootstrapped yet')
console.log('')
console.log('Run: bare hyper-wallet-autobase.cjs device bootstrap "Device Name"')
console.log('')
}
await closeStorage()
}
// ============================================================================
// WALLET COMMANDS (WDK Integration)
// ============================================================================
async function cmdWalletInit(args) {
console.log('💰 Initializing Wallet (WDK)')
console.log('')
// Check if wallet mnemonic exists
if (!walletMnemonicExists()) {
console.log('❌ No wallet mnemonic found!')
console.log('')
console.log('Run: bare hyper-wallet-autobase.cjs init')
console.log('')
process.exit(1)
}
// Load wallet mnemonic
const walletMnemonic = loadWalletMnemonic()
console.log('Initializing WDK...')
try {
// Initialize WDK (testnet mode)
const { wdk } = await initializeWDK(walletMnemonic, { testnet: true })
console.log('✅ WDK initialized')
console.log('')
// Get first addresses for testing
console.log('Deriving first addresses...')
const btcAddress = await getFirstAddress(wdk, 'bitcoin')
const ethAddress = await getFirstAddress(wdk, 'ethereum')
console.log('')
console.log('Bitcoin (Testnet):')
console.log(` Address: ${btcAddress}`)
console.log(` Path: m/84'/1'/0'/0/0`)
console.log(` Network: testnet`)
console.log('')
console.log('Ethereum (Sepolia):')
console.log(` Address: ${ethAddress}`)
console.log(` Path: m/44'/60'/0'/0/0`)
console.log(` Network: sepolia`)
console.log('')
console.log('✅ WDK initialization successful!')
console.log('')
console.log('Next steps:')
console.log(' 1. Generate addresses: bare hyper-wallet-autobase.cjs address new btc')
console.log(' 2. Get testnet coins: https://testnet-faucet.mempool.co/')
console.log('')
} catch (err) {
console.log('')
console.log('❌ WDK initialization failed:', err.message)
console.log('')
if (process.env.DEBUG) {
console.log('Stack trace:')
console.log(err.stack)
console.log('')
}
process.exit(1)
}
}
// ============================================================================
// ADDRESS COMMANDS (Phase 6.2)
// ============================================================================
async function cmdAddressNew(args) {
const chainArg = args[2] || 'testnet-bitcoin'
// Parse chain argument
let chain, network
if (chainArg.startsWith('testnet-')) {
network = 'testnet'
chain = chainArg.replace('testnet-', '')
} else if (chainArg.startsWith('mainnet-')) {
network = 'mainnet'
chain = chainArg.replace('mainnet-', '')
} else {
// Legacy support
network = 'testnet'
chain = chainArg
}
console.log('🔑 Generating New Address')
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
try {
console.log(`Chain: ${chainArg}`)
console.log('Deriving address...')
console.log('')
const addr = await generateNewAddress(chain, { testnet: network === 'testnet' })
console.log('✅ Address generated!')
console.log('')
console.log(`Chain: ${addr.chain}`)
console.log(`Address: ${addr.address}`)
console.log(`Path: ${addr.path}`)
console.log(`Index: ${addr.index}`)
console.log('')
console.log('📡 Synced to all devices.')
console.log('')
} catch (err) {
console.log('')
console.log('❌ Error generating address:', err.message)
console.log('')
if (process.env.DEBUG) {
console.log('Stack trace:')
console.log(err.stack)
console.log('')
}
}
await closeStorage()
}
async function cmdAddressList(args) {
const chain = args[2] || 'all'
console.log('📋 Address List')
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
try {
const addresses = await listAddressesFromManager(chain)
if (addresses.length === 0) {
console.log(`No addresses found for ${chain}.`)
console.log('')
console.log('Generate an address:')
console.log(' bare hyper-wallet-autobase.cjs address new bitcoin')
console.log('')
await closeStorage()
return
}
for (const addr of addresses) {
const icon = addr.chain === 'bitcoin' ? '₿' : '⟠'
console.log(`${icon} ${addr.chain} #${addr.index}`)
console.log(` ${addr.address}`)
console.log(` ${addr.path}`)
console.log(` Device: ${addr.device}`)
console.log(` Created: ${new Date(addr.createdAt).toLocaleString()}`)
console.log('')
}
console.log(`Total: ${addresses.length} address${addresses.length === 1 ? '' : 'es'}`)
console.log('')
} catch (err) {
console.log('')
console.log('❌ Error listing addresses:', err.message)
console.log('')
}
await closeStorage()
}
async function cmdAddressCurrent(args) {
const chainArg = args[2]
if (!chainArg) {
console.error('❌ Missing chain argument')
console.error('')
console.error('Usage: bare hyper-wallet-autobase.cjs address current <chain>')
console.error('Example: bare hyper-wallet-autobase.cjs address current testnet-bitcoin')
console.error(' bare hyper-wallet-autobase.cjs address current testnet-ethereum')
console.error('')
process.exit(1)
}
// Parse chain argument
let chain
if (chainArg.startsWith('testnet-')) {
chain = chainArg.replace('testnet-', '')
} else if (chainArg.startsWith('mainnet-')) {
chain = chainArg.replace('mainnet-', '')
} else {
// Legacy support
chain = chainArg
}
console.log('🔑 Current Address')
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
try {
const addr = await getCurrentAddressFromManager(chain)
if (!addr) {
console.log(`No addresses found for ${chainArg}.`)
console.log('')
console.log('Generate an address:')
console.log(` bare hyper-wallet-autobase.cjs address new ${chainArg}`)
console.log('')
await closeStorage()
return
}
const icon = addr.chain === 'bitcoin' ? '₿' : '⟠'
console.log(`${icon} ${chainArg} #${addr.index}`)
console.log(` ${addr.address}`)
console.log(` ${addr.path}`)
console.log('')
} catch (err) {
console.log('')
console.log('❌ Error getting current address:', err.message)
console.log('')
}
await closeStorage()
}
// ============================================================================
// UTXO & BALANCE COMMANDS (Phase 6.3)
// ============================================================================
async function cmdBalance(args) {
const chainArg = args[1] || 'testnet-bitcoin'
console.log('💰 Balance')
console.log('')
// Parse chain argument (testnet-bitcoin, testnet4-bitcoin, testnet-ethereum, bitcoin, ethereum)
let chain, network, testnet4 = false
if (chainArg.startsWith('testnet4-')) {
network = 'testnet'
testnet4 = true
chain = chainArg.replace('testnet4-', '')
} else if (chainArg.startsWith('testnet-')) {
network = 'testnet'
chain = chainArg.replace('testnet-', '')
} else if (chainArg.startsWith('mainnet-')) {
network = 'mainnet'
chain = chainArg.replace('mainnet-', '')
} else {
// Legacy support: assume testnet for backward compatibility
network = 'testnet'
chain = chainArg
console.log(`⚠️ Using testnet (use 'testnet-${chain}' to be explicit)`)
console.log('')
}
const identity = await loadIdentity()
await initializeStorage(identity)
try {
// Check if wallet mnemonic exists
const walletMnemonic = loadWalletMnemonic()
if (!walletMnemonic) {
console.log('❌ No wallet mnemonic found!')
console.log('')
console.log('Initialize WDK first:')
console.log(' bare hyper-wallet-autobase.cjs wallet init')
console.log('')
await closeStorage()
return
}
// Initialize WDK and get account
const { wdk } = await initializeWDK(walletMnemonic, { testnet: network === 'testnet', testnet4 })
let account
if (chain === 'bitcoin') {
const networkLabel = testnet4 ? 'testnet4' : network
console.log(`📡 Connecting to ${networkLabel} blockchain...`)
account = await wdk.getAccount('bitcoin', 0)
} else if (chain === 'ethereum') {
console.log(`📡 Connecting to ${network} blockchain...`)
account = await wdk.getAccount('ethereum', 0)
} else {
console.log(`❌ Unsupported chain: ${chain}`)
console.log('')
console.log('Supported chains: testnet-bitcoin, testnet-ethereum')
console.log('')
await closeStorage()
return
}
// Get balance
const balance = await account.getBalance()
const balanceNumber = Number(balance)
console.log('')
if (chain === 'bitcoin') {
const networkLabel = network === 'testnet' ? 'tBTC (testnet)' : 'BTC'
console.log(`Balance: ${(balanceNumber / 100000000).toFixed(8)} ${networkLabel}`)
console.log(` ${balanceNumber.toLocaleString()} satoshis`)
console.log('')
if (balanceNumber === 0 && network === 'testnet') {
console.log('💡 To receive testnet Bitcoin:')
console.log(' 1. Get your address: bare hyper-wallet-autobase.cjs address current testnet-bitcoin')
console.log(' 2. Visit faucet: https://testnet-faucet.mempool.co/')
console.log(' 3. Sync: bare hyper-wallet-autobase.cjs sync testnet-bitcoin')
console.log('')
}
} else if (chain === 'ethereum') {
// Balance is in wei (10^18 wei = 1 ETH)
const ethBalance = balanceNumber / 1e18
const networkLabel = network === 'testnet' ? 'SepoliaETH (testnet)' : 'ETH'
console.log(`Balance: ${ethBalance.toFixed(18).replace(/\.?0+$/, '')} ${networkLabel}`)
console.log(` ${balanceNumber.toLocaleString()} wei`)
console.log('')
if (balanceNumber === 0 && network === 'testnet') {
console.log('💡 To receive testnet Sepolia ETH:')
console.log(' 1. Get your address: bare hyper-wallet-autobase.cjs address current testnet-ethereum')
console.log(' 2. Visit faucet: https://sepolia-faucet.pk910.de/')
console.log(' 3. Check balance: bare hyper-wallet-autobase.cjs balance testnet-ethereum')
console.log('')
}
}
// Clean up WDK resources
wdk.dispose()
} catch (err) {
console.log('')
console.log('❌ Error getting balance:', err.message)
console.log('')
}
await closeStorage()
}
async function cmdSync(args) {
const chainArg = args[1] || 'testnet-bitcoin'
// Parse chain argument
let chain, network
if (chainArg.startsWith('testnet-')) {
network = 'testnet'
chain = chainArg.replace('testnet-', '')
} else if (chainArg.startsWith('mainnet-')) {
network = 'mainnet'
chain = chainArg.replace('mainnet-', '')
} else {
// Legacy support: assume testnet
network = 'testnet'
chain = chainArg
}
console.log('🔄 Syncing Balance')
console.log('')
console.log(`Chain: ${chainArg}`)
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
try {
// Initialize WDK
const walletMnemonic = loadWalletMnemonic()
if (!walletMnemonic) {
console.log('❌ No wallet mnemonic found!')
console.log('')
console.log('Initialize WDK first:')
console.log(' bare hyper-wallet-autobase.cjs wallet init')
console.log('')
await closeStorage()
return
}
const { wdk } = await initializeWDK(walletMnemonic, { testnet: network === 'testnet' })
// Get account for chain
let account
console.log(`📡 Connecting to ${network} blockchain...`)
if (chain === 'bitcoin') {
account = await wdk.getAccount('bitcoin', 0)
} else if (chain === 'ethereum') {
account = await wdk.getAccount('ethereum', 0)
} else {
console.log(`❌ Unsupported chain: ${chain}`)
console.log('')
console.log('Supported chains: testnet-bitcoin, testnet-ethereum')
console.log('')
await closeStorage()
return
}
console.log('✅ Connected to blockchain')
console.log('')
// Get balance from blockchain
console.log('Querying balance...')
const balance = await account.getBalance()
const balanceNumber = Number(balance)
console.log('')
if (chain === 'bitcoin') {
const networkLabel = network === 'testnet' ? 'tBTC (testnet)' : 'BTC'
console.log(`✅ Balance: ${(balanceNumber / 100000000).toFixed(8)} ${networkLabel} (${balanceNumber.toLocaleString()} sats)`)
console.log('')
if (balanceNumber === 0 && network === 'testnet') {
console.log('No transactions found.')
console.log('')
console.log('💡 To receive testnet Bitcoin:')
console.log(' 1. Your address: tb1qfej77dh7cnxj3zxpxlj0hksxhjgpfyg7ajgvvd')
console.log(' 2. Visit faucet: https://testnet-faucet.mempool.co/')
console.log(' 3. Wait 1-2 minutes and run: bare hyper-wallet-autobase.cjs sync testnet-bitcoin')
console.log('')
} else if (balanceNumber > 0) {
console.log('✅ Wallet has balance! Ready for transactions.')
console.log('💡 WDK manages UTXOs internally for sending.')
console.log('')
}
} else if (chain === 'ethereum') {
const ethBalance = balanceNumber / 1e18
const networkLabel = network === 'testnet' ? 'SepoliaETH (testnet)' : 'ETH'
console.log(`✅ Balance: ${ethBalance.toFixed(18).replace(/\.?0+$/, '')} ${networkLabel} (${balanceNumber.toLocaleString()} wei)`)
console.log('')
if (balanceNumber === 0 && network === 'testnet') {
console.log('No transactions found.')
console.log('')
console.log('💡 To receive testnet Sepolia ETH:')
console.log(' 1. Your address: 0xB91cc8b6803f55c2e1D2B56623790615C9951340')
console.log(' 2. Visit faucet: https://sepolia-faucet.pk910.de/')
console.log(' 3. Wait 1-2 minutes and run: bare hyper-wallet-autobase.cjs sync testnet-ethereum')
console.log('')
} else if (balanceNumber > 0) {
console.log('✅ Wallet has balance! Ready for transactions.')
console.log('💡 WDK manages nonce and gas internally for sending.')
console.log('')
}
}
// Clean up WDK resources
wdk.dispose()
} catch (err) {
console.log('')
console.log('❌ Error syncing UTXOs:', err.message)
console.log('')
}
await closeStorage()
}
// ============================================================================
// SEND COMMAND (Phase 6.4/6.5)
// ============================================================================
async function cmdSend(args) {
const chainArg = args[1]
const toAddress = args[2]
const amountArg = args[3]
if (!chainArg || !toAddress || !amountArg) {
console.error('❌ Missing arguments')
console.error('')
console.error('Usage: bare hyper-wallet-autobase.cjs send <chain> <address> <amount>')
console.error('Example: bare hyper-wallet-autobase.cjs send testnet-bitcoin tb1q... 0.0001')
console.error(' bare hyper-wallet-autobase.cjs send testnet-ethereum 0x... 0.01')
console.error('')
process.exit(1)
}
// Parse chain argument
let chain, network, testnet4 = false
if (chainArg.startsWith('testnet4-')) {
network = 'testnet'
testnet4 = true
chain = chainArg.replace('testnet4-', '')
} else if (chainArg.startsWith('testnet-')) {
network = 'testnet'
chain = chainArg.replace('testnet-', '')
} else if (chainArg.startsWith('mainnet-')) {
network = 'mainnet'
chain = chainArg.replace('mainnet-', '')
} else {
// Legacy support: assume testnet
network = 'testnet'
chain = chainArg
}
console.log('💸 Send Transaction')
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
try {
// Initialize WDK
const walletMnemonic = loadWalletMnemonic()
if (!walletMnemonic) {
console.log('❌ No wallet mnemonic found!')
console.log('')
console.log('Initialize WDK first:')
console.log(' bare hyper-wallet-autobase.cjs wallet init')
console.log('')
await closeStorage()
return
}
const { wdk } = await initializeWDK(walletMnemonic, { testnet: network === 'testnet', testnet4 })
// Get account for chain
let account, value, unit
console.log(`📡 Connecting to ${network} blockchain...`)
if (chain === 'bitcoin') {
account = await wdk.getAccount('bitcoin', 0)
// Convert BTC to satoshis
value = BigInt(Math.round(parseFloat(amountArg) * 100000000))
unit = 'BTC'
} else if (chain === 'ethereum') {
account = await wdk.getAccount('ethereum', 0)
// Convert ETH to wei
value = BigInt(Math.round(parseFloat(amountArg) * 1e18))
unit = 'ETH'
} else {
console.log(`❌ Unsupported chain: ${chain}`)
console.log('')
await closeStorage()
return
}
console.log('✅ Connected')
console.log('')
// Show transaction details
console.log('Transaction Details:')
console.log(` To: ${toAddress}`)
console.log(` Amount: ${amountArg} ${unit}`)
console.log(` Chain: ${chainArg}`)
console.log('')
// Get our address (from address)
const fromAddress = await account.getAddress()
// Send transaction (WDK handles UTXO selection, fee, change, signing, broadcasting)
console.log('Building and broadcasting transaction...')
const result = await account.sendTransaction({
to: toAddress,
value
})
// Store transaction in Autobase for multi-device sync (Phase 6.6)
await storeTransaction({
hash: result.hash,
chain: chain,
network: network, // Add network (testnet/mainnet)
direction: 'sent',
from: fromAddress,
to: toAddress,
amount: value,
fee: result.fee,
status: 'pending',
timestamp: Date.now()
})
console.log('')
console.log('✅ Transaction sent successfully!')
console.log('')
console.log(`📋 Transaction ID: ${result.hash}`)
console.log(`💰 Fee: ${Number(result.fee).toLocaleString()} ${chain === 'bitcoin' ? 'satoshis' : 'wei'}`)
console.log('')
console.log('🔍 View transaction:')
if (chain === 'bitcoin') {
console.log(` https://blockstream.info/testnet/tx/${result.hash}`)
} else if (chain === 'ethereum') {
console.log(` https://sepolia.etherscan.io/tx/${result.hash}`)
}
console.log('')
console.log('📝 Transaction stored and synced across devices')
console.log('')
// Clean up WDK resources
wdk.dispose()
} catch (err) {
console.log('')
console.log('❌ Error sending transaction:', err.message)
console.log('')
if (err.message.includes('Insufficient')) {
console.log('💡 Check your balance:')
console.log(` bare hyper-wallet-autobase.cjs balance ${chainArg}`)
console.log('')
}
}
await closeStorage()
}
// ============================================================================
// HISTORY COMMAND (Phase 6.6)
// ============================================================================
async function cmdHistory(args) {
const chainArg = args[1] || 'all'
console.log('📜 Transaction History')
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
try {
// Parse chain argument
let chain = chainArg
if (chainArg.startsWith('testnet-') || chainArg.startsWith('mainnet-')) {
chain = chainArg.replace(/^(testnet|mainnet)-/, '')
}
const transactions = await getTransactions(chain)
if (transactions.length === 0) {
console.log(`No transactions found for ${chainArg}.`)
console.log('')
console.log('💡 Transactions are automatically stored when you send coins.')
console.log('')
await closeStorage()
return
}
console.log(`Found ${transactions.length} transaction${transactions.length === 1 ? '' : 's'}:`)
console.log('')
for (const tx of transactions) {
const date = new Date(tx.timestamp).toLocaleString()
const icon = tx.direction === 'sent' ? '📤' : '📥'
const status = tx.status === 'confirmed' ? '✅' : '⏳'
console.log(`${icon} ${status} ${tx.direction.toUpperCase()} - ${tx.chain}`)
console.log(` TX: ${tx.hash}`)
console.log(` From: ${tx.from}`)
console.log(` To: ${tx.to}`)
if (tx.chain === 'bitcoin') {
const btc = Number(tx.amount) / 100000000
const feeSats = Number(tx.fee)
console.log(` Amount: ${btc.toFixed(8)} BTC`)
console.log(` Fee: ${feeSats.toLocaleString()} satoshis`)
} else if (tx.chain === 'ethereum') {
const eth = Number(tx.amount) / 1e18
const feeWei = Number(tx.fee)
console.log(` Amount: ${eth.toFixed(18).replace(/\.?0+$/, '')} ETH`)
console.log(` Fee: ${feeWei.toLocaleString()} wei`)
}
console.log(` Date: ${date}`)
console.log(` Status: ${tx.status}`)
if (tx.blockHeight) {
console.log(` Block: ${tx.blockHeight}`)
}
console.log('')
}
console.log(`Total: ${transactions.length} transaction${transactions.length === 1 ? '' : 's'}`)
console.log('')
} catch (err) {
console.log('')
console.log('❌ Error loading transaction history:', err.message)
console.log('')
}
await closeStorage()
}
/**
* Sync transaction history from blockchain (Phase 6.8)
* Uses WDK's getTransfers() to detect ALL transactions (sent + received)
*/
async function cmdSyncHistory(args) {
const chainArg = args[1] || 'all'
console.log('🔄 Sync Transaction History')
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
// Load configuration
const { getConfig } = require('./lib/hyper-wallet-lib/config.cjs')
const { fetchTransactions } = require('./lib/hyper-wallet-lib/blockchain-providers.cjs')
const config = getConfig()
try {
// Parse chain argument
let chains = []
let network = config.network // Use config network by default
if (chainArg === 'all') {
chains = ['bitcoin', 'ethereum']
} else if (chainArg.startsWith('testnet-') || chainArg.startsWith('testnet4-') || chainArg.startsWith('mainnet-')) {
// Override network if explicitly specified in argument
if (chainArg.startsWith('testnet4-')) {
network = 'testnet4'
chains = [chainArg.replace('testnet4-', '')]
} else if (chainArg.startsWith('testnet-')) {
network = 'testnet'
chains = [chainArg.replace('testnet-', '')]
} else {
network = 'mainnet'
chains = [chainArg.replace('mainnet-', '')]
}
} else {
chains = [chainArg]
}
// Load wallet mnemonic
const walletMnemonic = loadWalletMnemonic()
if (!walletMnemonic) {
console.error('❌ No wallet found. Run "wallet init" first.')
console.error('')
await closeStorage()
process.exit(1)
}
// Initialize WDK
console.log(`📡 Connecting to ${network} blockchain...`)
const { wdk } = await initializeWDK(walletMnemonic, {
testnet: network === 'testnet' || network === 'testnet4',
testnet4: network === 'testnet4'
})
console.log('✅ Connected')
console.log('')
let totalNew = 0
let totalUpdated = 0
for (const chain of chains) {
if (chain !== 'bitcoin' && chain !== 'ethereum') {
console.log(`⚠️ Skipping unsupported chain: ${chain}`)
continue
}
console.log(`🔍 Syncing ${chain} transactions...`)
console.log(` Provider: ${config.blockchain[chain].provider}`)
try {
const account = await wdk.getAccount(chain, 0)
const myAddress = await account.getAddress()
let transfers = []
// Use blockchain provider system with automatic fallback
const result = await fetchTransactions(chain, myAddress, network, config)
if (result.useNativeWDK) {
// Use WDK's built-in method (Bitcoin Electrum)
console.log(` Using WDK native method (${result.provider})...`)
transfers = await account.getTransfers({
direction: 'all',
limit: 100
})
} else {
// Got transactions from HTTP API
// Convert to WDK-compatible format
transfers = result.map(tx => ({
txid: tx.hash,
address: myAddress,
height: tx.blockNumber || 0,
value: tx.value,
direction: tx.to && tx.to.toLowerCase() === myAddress.toLowerCase() ? 'incoming' : 'outgoing',
fee: tx.fee || '0',
recipient: tx.to,
from: tx.from
}))
}
console.log(` Found ${transfers.length} transfer${transfers.length === 1 ? '' : 's'} on blockchain`)
for (const transfer of transfers) {
try {
// Skip transfers without value (malformed data)
if (transfer.value === null || transfer.value === undefined) {
console.log(` ⚠️ Skipping transfer with no value: ${transfer.txid.substring(0, 16)}...`)
continue
}
// Check if we already have this transaction
const existing = await getTransaction(chain, transfer.txid)
if (!existing) {
// New transaction - store it
const isIncoming = transfer.direction === 'incoming'
// Get recipient address for outgoing transfers
const toAddress = isIncoming ? myAddress : (transfer.recipient || '(unknown)')
await storeTransaction({
hash: transfer.txid,
chain: chain,
network: network, // Add network (testnet/mainnet)
direction: isIncoming ? 'received' : 'sent',
from: isIncoming ? '(external)' : myAddress,
to: toAddress,
amount: String(transfer.value || 0), // Convert to string (BigInts can't be JSON stringified)
fee: String(transfer.fee || 0),
status: transfer.height > 0 ? 'confirmed' : 'pending',
timestamp: Date.now(),
blockHeight: transfer.height || null
})
totalNew++
console.log(` ✅ New ${transfer.direction} transaction: ${transfer.txid.substring(0, 16)}...`)
} else if (existing.status === 'pending' && transfer.height > 0) {
// Existing pending transaction got confirmed - update it
await updateTransactionStatus(chain, transfer.txid, {
status: 'confirmed',
blockHeight: transfer.height
})
totalUpdated++
console.log(` ✅ Transaction confirmed: ${transfer.txid.substring(0, 16)}... (block ${transfer.height})`)
}
} catch (err) {
console.log(` ⚠️ Error processing ${transfer.txid.substring(0, 16)}...: ${String(err.message || err)}`)
}
}
console.log('')
} catch (err) {
console.log(` ❌ Error syncing ${chain}: ${err.message}`)
console.log('')
}
}
wdk.dispose()
console.log('📊 Summary:')
console.log(` New transactions: ${totalNew}`)
console.log(` Updated transactions: ${totalUpdated}`)
console.log('')
if (totalNew > 0 || totalUpdated > 0) {
console.log('💡 Run "history" command to view updated transaction list')
console.log('')
}
} catch (err) {
console.log('')
console.log('❌ Error syncing history:', err.message)
console.log('')
}
await closeStorage()
}
/**
* Monitor and update transaction statuses (Phase 6.8)
* Checks pending transactions and updates them when confirmed
* Now uses WDK's getTransfers() API for actual status checks
*/
async function cmdMonitor(args) {
const chainArg = args[1] || 'all'
console.log('🔍 Transaction Status Monitor')
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
try {
// Parse chain argument
let chain = chainArg
let network = 'testnet'
if (chainArg.startsWith('testnet-') || chainArg.startsWith('mainnet-')) {
network = chainArg.startsWith('testnet-') ? 'testnet' : 'mainnet'
chain = chainArg.replace(/^(testnet|mainnet)-/, '')
}
// Load all transactions
const transactions = await getTransactions(chain)
const pendingTransactions = transactions.filter(tx => tx.status === 'pending')
if (pendingTransactions.length === 0) {
console.log(`No pending transactions for ${chainArg}.`)
console.log('')
await closeStorage()
return
}
console.log(`Found ${pendingTransactions.length} pending transaction${pendingTransactions.length === 1 ? '' : 's'}`)
console.log('')
// Load wallet mnemonic for WDK access
const walletMnemonic = loadWalletMnemonic()
if (!walletMnemonic) {
console.error('❌ No wallet found. Run "wallet init" first.')
console.error('')
await closeStorage()
process.exit(1)
}
// Initialize WDK
const { wdk } = await initializeWDK(walletMnemonic, { testnet: network === 'testnet' })
let updatedCount = 0
// Check each chain that has pending transactions
const chainsToCheck = chain === 'all'
? [...new Set(pendingTransactions.map(tx => tx.chain))]
: [chain]
for (const checkChain of chainsToCheck) {
try {
const account = await wdk.getAccount(checkChain, 0)
// Get all transfers to check status
const transfers = await account.getTransfers({ direction: 'all', limit: 100 })
// Build a map of txid -> transfer for quick lookup
const transferMap = new Map()
for (const transfer of transfers) {
transferMap.set(transfer.txid, transfer)
}
// Check each pending transaction
const chainPending = pendingTransactions.filter(tx => tx.chain === checkChain)
for (const tx of chainPending) {
const transfer = transferMap.get(tx.hash)
if (transfer && transfer.height > 0) {
// Transaction is confirmed!
await updateTransactionStatus(checkChain, tx.hash, {
status: 'confirmed',
blockHeight: transfer.height
})
updatedCount++
console.log(`✅ ${tx.hash.substring(0, 16)}... confirmed at block ${transfer.height}`)
} else if (transfer) {
console.log(`⏳ ${tx.hash.substring(0, 16)}... still pending`)
} else {
console.log(`❓ ${tx.hash.substring(0, 16)}... not found in transfers (may take time to appear)`)
}
}
} catch (err) {
console.log(`❌ Error checking ${checkChain}: ${err.message}`)
}
}
console.log('')
wdk.dispose()
if (updatedCount > 0) {
console.log(`✅ Updated ${updatedCount} transaction${updatedCount === 1 ? '' : 's'} to confirmed`)
console.log('')
} else {
console.log('💡 No status changes detected.')
console.log('')
}
} catch (err) {
console.log('')
console.log('❌ Error monitoring transactions:', err.message)
console.log('')
}
await closeStorage()
}
/**
* Background daemon for automatic transaction sync (Phase 6.9)
* Runs sync-history periodically to detect new transactions
*/
async function cmdDaemon(args) {
const subcommand = args[1]
const DAEMON_PID_FILE = path.join(os.homedir(), '.hyper-wallet', 'daemon.pid')
const DAEMON_LOG_FILE = path.join(os.homedir(), '.hyper-wallet', 'daemon.log')
const DAEMON_INTERVAL = 5 * 60 * 1000 // 5 minutes
if (subcommand === 'start') {
// Check if already running
if (fs.existsSync(DAEMON_PID_FILE)) {
const pid = fs.readFileSync(DAEMON_PID_FILE, 'utf8').trim()
console.log(`⚠️ Daemon already running with PID ${pid}`)
console.log(' Run "daemon stop" first to restart')
console.log('')
return
}
console.log('🚀 Starting transaction sync daemon')
console.log(` Sync interval: ${DAEMON_INTERVAL / 1000 / 60} minutes`)
console.log(` Log file: ${DAEMON_LOG_FILE}`)
console.log('')
// Fork a background process
const childProcess = require('bare-subprocess')
const daemon = childProcess.spawn(process.argv[0], [
process.argv[1],
'daemon',
'run'
], {
detached: true,
stdio: 'ignore'
})
daemon.unref()
// Save PID
const dir = path.dirname(DAEMON_PID_FILE)
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true, mode: 0o700 })
}
fs.writeFileSync(DAEMON_PID_FILE, daemon.pid.toString())
console.log(`✅ Daemon started with PID ${daemon.pid}`)
console.log('')
console.log('💡 Commands:')
console.log(' bare hyper-wallet-autobase.cjs daemon status - Check status')
console.log(' bare hyper-wallet-autobase.cjs daemon stop - Stop daemon')
console.log(' tail -f ' + DAEMON_LOG_FILE + ' - View logs')
console.log('')
} else if (subcommand === 'stop') {
if (!fs.existsSync(DAEMON_PID_FILE)) {
console.log('⚠️ Daemon is not running')
console.log('')
return
}
const pid = parseInt(fs.readFileSync(DAEMON_PID_FILE, 'utf8').trim())
console.log(`🛑 Stopping daemon (PID ${pid})...`)
try {
process.kill(pid, 'SIGTERM')
fs.unlinkSync(DAEMON_PID_FILE)
console.log('✅ Daemon stopped')
console.log('')
} catch (err) {
console.log(`❌ Error stopping daemon: ${err.message}`)
console.log(' Process may have already died. Cleaning up PID file...')
fs.unlinkSync(DAEMON_PID_FILE)
console.log('')
}
} else if (subcommand === 'status') {
if (!fs.existsSync(DAEMON_PID_FILE)) {
console.log('⚪ Daemon is not running')
console.log('')
console.log('💡 Start with: bare hyper-wallet-autobase.cjs daemon start')
console.log('')
return
}
const pid = parseInt(fs.readFileSync(DAEMON_PID_FILE, 'utf8').trim())
// Check if process is actually running
try {
process.kill(pid, 0) // Signal 0 checks if process exists
console.log('🟢 Daemon is running')
console.log(` PID: ${pid}`)
console.log(` Log: ${DAEMON_LOG_FILE}`)
console.log('')
console.log('💡 View logs: tail -f ' + DAEMON_LOG_FILE)
console.log('')
} catch (err) {
console.log('🔴 Daemon PID file exists but process is not running')
console.log(` Stale PID: ${pid}`)
console.log(' Cleaning up...')
fs.unlinkSync(DAEMON_PID_FILE)
console.log('✅ Cleaned up stale PID file')
console.log('')
}
} else if (subcommand === 'run') {
// This is the actual daemon loop (runs in background)
const logStream = fs.createWriteStream(DAEMON_LOG_FILE, { flags: 'a' })
function log(message) {
const timestamp = new Date().toISOString()
const line = `[${timestamp}] ${message}\n`
logStream.write(line)
}
log('Daemon started')
log(`Sync interval: ${DAEMON_INTERVAL / 1000 / 60} minutes`)
async function syncLoop() {
try {
log('Running sync-history...')
// Load identity
const identity = await loadIdentity()
await initializeStorage(identity)
// Load wallet mnemonic
const walletMnemonic = loadWalletMnemonic()
if (!walletMnemonic) {
log('ERROR: No wallet found')
return
}
// Initialize WDK
const { wdk } = await initializeWDK(walletMnemonic, { testnet: true })
let totalNew = 0
let totalUpdated = 0
// Sync Bitcoin
try {
const account = await wdk.getAccount('bitcoin', 0)
const myAddress = await account.getAddress()
const transfers = await account.getTransfers({ direction: 'all', limit: 100 })
for (const transfer of transfers) {
if (transfer.value === null || transfer.value === undefined) continue
const existing = await getTransaction('bitcoin', transfer.txid)
if (!existing) {
const isIncoming = transfer.direction === 'incoming'
const toAddress = isIncoming ? myAddress : (transfer.recipient || '(unknown)')
await storeTransaction({
hash: transfer.txid,
chain: 'bitcoin',
network: 'testnet', // Daemon is hardcoded to testnet for now
direction: isIncoming ? 'received' : 'sent',
from: isIncoming ? '(external)' : myAddress,
to: toAddress,
amount: Number(transfer.value),
fee: Number(transfer.fee || 0n),
status: transfer.height > 0 ? 'confirmed' : 'pending',
timestamp: Date.now(),
blockHeight: transfer.height || null
})
totalNew++
log(`New ${transfer.direction} transaction: ${transfer.txid.substring(0, 16)}...`)
} else if (existing.status === 'pending' && transfer.height > 0) {
await updateTransactionStatus('bitcoin', transfer.txid, {
status: 'confirmed',
blockHeight: transfer.height
})
totalUpdated++
log(`Transaction confirmed: ${transfer.txid.substring(0, 16)}... (block ${transfer.height})`)
}
}
} catch (err) {
log(`ERROR syncing Bitcoin: ${err.message}`)
}
wdk.dispose()
await closeStorage()
log(`Sync complete: ${totalNew} new, ${totalUpdated} updated`)
} catch (err) {
log(`ERROR in sync loop: ${err.message}`)
}
}
// Run immediately on start
syncLoop()
// Then run every DAEMON_INTERVAL
setInterval(syncLoop, DAEMON_INTERVAL)
// Keep process alive
process.on('SIGTERM', () => {
log('Received SIGTERM, shutting down...')
logStream.end()
process.exit(0)
})
} else {
console.error(`❌ Unknown daemon command: ${subcommand}`)
console.error('')
console.error('Available: start, stop, status')
console.error('')
process.exit(1)
}
}
/**
* Settings management (Phase 6.9)
* Manage blockchain providers and configuration
*/
async function cmdSettings(args) {
const { getConfig, updateConfig, saveConfig, DEFAULT_CONFIG } = require('./lib/hyper-wallet-lib/config.cjs')
const { getAvailableProviders, getProviderInfo } = require('./lib/hyper-wallet-lib/blockchain-providers.cjs')
const subcommand = args[1]
if (!subcommand || subcommand === 'show') {
// Display current configuration
const config = getConfig()
console.log('⚙️ Wallet Settings')
console.log('')
console.log('📡 Network')
console.log(` ${config.network}`)
console.log('')
console.log('🔗 Blockchain Providers')
console.log(` Bitcoin: ${config.blockchain.bitcoin.provider}`)
console.log(` Ethereum: ${config.blockchain.ethereum.provider}`)
if (config.blockchain.ethereum.apiKey) {
const masked = '***' + config.blockchain.ethereum.apiKey.slice(-4)
console.log(` API Key: ${masked}`)
}
console.log('')
console.log('🎨 UI Settings')
console.log(` Theme: ${config.ui.theme}`)
console.log(` Auto Lock: ${config.ui.autoLock} minutes`)
console.log(` Currency: ${config.ui.currency}`)
console.log('')
console.log('🔄 Sync Settings')
console.log(` Auto Sync: ${config.sync.autoSync ? 'enabled' : 'disabled'}`)
console.log(` Sync Interval: ${config.sync.syncInterval} minutes`)
console.log('')
console.log('🔒 Security')
console.log(` Require Password on Send: ${config.security.requirePasswordOnSend ? 'yes' : 'no'}`)
console.log(` Session Timeout: ${config.security.sessionTimeout} minutes`)
console.log('')
console.log('💡 Commands:')
console.log(' settings set <key> <value> - Update a setting')
console.log(' settings providers - List available providers')
console.log(' settings reset - Reset to defaults')
console.log('')
} else if (subcommand === 'set') {
// Update configuration value
const key = args[2]
const value = args[3]
if (!key || value === undefined) {
console.error('❌ Usage: settings set <key> <value>')
console.error('')
console.error('Examples:')
console.error(' settings set blockchain.ethereum.provider etherscan')
console.error(' settings set blockchain.ethereum.apiKey YOUR_API_KEY')
console.error(' settings set network mainnet')
console.error(' settings set ui.theme light')
console.error('')
process.exit(1)
}
// Parse value (handle booleans and numbers)
let parsedValue = value
if (value === 'true') parsedValue = true
else if (value === 'false') parsedValue = false
else if (value === 'null') parsedValue = null
else if (!isNaN(value)) parsedValue = Number(value)
try {
updateConfig(key, parsedValue)
console.log('✅ Setting updated')
console.log(` ${key} = ${parsedValue}`)
console.log('')
console.log('💡 View settings: bare hyper-wallet-autobase.cjs settings show')
console.log('')
} catch (err) {
console.error(`❌ Error updating setting: ${err.message}`)
console.error('')
process.exit(1)
}
} else if (subcommand === 'providers') {
// List available blockchain providers
console.log('📡 Available Blockchain Providers')
console.log('')
console.log('Ethereum:')
const ethProviders = getAvailableProviders('ethereum')
for (const name of ethProviders) {
const info = getProviderInfo('ethereum', name)
const apiKeyStatus = info.needsApiKey ? '🔑 Requires API key' : '✅ No API key needed'
console.log(` ${name.padEnd(12)} - ${info.name} (${apiKeyStatus})`)
}
console.log('')
console.log('Bitcoin:')
const btcProviders = getAvailableProviders('bitcoin')
for (const name of btcProviders) {
const info = getProviderInfo('bitcoin', name)
if (info.usesNativeWDK) {
console.log(` ${name.padEnd(12)} - ${info.name} (✅ Native WDK)`)
} else {
const apiKeyStatus = info.needsApiKey ? '🔑 Requires API key' : '✅ No API key needed'
console.log(` ${name.padEnd(12)} - ${info.name} (${apiKeyStatus})`)
}
}
console.log('')
console.log('💡 Change provider:')
console.log(' bare hyper-wallet-autobase.cjs settings set blockchain.ethereum.provider blockscout')
console.log('')
} else if (subcommand === 'reset') {
// Reset to defaults
console.log('⚠️ This will reset all settings to defaults')
console.log(' Current settings will be lost')
console.log('')
console.log('💡 To confirm, run:')
console.log(' bare hyper-wallet-autobase.cjs settings reset-confirm')
console.log('')
} else if (subcommand === 'reset-confirm') {
// Actually reset (requires confirmation)
saveConfig(DEFAULT_CONFIG)
console.log('✅ Settings reset to defaults')
console.log('')
console.log('💡 View settings: bare hyper-wallet-autobase.cjs settings show')
console.log('')
} else {
console.error(`❌ Unknown settings command: ${subcommand}`)
console.error('')
console.error('Available commands:')
console.error(' show - Display current settings')
console.error(' set - Update a setting')
console.error(' providers - List available blockchain providers')
console.error(' reset - Reset to defaults')
console.error('')
process.exit(1)
}
}
async function cmdUTXOList(args) {
const chain = args[2] || 'bitcoin'
console.log('📋 UTXO List')
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
try {
const utxos = await getUTXOs(chain)
if (utxos.length === 0) {
console.log(`No UTXOs found for ${chain}.`)
console.log('')
console.log('Sync UTXOs from blockchain:')
console.log(` bare hyper-wallet-autobase.cjs sync ${chain}`)
console.log('')
await closeStorage()
return
}
// Group by status
const spendable = utxos.filter(u => !u.spent && u.spendable)
const spent = utxos.filter(u => u.spent)
if (spendable.length > 0) {
console.log(`💎 Spendable (${spendable.length})`)
console.log('')
for (const utxo of spendable) {
const confirmIcon = utxo.confirmations >= 6 ? '✅' : '⏳'
const btcAmount = (utxo.amount / 100000000).toFixed(8)
console.log(`${confirmIcon} ${btcAmount} BTC (${utxo.amount.toLocaleString()} sats)`)
console.log(` TXID: ${utxo.txid}:${utxo.vout}`)
console.log(` Address: ${utxo.address}`)
console.log(` Confirmations: ${utxo.confirmations}`)
console.log('')
}
}
if (spent.length > 0) {
console.log(`❌ Spent (${spent.length})`)
console.log('')
for (const utxo of spent) {
const btcAmount = (utxo.amount / 100000000).toFixed(8)
console.log(` ${btcAmount} BTC`)
console.log(` TXID: ${utxo.txid}:${utxo.vout}`)
console.log(` Spent in: ${utxo.spentTxid || 'unknown'}`)
console.log('')
}
}
// Show total
const balance = calculateBalance(utxos)
const symbol = chain === 'bitcoin' ? 'BTC' : 'ETH'
console.log('─────────────────────────────────────')
console.log(formatBalance(balance, symbol))
console.log('')
} catch (err) {
console.log('')
console.log('❌ Error listing UTXOs:', err.message)
console.log('')
}
await closeStorage()
}
// ============================================================================
// DEVICE COMMANDS
// ============================================================================
async function cmdDeviceBootstrap(args) {
const deviceName = args[2] || 'Default Device'
console.log('🖥️ Bootstrapping Device')
console.log('')
console.log(`Device Name: ${deviceName}`)
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
const device = await bootstrapDevice(identity, deviceName)
console.log('✅ Device bootstrapped!')
console.log('')
console.log(`Name: ${device.name}`)
console.log(`Writer: ${b4a.toString(device.key, 'hex').slice(0, 16)}...`)
console.log(`Added: ${new Date(device.addedAt).toLocaleString()}`)
console.log('')
console.log('This device can now:')
console.log(' - Create invites for other devices')
console.log(' - Participate in multi-device sync')
console.log('')
await closeStorage()
}
async function cmdDeviceAdd(args) {
console.log('📱 Adding New Device')
console.log('')
const identity = await loadIdentity()
const identityMnemonic = loadMnemonic()
const walletMnemonic = loadWalletMnemonic()
if (!walletMnemonic) {
console.log('❌ Wallet mnemonic not found!')
console.log('')
console.log('Run: bare hyper-wallet-autobase.cjs init')
console.log('')
process.exit(1)
}
await initializeStorage(identity)
const invite = await createInvite(identity)
console.log('✅ Invite created!')
console.log('')
console.log('Share this invite code with your other device:')
console.log('')
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log(invite)
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('')
console.log('On the other device, run:')
console.log(`bare hyper-wallet-autobase.cjs device join "<invite>" "Device Name"`)
console.log('')
console.log('📡 Waiting for device to connect...')
console.log(' (Press Ctrl+C to cancel)')
console.log('')
try {
// Wait indefinitely for device to connect, send BOTH mnemonics
const { writerKey, deviceName } = await waitForNewDevice(0, identityMnemonic, walletMnemonic)
console.log('')
console.log('✅ Device added successfully!')
console.log(` Name: ${deviceName}`)
console.log(` Writer: ${b4a.toString(writerKey, 'hex').slice(0, 16)}...`)
console.log('')
console.log('The new device is now part of your wallet.')
console.log('')
} catch (err) {
console.log('')
console.log('❌ Error:', err.message)
console.log('')
}
await closeStorage()
}
async function cmdDeviceJoin(args) {
const inviteCode = args[2]
const deviceName = args[3] || 'New Device'
if (!inviteCode) {
console.error('❌ Missing invite code')
console.error('')
console.error('Usage: bare hyper-wallet-autobase.cjs device join <invite> "Device Name"')
console.error('')
process.exit(1)
}
console.log('🔗 Joining Wallet')
console.log('')
console.log(`Device Name: ${deviceName}`)
console.log('')
// Join wallet (receives BOTH mnemonics from primary device)
const { device, identityMnemonic, walletMnemonic } = await joinWallet(inviteCode, deviceName)
// Save both mnemonics
saveMnemonic(identityMnemonic)
saveWalletMnemonic(walletMnemonic)
console.log('✅ Joined wallet successfully!')
console.log('')
console.log(`Name: ${device.name}`)
console.log(`Writer: ${b4a.toString(device.key, 'hex').slice(0, 16)}...`)
console.log('')
console.log('🔐 Mnemonics received and saved securely')
console.log(' Identity: ~/.hyper-wallet/.mnemonic (24 words)')
console.log(' Wallet: ~/.hyper-wallet/.wallet-mnemonic (12 words)')
console.log('')
console.log('This device is now part of the wallet and has been approved.')
console.log('')
console.log('Next steps:')
console.log(' 1. Test WDK: bare hyper-wallet-autobase.cjs wallet init')
console.log('')
await closeStorage()
}
async function cmdDeviceList(args) {
console.log('📱 Device List')
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
const devices = listDevices()
if (devices.length === 0) {
console.log('No devices registered.')
console.log('')
console.log('Bootstrap this device first:')
console.log(' bare hyper-wallet-autobase.cjs device bootstrap "Device Name"')
console.log('')
await closeStorage()
return
}
const currentDevice = getCurrentDevice()
for (const device of devices) {
const isCurrent = currentDevice && b4a.equals(device.key, currentDevice.key)
const icon = device.isLocal ? '🖥️ ' : '📱'
const marker = isCurrent ? ' (this device)' : ''
console.log(`${icon} ${device.name}${marker}`)
console.log(` Writer: ${b4a.toString(device.key, 'hex').slice(0, 16)}...`)
console.log(` Added: ${new Date(device.addedAt).toLocaleString()}`)
console.log(` Local: ${device.isLocal}`)
console.log('')
}
console.log(`Total: ${devices.length} device${devices.length === 1 ? '' : 's'}`)
console.log('')
await closeStorage()
}
async function cmdDeviceStatus(args) {
console.log('📊 Device Status')
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
const status = getConnectionStatus()
const currentDevice = getCurrentDevice()
if (currentDevice) {
console.log('Current Device:')
console.log(` Name: ${currentDevice.name}`)
console.log(` Writer: ${b4a.toString(currentDevice.key, 'hex').slice(0, 16)}...`)
console.log('')
}
console.log('Network Status:')
console.log(` Connected: ${status.connected ? '✅ Yes' : '❌ No'}`)
console.log(` Peers: ${status.count}`)
console.log('')
if (status.count > 0) {
console.log('Active Connections:')
for (const conn of status.connections) {
console.log(` 📡 ${conn.remotePublicKey}`)
}
console.log('')
}
await closeStorage()
}
async function cmdDeviceSync(args) {
console.log('🔄 Starting Multi-Device Sync')
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
const currentDevice = getCurrentDevice()
if (!currentDevice) {
console.log('⚠️ Device not bootstrapped!')
console.log('')
console.log('Run: bare hyper-wallet-autobase.cjs device bootstrap "Device Name"')
console.log('')
await closeStorage()
return
}
console.log(`Device: ${currentDevice.name}`)
console.log('')
// Enable replication for all connections
enableReplication()
console.log('📡 Listening for connections...')
console.log('')
console.log('Press Ctrl+C to stop syncing.')
console.log('')
// Setup graceful shutdown
process.on('SIGINT', async () => {
console.log('')
console.log('🛑 Stopping sync...')
await closeStorage()
process.exit(0)
})
// Keep running for sync
await new Promise(() => {})
}
async function cmdDeviceApprove(args) {
const writerKeyHex = args[2]
const deviceName = args[3] || 'Approved Device'
console.log('✅ Approve Pending Writer')
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
// Get pending writers
const pending = getPendingWriters()
if (pending.length === 0) {
console.log('ℹ️ No pending writers found.')
console.log('')
console.log('Make sure the other device has run "device join" first.')
console.log('')
await closeStorage()
return
}
console.log(`Found ${pending.length} pending writer${pending.length === 1 ? '' : 's'}:`)
console.log('')
for (const p of pending) {
const keyHex = b4a.toString(p.key, 'hex')
console.log(`📝 ${keyHex.slice(0, 16)}...`)
console.log(` Full key: ${keyHex}`)
console.log(` Has proof: ${p.proof ? 'Yes ✅' : 'No ⚠️'}`)
console.log(` Added: ${new Date(p.addedAt).toLocaleString()}`)
console.log('')
}
// If writer key provided, approve it
if (writerKeyHex) {
// Find matching writer (allow partial match)
const pendingWriter = pending.find(p => {
const hex = b4a.toString(p.key, 'hex')
return hex === writerKeyHex || hex.startsWith(writerKeyHex)
})
if (!pendingWriter) {
console.log('❌ Writer not found in pending list')
console.log('')
console.log('Available writers listed above.')
console.log('')
await closeStorage()
return
}
console.log(`Approving: ${b4a.toString(pendingWriter.key, 'hex').slice(0, 16)}...`)
console.log('')
await approveWriter(pendingWriter.key, deviceName)
console.log('')
console.log('✅ Device approved successfully!')
console.log('')
console.log('The device can now participate in multi-device sync.')
console.log('')
} else {
console.log('To approve a writer, run:')
console.log(' bare hyper-wallet-autobase.cjs device approve <writer-key> "Device Name"')
console.log('')
console.log('You can use the short form (first 16 characters) or the full key.')
console.log('')
}
await closeStorage()
}
async function cmdDeviceRemove(args) {
const deviceKeyHex = args[2]
if (!deviceKeyHex) {
console.error('❌ Missing device key')
console.error('')
console.error('Usage: bare hyper-wallet-autobase.cjs device remove <device-key>')
console.error('')
console.error('Get device keys from: bare hyper-wallet-autobase.cjs device list')
console.error('')
process.exit(1)
}
console.log('🗑️ Remove Device')
console.log('')
const identity = await loadIdentity()
await initializeStorage(identity)
const devices = listDevices()
// Find matching device (allow partial match on key)
const device = devices.find(d => {
const keyHex = b4a.toString(d.key, 'hex')
return keyHex === deviceKeyHex || keyHex.startsWith(deviceKeyHex)
})
if (!device) {
console.log('❌ Device not found')
console.log('')
console.log('Available devices:')
for (const d of devices) {
console.log(` ${d.name}: ${b4a.toString(d.key, 'hex').slice(0, 16)}...`)
}
console.log('')
await closeStorage()
return
}
// Prevent removing local device
if (device.isLocal) {
console.log('❌ Cannot remove local device')
console.log('')
console.log('You cannot remove the device you are currently using.')
console.log('')
await closeStorage()
return
}
console.log(`Device: ${device.name}`)
console.log(`Key: ${b4a.toString(device.key, 'hex').slice(0, 16)}...`)
console.log('')
console.log('⚠️ WARNING: This will revoke write access for this device.')
console.log(' Existing data from this device will remain.')
console.log('')
await removeDevice(device.key)
console.log('✅ Device removed successfully')
console.log('')
console.log('The device can no longer write to the wallet.')
console.log('It may still be able to read until restarted.')
console.log('')
await closeStorage()
}
// ============================================================================
// HELP
// ============================================================================
function showHelp() {
console.log(`
Hyper Wallet - Multi-Device P2P Cryptocurrency Wallet
USAGE:
bare hyper-wallet-autobase.cjs <command> [options]
COMMANDS:
init Initialize wallet identity (creates 2 mnemonics)
whoami Show identity and current device info
WALLET MANAGEMENT (WDK):
wallet init Initialize WDK and test address generation
(Run after 'init' to verify cryptocurrency setup)
ADDRESS MANAGEMENT (Phase 6.2):
address new [chain]
Generate new receiving address
Chain: bitcoin (default), ethereum
Example: bare hyper-wallet-autobase.cjs address new bitcoin
address list [chain]
List all addresses for chain
Chain: bitcoin, ethereum, or all (default)
Example: bare hyper-wallet-autobase.cjs address list all
address current <chain>
Show current (latest) address for chain
Chain: bitcoin or ethereum (required)
Example: bare hyper-wallet-autobase.cjs address current bitcoin
BALANCE & UTXO MANAGEMENT (Phase 6.3):
balance [chain] Show balance for chain
Chain: bitcoin (default), ethereum
Example: bare hyper-wallet-autobase.cjs balance bitcoin
sync [chain] Sync UTXOs from blockchain
Chain: bitcoin (default), ethereum
Example: bare hyper-wallet-autobase.cjs sync bitcoin
utxo list [chain] List all UTXOs (spendable + spent)
Chain: bitcoin (default), ethereum
Example: bare hyper-wallet-autobase.cjs utxo list bitcoin
TRANSACTION MANAGEMENT (Phase 6.4-6.8):
send <chain> <address> <amount>
Send cryptocurrency to address
Chain: testnet-bitcoin, testnet-ethereum
Example: bare hyper-wallet-autobase.cjs send testnet-bitcoin tb1q... 0.0001
history [chain] View transaction history
Chain: testnet-bitcoin, testnet-ethereum, or all (default)
Example: bare hyper-wallet-autobase.cjs history testnet-bitcoin
sync-history [chain]
Sync transaction history from blockchain (detects received transactions)
Uses WDK's getTransfers() API to detect ALL transactions
Chain: testnet-bitcoin, testnet-ethereum, or all (default)
Example: bare hyper-wallet-autobase.cjs sync-history all
monitor [chain] Check and update pending transaction statuses
Uses WDK's getTransfers() API to check confirmations
Chain: testnet-bitcoin, testnet-ethereum, or all (default)
Example: bare hyper-wallet-autobase.cjs monitor all
daemon start Start background sync daemon (runs sync-history every 5 min)
daemon stop Stop background sync daemon
daemon status Check daemon status
settings show Display current configuration
settings set <key> <value>
Update a configuration value
Example: bare hyper-wallet-autobase.cjs settings set blockchain.ethereum.provider blockscout
settings providers
List available blockchain providers
settings reset Reset all settings to defaults
DEVICE MANAGEMENT:
device bootstrap "Device Name"
Bootstrap this device (first step)
device add Create invite for another device
(Automatically accepts connection)
device join <invite> "Device Name"
Join wallet using invite code
device list List all registered devices
device status Show connection status
device sync Keep running for multi-device sync
(Use when you want devices to stay connected)
device approve [writer-key] [name]
Approve a pending writer after device join
Run without args to list pending writers
device remove <device-key>
Remove a device (revoke write access)
Use short or full device key from device list
EXAMPLES:
# On Device A (first device):
bare hyper-wallet-autobase.cjs init
bare hyper-wallet-autobase.cjs device bootstrap "Tabertron"
bare hyper-wallet-autobase.cjs wallet init
bare hyper-wallet-autobase.cjs address new bitcoin
bare hyper-wallet-autobase.cjs address list all
# Sync UTXOs and check balance (Phase 6.3):
bare hyper-wallet-autobase.cjs sync bitcoin # Query blockchain
bare hyper-wallet-autobase.cjs balance bitcoin # Show balance
bare hyper-wallet-autobase.cjs utxo list bitcoin # List all UTXOs
# Send transactions (Phase 6.4-6.8):
bare hyper-wallet-autobase.cjs send testnet-bitcoin tb1q... 0.0001 # Send BTC
bare hyper-wallet-autobase.cjs history testnet-bitcoin # View history
bare hyper-wallet-autobase.cjs sync-history all # Sync from blockchain (detects received)
bare hyper-wallet-autobase.cjs monitor all # Update statuses
# Add another device:
bare hyper-wallet-autobase.cjs device add
# On Device B (joining):
bare hyper-wallet-autobase.cjs device join "<invite>" "Ashatron"
bare hyper-wallet-autobase.cjs wallet init # Same addresses!
bare hyper-wallet-autobase.cjs balance bitcoin # Same balance!
bare hyper-wallet-autobase.cjs address list bitcoin # Synced from Device A
# Back on Device A (device management):
bare hyper-wallet-autobase.cjs device approve # List pending
bare hyper-wallet-autobase.cjs device approve <key> # Approve specific
STORAGE:
~/.hyper-wallet/.mnemonic Identity mnemonic (24 words)
~/.hyper-wallet/.wallet-mnemonic Wallet mnemonic (12 words)
~/.hyper-wallet/.devices Device list
~/.hyper-wallet/storage/ Autobase storage (tx + addresses)
POWERED BY:
✅ Autobase (multi-writer CRDT)
✅ Hyperswarm (P2P networking)
✅ keet-identity-key (device attestation)
`)
}
// ============================================================================
// MAIN
// ============================================================================
async function main() {
const args = process.argv.slice(2)
const command = args[0]
const subcommand = args[1]
try {
if (command === 'wallet') {
// Wallet management subcommands
switch (subcommand) {
case 'init':
await cmdWalletInit(args)
break
default:
console.error(`❌ Unknown wallet command: ${subcommand}`)
console.error('')
console.error('Available: init')
console.error('')
process.exit(1)
}
} else if (command === 'address') {
// Address management subcommands (Phase 6.2)
switch (subcommand) {
case 'new':
await cmdAddressNew(args)
break
case 'list':
await cmdAddressList(args)
break
case 'current':
await cmdAddressCurrent(args)
break
default:
console.error(`❌ Unknown address command: ${subcommand}`)
console.error('')
console.error('Available: new, list, current')
console.error('')
process.exit(1)
}
} else if (command === 'balance') {
// Balance command (Phase 6.3)
await cmdBalance(args)
} else if (command === 'sync') {
// Sync command (Phase 6.3)
await cmdSync(args)
} else if (command === 'send') {
// Send transaction command (Phase 6.4/6.5)
await cmdSend(args)
} else if (command === 'history') {
// Transaction history command (Phase 6.6)
await cmdHistory(args)
} else if (command === 'sync-history') {
// Sync transaction history from blockchain (Phase 6.8)
await cmdSyncHistory(args)
} else if (command === 'monitor') {
// Transaction status monitor (Phase 6.8)
await cmdMonitor(args)
} else if (command === 'daemon') {
// Background sync daemon (Phase 6.9)
await cmdDaemon(args)
} else if (command === 'settings') {
// Settings management
await cmdSettings(args)
} else if (command === 'utxo') {
// UTXO management subcommands (Phase 6.3)
switch (subcommand) {
case 'list':
await cmdUTXOList(args)
break
default:
console.error(`❌ Unknown utxo command: ${subcommand}`)
console.error('')
console.error('Available: list')
console.error('')
process.exit(1)
}
} else if (command === 'device') {
// Device management subcommands
switch (subcommand) {
case 'bootstrap':
await cmdDeviceBootstrap(args)
break
case 'add':
await cmdDeviceAdd(args)
break
case 'join':
await cmdDeviceJoin(args)
break
case 'list':
await cmdDeviceList(args)
break
case 'status':
await cmdDeviceStatus(args)
break
case 'sync':
await cmdDeviceSync(args)
break
case 'approve':
await cmdDeviceApprove(args)
break
case 'remove':
await cmdDeviceRemove(args)
break
default:
console.error(`❌ Unknown device command: ${subcommand}`)
console.error('')
console.error('Available: bootstrap, add, join, list, status, sync, approve, remove')
console.error('')
process.exit(1)
}
} else {
// Top-level commands
switch (command) {
case 'init':
await cmdInit(args)
break
case 'whoami':
await cmdWhoami()
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()