OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-lab / tests / integration / test-phase6-wallet.js

226 lines · javascript ref:
#!/usr/bin/env node

/**
 * Phase 6.1 + 6.2 Integration Test
 *
 * Tests:
 * 1. Wallet initialization with separate mnemonics (24-word identity + 12-word wallet)
 * 2. WDK address generation (Bitcoin BIP-84 + Ethereum BIP-44)
 * 3. Address auto-increment via Autobase
 * 4. Multi-device sync (ashatron sees tabertron's addresses)
 * 5. Bidirectional address generation
 * 6. Deterministic derivation verification
 */

import { TmuxOrchestrator } from '../../lib/tmux-orchestrator.js'
import fs from 'fs'
import path from 'path'

const agents = JSON.parse(fs.readFileSync('agents.json', 'utf8')).agents

export default async function testPhase6Wallet(agentConfigs = agents, options = {}) {
  const test = new TmuxOrchestrator('phase6-wallet', agentConfigs)

  try {
    await test.connect()
    await test.createSession()

    await test.log('🧪 Phase 6.1 + 6.2 Integration Test', 'blue')
    await test.log('Testing wallet initialization, address generation, and sync', 'blue')

    // Step 1: Clear existing wallets on both devices
    await test.log('\n📋 Step 1: Clearing existing wallets', 'blue')

    await test.run('tabertron', 'rm', ['-rf', '~/.hyper-wallet'], { allowFailure: true })
    await test.sendKeys(test.paneMap.get('tabertron'), "echo '✅ Wallet cleared'")

    await test.run('ashatron', 'rm', ['-rf', '~/.hyper-wallet'], { allowFailure: true })
    await test.sendKeys(test.paneMap.get('ashatron'), "echo '✅ Wallet cleared'")

    await test.sleep(2000)

    // Step 2: Initialize wallet on tabertron (generates both mnemonics)
    await test.log('\n📋 Step 2: Initialize wallet on tabertron', 'blue')
    await test.sendKeys(test.paneMap.get('tabertron'), 'cd /storage/projects/code/hypercore-kb/hyper-fleeky/hyper-wallet')
    await test.sendKeys(test.paneMap.get('tabertron'), 'bare hyper-wallet-autobase.cjs init')

    await test.log('⏳ Waiting for wallet initialization...', 'yellow')
    await test.sleep(5000)

    // Capture identity mnemonic from tabertron
    await test.log('\n📋 Step 3: Capturing mnemonics from tabertron', 'blue')
    await test.sendKeys(test.paneMap.get('tabertron'), 'bare hyper-wallet-autobase.cjs export-identity')
    await test.sleep(1000)

    const tabertronOutput = await test.capturePane(test.paneMap.get('tabertron'), 100)
    const identityMatch = tabertronOutput.match(/Identity Mnemonic \(24 words\):\s*([a-z\s]+)/i)

    if (!identityMatch) {
      throw new Error('Failed to extract identity mnemonic from tabertron')
    }

    const identityMnemonic = identityMatch[1].trim()
    await test.log(`✅ Captured identity mnemonic (${identityMnemonic.split(' ').length} words)`, 'green')

    // Capture wallet mnemonic
    await test.sendKeys(test.paneMap.get('tabertron'), 'bare hyper-wallet-autobase.cjs export-wallet')
    await test.sleep(1000)

    const tabertronOutput2 = await test.capturePane(test.paneMap.get('tabertron'), 100)
    const walletMatch = tabertronOutput2.match(/Wallet Mnemonic \(12 words\):\s*([a-z\s]+)/i)

    if (!walletMatch) {
      throw new Error('Failed to extract wallet mnemonic from tabertron')
    }

    const walletMnemonic = walletMatch[1].trim()
    await test.log(`✅ Captured wallet mnemonic (${walletMnemonic.split(' ').length} words)`, 'green')

    // Step 4: Generate Bitcoin address on tabertron
    await test.log('\n📋 Step 4: Generate Bitcoin address on tabertron', 'blue')
    await test.sendKeys(test.paneMap.get('tabertron'), 'bare hyper-wallet-autobase.cjs address new bitcoin')
    await test.sleep(3000)

    const btcOutput = await test.capturePane(test.paneMap.get('tabertron'), 50)
    test.assert(btcOutput.includes('tb1q'), 'Bitcoin address should start with tb1q (testnet)')
    await test.log('✅ Bitcoin address generated', 'green')

    // Step 5: Generate Ethereum address on tabertron
    await test.log('\n📋 Step 5: Generate Ethereum address on tabertron', 'blue')
    await test.sendKeys(test.paneMap.get('tabertron'), 'bare hyper-wallet-autobase.cjs address new ethereum')
    await test.sleep(3000)

    const ethOutput = await test.capturePane(test.paneMap.get('tabertron'), 50)
    test.assert(ethOutput.includes('0x'), 'Ethereum address should start with 0x')
    await test.log('✅ Ethereum address generated', 'green')

    // Step 6: List addresses on tabertron
    await test.log('\n📋 Step 6: List addresses on tabertron', 'blue')
    await test.sendKeys(test.paneMap.get('tabertron'), 'bare hyper-wallet-autobase.cjs address list')
    await test.sleep(2000)

    const listOutput = await test.capturePane(test.paneMap.get('tabertron'), 50)
    test.assert(listOutput.includes('Total: 2 addresses'), 'Should have 2 addresses')
    await test.log('✅ Address list verified', 'green')

    // Step 7: Initialize ashatron with SAME mnemonics
    await test.log('\n📋 Step 7: Pairing ashatron with same mnemonics', 'blue')
    await test.log('⚠️  This requires manual mnemonic entry in ashatron pane', 'yellow')
    await test.log(`Identity (24 words): ${identityMnemonic}`, 'yellow')
    await test.log(`Wallet (12 words): ${walletMnemonic}`, 'yellow')

    await test.sendKeys(test.paneMap.get('ashatron'), 'cd /home/fleeky/projects/code/hypercore-kb/hyper-fleeky/hyper-wallet')

    // Write mnemonics to temp files for automated import
    const tmpDir = '/tmp/hyper-lab-mnemonics'
    await test.sendKeys(test.paneMap.get('tabertron'), `mkdir -p ${tmpDir}`)
    await test.sendKeys(test.paneMap.get('tabertron'), `echo "${identityMnemonic}" > ${tmpDir}/identity.txt`)
    await test.sendKeys(test.paneMap.get('tabertron'), `echo "${walletMnemonic}" > ${tmpDir}/wallet.txt`)
    await test.sleep(1000)

    await test.log('💡 Manual step required:', 'yellow')
    await test.log('   In the ashatron pane, run:', 'yellow')
    await test.log('   bare hyper-wallet-autobase.cjs init', 'yellow')
    await test.log('   Then paste the identity mnemonic when prompted', 'yellow')
    await test.log('   Then paste the wallet mnemonic when prompted', 'yellow')
    await test.log('\n   Press Enter in this terminal when done...', 'yellow')

    // Pause for manual mnemonic entry
    await test.log('⏸️  Waiting for manual pairing...', 'yellow')
    await test.sleep(30000) // 30 second pause for manual entry

    // Step 8: Verify sync on ashatron
    await test.log('\n📋 Step 8: Verifying address sync on ashatron', 'blue')
    await test.sendKeys(test.paneMap.get('ashatron'), 'bare hyper-wallet-autobase.cjs address list')
    await test.sleep(5000) // Extra time for Autobase sync

    const ashatronListOutput = await test.capturePane(test.paneMap.get('ashatron'), 50)
    test.assert(ashatronListOutput.includes('Total: 2 addresses'), 'Ashatron should see 2 synced addresses')
    test.assert(ashatronListOutput.includes('tb1q'), 'Should include Bitcoin address')
    test.assert(ashatronListOutput.includes('0x'), 'Should include Ethereum address')
    await test.log('✅ Multi-device sync verified!', 'green')

    // Step 9: Bidirectional sync test - generate address on ashatron
    await test.log('\n📋 Step 9: Testing bidirectional sync (ashatron → tabertron)', 'blue')
    await test.sendKeys(test.paneMap.get('ashatron'), 'bare hyper-wallet-autobase.cjs address new bitcoin')
    await test.sleep(3000)

    // Verify on ashatron first
    await test.sendKeys(test.paneMap.get('ashatron'), 'bare hyper-wallet-autobase.cjs address list')
    await test.sleep(2000)

    const ashatronListOutput2 = await test.capturePane(test.paneMap.get('ashatron'), 50)
    test.assert(ashatronListOutput2.includes('Total: 3 addresses'), 'Ashatron should have 3 addresses')

    // Wait for sync to tabertron
    await test.log('⏳ Waiting for reverse sync...', 'yellow')
    await test.sleep(8000)

    // Verify on tabertron
    await test.sendKeys(test.paneMap.get('tabertron'), 'bare hyper-wallet-autobase.cjs address list')
    await test.sleep(2000)

    const tabertronListOutput = await test.capturePane(test.paneMap.get('tabertron'), 50)
    test.assert(tabertronListOutput.includes('Total: 3 addresses'), 'Tabertron should sync ashatron\'s address')
    await test.log('✅ Bidirectional sync verified!', 'green')

    // Step 10: Verify deterministic derivation
    await test.log('\n📋 Step 10: Verifying deterministic address derivation', 'blue')

    // Extract first Bitcoin address from both devices
    const tabBtcMatch = tabertronListOutput.match(/(tb1q[a-z0-9]+)/i)
    const ashaBtcMatch = ashatronListOutput.match(/(tb1q[a-z0-9]+)/i)

    if (tabBtcMatch && ashaBtcMatch) {
      test.assert(
        tabBtcMatch[1] === ashaBtcMatch[1],
        'Same mnemonic should produce same addresses'
      )
      await test.log(`✅ Deterministic derivation verified: ${tabBtcMatch[1]}`, 'green')
    } else {
      await test.log('⚠️  Could not verify deterministic derivation', 'yellow')
    }

    // Final summary
    await test.log('\n' + '='.repeat(60), 'green')
    await test.log('🎉 Phase 6.1 + 6.2 Integration Test PASSED', 'green')
    await test.log('='.repeat(60), 'green')
    await test.log('\n✅ Verified:', 'green')
    await test.log('  • Separate identity (24-word) and wallet (12-word) mnemonics', 'green')
    await test.log('  • Bitcoin BIP-84 address generation (tb1q...)', 'green')
    await test.log('  • Ethereum BIP-44 address generation (0x...)', 'green')
    await test.log('  • Auto-incrementing address index', 'green')
    await test.log('  • Multi-device pairing with same mnemonics', 'green')
    await test.log('  • Unidirectional sync (tabertron → ashatron)', 'green')
    await test.log('  • Bidirectional sync (ashatron → tabertron)', 'green')
    await test.log('  • Deterministic address derivation', 'green')
    await test.log('\n📊 Final State:', 'blue')
    await test.log('  • Both devices have 3 addresses (2 BTC + 1 ETH)', 'blue')
    await test.log('  • All addresses synced across devices', 'blue')
    await test.log('  • Autobase replication verified', 'blue')

    return test.sessionName

  } catch (error) {
    await test.log(`\n❌ Test failed: ${error.message}`, 'red')
    console.error(error)
    throw error
  } finally {
    await test.cleanup()
  }
}

// Run if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
  testPhase6Wallet()
    .then(sessionName => {
      console.log(`\n✅ Test complete - tmux session: ${sessionName}`)
      console.log(`   Attach: tmux attach -t ${sessionName}`)
      process.exit(0)
    })
    .catch(error => {
      console.error('\n❌ Test failed:', error)
      process.exit(1)
    })
}