#!/usr/bin/env bare
// hyper-wallet-client.cjs - HTTP Client for Hyper Wallet Daemon
// Communicates with wallet-server.cjs daemon via HTTP API
require('bare-node-runtime/global')
const os = require('bare-os')
const DAEMON_URL = process.env.WALLET_DAEMON || 'http://localhost:4004'
// Helper to check if daemon is running
async function checkDaemon() {
try {
const res = await fetch(`${DAEMON_URL}/api/status`)
if (!res.ok) throw new Error('Daemon not responding')
return true
} catch (err) {
return false
}
}
// Helper to make API calls
async function apiCall(endpoint, options = {}) {
const url = `${DAEMON_URL}${endpoint}`
try {
const res = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
})
const data = await res.json()
if (data.error) {
throw new Error(data.error)
}
return data
} catch (err) {
if (err.code === 'ECONNREFUSED') {
console.error('❌ Error: Wallet daemon not running')
console.error('')
console.error('Start the daemon with:')
console.error(' bare wallet-server.cjs')
console.error('')
process.exit(1)
}
throw err
}
}
// Commands
async function cmdStatus() {
const data = await apiCall('/api/status')
console.log('💰 Hyper Wallet Status')
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('')
console.log(`Initialized: ${data.initialized ? '✅' : '❌'}`)
console.log(`Has Identity: ${data.hasIdentity ? '✅' : '❌'}`)
console.log(`Has Wallet: ${data.hasWallet ? '✅' : '❌'}`)
console.log(`Network: ${data.network}`)
console.log(`Version: ${data.version}`)
console.log('')
}
async function cmdBalance(chain) {
if (!chain) {
console.error('Usage: bare hyper-wallet-client.cjs balance <chain>')
console.error('')
console.error('Examples:')
console.error(' bare hyper-wallet-client.cjs balance testnet-bitcoin')
console.error(' bare hyper-wallet-client.cjs balance testnet-ethereum')
console.error('')
process.exit(1)
}
const data = await apiCall(`/api/balance/${chain}`)
console.log(`💰 Balance: ${chain}`)
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('')
console.log(`Confirmed: ${data.confirmed}`)
console.log(`Unconfirmed: ${data.unconfirmed}`)
console.log(`Total: ${data.total}`)
console.log('')
}
async function cmdAddress(subcommand, chain) {
if (subcommand === 'current') {
if (!chain) {
console.error('Usage: bare hyper-wallet-client.cjs address current <chain>')
console.error('')
console.error('Examples:')
console.error(' bare hyper-wallet-client.cjs address current testnet-bitcoin')
console.error(' bare hyper-wallet-client.cjs address current testnet-ethereum')
console.error('')
process.exit(1)
}
const data = await apiCall(`/api/address/${chain}`)
console.log(`📍 Current Address: ${chain}`)
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('')
console.log(data.address)
console.log('')
} else {
console.error('Usage: bare hyper-wallet-client.cjs address <subcommand>')
console.error('')
console.error('Subcommands:')
console.error(' current <chain> - Show current receiving address')
console.error('')
process.exit(1)
}
}
async function cmdTransactions(chain = 'all') {
const data = await apiCall(`/api/transactions/${chain}`)
console.log(`📜 Transactions: ${chain}`)
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('')
console.log(`Total: ${data.count}`)
console.log('')
if (data.transactions.length === 0) {
console.log('No transactions found.')
console.log('')
return
}
for (const tx of data.transactions) {
const date = new Date(tx.timestamp).toLocaleString()
const type = tx.type === 'send' ? '📤' : '📥'
console.log(`${type} ${tx.type.toUpperCase()}`)
console.log(` Chain: ${tx.chain}`)
console.log(` Amount: ${tx.amount}`)
console.log(` Status: ${tx.status}`)
console.log(` Date: ${date}`)
if (tx.txid) {
console.log(` TxID: ${tx.txid.slice(0, 16)}...`)
}
console.log('')
}
}
async function cmdDeviceList() {
const data = await apiCall('/api/device/list')
console.log('📱 Devices')
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('')
console.log(`Total: ${data.devices.length}`)
console.log('')
if (data.devices.length === 0) {
console.log('No devices found.')
console.log('')
return
}
for (const device of data.devices) {
const badge = device.isCurrentDevice ? ' (current)' : ''
const date = new Date(device.addedAt).toLocaleString()
console.log(`${device.name}${badge}`)
console.log(` Identity: ${device.identityKeyShort || 'N/A'}`)
console.log(` Writer: ${device.writerKeyShort}`)
console.log(` Added: ${date}`)
console.log('')
}
}
async function cmdDeviceAdd() {
console.log('📱 Adding New Device')
console.log('')
// Start device add
const data = await apiCall('/api/device/add', { method: 'POST' })
console.log('✅ Invite created!')
console.log('')
console.log('Share this invite code with your other device:')
console.log('')
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log(data.inviteCode)
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('')
console.log('On the other device, run:')
console.log(`bare hyper-wallet-client.cjs device join "<invite>" "Device Name"`)
console.log('')
console.log('📡 Waiting for device to connect...')
console.log(' (Press Ctrl+C to cancel)')
console.log('')
// Poll for status
const sessionId = data.sessionId
let completed = false
const interval = setInterval(async () => {
try {
const status = await apiCall(`/api/device/add/${sessionId}`)
if (status.status === 'completed') {
clearInterval(interval)
completed = true
console.log('')
console.log('✅ Device added successfully!')
console.log(` Name: ${status.deviceName}`)
console.log(` Writer: ${status.writerKey.slice(0, 16)}...`)
console.log('')
process.exit(0)
} else if (status.status === 'error') {
clearInterval(interval)
console.log('')
console.log(`❌ Error: ${status.error}`)
console.log('')
process.exit(1)
}
} catch (err) {
clearInterval(interval)
console.error('Error polling status:', err.message)
process.exit(1)
}
}, 2000)
// Handle Ctrl+C
process.on('SIGINT', () => {
clearInterval(interval)
console.log('')
console.log('Cancelled.')
console.log('')
process.exit(0)
})
}
async function cmdDeviceJoin(inviteCode, deviceName) {
if (!inviteCode || !deviceName) {
console.error('Usage: bare hyper-wallet-client.cjs device join <invite> "Device Name"')
console.error('')
process.exit(1)
}
console.log('🔗 Joining Wallet')
console.log('')
console.log(`Device Name: ${deviceName}`)
console.log('')
const data = await apiCall('/api/device/join', {
method: 'POST',
body: JSON.stringify({ inviteCode, deviceName })
})
console.log('✅ Joined wallet successfully!')
console.log('')
console.log(`Name: ${data.device.name}`)
console.log(`Writer: ${data.device.writerKey}`)
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('')
}
async function cmdWhoAmI() {
const data = await apiCall('/api/whoami')
console.log('👤 Device Identity')
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('')
console.log(`Public Key: ${data.publicKey}`)
console.log(`Hostname: ${data.hostname}`)
console.log(`Platform: ${data.platform || os.platform()}`)
console.log('')
}
async function cmdHelp() {
console.log('💰 Hyper Wallet Client (HTTP Mode)')
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━')
console.log('')
console.log('Connects to wallet daemon at:', DAEMON_URL)
console.log('')
console.log('Usage: bare hyper-wallet-client.cjs <command> [options]')
console.log('')
console.log('Commands:')
console.log('')
console.log(' status - Show wallet status')
console.log(' whoami - Show device identity')
console.log(' balance <chain> - Show balance for chain')
console.log(' address current <chain> - Show current receiving address')
console.log(' transactions [chain] - List transactions (default: all)')
console.log('')
console.log(' device list - List all devices')
console.log(' device add - Create invite for new device')
console.log(' device join <invite> "name" - Join wallet using invite')
console.log('')
console.log('Environment:')
console.log(' WALLET_DAEMON - Override daemon URL (default: http://localhost:4004)')
console.log('')
console.log('Examples:')
console.log(' bare hyper-wallet-client.cjs status')
console.log(' bare hyper-wallet-client.cjs balance testnet-bitcoin')
console.log(' bare hyper-wallet-client.cjs device list')
console.log(' bare hyper-wallet-client.cjs device add')
console.log('')
console.log('Start daemon:')
console.log(' bare wallet-server.cjs')
console.log('')
}
// Main
async function main() {
const args = process.argv.slice(1)
const command = args[1]
// Check daemon first (except for help)
if (command !== 'help' && command !== '--help') {
const running = await checkDaemon()
if (!running) {
console.error('❌ Error: Wallet daemon not running')
console.error('')
console.error('Start the daemon with:')
console.error(' bare wallet-server.cjs')
console.error('')
console.error('Or set WALLET_DAEMON environment variable:')
console.error(' WALLET_DAEMON=http://other-host:4004 bare hyper-wallet-client.cjs ...')
console.error('')
process.exit(1)
}
}
try {
if (!command || command === 'help' || command === '--help') {
await cmdHelp()
} else if (command === 'status') {
await cmdStatus()
} else if (command === 'whoami') {
await cmdWhoAmI()
} else if (command === 'balance') {
await cmdBalance(args[2])
} else if (command === 'address') {
await cmdAddress(args[2], args[3])
} else if (command === 'transactions') {
await cmdTransactions(args[2])
} else if (command === 'device') {
const subcmd = args[2]
if (subcmd === 'list') {
await cmdDeviceList()
} else if (subcmd === 'add') {
await cmdDeviceAdd()
} else if (subcmd === 'join') {
await cmdDeviceJoin(args[3], args[4])
} else {
console.error('Unknown device subcommand:', subcmd)
console.error('')
console.error('Available: list, add, join')
console.error('')
process.exit(1)
}
} else {
console.error('Unknown command:', command)
console.error('')
console.error('Run: bare hyper-wallet-client.cjs help')
console.error('')
process.exit(1)
}
} catch (err) {
console.error('Error:', err.message)
console.error('')
process.exit(1)
}
}
main()