2020-12-28 04:47:22 +07:00
import config from '../../config' ;
2021-01-30 16:25:22 +07:00
import axios , { AxiosRequestConfig } from 'axios' ;
2023-02-07 20:56:33 -06:00
import http from 'http' ;
2020-12-28 04:47:22 +07:00
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory' ;
import { IEsploraApi } from './esplora-api.interface' ;
2023-05-03 10:11:44 +04:00
import logger from '../../logger' ;
2020-12-28 04:47:22 +07:00
2023-02-07 20:56:33 -06:00
const axiosConnection = axios . create ( {
2023-03-09 03:36:14 -06:00
httpAgent : new http . Agent ( { keepAlive : true , } )
2023-02-07 20:56:33 -06:00
} ) ;
2020-12-28 04:47:22 +07:00
class ElectrsApi implements AbstractBitcoinApi {
2023-05-03 10:11:44 +04:00
private axiosConfigWithUnixSocket : AxiosRequestConfig = config . ESPLORA . UNIX_SOCKET_PATH ? {
2023-03-09 03:36:14 -06:00
socketPath : config.ESPLORA.UNIX_SOCKET_PATH ,
timeout : 10000 ,
} : {
2021-01-30 16:25:22 +07:00
timeout : 10000 ,
} ;
2023-05-03 10:11:44 +04:00
private axiosConfigTcpSocketOnly : AxiosRequestConfig = {
timeout : 10000 ,
} ;
unixSocketRetryTimeout ;
activeAxiosConfig ;
constructor ( ) {
this . activeAxiosConfig = this . axiosConfigWithUnixSocket ;
}
fallbackToTcpSocket() {
if ( ! this . unixSocketRetryTimeout ) {
logger . err ( ` Unable to connect to esplora unix socket. Falling back to tcp socket. Retrying unix socket in ${ config . ESPLORA . RETRY_UNIX_SOCKET_AFTER / 1000 } seconds ` ) ;
// Retry the unix socket after a few seconds
this . unixSocketRetryTimeout = setTimeout ( ( ) = > {
logger . info ( ` Retrying to use unix socket for esplora now (applied for the next query) ` ) ;
this . activeAxiosConfig = this . axiosConfigWithUnixSocket ;
this . unixSocketRetryTimeout = undefined ;
} , config . ESPLORA . RETRY_UNIX_SOCKET_AFTER ) ;
}
2023-04-05 16:27:13 +09:00
2023-05-03 10:11:44 +04:00
// Use the TCP socket (reach a different esplora instance through nginx)
this . activeAxiosConfig = this . axiosConfigTcpSocketOnly ;
}
$queryWrapper < T > ( url , responseType = 'json' ) : Promise < T > {
return axiosConnection . get < T > ( url , { . . . this . activeAxiosConfig , responseType : responseType } )
. then ( ( response ) = > response . data )
. catch ( ( e ) = > {
if ( e ? . code === 'ECONNREFUSED' ) {
this . fallbackToTcpSocket ( ) ;
// Retry immediately
return axiosConnection . get < T > ( url , this . activeAxiosConfig )
. then ( ( response ) = > response . data )
. catch ( ( e ) = > {
logger . warn ( ` Cannot query esplora through the unix socket nor the tcp socket. Exception ${ e } ` ) ;
throw e ;
} ) ;
} else {
throw e ;
}
} ) ;
}
2020-12-28 04:47:22 +07:00
$getRawMempool ( ) : Promise < IEsploraApi.Transaction [ 'txid' ] [ ] > {
2023-05-03 10:11:44 +04:00
return this . $queryWrapper < IEsploraApi.Transaction [ 'txid' ] [ ] > ( config . ESPLORA . REST_API_URL + '/mempool/txids' ) ;
2020-12-28 04:47:22 +07:00
}
$getRawTransaction ( txId : string ) : Promise < IEsploraApi.Transaction > {
2023-05-03 10:11:44 +04:00
return this . $queryWrapper < IEsploraApi.Transaction > ( config . ESPLORA . REST_API_URL + '/tx/' + txId ) ;
2020-12-28 04:47:22 +07:00
}
2022-11-22 21:45:05 +09:00
$getTransactionHex ( txId : string ) : Promise < string > {
2023-05-03 10:11:44 +04:00
return this . $queryWrapper < string > ( config . ESPLORA . REST_API_URL + '/tx/' + txId + '/hex' ) ;
2022-11-22 21:45:05 +09:00
}
2020-12-28 04:47:22 +07:00
$getBlockHeightTip ( ) : Promise < number > {
2023-05-03 10:11:44 +04:00
return this . $queryWrapper < number > ( config . ESPLORA . REST_API_URL + '/blocks/tip/height' ) ;
2020-12-28 04:47:22 +07:00
}
2022-06-22 13:15:44 +02:00
$getBlockHashTip ( ) : Promise < string > {
2023-05-03 10:11:44 +04:00
return this . $queryWrapper < string > ( config . ESPLORA . REST_API_URL + '/blocks/tip/hash' ) ;
2022-06-22 13:15:44 +02:00
}
2020-12-28 04:47:22 +07:00
$getTxIdsForBlock ( hash : string ) : Promise < string [ ] > {
2023-05-03 10:11:44 +04:00
return this . $queryWrapper < string [ ] > ( config . ESPLORA . REST_API_URL + '/block/' + hash + '/txids' ) ;
2020-12-28 04:47:22 +07:00
}
$getBlockHash ( height : number ) : Promise < string > {
2023-05-03 10:11:44 +04:00
return this . $queryWrapper < string > ( config . ESPLORA . REST_API_URL + '/block-height/' + height ) ;
2020-12-28 04:47:22 +07:00
}
2021-07-19 04:56:16 +05:30
$getBlockHeader ( hash : string ) : Promise < string > {
2023-05-03 10:11:44 +04:00
return this . $queryWrapper < string > ( config . ESPLORA . REST_API_URL + '/block/' + hash + '/header' ) ;
2021-07-19 04:56:16 +05:30
}
2020-12-28 04:47:22 +07:00
$getBlock ( hash : string ) : Promise < IEsploraApi.Block > {
2023-05-03 10:11:44 +04:00
return this . $queryWrapper < IEsploraApi.Block > ( config . ESPLORA . REST_API_URL + '/block/' + hash ) ;
2020-12-28 04:47:22 +07:00
}
2022-11-29 11:37:51 +09:00
$getRawBlock ( hash : string ) : Promise < Buffer > {
2023-05-03 10:11:44 +04:00
return this . $queryWrapper < any > ( config . ESPLORA . REST_API_URL + '/block/' + hash + "/raw" , 'arraybuffer' )
2022-11-29 11:37:51 +09:00
. then ( ( response ) = > { return Buffer . from ( response . data ) ; } ) ;
2022-07-25 14:54:00 -03:00
}
2020-12-28 04:47:22 +07:00
$getAddress ( address : string ) : Promise < IEsploraApi.Address > {
throw new Error ( 'Method getAddress not implemented.' ) ;
}
$getAddressTransactions ( address : string , txId? : string ) : Promise < IEsploraApi.Transaction [ ] > {
2023-07-22 17:51:45 +09:00
throw new Error ( 'Method getAddressTransactions not implemented.' ) ;
}
$getScriptHash ( scripthash : string ) : Promise < IEsploraApi.ScriptHash > {
2023-07-23 13:55:27 +09:00
throw new Error ( 'Method getScriptHash not implemented.' ) ;
2023-07-22 17:51:45 +09:00
}
$getScriptHashTransactions ( scripthash : string , txId? : string ) : Promise < IEsploraApi.Transaction [ ] > {
2023-07-23 13:55:27 +09:00
throw new Error ( 'Method getScriptHashTransactions not implemented.' ) ;
2020-12-28 04:47:22 +07:00
}
2021-01-11 01:51:57 +07:00
$getAddressPrefix ( prefix : string ) : string [ ] {
throw new Error ( 'Method not implemented.' ) ;
}
2021-09-26 22:18:44 +04:00
$sendRawTransaction ( rawTransaction : string ) : Promise < string > {
throw new Error ( 'Method not implemented.' ) ;
}
2022-01-15 22:09:04 +04:00
2022-07-06 11:58:06 +02:00
$getOutspend ( txId : string , vout : number ) : Promise < IEsploraApi.Outspend > {
2023-05-03 10:11:44 +04:00
return this . $queryWrapper < IEsploraApi.Outspend > ( config . ESPLORA . REST_API_URL + '/tx/' + txId + '/outspend/' + vout ) ;
2022-07-06 11:58:06 +02:00
}
2022-06-22 23:34:44 +02:00
$getOutspends ( txId : string ) : Promise < IEsploraApi.Outspend [ ] > {
2023-05-03 10:11:44 +04:00
return this . $queryWrapper < IEsploraApi.Outspend [ ] > ( config . ESPLORA . REST_API_URL + '/tx/' + txId + '/outspends' ) ;
2022-06-22 23:34:44 +02:00
}
async $getBatchedOutspends ( txId : string [ ] ) : Promise < IEsploraApi.Outspend [ ] [ ] > {
const outspends : IEsploraApi.Outspend [ ] [ ] = [ ] ;
for ( const tx of txId ) {
const outspend = await this . $getOutspends ( tx ) ;
outspends . push ( outspend ) ;
}
return outspends ;
2022-01-15 22:09:04 +04:00
}
2020-12-28 04:47:22 +07:00
}
export default ElectrsApi ;