OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-wallet-desktop / test / mocks / wdk-mock.cjs

232 lines · javascript ref:
// test/mocks/wdk-mock.cjs - WDK Mock Factory for Testing

const crypto = require('hypercore-crypto')
const b4a = require('b4a')

/**
 * Mock WDK class that simulates @tetherto/wdk behavior
 */
class MockWDK {
  constructor(mnemonic, options = {}) {
    this.mnemonic = mnemonic
    this.wallets = {}
    this.options = options
  }

  /**
   * Register wallet manager (chainable)
   * @param {string} chain - Chain name (bitcoin, ethereum)
   * @param {Function} Manager - Wallet manager class
   * @param {Object} config - Configuration
   * @returns {MockWDK} - This instance for chaining
   */
  registerWallet(chain, Manager, config) {
    this.wallets[chain] = {
      Manager,
      config,
      accounts: new Map()
    }
    return this
  }

  /**
   * Get account for chain at index
   * @param {string} chain - Chain name
   * @param {number} accountIndex - BIP44 account index
   * @returns {Promise<MockAccount>}
   */
  async getAccount(chain, accountIndex = 0) {
    const wallet = this.wallets[chain]
    if (!wallet) {
      throw new Error(`Wallet not registered for chain: ${chain}`)
    }

    const key = `${chain}:${accountIndex}`
    if (!wallet.accounts.has(key)) {
      wallet.accounts.set(key, new MockAccount(chain, accountIndex, this.mnemonic))
    }

    return wallet.accounts.get(key)
  }

  /**
   * Generate random seed phrase (12 words)
   * @returns {string} - BIP-39 mnemonic
   */
  static getRandomSeedPhrase() {
    // Generate deterministic test mnemonic from random bytes
    const entropy = crypto.randomBytes(16) // 128 bits for 12 words
    const checksum = entropy[0] % 16 // Simple checksum for testing

    const words = [
      'abandon', 'ability', 'able', 'about', 'above', 'absent',
      'absorb', 'abstract', 'absurd', 'abuse', 'access', 'about'
    ]

    return words.join(' ')
  }
}

/**
 * Mock Account class that simulates WDK account operations
 */
class MockAccount {
  constructor(chain, accountIndex, mnemonic) {
    this.chain = chain
    this.accountIndex = accountIndex
    this.mnemonic = mnemonic

    // Mock balances
    this.balance = BigInt(chain === 'bitcoin' ? 100000000 : 1000000000000000000) // 1 BTC or 1 ETH
    this.tokenBalances = new Map()

    // Default token balances
    this.tokenBalances.set('0x7169D38820dfd117C3FA1f22a697dBA58d90BA06', BigInt(500000000)) // 500 USDT (6 decimals)
    this.tokenBalances.set('0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238', BigInt(250000000)) // 250 USDC (6 decimals)
  }

  /**
   * Get account balance
   * @returns {Promise<BigInt>}
   */
  async getBalance() {
    return this.balance
  }

  /**
   * Get address for account
   * @param {number} addressIndex - BIP44 address index
   * @returns {Promise<string>}
   */
  async getAddress(addressIndex = 0) {
    if (this.chain === 'bitcoin') {
      // Mock Bitcoin testnet address
      return `tb1q${b4a.toString(crypto.randomBytes(32), 'hex').slice(0, 38)}`
    } else if (this.chain === 'ethereum') {
      // Mock Ethereum address
      return `0x${b4a.toString(crypto.randomBytes(20), 'hex')}`
    }
    throw new Error(`Unsupported chain: ${this.chain}`)
  }

  /**
   * Get token balance (ERC-20)
   * @param {string} contractAddress - Token contract address
   * @returns {Promise<BigInt>}
   */
  async getTokenBalance(contractAddress) {
    return this.tokenBalances.get(contractAddress) || BigInt(0)
  }

  /**
   * Send transaction
   * @param {Object} params - Transaction parameters
   * @returns {Promise<Object>} - Transaction result with hash
   */
  async sendTransaction(params) {
    const { to, value } = params

    // Validate balance
    if (value > this.balance) {
      throw new Error('Insufficient balance')
    }

    // Deduct from balance
    this.balance -= value

    // Return mock transaction
    return {
      hash: `0x${b4a.toString(crypto.randomBytes(32), 'hex')}`,
      from: await this.getAddress(),
      to,
      value: value.toString(),
      blockNumber: Math.floor(Math.random() * 1000000),
      timestamp: Date.now()
    }
  }

  /**
   * Quote transaction fee
   * @param {Object} params - Transaction parameters
   * @returns {Promise<Object>} - Fee estimate
   */
  async quoteSendTransaction(params) {
    const { value } = params

    // Mock fee calculation (0.1% of amount or minimum)
    const calculatedFee = value / BigInt(1000)
    const minFee = this.chain === 'bitcoin' ? BigInt(1000) : BigInt(21000) // 1000 sats or 21000 gwei

    const fee = calculatedFee > minFee ? calculatedFee : minFee

    return {
      fee,
      feeRate: this.chain === 'bitcoin' ? '10 sat/vB' : '50 gwei',
      estimatedConfirmation: '10 minutes'
    }
  }

  /**
   * Transfer ERC-20 token
   * @param {Object} params - Transfer parameters
   * @returns {Promise<Object>} - Transfer result
   */
  async transfer(params) {
    const { token, recipient, amount } = params

    // Validate token balance
    const tokenBalance = await this.getTokenBalance(token)
    if (amount > tokenBalance) {
      throw new Error('Insufficient token balance')
    }

    // Deduct from token balance
    this.tokenBalances.set(token, tokenBalance - amount)

    return {
      hash: `0x${b4a.toString(crypto.randomBytes(32), 'hex')}`,
      from: await this.getAddress(),
      to: recipient,
      token,
      amount: amount.toString(),
      blockNumber: Math.floor(Math.random() * 1000000),
      timestamp: Date.now()
    }
  }

  /**
   * Quote token transfer fee
   * @param {Object} params - Transfer parameters
   * @returns {Promise<Object>} - Fee estimate
   */
  async quoteTransfer(params) {
    // ERC-20 transfer costs more gas than ETH transfer
    const fee = BigInt(65000) // ~65k gas units

    return {
      fee,
      feeRate: '50 gwei',
      estimatedConfirmation: '10 minutes'
    }
  }
}

/**
 * Mock WalletManager classes (Bitcoin, EVM)
 */
class MockWalletManagerBtc {
  static default = MockWalletManagerBtc
}

class MockWalletManagerEvm {
  static default = MockWalletManagerEvm
}

module.exports = {
  default: MockWDK,
  MockWDK,
  MockAccount,
  WalletManagerBtc: MockWalletManagerBtc,
  WalletManagerEvm: MockWalletManagerEvm
}