@avalabs/evm-module 0.0.12 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/.turbo/turbo-build.log +10 -10
  2. package/.turbo/turbo-lint.log +1 -1
  3. package/.turbo/turbo-test.log +11 -6
  4. package/CHANGELOG.md +27 -0
  5. package/dist/index.cjs +10 -7
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +6 -5
  8. package/dist/index.d.ts +6 -5
  9. package/dist/index.js +10 -8
  10. package/dist/index.js.map +1 -1
  11. package/package.json +17 -10
  12. package/src/contracts/openzeppelin/ERC1155.ts +440 -0
  13. package/src/contracts/openzeppelin/ERC20.ts +330 -0
  14. package/src/contracts/openzeppelin/ERC721.ts +420 -0
  15. package/src/contracts/openzeppelin/common.ts +131 -0
  16. package/src/contracts/openzeppelin/factories/ERC1155__factory.ts +388 -0
  17. package/src/contracts/openzeppelin/factories/ERC20__factory.ts +353 -0
  18. package/src/contracts/openzeppelin/factories/ERC721__factory.ts +413 -0
  19. package/src/contracts/openzeppelin/factories/index.ts +6 -0
  20. package/src/contracts/openzeppelin/index.ts +10 -0
  21. package/src/handlers/eth-send-transaction/eth-send-transaction.test.ts +2 -2
  22. package/src/handlers/eth-send-transaction/eth-send-transaction.ts +1 -1
  23. package/src/handlers/get-balances/evm-balance-service/get-erc20-balances.test.ts +78 -0
  24. package/src/handlers/get-balances/evm-balance-service/get-erc20-balances.ts +85 -0
  25. package/src/handlers/get-balances/evm-balance-service/get-native-token-balances.test.ts +102 -0
  26. package/src/handlers/get-balances/evm-balance-service/get-native-token-balances.ts +54 -0
  27. package/src/handlers/get-balances/get-balances.test.ts +252 -0
  28. package/src/handlers/get-balances/get-balances.ts +109 -0
  29. package/src/handlers/get-balances/glacier-balance-service/get-erc20-balances.test.ts +76 -0
  30. package/src/handlers/get-balances/glacier-balance-service/get-erc20-balances.ts +107 -0
  31. package/src/handlers/get-balances/glacier-balance-service/get-native-token-balances.test.ts +61 -0
  32. package/src/handlers/get-balances/glacier-balance-service/get-native-token-balances.ts +46 -0
  33. package/src/handlers/get-network-fee/get-network-fee.test.ts +1 -1
  34. package/src/handlers/get-network-fee/get-network-fee.ts +1 -1
  35. package/src/handlers/get-transaction-history/converters/evm-transaction-converter/get-transaction-from-glacier.test.ts +72 -59
  36. package/src/handlers/get-transaction-history/converters/evm-transaction-converter/get-transactions-from-glacier.ts +4 -11
  37. package/src/handlers/get-transaction-history/get-transaction-history.test.ts +7 -2
  38. package/src/handlers/get-transaction-history/get-transaction-history.ts +13 -3
  39. package/src/module.ts +18 -8
  40. package/src/services/glacier-service/glacier-service.ts +259 -0
  41. package/tsconfig.json +1 -0
  42. /package/{src/manifest.json → manifest.json} +0 -0
