import BigNumber from "bignumber.js" ; import base58 from "bs58" ; import { assertType , ChainTransaction , DepositChain , ErrorWithCode , InputChainTransaction , OutputType , RenJSError , utils , } from "@renproject/utils" ; import { APIWithPriority , BitcoinAPI , CombinedAPI } from "./APIs/API" ; import { createAddressBuffer } from "./script/index" ; import { BitcoinNetworkConfig , BitcoinNetworkConfigMap , BitcoinNetworkInput , BitcoinReleasePayload , isBitcoinNetworkConfig , } from "./utils/types" ; import { hash160 , validateAddress } from "./utils/utils" ; /**
 * A base Bitcoin chain class that is extended by each Bitcoin chain/fork.
 */ export abstract class BitcoinBaseChain implements DepositChain < { chain : string ; } , BitcoinReleasePayload > { public static chain : string ; public chain : string ; public assets : { [ asset ] : string ; } = { } ; public static configMap : BitcoinNetworkConfigMap = { } ; public configMap : BitcoinNetworkConfigMap = { } ; public network : BitcoinNetworkConfig ; public api = new CombinedAPI ( ) ; public constructor ( { network } ) { const networkConfig = isBitcoinNetworkConfig ( network ) ? network : this . configMap [ network ] ; if ( ! networkConfig ) { if ( typeof network === "string" ) { throw new Error ( `Unknown network ${ network }.` ) ; } else { throw new Error ( `Invalid network config.` ) ; } } this . network = networkConfig ; this . chain = this . network . selector ; for ( const provider of this . network . providers ) { this . withAPI ( provider ) ; } } public withAPI ( api , { priority = 0 } = { } ) { this . api . withAPI ( api , { priority } ) ; return this ; } public getOutputPayload ( asset , _type , toPayload ) { this . _assertAssetIsSupported ( asset ) ; return { to : toPayload . address , toBytes : base58 . decode ( toPayload . address ) , payload : Buffer . from ( [ ] ) , } ; } public addressExplorerLink ( address ) { return this . network . explorer . address ( address ) ; } public transactionExplorerLink ( tx ) { return this . network . explorer . transaction ( this . formattedTransactionHash ( tx ) ) ; } public getBalance = async ( asset , address ) : Promise < BigNumber > => { this . _assertAssetIsSupported ( asset ) ; if ( ! this . validateAddress ( address ) ) { throw new Error ( `Invalid address ${ address }.` ) ; } return new BigNumber ( 0 ) ; } ; public encodeAddress ( bytes ) { return base58 . encode ( bytes ) ; } public validateAddress ( address ) { return validateAddress ( address , this . network . nativeAsset . symbol , this . network . isTestnet ? "testnet" : "prod" ) ; } public formattedTransactionHash ( transaction ) { return utils . fromBase64 ( transaction . txid ) . reverse ( ) . toString ( "hex" ) ; } public validateTransaction ( transaction ) { return ( utils . fromBase64 ( transaction . txid ) . length === 32 && ! new BigNumber ( transaction . txindex ) . isNaN ( ) ) ; } /**
     * See [[LockChain.isLockAsset]].
     */ public isLockAsset ( asset ) { return asset === this . network . nativeAsset . symbol ; } public isDepositAsset ( asset ) { this . _assertAssetIsSupported ( asset ) ; return true ; } private _assertAssetIsSupported ( asset ) { if ( ! this . isLockAsset ( asset ) ) { throw new Error ( `Asset ${ asset } not supported on ${ this . chain }.` ) ; } } /**
     * See [[LockChain.assetDecimals]].
     */ public assetDecimals ( asset ) { this . _assertAssetIsSupported ( asset ) ; return 8 ; } public async watchForDeposits ( asset , fromPayload , address , onInput , _removeInput , listenerCancelled ) { this . _assertAssetIsSupported ( asset ) ; if ( fromPayload . chain !== this . chain ) { throw new Error ( `Invalid payload for chain ${ fromPayload . chain } instead of ${ this . chain }.` ) ; } try { const txs = await utils . tryNTimes ( async ( ) => this . api . fetchTXs ( address ) , 2 ) ; txs . map ( ( tx ) => onInput ( { chain : this . chain , txid : utils . toURLBase64 ( utils . fromHex ( tx . txid ) . reverse ( ) ) , txidFormatted : tx . txid , txindex : tx . txindex , asset , amount : tx . amount , } ) ) ; } catch ( error ) { } while ( true ) { if ( listenerCancelled ( ) ) { return ; } try { const utxos = await this . api . fetchUTXOs ( address ) ; utxos . map ( ( tx ) => onInput ( { chain : this . chain , txid : utils . toURLBase64 ( utils . fromHex ( tx . txid ) . reverse ( ) ) , txidFormatted : tx . txid , txindex : tx . txindex , asset , amount : tx . amount , } ) ) ; } catch ( error ) { console . error ( error ) ; } await utils . sleep ( 15 * utils . sleep . SECONDS ) ; } } /**
     * See [[LockChain.transactionConfidence]].
     */ public transactionConfidence = async ( transaction ) : Promise < BigNumber > => { const { height } = await this . api . fetchUTXO ( this . formattedTransactionHash ( transaction ) , transaction . txindex ) ; if ( ! height ) { return new BigNumber ( 0 ) ; } else { const latestHeight = new BigNumber ( await this . api . fetchHeight ( ) ) ; return latestHeight . minus ( height ) . plus ( 1 ) ; } } ; /**
     * See [[LockChain.getGatewayAddress]].
     */ public createGatewayAddress ( asset , fromPayload , shardPublicKey , gHash ) { this . _assertAssetIsSupported ( asset ) ; if ( fromPayload . chain !== this . chain ) { throw new Error ( `Invalid payload for chain ${ fromPayload . chain } instead of ${ this . chain }.` ) ; } return this . encodeAddress ( createAddressBuffer ( hash160 ( shardPublicKey ) , gHash , this . network . p2shPrefix ) ) ; } category public Address ( address ) { assertType < string > ( "string" , { address } ) ; if ( ! this . validateAddress ( address ) ) { throw ErrorWithCode . from ( new Error ( `Invalid ${ this . chain } address: ${ String ( address ) }` ) , RenJSError . PARAMETER_ERROR ) ; } return { chain : this . chain , address , } ; } category public GatewayAddress ( ) { return { chain : this . chain , } ; } public toSats ( value ) { return new BigNumber ( value ) . shiftedBy ( 8 ) . decimalPlaces ( 0 ) . toFixed ( ) ; } public fromSats ( value ) { return new BigNumber ( value ) . shiftedBy ( - 8 ) . toFixed ( ) ; } }