-
Notifications
You must be signed in to change notification settings - Fork 174
Expand file tree
/
Copy pathscan.ts
More file actions
136 lines (110 loc) · 4.43 KB
/
scan.ts
File metadata and controls
136 lines (110 loc) · 4.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import log from 'electron-log'
import BigNumber from 'bignumber.js'
import { BigNumber as EthersBigNumber } from '@ethersproject/bignumber'
import { Interface } from '@ethersproject/abi'
import { addHexPrefix } from '@ethereumjs/util'
import multicall, { Call, supportsChain as multicallSupportsChain } from '../../multicall'
import erc20TokenAbi from './erc-20-abi'
import { groupByChain, TokensByChain } from './reducers'
import type { BytesLike } from '@ethersproject/bytes'
import type EthereumProvider from 'ethereum-provider'
import type { Balance, Token, TokenBalance } from '../../store/state/types'
const erc20Interface = new Interface(erc20TokenAbi)
export interface CurrencyBalance extends Balance {
chainId: number
}
export interface BalanceLoader {
getCurrencyBalances: (address: Address, chains: number[]) => Promise<CurrencyBalance[]>
getTokenBalances: (address: Address, tokens: Token[]) => Promise<TokenBalance[]>
}
function createBalance(rawBalance: string, decimals: number): Balance {
return {
balance: rawBalance,
displayBalance: new BigNumber(rawBalance).shiftedBy(-decimals).toString()
}
}
export default function (eth: EthereumProvider) {
function balanceCalls(owner: string, tokens: Token[]): Call<EthersBigNumber, Balance>[] {
return tokens.map((token) => ({
target: token.address,
call: ['function balanceOf(address address) returns (uint256 value)', owner],
returns: [
(bn?: EthersBigNumber) => {
const hexString = bn ? bn.toHexString() : '0x00'
return createBalance(hexString, token.decimals)
}
]
}))
}
async function getNativeCurrencyBalance(address: string, chainId: number) {
try {
const rawBalance: string = await eth.request({
method: 'eth_getBalance',
params: [address, 'latest'],
chainId: addHexPrefix(chainId.toString(16))
})
// TODO: do all coins have 18 decimals?
return { ...createBalance(rawBalance, 18), chainId }
} catch (e) {
log.error(`error loading native currency balance for chain id: ${chainId}`, e)
return { balance: '0x0', displayValue: '0.0', chainId }
}
}
async function getTokenBalance(token: Token, owner: string) {
const functionData = erc20Interface.encodeFunctionData('balanceOf', [owner])
const response: BytesLike = await eth.request({
method: 'eth_call',
chainId: addHexPrefix(token.chainId.toString(16)),
params: [{ to: token.address, value: '0x0', data: functionData }, 'latest']
})
const result = erc20Interface.decodeFunctionResult('balanceOf', response)
return result.balance.toHexString()
}
async function getTokenBalancesFromContracts(owner: string, tokens: Token[]) {
const balances = tokens.map(async (token) => {
try {
const rawBalance = await getTokenBalance(token, owner)
return {
...token,
...createBalance(rawBalance, token.decimals)
}
} catch (e) {
log.warn(`could not load balance for token with address ${token.address}`, e)
return undefined
}
})
const loadedBalances = await Promise.all(balances)
return loadedBalances.filter((bal) => bal !== undefined) as TokenBalance[]
}
async function getTokenBalancesFromMulticall(owner: string, tokens: Token[], chainId: number) {
const calls = balanceCalls(owner, tokens)
const results = await multicall(chainId, eth).batchCall(calls)
return results.reduce((acc, result, i) => {
if (result.success) {
acc.push({
...tokens[i],
...result.returnValues[0]
})
}
return acc
}, [] as TokenBalance[])
}
return {
getCurrencyBalances: async function (address: string, chains: number[]) {
const fetchChainBalance = getNativeCurrencyBalance.bind(null, address)
return Promise.all(chains.map(fetchChainBalance))
},
getTokenBalances: async function (owner: string, tokens: Token[]) {
const tokensByChain = tokens.reduce(groupByChain, {} as TokensByChain)
const tokenBalances = await Promise.all(
Object.entries(tokensByChain).map(([chain, tokens]) => {
const chainId = parseInt(chain)
return multicallSupportsChain(chainId)
? getTokenBalancesFromMulticall(owner, tokens, chainId)
: getTokenBalancesFromContracts(owner, tokens)
})
)
return ([] as TokenBalance[]).concat(...tokenBalances)
}
} as BalanceLoader
}