#!/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()