1
- import { extend , get , find } from 'lodash' ;
2
- import { api , rpc , wallet } from '@cityofzion/neon-js' ;
1
+ import { get , find , map , mapValues , chunk , filter , extend } from 'lodash' ;
2
+ import { u , sc , rpc , wallet } from '@cityofzion/neon-js' ;
3
3
4
4
import getRPCEndpoint from 'util/getRPCEndpoint' ;
5
5
6
6
import getTokens from './getTokens' ;
7
7
import { GAS , NEO , ASSETS } from '../values/assets' ;
8
8
9
- async function getTokenBalance ( endpoint , token , address ) {
10
- try {
11
- const response = await api . nep5 . getToken ( endpoint , token . scriptHash , address ) ;
12
- const balance = ( response . balance || 0 ) . toString ( ) ;
13
-
14
- return {
15
- [ token . scriptHash ] : { ...token , ...response , balance }
16
- } ;
17
- } catch ( err ) {
18
- // invalid scriptHash
19
- return { } ;
20
- }
9
+ const CHUNK_SIZE = 18 ;
10
+
11
+ function parseHexNum ( hex ) {
12
+ return hex ? parseInt ( u . reverseHex ( hex ) , 16 ) : 0 ;
13
+ }
14
+
15
+ function getRawTokenBalances ( url , tokens , address ) {
16
+ const addrScriptHash = u . reverseHex ( wallet . getScriptHashFromAddress ( address ) ) ;
17
+ const sb = new sc . ScriptBuilder ( ) ;
18
+
19
+ tokens . forEach ( ( { scriptHash } ) => {
20
+ sb . emitAppCall ( scriptHash , 'balanceOf' , [ addrScriptHash ] ) ;
21
+ } ) ;
22
+
23
+ return rpc . Query . invokeScript ( sb . str , false )
24
+ . execute ( url )
25
+ . then ( ( res ) => {
26
+ const tokenList = { } ;
27
+
28
+ if ( res && res . result && res . result . stack && res . result . stack . length >= 1 ) {
29
+ for ( let i = 0 ; i < res . result . stack . length ; i += 1 ) {
30
+ const { scriptHash, decimals } = tokens [ i ] ;
31
+ const value = parseHexNum ( res . result . stack [ i ] . value ) ;
32
+ tokenList [ scriptHash ] = new u . Fixed8 ( value ) . div ( 10 ** decimals ) . toString ( ) ;
33
+ }
34
+ }
35
+ return tokenList ;
36
+ } ) ;
37
+ }
38
+
39
+ async function getTokenBalances ( endpoint , address ) {
40
+ const tokens = await getTokens ( ) ;
41
+ const chunks = chunk ( map ( tokens , 'scriptHash' ) , CHUNK_SIZE ) ;
42
+
43
+ const balances = extend ( { } , ...await Promise . all ( chunks . map ( ( scriptHashes ) => {
44
+ const filteredTokens = filter ( tokens , ( token ) => scriptHashes . includes ( token . scriptHash ) ) ;
45
+ return getRawTokenBalances ( endpoint , filteredTokens , address ) ;
46
+ } ) ) ) ;
47
+
48
+ return mapValues ( balances , ( balance , scriptHash ) => ( {
49
+ ...find ( tokens , { scriptHash } ) ,
50
+ balance
51
+ } ) ) ;
21
52
}
22
53
23
54
async function getAssetBalances ( endpoint , address ) {
@@ -40,13 +71,8 @@ export default async function getBalances({ net, address }) {
40
71
throw new Error ( `Invalid address: "${ address } "` ) ;
41
72
}
42
73
43
- // token balances - // TODO use getTokenBalances to avoid multiple api calls
44
- const promises = ( await getTokens ( net ) ) . map ( ( token ) => {
45
- return getTokenBalance ( endpoint , token , address ) ;
46
- } ) ;
47
-
48
- // asset balances
49
- promises . unshift ( getAssetBalances ( endpoint , address ) ) ;
74
+ const assets = await getAssetBalances ( endpoint , address ) ;
75
+ const tokens = await getTokenBalances ( endpoint , address ) ;
50
76
51
- return extend ( { } , ...await Promise . all ( promises ) ) ;
77
+ return { ...assets , ... tokens } ;
52
78
}
0 commit comments