import { ASSETS } from "../consts" ; import { Query } from "../rpc" ; import { Transaction } from "../tx" ; import { BaseTransaction } from "../tx/transaction/BaseTransaction" ; import Fixed8 from "../u/Fixed8" ; import AssetBalance , { AssetBalanceLike } from "./components/AssetBalance" ; import Coin from "./components/Coin" ; export interface BalanceLike { address : string ; net : string ; assetSymbols : string [ ] ; assets : { [ sym ] : Partial < AssetBalanceLike > ; } ; tokenSymbols : string [ ] ; tokens : { [ sym ] : number | string | Fixed8 ; } ; } async function verifyCoin ( url , c ) { const response = await Query . getTxOut ( c . txid , c . index ) . execute ( url ) ; if ( ! response . result ) { return false ; } return c . index === response . result . n && c . value . equals ( response . result . value ) ; } param url param coinArr async function verifyCoins ( url , coinArr ) { const promises = coinArr . map ( ( c ) => verifyCoin ( url , c ) ) ; return await Promise . all ( promises ) ; } param url param assetBalance async function verifyAssetBalance ( url , assetBalance ) { const newAssetBalance = { balance : new Fixed8 ( 0 ) , spent : [ ] as Coin [ ] , unspent : [ ] as Coin [ ] , unconfirmed : [ ] as Coin [ ] , } ; const validCoins = await verifyCoins ( url , assetBalance . unspent ) ; validCoins . forEach ( ( valid , i ) => { const coin = assetBalance . unspent [ i ] ; if ( valid ) { newAssetBalance . unspent . push ( coin ) ; newAssetBalance . balance = newAssetBalance . balance . add ( coin . value ) ; } else { newAssetBalance . spent . push ( coin ) ; } } ) ; return new AssetBalance ( newAssetBalance ) ; } function exportAssets ( assets ) { const output = { } ; const keys = Object . keys ( assets ) ; for ( const key of keys ) { output [ key ] = assets [ key ] . export ( ) ; } return output ; } function exportTokens ( tokens ) { const out = { } ; Object . keys ( tokens ) . forEach ( ( symbol ) => { out [ symbol ] = tokens [ symbol ] . toNumber ( ) ; } ) ; return out ; } /**
 * Represents a balance available for an Account.
 * Contains balances for both UTXO assets and NEP5 tokens.
 */ export class Balance { /** The address for this Balance */ public address : string ; /** The network for this Balance */ public net : string ; /** The symbols of assets found in this Balance. Use this symbol to find the corresponding key in the assets object. */ public assetSymbols : string [ ] ; /** The object containing the balances for each asset keyed by its symbol. */ public assets : { [ sym ] : AssetBalance ; } ; /** The symbols of NEP5 tokens found in this Balance. Use this symbol to find the corresponding balance in the tokens object. */ public tokenSymbols : string [ ] ; /** The token balances in this Balance for each token keyed by its symbol. */ public tokens : { [ sym ] : Fixed8 ; } ; public constructor ( bal = { } ) { this . address = bal . address || "" ; this . net = bal . net || "NoNet" ; this . assetSymbols = [ ] ; this . assets = { } ; if ( typeof bal . assets === "object" ) { const keys = Object . keys ( bal . assets ) ; for ( const key of keys ) { if ( typeof bal . assets [ key ] === "object" ) { this . addAsset ( key , bal . assets ?. [ key ] ) ; } } } this . tokenSymbols = [ ] ; this . tokens = { } ; if ( typeof bal . tokens === "object" ) { const keys = Object . keys ( bal . tokens ) ; for ( const key of keys ) { this . addToken ( key , bal . tokens ?. [ key ] ) ; } } } public get [ Symbol . toStringTag ] ( ) : string { return "Balance" ; } param sym param assetBalance public addAsset ( sym , assetBalance ? ) { sym = sym . toUpperCase ( ) ; this . assetSymbols . push ( sym ) ; this . assetSymbols . sort ( ) ; const cleanedAssetBalance = new AssetBalance ( assetBalance ) ; this . assets [ sym ] = cleanedAssetBalance ; return this ; } param sym param tokenBalance public addToken ( sym , tokenBalance = 0 ) { sym = sym . toUpperCase ( ) ; this . tokenSymbols . push ( sym ) ; this . tokens [ sym ] = new Fixed8 ( tokenBalance ) ; return this ; } param tx param confirmed public applyTx ( tx , confirmed = false ) { tx = tx instanceof BaseTransaction ? tx : Transaction . deserialize ( tx ) ; const symbols = this . assetSymbols ; for ( const input of tx . inputs ) { const findFunc = ( el ) : boolean => el . txid === input . prevHash && el . index === input . prevIndex ; for ( const sym of symbols ) { const assetBalance = this . assets [ sym ] ; const ind = assetBalance . unspent . findIndex ( findFunc ) ; if ( ind >= 0 ) { const spentCoin = assetBalance . unspent . splice ( ind , 1 ) ; assetBalance . spent = assetBalance . spent . concat ( spentCoin ) ; break ; } } } const hash = tx . hash ; for ( let i = 0 ; i < tx . outputs . length ; i ++ ) { const output = tx . outputs [ i ] ; const sym = ASSETS [ output . assetId ] ; const assetBalance = this . assets [ sym ] ; if ( ! assetBalance ) { this . addAsset ( sym ) ; } const coin = new Coin ( { index : i , txid : hash , value : output . value } ) ; if ( confirmed ) { const findFunc = ( el ) : boolean => el . txid === coin . txid && el . index === coin . index ; const unconfirmedIndex = assetBalance . unconfirmed . findIndex ( findFunc ) ; if ( unconfirmedIndex >= 0 ) { assetBalance . unconfirmed . splice ( unconfirmedIndex , 1 ) ; } if ( ! assetBalance . unspent ) { assetBalance . unspent = [ ] ; } assetBalance . unspent . push ( coin ) ; } else { if ( ! assetBalance . unconfirmed ) { assetBalance . unconfirmed = [ ] ; } assetBalance . unconfirmed . push ( coin ) ; } this . assets [ sym ] = assetBalance ; } return this ; } /**
     * Informs the Balance that the next block is confirmed, thus moving all unconfirmed transaction to unspent.
     */ public confirm ( ) { for ( const sym of this . assetSymbols ) { const assetBalance = this . assets [ sym ] ; assetBalance . unspent = assetBalance . unspent . concat ( assetBalance . unconfirmed ) ; assetBalance . unconfirmed = [ ] ; } return this ; } /**
     * Export this class as a plain JS object
     */ public export ( ) { return { net : this . net , address : this . address , assetSymbols : this . assetSymbols , assets : exportAssets ( this . assets ) , tokenSymbols : this . tokenSymbols , tokens : exportTokens ( this . tokens ) , } ; } param url public verifyAssets ( url ) { const promises = [ ] ; const symbols = this . assetSymbols ; symbols . map ( ( key ) => { const assetBalance = this . assets [ key ] ; promises . push ( verifyAssetBalance ( url , assetBalance ) ) ; } ) ; return Promise . all ( promises ) . then ( ( newBalances ) => { symbols . map ( ( sym , i ) => { this . assets [ sym ] = newBalances [ i ] ; } ) ; return this ; } ) ; } } export default Balance ;