OverviewFilesCommitsIssuesDocsReleasesPeers

P2P-Hiverelay / packages / core / incentive / payment / mock-provider.js

116 lines · javascript ref:
/**
 * Mock Lightning Payment Provider
 *
 * Same interface as the real LND provider but runs entirely in-memory.
 * Used for testing and development without a Lightning node.
 */

import { EventEmitter } from 'events'
import { randomBytes } from 'crypto'

export class MockProvider extends EventEmitter {
  constructor (opts = {}) {
    super()
    this.connected = false
    this.balance = opts.balance || 1_000_000 // default 1M sats
    this.payments = [] // { invoice, amount, timestamp }
    this.invoices = [] // { bolt11, amount, memo, timestamp }
    this.failNext = false // set true to simulate payment failure
  }

  async connect () {
    this.connected = true
    this.emit('connected')
  }

  async pay (invoice, amount, opts = {}) {
    if (!this.connected) throw new Error('Provider not connected')
    if (this.failNext) {
      this.failNext = false
      throw new Error('MOCK_PAYMENT_FAILED: simulated failure')
    }
    if (amount > this.balance) {
      throw new Error('INSUFFICIENT_BALANCE')
    }

    const asset = opts.asset || 'BTC'
    this.balance -= amount
    const payment = {
      invoice,
      amount,
      asset,
      timestamp: Date.now(),
      preimage: randomBytes(32).toString('hex')
    }
    this.payments.push(payment)
    this.emit('payment-sent', payment)
    return payment
  }

  async createInvoice (amount, memo = '', opts = {}) {
    if (!this.connected) throw new Error('Provider not connected')

    const asset = opts.asset || 'BTC'
    const bolt11 = 'lnbc' + amount + 'mock' + randomBytes(16).toString('hex')
    const rHash = randomBytes(32).toString('hex')
    const invoice = {
      bolt11,
      amount,
      asset,
      memo,
      rHash,
      settled: false,
      timestamp: Date.now()
    }
    this.invoices.push(invoice)
    this.emit('invoice-created', invoice)
    return invoice
  }

  capabilities () {
    return {
      name: 'MockProvider',
      assets: ['BTC', 'USDT'], // mock knows both for test flexibility
      rails: ['lightning'],
      micropayments: true,
      topUpModel: false,
      connected: this.connected
    }
  }

  async getBalance () {
    if (!this.connected) throw new Error('Provider not connected')
    return { confirmed: this.balance, unconfirmed: 0 }
  }

  async getInfo () {
    if (!this.connected) throw new Error('Provider not connected')
    return {
      pubkey: randomBytes(33).toString('hex'),
      alias: 'mock-node',
      channels: { active: 3, inactive: 0, pending: 0 },
      blockHeight: 800000
    }
  }

  async lookupInvoice (rHash) {
    if (!this.connected) throw new Error('Provider not connected')
    // Check if any invoice was marked as settled
    const inv = this.invoices.find(i => i.rHash === rHash)
    return inv ? { settled: !!inv.settled, amount: inv.amount } : null
  }

  /**
   * Mark a mock invoice as settled (for testing settlement detection).
   */
  settleInvoice (rHash) {
    const inv = this.invoices.find(i => i.rHash === rHash)
    if (inv) inv.settled = true
  }

  async disconnect () {
    this.connected = false
    this.emit('disconnected')
  }
}