DeFiCh/jellyfish

View on GitHub
apps/whale-api/src/module.indexer/model/script.activity.ts

Summary

Maintainability
A
3 hrs
Test Coverage
import { defid, Indexer, RawBlock } from './_abstract'
import { ScriptActivity, ScriptActivityMapper } from '../../module.model/script.activity'
import { Injectable } from '@nestjs/common'
import { HexEncoder } from '../../module.model/_hex.encoder'
import { TransactionVout } from '../../module.model/transaction.vout'
import { VoutFinder } from './_vout_finder'
import { NotFoundIndexerError } from '../error'
import { checkIfEvmTx } from '../helper'

@Injectable()
export class ScriptActivityIndexer extends Indexer {
  constructor (
    private readonly mapper: ScriptActivityMapper,
    private readonly voutFinder: VoutFinder
  ) {
    super()
  }

  async index (block: RawBlock): Promise<void> {
    for (const txn of block.tx) {
      const isEvmTx = checkIfEvmTx(txn)

      for (const vin of txn.vin) {
        if (vin.coinbase !== undefined || isEvmTx) {
          continue
        }
        const vout = await this.voutFinder.findVout(block, vin.txid, vin.vout)
        if (vout === undefined) {
          throw new NotFoundIndexerError('index', 'TransactionVout', `${vin.txid} - ${vin.vout}`)
        }
        await this.mapper.put(this.mapVin(block, txn, vin, vout))
      }

      for (const vout of txn.vout) {
        if (vout.scriptPubKey.hex.startsWith('6a')) {
          continue
        }
        await this.mapper.put(this.mapVout(block, txn, vout))
      }
    }
  }

  async invalidate (block: RawBlock): Promise<void> {
    for (const txn of block.tx) {
      const isEvmTx = checkIfEvmTx(txn)

      for (const vin of txn.vin) {
        if (vin.coinbase !== undefined || isEvmTx) {
          continue
        }

        await this.mapper.delete(this.mapId(block, 'vin', vin.txid, vin.vout))
      }

      for (const vout of txn.vout) {
        if (vout.scriptPubKey.hex.startsWith('6a')) {
          continue
        }
        await this.mapper.delete(this.mapId(block, 'vout', txn.txid, vout.n))
      }
    }
  }

  mapVin (block: RawBlock, txn: defid.Transaction, vin: defid.Vin, vout: TransactionVout): ScriptActivity {
    return {
      id: this.mapId(block, 'vin', vin.txid, vin.vout),
      hid: HexEncoder.asSHA256(vout.script.hex),
      type: 'vin',
      typeHex: ScriptActivityMapper.typeAsHex('vin'),
      txid: txn.txid,
      block: {
        hash: block.hash,
        height: block.height,
        time: block.time,
        medianTime: block.mediantime
      },
      script: {
        type: vout.script.type,
        hex: vout.script.hex
      },
      vin: {
        txid: vin.txid,
        n: vin.vout
      },
      value: vout.value,
      tokenId: vout.tokenId
    }
  }

  mapVout (block: RawBlock, txn: defid.Transaction, vout: defid.Vout): ScriptActivity {
    return {
      id: this.mapId(block, 'vout', txn.txid, vout.n),
      hid: HexEncoder.asSHA256(vout.scriptPubKey.hex),
      type: 'vout',
      typeHex: ScriptActivityMapper.typeAsHex('vout'),
      txid: txn.txid,
      block: {
        hash: block.hash,
        height: block.height,
        time: block.time,
        medianTime: block.mediantime
      },
      script: {
        type: vout.scriptPubKey.type,
        hex: vout.scriptPubKey.hex
      },
      vout: {
        txid: txn.txid,
        n: vout.n
      },
      value: vout.value.toFixed(8),
      tokenId: vout.tokenId
    }
  }

  mapId (block: RawBlock, type: 'vin' | 'vout', txid: string, n: number): string {
    const height = HexEncoder.encodeHeight(block.height)
    const typeHex = ScriptActivityMapper.typeAsHex(type)
    const index = HexEncoder.encodeVoutIndex(n)
    return `${height}${typeHex}${txid}${index}`
  }
}