import axios from "axios" ; import https from "https" ; import { utils } from "@renproject/utils" ; import { BitcoinAPI , DEFAULT_TIMEOUT , fixUTXO , fixValue , sortUTXOs , UTXO , } from "./API" ; export class Blockbook implements BitcoinAPI { public url : string ; public constructor ( url ) { this . url = url . replace ( /\/$/ , "" ) ; } public async fetchHeight ( ) { return ( await axios . get < { bestHeight : number ; } > ( `${ this . url }` , { timeout : DEFAULT_TIMEOUT , } ) ) . data . bestHeight . toString ( ) ; } public async fetchUTXOs ( address ) { const url = `${ this . url }/utxo/${ address }` ; const response = await axios . get < FetchUTXOResult > ( url , { httpsAgent : new https . Agent ( { rejectUnauthorized : false , } ) , timeout : DEFAULT_TIMEOUT , } ) ; const data = typeof response . data === "string" ? JSON . parse ( response . data ) : response . data ; return ( await Promise . all ( data . map ( ( utxo ) => ( { txid : utxo . txid , amount : utils . isDefined ( utxo . satoshis ) ? utxo . satoshis . toString ( ) : utils . isDefined ( utxo . amount ) ? fixValue ( utxo . amount , 8 ) . toFixed ( ) : undefined , txindex : utxo . vout . toString ( ) , height : utxo . height && utxo . height > 0 ? utxo . height . toString ( ) : null , } ) ) . map ( ( utxo ) => utils . isDefined ( utxo . amount ) ? ( utxo as UTXO ) : this . fetchUTXO ( utxo . txid , utxo . txindex ) ) ) ) . sort ( sortUTXOs ) ; } public async fetchUTXO ( txid , txindex ) { const url = `${ this . url }/tx/${ txid }` ; const tx = ( await axios . get < TxResponse > ( url , { timeout : DEFAULT_TIMEOUT } ) ) . data ; return fixUTXO ( { txid , amount : tx . vout [ parseInt ( txindex , 10 ) ] . value . toString ( ) , txindex , height : tx . blockheight && tx . blockheight > 0 ? tx . blockheight . toString ( ) : null , } , 8 ) ; } public async broadcastTransaction ( txHex ) { const url = `${ this . url }/tx/send` ; const response = await axios . post < { error : string | null ; id : null ; txid : string ; } > ( url , { rawtx : txHex } , { timeout : DEFAULT_TIMEOUT } ) ; if ( response . data . error ) { throw new Error ( response . data . error ) ; } return response . data . txid ; } } export interface ScriptSig { hex : string ; asm : string ; } export interface Vin { txid : string ; vout : number ; sequence : number ; n : number ; scriptSig : ScriptSig ; addr : string ; valueSat : number ; value : number ; doubleSpentTxID ? : unknown ; } export interface ScriptPubKey { hex : string ; asm : string ; addresses : string [ ] ; type : string ; } export interface Vout { value : string ; n : number ; scriptPubKey : ScriptPubKey ; spentTxId : string ; spentIndex : number ; spentHeight : number ; } export interface TxResponse { txid : string ; version : number ; locktime : number ; vin : Vin [ ] ; vout : Vout [ ] ; vjoinsplit : unknown [ ] ; blockhash : string ; blockheight : number ; confirmations : number ; time : number ; blocktime : number ; valueOut : number ; size : number ; valueIn : number ; fees : number ; fOverwintered : boolean ; nVersionGroupId : number ; nExpiryHeight : number ; valueBalance : number ; spendDescs : unknown [ ] ; outputDescs : unknown [ ] ; } export interface FetchTXsResult { pagesTotal : number ; txs : TxResponse [ ] ; } type FetchUTXOResult = ReadonlyArray < { txid : string ; vout : number ; amount : string ; satoshis : number ; height : number ; confirmations : number ; } > ;