@@ -0,0 +1,102 @@
1
+ import { BN } from 'bn.js';
2
+ import { getNativeTokenBalances } from './get-native-token-balances';
3
+
4
+ describe('get-native-token-balances', () => {
5
+ it('should return native token balances', async () => {
6
+ const balance = getNativeTokenBalances({
7
+ provider: {
8
+ getBalance: jest.fn().mockResolvedValue(new BN('1000000000000000000')),
9
+ } as never,
10
+ tokenService: {
11
+ getSimplePrice: jest.fn().mockResolvedValue({
12
+ '123': {
13
+ USD: {
14
+ price: 1000,
15
+ marketCap: 0,
16
+ vol24: 0,
17
+ change24: 0,
18
+ },
19
+ },
20
+ }),
21
+ } as never,
22
+ address: '0x123',
23
+ currency: 'USD',
24
+ network: {
25
+ pricingProviders: {
26
+ coingecko: {
27
+ nativeTokenId: '123',
28
+ },
29
+ },
30
+ networkToken: {
31
+ address: '0x123',
32
+ name: 'Ethereum',
33
+ symbol: 'ETH',
34
+ decimals: 18,
35
+ logoUri: 'https://example.com/logo.png',
36
+ },
37
+ } as never,
38
+ });
39
+
40
+ expect(balance).resolves.toEqual({
41
+ address: '0x123',
42
+ name: 'Ethereum',
43
+ symbol: 'ETH',
44
+ decimals: 18,
45
+ logoUri: 'https://example.com/logo.png',
46
+ balance: new BN('1000000000000000000'),
47
+ balanceCurrencyDisplayValue: '1000.00',
48
+ balanceDisplayValue: '1',
49
+ balanceInCurrency: 1000,
50
+ priceInCurrency: 1000,
51
+ coingeckoId: '123',
52
+ type: 'NATIVE',
53
+ marketCap: 0,
54
+ vol24: 0,
55
+ change24: 0,
56
+ });
57
+ });
58
+
59
+ it('should return native token object without balance data', async () => {
60
+ const balance = getNativeTokenBalances({
61
+ provider: {
62
+ getBalance: jest.fn().mockResolvedValue(new BN('0')),
63
+ } as never,
64
+ tokenService: {
65
+ getSimplePrice: jest.fn().mockResolvedValue({}),
66
+ } as never,
67
+ address: '0x123',
68
+ currency: 'USD',
69
+ network: {
70
+ pricingProviders: {
71
+ coingecko: {
72
+ nativeTokenId: '123',
73
+ },
74
+ },
75
+ networkToken: {
76
+ address: '0x123',
77
+ name: 'Ethereum',
78
+ symbol: 'ETH',
79
+ decimals: 18,
80
+ logoUri: 'https://example.com/logo.png',
81
+ },
82
+ } as never,
83
+ });
84
+ expect(balance).resolves.toEqual({
85
+ address: '0x123',
86
+ name: 'Ethereum',
87
+ symbol: 'ETH',
88
+ decimals: 18,
89
+ logoUri: 'https://example.com/logo.png',
90
+ balance: new BN('0'),
91
+ balanceCurrencyDisplayValue: '0.00',
92
+ balanceDisplayValue: '0',
93
+ balanceInCurrency: 0,
94
+ priceInCurrency: 0,
95
+ coingeckoId: '123',
96
+ type: 'NATIVE',
97
+ marketCap: 0,
98
+ vol24: 0,
99
+ change24: 0,
100
+ });
101
+ });
102
+ });
@@ -0,0 +1,54 @@
1
+ import { balanceToDisplayValue, bigToBN, bigintToBig, bnToBig } from '@avalabs/utils-sdk';
2
+ import { TokenType, type Network, type NetworkTokenWithBalance } from '@avalabs/vm-module-types';
3
+ import type { VsCurrencyType } from '@avalabs/coingecko-sdk';
4
+ import { TokenService } from '@internal/utils';
5
+ import type { Provider } from 'ethers';
6
+
7
+ export const getNativeTokenBalances = async ({
8
+ provider,
9
+ tokenService,
10
+ address,
11
+ currency,
12
+ network,
13
+ }: {
14
+ provider: Provider;
15
+ tokenService: TokenService;
16
+ address: string;
17
+ currency: string;
18
+ network: Network;
19
+ }): Promise<NetworkTokenWithBalance> => {
20
+ const coingeckoTokenId = network.pricingProviders?.coingecko.nativeTokenId;
21
+ const networkToken = network.networkToken;
22
+ const simplePriceResponse = coingeckoTokenId
23
+ ? await tokenService.getSimplePrice({
24
+ coinIds: [coingeckoTokenId],
25
+ currencies: [currency] as VsCurrencyType[],
26
+ })
27
+ : {};
28
+
29
+ const priceInCurrency = simplePriceResponse?.[coingeckoTokenId ?? '']?.[currency]?.price ?? 0;
30
+ const marketCap = simplePriceResponse?.[coingeckoTokenId ?? '']?.[currency]?.marketCap ?? 0;
31
+ const vol24 = simplePriceResponse?.[coingeckoTokenId ?? '']?.[currency]?.vol24 ?? 0;
32
+ const change24 = simplePriceResponse?.[coingeckoTokenId ?? '']?.[currency]?.change24 ?? 0;
33
+
34
+ const balanceBigint = await provider.getBalance(address);
35
+ const balaceBig = bigintToBig(balanceBigint, networkToken.decimals);
36
+ const balance = bigToBN(balaceBig, networkToken.decimals);
37
+ const balanceDisplayValue = balanceToDisplayValue(balance, networkToken.decimals);
38
+ const balanceInCurrency = bnToBig(balance, networkToken.decimals).mul(priceInCurrency).toNumber();
39
+ const balanceCurrencyDisplayValue = balanceInCurrency.toFixed(2);
40
+
41
+ return {
42
+ ...networkToken,
43
+ coingeckoId: coingeckoTokenId ?? '',
44
+ type: TokenType.NATIVE,
45
+ balance,
46
+ balanceDisplayValue,
47
+ balanceInCurrency,
48
+ balanceCurrencyDisplayValue,
49
+ priceInCurrency,
50
+ marketCap,
51
+ vol24,
52
+ change24,
53
+ };
54
+ };
@@ -0,0 +1,252 @@
1
+ import { TokenType } from '@avalabs/vm-module-types';
2
+ import { getBalances } from './get-balances';
3
+ import * as GlacierNativeToken from './glacier-balance-service/get-native-token-balances';
4
+ import { BN } from 'bn.js';
5
+ import * as GlacierERC20Token from './glacier-balance-service/get-erc20-balances';
6
+ import * as EvmNativeToken from './evm-balance-service/get-native-token-balances';
7
+ import * as EvmERC20Token from './evm-balance-service/get-erc20-balances';
8
+ import type { EvmGlacierService } from '../../services/glacier-service/glacier-service';
9
+
10
+ describe('get-balances', () => {
11
+ it('should get balances from glacier', async () => {
12
+ const mockGlacierService: EvmGlacierService = {
13
+ ...expect.any(Object),
14
+ isNetworkSupported: () => true,
15
+ isHealthy: () => true,
16
+ };
17
+ jest.spyOn(GlacierNativeToken, 'getNativeTokenBalances').mockImplementationOnce(async () => {
18
+ return {
19
+ name: 'Ether',
20
+ symbol: 'ETH',
21
+ decimals: 18,
22
+ type: TokenType.NATIVE,
23
+ logoUri:
24
+ 'https://images.ctfassets.net/gcj8jwzm6086/6l56QLVZmvacuBfjHBTThP/791d743dd2c526692562780c2325fedf/eth-circle__1_.svg',
25
+ balance: new BN(1),
26
+ balanceDisplayValue: '1',
27
+ balanceInCurrency: 1,
28
+ balanceCurrencyDisplayValue: '1',
29
+ priceInCurrency: 1,
30
+ marketCap: 0,
31
+ vol24: 0,
32
+ change24: 0,
33
+ coingeckoId: '',
34
+ };
35
+ });
36
+
37
+ jest.spyOn(GlacierERC20Token, 'getErc20Balances').mockImplementationOnce(async () => {
38
+ return {
39
+ '0x123': {
40
+ chainId: 1,
41
+ address: '0x123',
42
+ name: 'DAI',
43
+ symbol: 'DAI',
44
+ decimals: 18,
45
+ logoUri: 'https://s3.us-east-2.amazonaws.com/nomics-api/static/images/currencies/dai.svg',
46
+ balance: new BN(1),
47
+ balanceCurrencyDisplayValue: '1',
48
+ balanceDisplayValue: '1',
49
+ balanceInCurrency: 1,
50
+ priceInCurrency: 1,
51
+ contractType: 'ERC-20',
52
+ type: TokenType.ERC20,
53
+ change24: 0,
54
+ marketCap: 0,
55
+ vol24: 0,
56
+ },
57
+ };
58
+ });
59
+ const balances = await getBalances({
60
+ addresses: ['0x123'],
61
+ currency: 'USD',
62
+ network: {
63
+ chainId: 1,
64
+ chainName: 'Ethereum',
65
+ isTestnet: false,
66
+ networkToken: {
67
+ name: 'Ether',
68
+ decimals: 18,
69
+ symbol: 'ETH',
70
+ description:
71
+ 'Ether is used to pay for transaction fees and computational services on Etherum. Users can send Ether to other users, and developers can write smart contracts that receive, hold, and send Ether.',
72
+ logoUri:
73
+ 'https://images.ctfassets.net/gcj8jwzm6086/6l56QLVZmvacuBfjHBTThP/791d743dd2c526692562780c2325fedf/eth-circle__1_.svg',
74
+ },
75
+ pricingProviders: { coingecko: { nativeTokenId: 'ethereum', assetPlatformId: 'ethereum' } },
76
+ rpcUrl: 'https://proxy-api.avax.network/proxy/infura/mainnet',
77
+ utilityAddresses: { multicall: '0x5ba1e12693dc8f9c48aad8770482f4739beed696' },
78
+ },
79
+ proxyApiUrl: 'proxyApiUrl',
80
+ glacierService: mockGlacierService,
81
+ });
82
+ expect(balances).toEqual({
83
+ '0x123': {
84
+ '0x123': {
85
+ address: '0x123',
86
+ balance: new BN(1),
87
+ balanceCurrencyDisplayValue: '1',
88
+ balanceDisplayValue: '1',
89
+ balanceInCurrency: 1,
90
+ chainId: 1,
91
+ change24: 0,
92
+ contractType: 'ERC-20',
93
+ decimals: 18,
94
+ logoUri: 'https://s3.us-east-2.amazonaws.com/nomics-api/static/images/currencies/dai.svg',
95
+ marketCap: 0,
96
+ name: 'DAI',
97
+ priceInCurrency: 1,
98
+ symbol: 'DAI',
99
+ type: 'ERC20',
100
+ vol24: 0,
101
+ },
102
+ ETH: {
103
+ balance: new BN(1),
104
+ balanceCurrencyDisplayValue: '1',
105
+ balanceDisplayValue: '1',
106
+ balanceInCurrency: 1,
107
+ change24: 0,
108
+ coingeckoId: '',
109
+ decimals: 18,
110
+ logoUri:
111
+ 'https://images.ctfassets.net/gcj8jwzm6086/6l56QLVZmvacuBfjHBTThP/791d743dd2c526692562780c2325fedf/eth-circle__1_.svg',
112
+ marketCap: 0,
113
+ name: 'Ether',
114
+ priceInCurrency: 1,
115
+ symbol: 'ETH',
116
+ type: 'NATIVE',
117
+ vol24: 0,
118
+ },
119
+ },
120
+ });
121
+ });
122
+
123
+ it('should get balances from evm', async () => {
124
+ const mockGlacierService: EvmGlacierService = {
125
+ ...expect.any(Object),
126
+ isNetworkSupported: () => false,
127
+ isHealthy: () => false,
128
+ };
129
+ global.fetch = jest.fn(() =>
130
+ Promise.resolve({
131
+ ok: true,
132
+ json: () =>
133
+ Promise.resolve([
134
+ {
135
+ chainId: 2,
136
+ address: '0x456',
137
+ name: 'DAI2',
138
+ symbol: 'DAI2',
139
+ decimals: 18,
140
+ logoUri: 'https://s3.us-east-2.amazonaws.com/nomics-api/static/images/currencies/dai.svg',
141
+ contractType: 'ERC-20',
142
+ type: TokenType.ERC20,
143
+ },
144
+ ]),
145
+ }),
146
+ ) as jest.Mock;
147
+ jest.spyOn(EvmNativeToken, 'getNativeTokenBalances').mockImplementationOnce(async () => {
148
+ return {
149
+ name: 'Ether2',
150
+ symbol: 'ETH2',
151
+ decimals: 18,
152
+ type: TokenType.NATIVE,
153
+ logoUri:
154
+ 'https://images.ctfassets.net/gcj8jwzm6086/6l56QLVZmvacuBfjHBTThP/791d743dd2c526692562780c2325fedf/eth-circle__1_.svg',
155
+ balance: new BN(1),
156
+ balanceDisplayValue: '1',
157
+ balanceInCurrency: 1,
158
+ balanceCurrencyDisplayValue: '1',
159
+ priceInCurrency: 1,
160
+ marketCap: 0,
161
+ vol24: 0,
162
+ change24: 0,
163
+ coingeckoId: '',
164
+ };
165
+ });
166
+
167
+ jest.spyOn(EvmERC20Token, 'getErc20Balances').mockImplementationOnce(async () => {
168
+ return {
169
+ '0x456': {
170
+ chainId: 2,
171
+ address: '0x456',
172
+ name: 'DAI2',
173
+ symbol: 'DAI2',
174
+ decimals: 18,
175
+ logoUri: 'https://s3.us-east-2.amazonaws.com/nomics-api/static/images/currencies/dai.svg',
176
+ balance: new BN(1),
177
+ balanceCurrencyDisplayValue: '1',
178
+ balanceDisplayValue: '1',
179
+ balanceInCurrency: 1,
180
+ priceInCurrency: 1,
181
+ contractType: 'ERC-20',
182
+ type: TokenType.ERC20,
183
+ change24: 0,
184
+ marketCap: 0,
185
+ vol24: 0,
186
+ },
187
+ };
188
+ });
189
+ const balances = await getBalances({
190
+ addresses: ['0x456'],
191
+ currency: 'USD',
192
+ network: {
193
+ chainId: 2,
194
+ chainName: '2',
195
+ isTestnet: false,
196
+ networkToken: {
197
+ name: 'Ether2',
198
+ decimals: 18,
199
+ symbol: 'ETH2',
200
+ description:
201
+ 'Ether is used to pay for transaction fees and computational services on Etherum. Users can send Ether to other users, and developers can write smart contracts that receive, hold, and send Ether.',
202
+ logoUri:
203
+ 'https://images.ctfassets.net/gcj8jwzm6086/6l56QLVZmvacuBfjHBTThP/791d743dd2c526692562780c2325fedf/eth-circle__1_.svg',
204
+ },
205
+ pricingProviders: { coingecko: { nativeTokenId: 'ethereum', assetPlatformId: 'ethereum' } },
206
+ rpcUrl: 'https://proxy-api.avax.network/proxy/infura/mainnet',
207
+ utilityAddresses: { multicall: '0x5ba1e12693dc8f9c48aad8770482f4739beed696' },
208
+ },
209
+ proxyApiUrl: 'proxyApiUrl',
210
+ glacierService: mockGlacierService,
211
+ });
212
+ expect(balances).toEqual({
213
+ '0x456': {
214
+ '0x456': {
215
+ address: '0x456',
216
+ balance: new BN(1),
217
+ balanceCurrencyDisplayValue: '1',
218
+ balanceDisplayValue: '1',
219
+ balanceInCurrency: 1,
220
+ chainId: 2,
221
+ change24: 0,
222
+ contractType: 'ERC-20',
223
+ decimals: 18,
224
+ logoUri: 'https://s3.us-east-2.amazonaws.com/nomics-api/static/images/currencies/dai.svg',
225
+ marketCap: 0,
226
+ name: 'DAI2',
227
+ priceInCurrency: 1,
228
+ symbol: 'DAI2',
229
+ type: 'ERC20',
230
+ vol24: 0,
231
+ },
232
+ ETH2: {
233
+ balance: new BN(1),
234
+ balanceCurrencyDisplayValue: '1',
235
+ balanceDisplayValue: '1',
236
+ balanceInCurrency: 1,
237
+ change24: 0,
238
+ coingeckoId: '',
239
+ decimals: 18,
240
+ logoUri:
241
+ 'https://images.ctfassets.net/gcj8jwzm6086/6l56QLVZmvacuBfjHBTThP/791d743dd2c526692562780c2325fedf/eth-circle__1_.svg',
242
+ marketCap: 0,
243
+ name: 'Ether2',
244
+ priceInCurrency: 1,
245
+ symbol: 'ETH2',
246
+ type: 'NATIVE',
247
+ vol24: 0,
248
+ },
249
+ },
250
+ });
251
+ });
252
+ });
@@ -0,0 +1,109 @@
1
+ import type { GetBalancesResponse, GetBalancesParams, Storage } from '@avalabs/vm-module-types';
2
+ import { getErc20Balances } from './evm-balance-service/get-erc20-balances';
3
+ import { TokenService } from '@internal/utils';
4
+ import { getProvider } from '../../utils/get-provider';
5
+ import { getTokens } from '../get-tokens/get-tokens';
6
+ import { getNativeTokenBalances } from './evm-balance-service/get-native-token-balances';
7
+ import { getNativeTokenBalances as getNativeTokenBalancesFromGlacier } from './glacier-balance-service/get-native-token-balances';
8
+ import { getErc20Balances as getErc20BalancesFromGlacier } from './glacier-balance-service/get-erc20-balances';
9
+ import type { EvmGlacierService } from '../../services/glacier-service/glacier-service';
10
+
11
+ export const getBalances = async ({
12
+ addresses,
13
+ currency,
14
+ network,
15
+ proxyApiUrl,
16
+ customTokens = [],
17
+ storage,
18
+ glacierService,
19
+ }: GetBalancesParams & {
20
+ proxyApiUrl: string;
21
+ glacierService: EvmGlacierService;
22
+ storage?: Storage;
23
+ }): Promise<GetBalancesResponse> => {
24
+ const chainId = network.chainId;
25
+ const isNetworkSupported = await glacierService.isNetworkSupported(network.chainId);
26
+ const isHealthy = glacierService.isHealthy();
27
+
28
+ let balances = [];
29
+ if (isHealthy && isNetworkSupported) {
30
+ balances = await Promise.allSettled(
31
+ addresses.map(async (address) => {
32
+ const nativeToken = await getNativeTokenBalancesFromGlacier({
33
+ address,
34
+ currency,
35
+ chainId,
36
+ glacierService,
37
+ });
38
+
39
+ const erc20Tokens = await getErc20BalancesFromGlacier({
40
+ customTokens,
41
+ glacierService,
42
+ currency,
43
+ chainId,
44
+ address,
45
+ });
46
+
47
+ return {
48
+ address,
49
+ balances: {
50
+ [nativeToken.symbol]: nativeToken,
51
+ ...erc20Tokens,
52
+ },
53
+ };
54
+ }),
55
+ );
56
+ } else {
57
+ const tokenService = new TokenService({ storage, proxyApiUrl });
58
+ const tokens = await getTokens({ chainId: Number(chainId), proxyApiUrl });
59
+ const allTokens = [...tokens, ...customTokens];
60
+ const provider = getProvider({
61
+ chainId,
62
+ chainName: network.chainName,
63
+ rpcUrl: network.rpcUrl,
64
+ multiContractAddress: network.utilityAddresses?.multicall,
65
+ });
66
+
67
+ balances = await Promise.allSettled(
68
+ addresses.map(async (address) => {
69
+ const nativeToken = await getNativeTokenBalances({
70
+ network,
71
+ tokenService,
72
+ address,
73
+ currency,
74
+ provider,
75
+ });
76
+
77
+ const erc20Tokens = await getErc20Balances({
78
+ provider,
79
+ network,
80
+ tokenService,
81
+ address,
82
+ currency,
83
+ tokens: allTokens,
84
+ });
85
+
86
+ return {
87
+ address,
88
+ balances: {
89
+ [nativeToken.symbol]: nativeToken,
90
+ ...erc20Tokens,
91
+ },
92
+ };
93
+ }),
94
+ );
95
+ }
96
+
97
+ const filterBalances = balances.reduce((acc, result) => {
98
+ if (result.status === 'rejected') {
99
+ return acc;
100
+ }
101
+
102
+ return {
103
+ ...acc,
104
+ [result.value.address]: result.value.balances,
105
+ };
106
+ }, {} as GetBalancesResponse);
107
+
108
+ return filterBalances;
109
+ };
@@ -0,0 +1,76 @@
1
+ import { BN } from 'bn.js';
2
+ import { getErc20Balances } from './get-erc20-balances';
3
+ import type { EvmGlacierService } from '../../../services/glacier-service/glacier-service';
4
+
5
+ describe('get-erc20-balances', () => {
6
+ it('should return erc20 token balances', async () => {
7
+ const mockGlacierService: EvmGlacierService = {
8
+ ...expect.any(Object),
9
+ listErc20Balances: jest.fn().mockResolvedValue({
10
+ erc20TokenBalances: [
11
+ {
12
+ address: '0x123',
13
+ name: 'Ethereum',
14
+ symbol: 'ETH',
15
+ decimals: 18,
16
+ logoUri: 'https://example.com/logo.png',
17
+ ercType: 'ERC20',
18
+ price: {
19
+ currency: 'USD',
20
+ value: 1000,
21
+ },
22
+ chainId: '123',
23
+ balance: '1000000000000000000',
24
+ balanceValue: {
25
+ currency: 'USD',
26
+ value: 1000,
27
+ },
28
+ },
29
+ ],
30
+ }),
31
+ };
32
+ const balance = getErc20Balances({
33
+ glacierService: mockGlacierService,
34
+ currency: 'USD',
35
+ chainId: 123,
36
+ address: '0x123',
37
+ customTokens: [],
38
+ });
39
+
40
+ expect(balance).resolves.toEqual({
41
+ '0x123': {
42
+ chainId: 123,
43
+ address: '0x123',
44
+ name: 'Ethereum',
45
+ symbol: 'ETH',
46
+ decimals: 18,
47
+ logoUri: 'https://example.com/logo.png',
48
+ balance: new BN('1000000000000000000'),
49
+ balanceCurrencyDisplayValue: '1000',
50
+ balanceDisplayValue: '1',
51
+ balanceInCurrency: 1000,
52
+ priceInCurrency: 1000,
53
+ contractType: 'ERC-20',
54
+ type: 'ERC20',
55
+ change24: 0,
56
+ marketCap: 0,
57
+ vol24: 0,
58
+ },
59
+ });
60
+ });
61
+
62
+ it('should return native token object without balance data', async () => {
63
+ const mockGlacierService: EvmGlacierService = {
64
+ ...expect.any(Object),
65
+ listErc20Balances: jest.fn().mockRejectedValue(new Error('Failed to get erc20 balance')),
66
+ };
67
+ const balance = getErc20Balances({
68
+ glacierService: mockGlacierService,
69
+ currency: 'USD',
70
+ chainId: 123,
71
+ address: '0x123',
72
+ customTokens: [],
73
+ });
74
+ expect(balance).rejects.toEqual(new Error('Failed to get erc20 balance'));
75
+ });
76
+ });
@@ -0,0 +1,107 @@
1
+ import { balanceToDisplayValue, bnToBig } from '@avalabs/utils-sdk';
2
+ import {
3
+ TokenType,
4
+ type TokenWithBalanceERC20,
5
+ type NetworkContractToken,
6
+ type TokenWithBalance,
7
+ } from '@avalabs/vm-module-types';
8
+ import { CurrencyCode, Erc20TokenBalance } from '@avalabs/glacier-sdk';
9
+ import BN from 'bn.js';
10
+ import type { EvmGlacierService } from '../../../services/glacier-service/glacier-service';
11
+
12
+ export const getErc20Balances = async ({
13
+ glacierService,
14
+ currency,
15
+ chainId,
16
+ address,
17
+ customTokens,
18
+ }: {
19
+ glacierService: EvmGlacierService;
20
+ address: string;
21
+ currency: string;
22
+ chainId: number;
23
+ customTokens: NetworkContractToken[];
24
+ }): Promise<Record<string, TokenWithBalance>> => {
25
+ const tokensWithBalance: TokenWithBalanceERC20[] = [];
26
+ /**
27
+ * Load all pages to make sure we have all the tokens with balances
28
+ */
29
+ let nextPageToken: string | undefined;
30
+ do {
31
+ const response = await glacierService.listErc20Balances({
32
+ chainId: chainId.toString(),
33
+ address,
34
+ currency: currency.toLocaleLowerCase() as CurrencyCode,
35
+ // glacier has a cap on page size of 100
36
+ pageSize: 100,
37
+ pageToken: nextPageToken,
38
+ });
39
+
40
+ tokensWithBalance.push(...convertErc20ToTokenWithBalance(response.erc20TokenBalances, Number(chainId)));
41
+ nextPageToken = response.nextPageToken;
42
+ } while (nextPageToken);
43
+
44
+ /**
45
+ * Glacier doesnt return tokens without balances so we need to polyfill that list
46
+ * from our own list of tokens. We just set the balance to 0, these zero balance
47
+ * tokens are only used for swap, bridge and tx parsing.
48
+ */
49
+ return [
50
+ ...convertNetworkTokenToTokenWithBalance(customTokens),
51
+ ...tokensWithBalance, // this needs to be second in the list so it overwrites its zero balance counterpart if there is one
52
+ ].reduce(
53
+ (acc, token) => {
54
+ return { ...acc, [token.address.toLowerCase()]: token };
55
+ },
56
+ {} as Record<string, TokenWithBalance>,
57
+ );
58
+ };
59
+
60
+ const convertNetworkTokenToTokenWithBalance = (tokens: NetworkContractToken[]): TokenWithBalanceERC20[] => {
61
+ return tokens.map((token) => {
62
+ return {
63
+ ...token,
64
+ type: TokenType.ERC20,
65
+ balance: new BN(0),
66
+ balanceInCurrency: 0,
67
+ balanceDisplayValue: '0',
68
+ balanceCurrencyDisplayValue: '0',
69
+ priceInCurrency: 0,
70
+ marketCap: 0,
71
+ change24: 0,
72
+ vol24: 0,
73
+ };
74
+ });
75
+ };
76
+
77
+ const convertErc20ToTokenWithBalance = (
78
+ tokenBalances: Erc20TokenBalance[],
79
+ chainId: number,
80
+ ): TokenWithBalanceERC20[] => {
81
+ return tokenBalances.map((token: Erc20TokenBalance): TokenWithBalanceERC20 => {
82
+ const balance = new BN(token.balance);
83
+ const balanceDisplayValue = balanceToDisplayValue(balance, token.decimals);
84
+ const balanceCurrencyDisplayValue = token.balanceValue?.value.toString() ?? '0';
85
+ const priceInCurrency = token.price?.value ?? 0;
86
+ const balanceInCurrency = bnToBig(balance, token.decimals).mul(priceInCurrency).toNumber();
87
+
88
+ return {
89
+ chainId,
90
+ address: token.address,
91
+ name: token.name,
92
+ symbol: token.symbol,
93
+ decimals: token.decimals,
94
+ logoUri: token.logoUri,
95
+ balance,
96
+ balanceCurrencyDisplayValue,
97
+ balanceDisplayValue,
98
+ balanceInCurrency,
99
+ priceInCurrency,
100
+ contractType: 'ERC-20',
101
+ type: TokenType.ERC20,
102
+ change24: 0,
103
+ marketCap: 0,
104
+ vol24: 0,
105
+ };
106
+ });
107
+ };