2020-12-28 04:47:22 +07:00
import config from '../../config' ;
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory' ;
import { IBitcoinApi } from './bitcoin-api.interface' ;
import { IEsploraApi } from './esplora-api.interface' ;
import { IElectrumApi } from './electrum-api.interface' ;
import BitcoinApi from './bitcoin-api' ;
import bitcoinBaseApi from './bitcoin-base.api' ;
2020-12-28 20:17:32 +07:00
import mempool from '../mempool' ;
2020-12-29 00:41:02 +07:00
import logger from '../../logger' ;
2020-12-29 14:14:34 +07:00
import * as ElectrumClient from '@mempool/electrum-client' ;
import * as sha256 from 'crypto-js/sha256' ;
import * as hexEnc from 'crypto-js/enc-hex' ;
2021-01-08 21:44:36 +07:00
import loadingIndicators from '../loading-indicators' ;
2020-12-29 00:41:02 +07:00
2020-12-28 04:47:22 +07:00
class BitcoindElectrsApi extends BitcoinApi implements AbstractBitcoinApi {
private electrumClient : any ;
constructor ( ) {
super ( ) ;
2020-12-29 00:41:02 +07:00
const electrumConfig = { client : 'mempool-v2' , version : '1.4' } ;
const electrumPersistencePolicy = { retryPeriod : 10000 , maxRetry : 1000 , callback : null } ;
const electrumCallbacks = {
2021-01-05 03:06:57 +07:00
onConnect : ( client , versionInfo ) = > { logger . info ( ` Connected to Electrum Server at ${ config . ELECTRUM . HOST } : ${ config . ELECTRUM . PORT } ( ${ JSON . stringify ( versionInfo ) } ) ` ) ; } ,
onClose : ( client ) = > { logger . info ( ` Disconnected from Electrum Server at ${ config . ELECTRUM . HOST } : ${ config . ELECTRUM . PORT } ` ) ; } ,
2020-12-29 00:41:02 +07:00
onError : ( err ) = > { logger . err ( ` Electrum error: ${ JSON . stringify ( err ) } ` ) ; } ,
onLog : ( str ) = > { logger . debug ( str ) ; } ,
} ;
2020-12-28 04:47:22 +07:00
this . electrumClient = new ElectrumClient (
2021-01-05 03:06:57 +07:00
config . ELECTRUM . PORT ,
config . ELECTRUM . HOST ,
2021-01-06 22:49:28 +07:00
config . ELECTRUM . TLS_ENABLED ? 'tls' : 'tcp' ,
2020-12-29 00:41:02 +07:00
null ,
electrumCallbacks
2020-12-28 04:47:22 +07:00
) ;
2020-12-29 00:41:02 +07:00
this . electrumClient . initElectrum ( electrumConfig , electrumPersistencePolicy )
. then ( ( ) = > { } )
. catch ( ( err ) = > {
2021-01-05 03:06:57 +07:00
logger . err ( ` Error connecting to Electrum Server at ${ config . ELECTRUM . HOST } : ${ config . ELECTRUM . PORT } ` ) ;
2020-12-29 00:41:02 +07:00
} ) ;
2020-12-28 04:47:22 +07:00
}
async $getRawTransaction ( txId : string , skipConversion = false , addPrevout = false ) : Promise < IEsploraApi.Transaction > {
2021-01-05 03:06:57 +07:00
if ( ! config . ELECTRUM . TX_LOOKUPS ) {
return super . $getRawTransaction ( txId , skipConversion , addPrevout ) ;
}
2020-12-28 20:17:32 +07:00
const txInMempool = mempool . getMempool ( ) [ txId ] ;
if ( txInMempool && addPrevout ) {
return this . $addPrevouts ( txInMempool ) ;
}
2020-12-29 00:41:02 +07:00
const transaction : IBitcoinApi.Transaction = await this . electrumClient . blockchainTransaction_get ( txId , true ) ;
2020-12-28 04:47:22 +07:00
if ( ! transaction ) {
throw new Error ( 'Unable to get transaction: ' + txId ) ;
}
if ( skipConversion ) {
// @ts-ignore
return transaction ;
}
return this . $convertTransaction ( transaction , addPrevout ) ;
}
async $getAddress ( address : string ) : Promise < IEsploraApi.Address > {
const addressInfo = await bitcoinBaseApi . $validateAddress ( address ) ;
if ( ! addressInfo || ! addressInfo . isvalid ) {
return ( {
'address' : 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
}
} ) ;
}
2021-01-05 17:30:53 +07:00
try {
const balance = await this . $getScriptHashBalance ( addressInfo . scriptPubKey ) ;
const history = await this . $getScriptHashHistory ( addressInfo . scriptPubKey ) ;
const unconfirmed = history . filter ( ( h ) = > h . fee ) . length ;
2020-12-28 04:47:22 +07:00
2021-01-05 17:30:53 +07:00
return {
'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 ) {
if ( e === 'failed to get confirmed status' ) {
e = 'The number of transactions on this address exceeds the Electrum server limit' ;
}
throw new Error ( e ) ;
}
2020-12-28 04:47:22 +07:00
}
async $getAddressTransactions ( address : string , lastSeenTxId : string ) : Promise < IEsploraApi.Transaction [ ] > {
const addressInfo = await bitcoinBaseApi . $validateAddress ( address ) ;
if ( ! addressInfo || ! addressInfo . isvalid ) {
return [ ] ;
}
2021-01-05 17:30:53 +07:00
try {
2021-01-08 21:44:36 +07:00
loadingIndicators . setProgress ( 'address-' + address , 0 ) ;
2021-01-05 17:30:53 +07:00
const transactions : IEsploraApi.Transaction [ ] = [ ] ;
const history = await this . $getScriptHashHistory ( addressInfo . scriptPubKey ) ;
history . reverse ( ) ;
let startingIndex = 0 ;
if ( lastSeenTxId ) {
const pos = history . findIndex ( ( historicalTx ) = > historicalTx . tx_hash === lastSeenTxId ) ;
if ( pos ) {
startingIndex = pos + 1 ;
}
2020-12-30 02:27:34 +07:00
}
2021-01-08 21:44:36 +07:00
const endIndex = Math . min ( startingIndex + 10 , history . length ) ;
2020-12-30 02:27:34 +07:00
2021-01-08 21:44:36 +07:00
for ( let i = startingIndex ; i < endIndex ; i ++ ) {
2021-01-05 17:30:53 +07:00
const tx = await this . $getRawTransaction ( history [ i ] . tx_hash , false , true ) ;
2021-01-08 21:44:36 +07:00
transactions . push ( tx ) ;
loadingIndicators . setProgress ( 'address-' + address , ( i + 1 ) / endIndex * 100 ) ;
2020-12-28 04:47:22 +07:00
}
2020-12-30 02:27:34 +07:00
2021-01-05 17:30:53 +07:00
return transactions ;
} catch ( e ) {
2021-01-08 21:44:36 +07:00
loadingIndicators . setProgress ( 'address-' + address , 100 ) ;
2021-01-05 17:30:53 +07:00
if ( e === 'failed to get confirmed status' ) {
e = 'The number of transactions on this address exceeds the Electrum server limit' ;
}
throw new Error ( e ) ;
}
2020-12-28 04:47:22 +07:00
}
private $getScriptHashBalance ( scriptHash : string ) : Promise < IElectrumApi.ScriptHashBalance > {
2020-12-29 00:41:02 +07:00
return this . electrumClient . blockchainScripthash_getBalance ( this . encodeScriptHash ( scriptHash ) ) ;
2020-12-28 04:47:22 +07:00
}
private $getScriptHashHistory ( scriptHash : string ) : Promise < IElectrumApi.ScriptHashHistory [ ] > {
2020-12-29 00:41:02 +07:00
return this . electrumClient . blockchainScripthash_getHistory ( this . encodeScriptHash ( scriptHash ) ) ;
2020-12-28 04:47:22 +07:00
}
private encodeScriptHash ( scriptPubKey : string ) : string {
const addrScripthash = hexEnc . stringify ( sha256 ( hexEnc . parse ( scriptPubKey ) ) ) ;
return addrScripthash . match ( /.{2}/g ) . reverse ( ) . join ( '' ) ;
}
}
export default BitcoindElectrsApi ;