Address page mostly working.
This commit is contained in:
		
							parent
							
								
									4593a76675
								
							
						
					
					
						commit
						86c4119e1c
					
				@ -30,6 +30,7 @@
 | 
			
		||||
    "@codewarriorr/electrum-client-js": "^0.1.1",
 | 
			
		||||
    "@mempool/bitcoin": "^3.0.2",
 | 
			
		||||
    "axios": "^0.21.0",
 | 
			
		||||
    "crypto-js": "^4.0.0",
 | 
			
		||||
    "express": "^4.17.1",
 | 
			
		||||
    "locutus": "^2.0.12",
 | 
			
		||||
    "mysql2": "^1.6.1",
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,5 @@
 | 
			
		||||
import { MempoolInfo, Transaction, Block, MempoolEntries, MempoolEntry, Address } from '../../interfaces';
 | 
			
		||||
import { MempoolInfo, Transaction, Block, MempoolEntries, MempoolEntry, Address, AddressInformation,
 | 
			
		||||
  ScriptHashBalance, ScriptHashHistory } from '../../interfaces';
 | 
			
		||||
 | 
			
		||||
export interface AbstractBitcoinApi {
 | 
			
		||||
  $getMempoolInfo(): Promise<MempoolInfo>;
 | 
			
		||||
@ -10,6 +11,9 @@ export interface AbstractBitcoinApi {
 | 
			
		||||
  $getBlock(hash: string): Promise<Block>;
 | 
			
		||||
  $getMempoolEntry(txid: string): Promise<MempoolEntry>;
 | 
			
		||||
  $getAddress(address: string): Promise<Address>;
 | 
			
		||||
  $validateAddress(address: string): Promise<AddressInformation>;
 | 
			
		||||
  $getScriptHashBalance(scriptHash: string): Promise<ScriptHashBalance>;
 | 
			
		||||
  $getScriptHashHistory(scriptHash: string): Promise<ScriptHashHistory[]>;
 | 
			
		||||
 | 
			
		||||
  // Custom
 | 
			
		||||
  $getRawMempoolVerbose(): Promise<MempoolEntries>;
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,10 @@
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
import { Transaction, Block, MempoolInfo, RpcBlock, MempoolEntries, MempoolEntry, Address } from '../../interfaces';
 | 
			
		||||
import { Transaction, Block, MempoolInfo, RpcBlock, MempoolEntries, MempoolEntry, Address,
 | 
			
		||||
    AddressInformation, ScriptHashBalance, ScriptHashHistory } from '../../interfaces';
 | 
			
		||||
import * as bitcoin from '@mempool/bitcoin';
 | 
			
		||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
 | 
			
		||||
 | 
			
		||||
class BitcoindApi {
 | 
			
		||||
class BitcoindApi implements AbstractBitcoinApi {
 | 
			
		||||
  bitcoindClient: any;
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
@ -82,6 +84,18 @@ class BitcoindApi {
 | 
			
		||||
  $getAddress(address: string): Promise<Address> {
 | 
			
		||||
    throw new Error('Method not implemented.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $validateAddress(address: string): Promise<AddressInformation> {
 | 
			
		||||
    return this.bitcoindClient.validateAddress(address);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $getScriptHashBalance(scriptHash: string): Promise<ScriptHashBalance> {
 | 
			
		||||
    throw new Error('Method not implemented.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $getScriptHashHistory(scriptHash: string): Promise<ScriptHashHistory[]> {
 | 
			
		||||
    throw new Error('Method not implemented.');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default BitcoindApi;
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,13 @@
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
 | 
			
		||||
import { Transaction, Block, MempoolInfo, RpcBlock, MempoolEntries, MempoolEntry, Address } from '../../interfaces';
 | 
			
		||||
import { Transaction, Block, MempoolInfo, RpcBlock, MempoolEntries, MempoolEntry, Address,
 | 
			
		||||
  AddressInformation, ScriptHashBalance, ScriptHashHistory } from '../../interfaces';
 | 
			
		||||
import * as bitcoin from '@mempool/bitcoin';
 | 
			
		||||
import * as ElectrumClient from '@codewarriorr/electrum-client-js';
 | 
			
		||||
import logger from '../../logger';
 | 
			
		||||
import transactionUtils from '../transaction-utils';
 | 
			
		||||
 | 
			
		||||
import * as sha256 from 'crypto-js/sha256';
 | 
			
		||||
import * as hexEnc from 'crypto-js/enc-hex';
 | 
			
		||||
class BitcoindElectrsApi implements AbstractBitcoinApi {
 | 
			
		||||
  bitcoindClient: any;
 | 
			
		||||
  electrumClient: any;
 | 
			
		||||
@ -117,6 +119,23 @@ class BitcoindElectrsApi implements AbstractBitcoinApi {
 | 
			
		||||
      throw new Error(e);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $validateAddress(address: string): Promise<AddressInformation> {
 | 
			
		||||
    return this.bitcoindClient.validateAddress(address);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $getScriptHashBalance(scriptHash: string): Promise<ScriptHashBalance> {
 | 
			
		||||
    return this.electrumClient.blockchain_scripthash_getBalance(this.encodeScriptHash(scriptHash));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $getScriptHashHistory(scriptHash: string): Promise<ScriptHashHistory[]> {
 | 
			
		||||
    return this.electrumClient.blockchain_scripthash_getHistory(this.encodeScriptHash(scriptHash));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private encodeScriptHash(scriptPubKey: string): string {
 | 
			
		||||
    const addrScripthash = hexEnc.stringify(sha256(hexEnc.parse(scriptPubKey)));
 | 
			
		||||
    return addrScripthash.match(/.{2}/g).reverse().join('');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default BitcoindElectrsApi;
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,7 @@
 | 
			
		||||
import config from '../../config';
 | 
			
		||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
 | 
			
		||||
import { Transaction, Block, MempoolInfo, MempoolEntry, MempoolEntries, Address } from '../../interfaces';
 | 
			
		||||
import { Transaction, Block, MempoolInfo, MempoolEntry, MempoolEntries, Address,
 | 
			
		||||
  AddressInformation, ScriptHashBalance, ScriptHashHistory } from '../../interfaces';
 | 
			
		||||
import axios from 'axios';
 | 
			
		||||
 | 
			
		||||
class ElectrsApi implements AbstractBitcoinApi {
 | 
			
		||||
@ -63,6 +64,19 @@ class ElectrsApi implements AbstractBitcoinApi {
 | 
			
		||||
  $getAddress(address: string): Promise<Address> {
 | 
			
		||||
    throw new Error('Method not implemented.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $getScriptHashBalance(scriptHash: string): Promise<ScriptHashBalance> {
 | 
			
		||||
    throw new Error('Method not implemented.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $getScriptHashHistory(scriptHash: string): Promise<ScriptHashHistory[]> {
 | 
			
		||||
    throw new Error('Method not implemented.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  $validateAddress(address: string): Promise<AddressInformation> {
 | 
			
		||||
    throw new Error('Method not implemented.');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default ElectrsApi;
 | 
			
		||||
 | 
			
		||||
@ -67,17 +67,22 @@ class Blocks {
 | 
			
		||||
      let found = 0;
 | 
			
		||||
 | 
			
		||||
      for (let i = 0; i < txIds.length; i++) {
 | 
			
		||||
        // When using bitcoind, just fetch the coinbase tx for now
 | 
			
		||||
        if ((config.MEMPOOL.BACKEND === 'bitcoind' ||
 | 
			
		||||
            config.MEMPOOL.BACKEND === 'bitcoind-electrs') && i === 0) {
 | 
			
		||||
          const tx = await transactionUtils.getTransactionExtended(txIds[i], true);
 | 
			
		||||
          if (tx) {
 | 
			
		||||
            transactions.push(tx);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        if (mempool[txIds[i]]) {
 | 
			
		||||
          transactions.push(mempool[txIds[i]]);
 | 
			
		||||
          found++;
 | 
			
		||||
        } else {
 | 
			
		||||
          // When using bitcoind, just skip parsing past block tx's for now except for coinbase
 | 
			
		||||
          if (config.MEMPOOL.BACKEND === 'electrs' || i === 0) { //
 | 
			
		||||
            logger.debug(`Fetching block tx ${i} of ${txIds.length}`);
 | 
			
		||||
            const tx = await transactionUtils.getTransactionExtended(txIds[i]);
 | 
			
		||||
            if (tx) {
 | 
			
		||||
              transactions.push(tx);
 | 
			
		||||
            }
 | 
			
		||||
        } else if (config.MEMPOOL.BACKEND === 'electrs') {
 | 
			
		||||
          logger.debug(`Fetching block tx ${i} of ${txIds.length}`);
 | 
			
		||||
          const tx = await transactionUtils.getTransactionExtended(txIds[i]);
 | 
			
		||||
          if (tx) {
 | 
			
		||||
            transactions.push(tx);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@ -34,13 +34,14 @@ class TransactionUtils {
 | 
			
		||||
    return this.extendTransaction(transaction);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public extendTransaction(transaction: Transaction | MempoolEntry): TransactionExtended {
 | 
			
		||||
  public extendTransaction(transaction: Transaction): TransactionExtended {
 | 
			
		||||
    transaction['vsize'] = Math.round(transaction.weight / 4);
 | 
			
		||||
    transaction['feePerVsize'] = Math.max(1, (transaction.fee || 0) / (transaction.weight / 4));
 | 
			
		||||
    if (!transaction.in_active_chain) {
 | 
			
		||||
      transaction['firstSeen'] = Math.round((new Date().getTime() / 1000));
 | 
			
		||||
    }
 | 
			
		||||
    // @ts-ignore
 | 
			
		||||
    return Object.assign({
 | 
			
		||||
      vsize: Math.round(transaction.weight / 4),
 | 
			
		||||
      feePerVsize: Math.max(1, (transaction.fee || 0) / (transaction.weight / 4)),
 | 
			
		||||
      firstSeen: Math.round((new Date().getTime() / 1000)),
 | 
			
		||||
    }, transaction);
 | 
			
		||||
    return transaction;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public stripCoinbaseTransaction(tx: TransactionExtended): TransactionMinerInfo {
 | 
			
		||||
@ -88,7 +89,7 @@ class TransactionUtils {
 | 
			
		||||
          scriptpubkey: vout.scriptPubKey.hex,
 | 
			
		||||
          scriptpubkey_address: vout.scriptPubKey && vout.scriptPubKey.addresses ? vout.scriptPubKey.addresses[0] : null,
 | 
			
		||||
          scriptpubkey_asm: vout.scriptPubKey.asm,
 | 
			
		||||
          scriptpubkey_type: vout.scriptPubKey.type,
 | 
			
		||||
          scriptpubkey_type: this.translateScriptPubKeyType(vout.scriptPubKey.type),
 | 
			
		||||
        };
 | 
			
		||||
      });
 | 
			
		||||
      if (transaction.confirmations) {
 | 
			
		||||
@ -106,6 +107,25 @@ class TransactionUtils {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private translateScriptPubKeyType(outputType: string): string {
 | 
			
		||||
    const map = {
 | 
			
		||||
      'pubkey': 'p2pk',
 | 
			
		||||
      'pubkeyhash': 'p2pkh',
 | 
			
		||||
      'scripthash': 'p2sh',
 | 
			
		||||
      'witness_v0_keyhash': 'v0_p2wpkh',
 | 
			
		||||
      'witness_v0_scripthash': 'v0_p2wsh',
 | 
			
		||||
      'witness_v1_taproot': 'v1_p2tr',
 | 
			
		||||
      'nonstandard': 'nonstandard',
 | 
			
		||||
      'nulldata': 'nulldata'
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if (map[outputType]) {
 | 
			
		||||
      return map[outputType];
 | 
			
		||||
    } else {
 | 
			
		||||
      return '';
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async $appendFeeData(transaction: Transaction): Promise<Transaction> {
 | 
			
		||||
    let mempoolEntry: MempoolEntry;
 | 
			
		||||
    if (!mempool.isInSync() && !this.mempoolEntriesCache) {
 | 
			
		||||
 | 
			
		||||
@ -30,6 +30,9 @@ export interface Transaction {
 | 
			
		||||
  vin: Vin[];
 | 
			
		||||
  vout: Vout[];
 | 
			
		||||
  status: Status;
 | 
			
		||||
 | 
			
		||||
  // bitcoind (temp?)
 | 
			
		||||
  in_active_chain?: boolean;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface TransactionMinerInfo {
 | 
			
		||||
@ -294,3 +297,24 @@ interface RequiredParams {
 | 
			
		||||
  required: boolean;
 | 
			
		||||
  types: ('@string' | '@number' | '@boolean' | string)[];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface AddressInformation {
 | 
			
		||||
  isvalid: boolean;
 | 
			
		||||
  address: string;
 | 
			
		||||
  scriptPubKey: string;
 | 
			
		||||
  isscript: boolean;
 | 
			
		||||
  iswitness: boolean;
 | 
			
		||||
  witness_version?: boolean;
 | 
			
		||||
  witness_program: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ScriptHashBalance {
 | 
			
		||||
  confirmed: number;
 | 
			
		||||
  unconfirmed: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface ScriptHashHistory {
 | 
			
		||||
  height: number;
 | 
			
		||||
  tx_hash: string;
 | 
			
		||||
  fee?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
import config from './config';
 | 
			
		||||
import { Request, Response } from 'express';
 | 
			
		||||
import { json, Request, Response } from 'express';
 | 
			
		||||
import statistics from './api/statistics';
 | 
			
		||||
import feeApi from './api/fee-api';
 | 
			
		||||
import backendInfo from './api/backend-info';
 | 
			
		||||
@ -568,16 +568,85 @@ class Routes {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async getAddress(req: Request, res: Response) {
 | 
			
		||||
    if (config.MEMPOOL.BACKEND === 'bitcoind') {
 | 
			
		||||
      res.status(405).send('Address lookups cannot be used with bitcoind as backend.');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
      const result = await bitcoinApi.$getAddress(req.params.hash);
 | 
			
		||||
      res.json(result);
 | 
			
		||||
      const addressInfo = await bitcoinApi.$validateAddress(req.params.address);
 | 
			
		||||
      if (!addressInfo || !addressInfo.isvalid) {
 | 
			
		||||
        res.json({
 | 
			
		||||
          'address': req.params.address,
 | 
			
		||||
          'chain_stats': {
 | 
			
		||||
            'funded_txo_count': 0,
 | 
			
		||||
            'funded_txo_sum': 0,
 | 
			
		||||
            'spent_txo_count': 0,
 | 
			
		||||
            'spent_txo_sum': 0,
 | 
			
		||||
            'tx_count': 0
 | 
			
		||||
          },
 | 
			
		||||
          'mempool_stats': {
 | 
			
		||||
            'funded_txo_count': 0,
 | 
			
		||||
            'funded_txo_sum': 0,
 | 
			
		||||
            'spent_txo_count': 0,
 | 
			
		||||
            'spent_txo_sum': 0,
 | 
			
		||||
            'tx_count': 0
 | 
			
		||||
          }
 | 
			
		||||
        });
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const balance = await bitcoinApi.$getScriptHashBalance(addressInfo.scriptPubKey);
 | 
			
		||||
      const history = await bitcoinApi.$getScriptHashHistory(addressInfo.scriptPubKey);
 | 
			
		||||
 | 
			
		||||
      const unconfirmed = history.filter((h) => h.fee).length;
 | 
			
		||||
 | 
			
		||||
      res.json({
 | 
			
		||||
        'address': addressInfo.address,
 | 
			
		||||
        'chain_stats': {
 | 
			
		||||
          'funded_txo_count': 0,
 | 
			
		||||
          'funded_txo_sum': balance.confirmed ? balance.confirmed : 0,
 | 
			
		||||
          'spent_txo_count': 0,
 | 
			
		||||
          'spent_txo_sum': balance.confirmed < 0 ? balance.confirmed : 0,
 | 
			
		||||
          'tx_count': history.length - unconfirmed,
 | 
			
		||||
        },
 | 
			
		||||
        'mempool_stats': {
 | 
			
		||||
          'funded_txo_count': 0,
 | 
			
		||||
          'funded_txo_sum': balance.unconfirmed > 0 ? balance.unconfirmed : 0,
 | 
			
		||||
          'spent_txo_count': 0,
 | 
			
		||||
          'spent_txo_sum': balance.unconfirmed < 0 ? -balance.unconfirmed : 0,
 | 
			
		||||
          'tx_count': unconfirmed,
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).send(e.message);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async getAddressTransactions(req: Request, res: Response) {
 | 
			
		||||
    res.status(404).send('Not implemented');
 | 
			
		||||
    if (config.MEMPOOL.BACKEND === 'bitcoind') {
 | 
			
		||||
      res.status(405).send('Address lookups cannot be used with bitcoind as backend.');
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const addressInfo = await bitcoinApi.$validateAddress(req.params.address);
 | 
			
		||||
      if (!addressInfo || !addressInfo.isvalid) {
 | 
			
		||||
        res.json([]);
 | 
			
		||||
      }
 | 
			
		||||
      const history = await bitcoinApi.$getScriptHashHistory(addressInfo.scriptPubKey);
 | 
			
		||||
      const transactions: TransactionExtended[] = [];
 | 
			
		||||
      for (const h of history) {
 | 
			
		||||
        let tx = await transactionUtils.getTransactionExtended(h.tx_hash);
 | 
			
		||||
        if (tx) {
 | 
			
		||||
          tx = await transactionUtils.$addPrevoutsToTransaction(tx);
 | 
			
		||||
          transactions.push(tx);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      res.json(transactions);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      res.status(500).send(e.message);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async getAdressTxChain(req: Request, res: Response) {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user