import Big , { BigSource } from "big.js" ; import * as crypto from "../crypto" ; import LedgerApp , { PublicKey , SignedSignature } from "../ledger/ledger-app" ; import Transaction from "../tx" ; import { AminoPrefix , Coin , ListMiniMsg } from "../types/" ; import HttpRequest from "../utils/request" ; import { checkNumber } from "../utils/validateHelper" ; import Gov from "./gov" ; import Swap from "./swap" ; import TokenManagement , { validateMiniTokenSymbol } from "./token" ; import { Bridge } from "./bridge" ; import { Stake } from "./stake" ; const BASENUMBER = Math . pow ( 10 , 8 ) ; export const api = { broadcast : "/api/v1/broadcast" , nodeInfo : "/api/v1/node-info" , getAccount : "/api/v1/account" , getMarkets : "/api/v1/markets" , getSwaps : "/api/v1/atomic-swaps" , getOpenOrders : "/api/v1/orders/open" , getDepth : "/api/v1/depth" , getTransactions : "/api/v1/transactions" , getTxs : "/bc/api/v1/txs" , getTx : "/api/v1/tx" , } ; export const NETWORK_PREFIX_MAPPING = { testnet : "tbnb" , mainnet : "bnb" , } as const ; export type Transfer = { to : string ; coins : Coin [ ] ; } ; param { Transaction } tx param { Object } signMsg return { Transaction } export const DefaultSigningDelegate = async function ( this , tx , signMsg ? ) : Promise < Transaction > { const privateKey = this . getPrivateKey ( ) ; if ( ! privateKey ) { return Promise . reject ( "Private key has to be set before signing a transaction" ) ; } return tx . sign ( privateKey , signMsg ) ; } ; param { Transaction } signedTx export const DefaultBroadcastDelegate = async function ( this , signedTx ) { return this . sendTransaction ( signedTx , true ) ; } ; param { LedgerApp } ledgerApp param { preSignCb } function param { postSignCb } function param { errCb } function return { function } export const LedgerSigningDelegate = ( ledgerApp , preSignCb , postSignCb , errCb , hdPath ) : typeof DefaultSigningDelegate => async function ( tx , signMsg ) { const signBytes = tx . getSignBytes ( signMsg ) ; preSignCb && preSignCb ( signBytes ) ; let pubKeyResp , sigResp ; try { pubKeyResp = await ledgerApp . getPublicKey ( hdPath ) ; sigResp = await ledgerApp . sign ( signBytes , hdPath ) ; postSignCb && postSignCb ( pubKeyResp , sigResp ) ; } catch ( err ) { console . warn ( "LedgerSigningDelegate error" , err ) ; errCb && errCb ( err ) ; } if ( sigResp ! && sigResp ! . signature ) { const pubKey = crypto . getPublicKey ( pubKeyResp ! . pk ! . toString ( "hex" ) ) ; return tx . addSignature ( pubKey , sigResp ! . signature ) ; } return tx ; } ; param { Array } outputs const checkOutputs = ( outputs ) => { outputs . forEach ( ( transfer ) => { const coins = transfer . coins || [ ] ; coins . forEach ( ( coin ) => { checkNumber ( coin . amount ) ; if ( ! coin . denom ) { throw new Error ( "invalid denmon" ) ; } } ) ; } ) ; } ; param { Array } inputs param { Array } coins const calInputCoins = ( inputs , coins ) => { coins . forEach ( ( coin ) => { const existCoin = inputs [ 0 ] . coins . find ( ( c ) => c . denom === coin . denom ) ; if ( existCoin ) { const existAmount = new Big ( existCoin . amount ) ; existCoin . amount = Number ( existAmount . plus ( coin . amount ) . toString ( ) ) ; } else { inputs [ 0 ] . coins . push ( { ... coin } ) ; } } ) ; } ; /**
 * The Binance Chain client.
 */ export class BncClient { public _httpClient : HttpRequest ; public _signingDelegate : typeof DefaultSigningDelegate ; public _broadcastDelegate : typeof DefaultBroadcastDelegate ; public _useAsyncBroadcast : boolean ; public _source : number ; public tokens : TokenManagement ; public swap : Swap ; public gov : Gov ; public bridge : Bridge ; public stake : Stake ; public chainId ? : string | null ; public addressPrefix : typeof NETWORK_PREFIX_MAPPING [ keyof typeof NETWORK_PREFIX_MAPPING ] = "tbnb" ; public network : keyof typeof NETWORK_PREFIX_MAPPING = "testnet" ; public address ? : string ; public _setPkPromise ? : ReturnType < HttpRequest [ "request" ] > ; public account_number ? : string | number ; private _privateKey : string | null = null ; param { String } server param { Boolean } useAsyncBroadcast param { Number } source constructor ( server , useAsyncBroadcast = false , source = 0 ) { if ( ! server ) { throw new Error ( "Binance chain server should not be null" ) ; } this . _httpClient = new HttpRequest ( server ) ; this . _signingDelegate = DefaultSigningDelegate ; this . _broadcastDelegate = DefaultBroadcastDelegate ; this . _useAsyncBroadcast = useAsyncBroadcast ; this . _source = source ; this . tokens = new TokenManagement ( this ) ; this . swap = new Swap ( this ) ; this . gov = new Gov ( this ) ; this . bridge = new Bridge ( this ) ; this . stake = new Stake ( this ) ; } return { Promise } async initChain ( ) { if ( ! this . chainId ) { const data = await this . _httpClient . request ( "get" , api . nodeInfo ) ; this . chainId = data . result . node_info && data . result . node_info . network ; } return this ; } param { String } network chooseNetwork ( network ) { this . addressPrefix = NETWORK_PREFIX_MAPPING [ network ] || "tbnb" ; this . network = NETWORK_PREFIX_MAPPING [ network ] ? network : "testnet" ; } param { string } privateKey param { boolean } localOnly return { Promise } async setPrivateKey ( privateKey , localOnly = false ) { if ( privateKey !== this . _privateKey ) { const address = crypto . getAddressFromPrivateKey ( privateKey , this . addressPrefix ) ; if ( ! address ) throw new Error ( `address is falsy: ${ address }. invalid private key?` ) ; this . _privateKey = privateKey ; this . address = address ; if ( ! localOnly ) { try { const promise = ( this . _setPkPromise = this . _httpClient . request ( "get" , `${ api . getAccount }/${ address }` ) ) ; const data = await promise ; this . account_number = data . result . account_number ; } catch ( e ) { throw new Error ( `unable to query the address on the blockchain. try sending it some funds first: ${ address }` ) ; } } } return this ; } return { BncClient } removePrivateKey ( ) { this . _privateKey = null ; return this ; } return { string | null } getPrivateKey ( ) { return this . _privateKey ; } param { number } accountNumber setAccountNumber ( accountNumber ) { this . account_number = accountNumber ; } param { Boolean } useAsyncBroadcast return { BncClient } useAsyncBroadcast ( useAsyncBroadcast = true ) { this . _useAsyncBroadcast = useAsyncBroadcast ; return this ; } param { function } delegate return { BncClient } setSigningDelegate ( delegate ) { if ( typeof delegate !== "function" ) throw new Error ( "signing delegate must be a function" ) ; this . _signingDelegate = delegate ; return this ; } param { function } delegate return { BncClient } setBroadcastDelegate ( delegate ) { if ( typeof delegate !== "function" ) throw new Error ( "broadcast delegate must be a function" ) ; this . _broadcastDelegate = delegate ; return this ; } return { BncClient } useDefaultSigningDelegate ( ) { this . _signingDelegate = DefaultSigningDelegate ; return this ; } return { BncClient } useDefaultBroadcastDelegate ( ) { this . _broadcastDelegate = DefaultBroadcastDelegate ; return this ; } param { function } ledgerApp param { function } preSignCb param { function } postSignCb param { function } errCb return { BncClient } useLedgerSigningDelegate ( ... args ) { this . _signingDelegate = LedgerSigningDelegate ( ... args ) ; return this ; } param { String } fromAddress param { String } toAddress param { Number } amount param { String } asset param { String } memo param { Number } sequence return { Promise } async transfer ( fromAddress , toAddress , amount , asset , memo = "" , sequence = null ) { const accCode = crypto . decodeAddress ( fromAddress ) ; const toAccCode = crypto . decodeAddress ( toAddress ) ; amount = new Big ( amount ) ; amount = Number ( amount . mul ( BASENUMBER ) . toString ( ) ) ; checkNumber ( amount , "amount" ) ; const coin = { denom : asset , amount : amount , } ; const msg = { inputs : [ { address : accCode , coins : [ coin ] , } , ] , outputs : [ { address : toAccCode , coins : [ coin ] , } , ] , aminoPrefix : AminoPrefix . MsgSend , } ; const signMsg = { inputs : [ { address : fromAddress , coins : [ { amount : amount , denom : asset , } , ] , } , ] , outputs : [ { address : toAddress , coins : [ { amount : amount , denom : asset , } , ] , } , ] , } ; const signedTx = await this . _prepareTransaction ( msg , signMsg , fromAddress , sequence , memo ) ; return this . _broadcastDelegate ( signedTx ) ; } param { String } fromAddress param { Array } outputs example param { String } memo param { Number } sequence return { Promise } async multiSend ( fromAddress , outputs , memo = "" , sequence = null ) { if ( ! fromAddress ) { throw new Error ( "fromAddress should not be falsy" ) ; } if ( ! Array . isArray ( outputs ) ) { throw new Error ( "outputs should be array" ) ; } checkOutputs ( outputs ) ; outputs . forEach ( ( item ) => { item . coins = item . coins . sort ( ( a , b ) => a . denom . localeCompare ( b . denom ) ) ; item . coins . forEach ( ( coin ) => { const amount = new Big ( coin . amount ) ; coin . amount = Number ( amount . mul ( BASENUMBER ) . toString ( ) ) ; } ) ; } ) ; type AddressBufferCoins = { address : Buffer ; coins : Coin [ ] ; } ; type AddressStringCoins = { address : string ; coins : Coin [ ] ; } ; const fromAddrCode = crypto . decodeAddress ( fromAddress ) ; const inputs = [ { address : fromAddrCode , coins : [ ] } ] ; const transfers = [ ] ; outputs . forEach ( ( item ) => { const toAddcCode = crypto . decodeAddress ( item . to ) ; calInputCoins ( inputs , item . coins ) ; transfers . push ( { address : toAddcCode , coins : item . coins } ) ; } ) ; const msg = { inputs , outputs : transfers , aminoPrefix : AminoPrefix . MsgSend , } ; const signInputs = [ { address : fromAddress , coins : [ ] } ] ; const signOutputs = [ ] ; outputs . forEach ( ( item , index ) => { signOutputs . push ( { address : item . to , coins : [ ] } ) ; item . coins . forEach ( ( c ) => { signOutputs [ index ] . coins . push ( c ) ; } ) ; calInputCoins ( signInputs , item . coins ) ; } ) ; const signMsg = { inputs : signInputs , outputs : signOutputs , } ; const signedTx = await this . _prepareTransaction ( msg , signMsg , fromAddress , sequence , memo ) ; return this . _broadcastDelegate ( signedTx ) ; } param { String } fromAddress param { String } symbol param { String } refid param { Number } sequence return { Promise } async cancelOrder ( fromAddress , symbol , refid , sequence = null ) { const accCode = crypto . decodeAddress ( fromAddress ) ; const msg = { sender : accCode , symbol : symbol , refid : refid , aminoPrefix : AminoPrefix . CancelOrderMsg , } ; const signMsg = { refid : refid , sender : fromAddress , symbol : symbol , } ; const signedTx = await this . _prepareTransaction ( msg , signMsg , fromAddress , sequence , "" ) ; return this . _broadcastDelegate ( signedTx ) ; } param { String } address param { String } symbol param { Number } side param { Number } price param { Number } quantity param { Number } sequence param { Number } timeinforce return { Promise } async placeOrder ( address = this . address ! , symbol , side , price , quantity , sequence = null , timeinforce = 1 ) { if ( ! address ) { throw new Error ( "address should not be falsy" ) ; } if ( ! symbol ) { throw new Error ( "symbol should not be falsy" ) ; } if ( side !== 1 && side !== 2 ) { throw new Error ( "side can only be 1 or 2" ) ; } if ( timeinforce !== 1 && timeinforce !== 3 ) { throw new Error ( "timeinforce can only be 1 or 3" ) ; } const accCode = crypto . decodeAddress ( address ) ; if ( sequence !== 0 && ! sequence ) { const data = await this . _httpClient . request ( "get" , `${ api . getAccount }/${ address }` ) ; sequence = data . result && data . result . sequence ; } const bigPrice = new Big ( price ) ; const bigQuantity = new Big ( quantity ) ; const placeOrderMsg = { sender : accCode , id : `${ accCode . toString ( "hex" ) }-${ sequence ! + 1 }` . toUpperCase ( ) , symbol : symbol , ordertype : 2 , side , price : parseFloat ( bigPrice . mul ( BASENUMBER ) . toString ( ) ) , quantity : parseFloat ( bigQuantity . mul ( BASENUMBER ) . toString ( ) ) , timeinforce : timeinforce , aminoPrefix : AminoPrefix . NewOrderMsg , } ; const signMsg = { id : placeOrderMsg . id , ordertype : placeOrderMsg . ordertype , price : placeOrderMsg . price , quantity : placeOrderMsg . quantity , sender : address , side : placeOrderMsg . side , symbol : placeOrderMsg . symbol , timeinforce : timeinforce , } ; checkNumber ( placeOrderMsg . price , "price" ) ; checkNumber ( placeOrderMsg . quantity , "quantity" ) ; const signedTx = await this . _prepareTransaction ( placeOrderMsg , signMsg , address , sequence , "" ) ; return this . _broadcastDelegate ( signedTx ) ; } param { String } address param { Number } proposalId param { String } baseAsset param { String } quoteAsset param { Number } initPrice param { Number } sequence return { Promise } async list ( address , proposalId , baseAsset , quoteAsset , initPrice , sequence = null ) { const accCode = crypto . decodeAddress ( address ) ; if ( ! address ) { throw new Error ( "address should not be falsy" ) ; } if ( proposalId <= 0 ) { throw new Error ( "proposal id should larger than 0" ) ; } if ( initPrice <= 0 ) { throw new Error ( "price should larger than 0" ) ; } if ( ! baseAsset ) { throw new Error ( "baseAsset should not be falsy" ) ; } if ( ! quoteAsset ) { throw new Error ( "quoteAsset should not be falsy" ) ; } const init_price = Number ( new Big ( initPrice ) . mul ( BASENUMBER ) . toString ( ) ) ; const listMsg = { from : accCode , proposal_id : proposalId , base_asset_symbol : baseAsset , quote_asset_symbol : quoteAsset , init_price : init_price , aminoPrefix : AminoPrefix . ListMsg , } ; const signMsg = { base_asset_symbol : baseAsset , from : address , init_price : init_price , proposal_id : proposalId , quote_asset_symbol : quoteAsset , } ; const signedTx = await this . _prepareTransaction ( listMsg , signMsg , address , sequence , "" ) ; return this . _broadcastDelegate ( signedTx ) ; } /**
     * list miniToken
     */ async listMiniToken ( { from , baseAsset , quoteAsset , initPrice , sequence = null , } ) { validateMiniTokenSymbol ( baseAsset ) ; if ( initPrice <= 0 ) { throw new Error ( "price should larger than 0" ) ; } if ( ! from ) { throw new Error ( "address should not be falsy" ) ; } if ( ! quoteAsset ) { throw new Error ( "quoteAsset should not be falsy" ) ; } const init_price = Number ( new Big ( initPrice ) . mul ( BASENUMBER ) . toString ( ) ) ; const listMiniMsg = new ListMiniMsg ( { from , base_asset_symbol : baseAsset , quote_asset_symbol : quoteAsset , init_price , } ) ; const signedTx = await this . _prepareTransaction ( listMiniMsg . getMsg ( ) , listMiniMsg . getSignMsg ( ) , from , sequence ) ; return this . _broadcastDelegate ( signedTx ) ; } param { String } address param { Number } flags param { Number } sequence return { Promise } async setAccountFlags ( address , flags , sequence = null ) { const accCode = crypto . decodeAddress ( address ) ; const msg = { from : accCode , flags : flags , aminoPrefix : AminoPrefix . SetAccountFlagsMsg , } ; const signMsg = { flags : flags , from : address , } ; const signedTx = await this . _prepareTransaction ( msg , signMsg , address , sequence , "" ) ; return this . _broadcastDelegate ( signedTx ) ; } param { Object } msg param { Object } stdSignMsg param { String } address param { Number } sequence param { String } memo return { Transaction } async _prepareTransaction ( msg , stdSignMsg , address , sequence = null , memo = "" ) { if ( ( ! this . account_number || ( sequence !== 0 && ! sequence ) ) && address ) { const data = await this . _httpClient . request ( "get" , `${ api . getAccount }/${ address }` ) ; sequence = data . result . sequence ; this . account_number = data . result . account_number ; } else if ( this . _setPkPromise ) { await this . _setPkPromise ; } const tx = new Transaction ( { accountNumber : typeof this . account_number !== "number" ? parseInt ( this . account_number ! ) : this . account_number , chainId : this . chainId ! , memo : memo , msg , sequence : typeof sequence !== "number" ? parseInt ( sequence ! ) : sequence , source : this . _source , } ) ; return this . _signingDelegate . call ( this , tx , stdSignMsg ) ; } param { signedTx } tx param { Boolean } sync return { Promise } async sendTransaction ( signedTx , sync ) { const signedBz = signedTx . serialize ( ) ; return this . sendRawTransaction ( signedBz , sync ) ; } param { String } signedBz param { Boolean } sync return { Promise } async sendRawTransaction ( signedBz , sync = ! this . _useAsyncBroadcast ) { const opts = { data : signedBz , headers : { "content-type" : "text/plain" , } , } ; return this . _httpClient . request ( "post" , `${ api . broadcast }?sync=${ sync }` , null , opts ) ; } param { Object } msg param { Object } stdSignMsg param { String } address param { Number } sequence param { String } memo param { Boolean } sync return { Promise } async _sendTransaction ( msg , stdSignMsg , address , sequence = null , memo = "" , sync = ! this . _useAsyncBroadcast ) { const signedTx = await this . _prepareTransaction ( msg , stdSignMsg , address , sequence , memo ) ; return this . sendTransaction ( signedTx , sync ) ; } param { String } address return { Promise } async getAccount ( address = this . address ) { if ( ! address ) { throw new Error ( "address should not be falsy" ) ; } try { const data = await this . _httpClient . request ( "get" , `${ api . getAccount }/${ address }` ) ; return data ; } catch ( err ) { return null ; } } param { String } address return { Promise } async getBalance ( address = this . address ) { try { const data = await this . getAccount ( address ) ; return data ! . result . balances ; } catch ( err ) { return [ ] ; } } param { Number } limit param { Number } offset return { Promise } async getMarkets ( limit = 1000 , offset = 0 ) { try { const data = await this . _httpClient . request ( "get" , `${ api . getMarkets }?limit=${ limit }&offset=${ offset }` ) ; return data ; } catch ( err ) { console . warn ( "getMarkets error" , err ) ; return [ ] ; } } param { String } address param { Number } offset return { Promise } deprecated async getTransactions ( address = this . address , offset = 0 ) { try { const data = await this . _httpClient . request ( "get" , `${ api . getTransactions }?address=${ address }&offset=${ offset }` ) ; return data ; } catch ( err ) { console . warn ( "getTransactions error" , err ) ; return [ ] ; } } param { String } address param { Number } startTime param { Number } endTime return { Promise } async getTxs ( address = this . address , startTime , endTime ) { try { const data = await this . _httpClient . request ( "get" , `${ api . getTxs }?address=${ address }&startTime=${ startTime }&endTime=${ endTime }` ) ; return data ; } catch ( err ) { console . warn ( "getTxs error" , err ) ; return [ ] ; } } param { String } hash return { Promise } async getTx ( hash ) { try { const data = await this . _httpClient . request ( "get" , `${ api . getTx }/${ hash }` ) ; return data ; } catch ( err ) { console . warn ( "getTx error" , err ) ; return [ ] ; } } param { String } symbol return { Promise } async getDepth ( symbol = "BNB_BUSD-BD1" ) { try { const data = await this . _httpClient . request ( "get" , `${ api . getDepth }?symbol=${ symbol }` ) ; return data ; } catch ( err ) { console . warn ( "getDepth error" , err ) ; return [ ] ; } } param { String } address param { String } symbol return { Promise } async getOpenOrders ( address = this . address ! ) { try { const data = await this . _httpClient . request ( "get" , `${ api . getOpenOrders }?address=${ address }` ) ; return data ; } catch ( err ) { console . warn ( "getOpenOrders error" , err ) ; return [ ] ; } } param { String } swapID return { Promise } async getSwapByID ( swapID ) { try { const data = await this . _httpClient . request ( "get" , `${ api . getSwaps }/${ swapID }` ) ; return data ; } catch ( err ) { console . warn ( "query swap by swapID error" , err ) ; return [ ] ; } } param { String } creator param { Number } offset param { Number } limit return { Promise } async getSwapByCreator ( creator , limit = 100 , offset = 0 ) { try { const data = await this . _httpClient . request ( "get" , `${ api . getSwaps }?fromAddress=${ creator }&limit=${ limit }&offset=${ offset }` ) ; return data ; } catch ( err ) { console . warn ( "query swap list by swap creator error" , err ) ; return [ ] ; } } param { String } recipient param { Number } offset param { Number } limit return { Promise } async getSwapByRecipient ( recipient , limit = 100 , offset = 0 ) { try { const data = await this . _httpClient . request ( "get" , `${ api . getSwaps }?toAddress=${ recipient }&limit=${ limit }&offset=${ offset }` ) ; return data ; } catch ( err ) { console . warn ( "query swap list by swap recipient error" , err ) ; return [ ] ; } } return { object } createAccount ( ) { const privateKey = crypto . generatePrivateKey ( ) ; return { privateKey , address : crypto . getAddressFromPrivateKey ( privateKey , this . addressPrefix ) , } ; } param { String } password createAccountWithKeystore ( password ) { if ( ! password ) { throw new Error ( "password should not be falsy" ) ; } const privateKey = crypto . generatePrivateKey ( ) ; const address = crypto . getAddressFromPrivateKey ( privateKey , this . addressPrefix ) ; const keystore = crypto . generateKeyStore ( privateKey , password ) ; return { privateKey , address , keystore , } ; } return { object } createAccountWithMneomnic ( ) { const mnemonic = crypto . generateMnemonic ( ) ; const privateKey = crypto . getPrivateKeyFromMnemonic ( mnemonic ) ; const address = crypto . getAddressFromPrivateKey ( privateKey , this . addressPrefix ) ; return { privateKey , address , mnemonic , } ; } param { object } keystore param { string } password recoverAccountFromKeystore ( keystore , password ) { const privateKey = crypto . getPrivateKeyFromKeyStore ( keystore , password ) ; const address = crypto . getAddressFromPrivateKey ( privateKey , this . addressPrefix ) ; return { privateKey , address , } ; } param { string } mneomnic recoverAccountFromMnemonic ( mnemonic ) { const privateKey = crypto . getPrivateKeyFromMnemonic ( mnemonic ) ; const address = crypto . getAddressFromPrivateKey ( privateKey , this . addressPrefix ) ; return { privateKey , address , } ; } recoverAccountFromMneomnic ( mnemonic ) { return this . recoverAccountFromMnemonic ( mnemonic ) ; } param { String } privateKey recoverAccountFromPrivateKey ( privateKey ) { const address = crypto . getAddressFromPrivateKey ( privateKey , this . addressPrefix ) ; return { privateKey , address , } ; } param { String } address param { String } prefix return { Boolean } checkAddress ( address , prefix = this . addressPrefix ) { return crypto . checkAddress ( address , prefix ) ; } return { String } getClientKeyAddress ( ) { if ( ! this . _privateKey ) throw new Error ( "no private key is set on this client" ) ; const address = crypto . getAddressFromPrivateKey ( this . _privateKey , this . addressPrefix ) ; this . address = address ; return address ; } }