-
-
Notifications
You must be signed in to change notification settings - Fork 196
/
TokenBalancesController.ts
138 lines (127 loc) · 4.11 KB
/
TokenBalancesController.ts
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
137
138
import { BN } from 'ethereumjs-util';
import {
BaseController,
BaseConfig,
BaseState,
} from '@metamask/base-controller';
import { safelyExecute } from '@metamask/controller-utils';
import type { PreferencesState } from '@metamask/preferences-controller';
import { Token } from './TokenRatesController';
import { TokensState } from './TokensController';
import type { AssetsContractController } from './AssetsContractController';
// TODO: Remove this export in the next major release
export { BN };
/**
* @type TokenBalancesConfig
*
* Token balances controller configuration
* @property interval - Polling interval used to fetch new token balances
* @property tokens - List of tokens to track balances for
*/
export interface TokenBalancesConfig extends BaseConfig {
interval: number;
tokens: Token[];
}
/**
* @type TokenBalancesState
*
* Token balances controller state
* @property contractBalances - Hash of token contract addresses to balances
*/
export interface TokenBalancesState extends BaseState {
contractBalances: { [address: string]: BN };
}
/**
* Controller that passively polls on a set interval token balances
* for tokens stored in the TokensController
*/
export class TokenBalancesController extends BaseController<
TokenBalancesConfig,
TokenBalancesState
> {
private handle?: ReturnType<typeof setTimeout>;
/**
* Name of this controller used during composition
*/
override name = 'TokenBalancesController';
private getSelectedAddress: () => PreferencesState['selectedAddress'];
private getERC20BalanceOf: AssetsContractController['getERC20BalanceOf'];
/**
* Creates a TokenBalancesController instance.
*
* @param options - The controller options.
* @param options.onTokensStateChange - Allows subscribing to assets controller state changes.
* @param options.getSelectedAddress - Gets the current selected address.
* @param options.getERC20BalanceOf - Gets the balance of the given account at the given contract address.
* @param config - Initial options used to configure this controller.
* @param state - Initial state to set on this controller.
*/
constructor(
{
onTokensStateChange,
getSelectedAddress,
getERC20BalanceOf,
}: {
onTokensStateChange: (
listener: (tokenState: TokensState) => void,
) => void;
getSelectedAddress: () => PreferencesState['selectedAddress'];
getERC20BalanceOf: AssetsContractController['getERC20BalanceOf'];
},
config?: Partial<TokenBalancesConfig>,
state?: Partial<TokenBalancesState>,
) {
super(config, state);
this.defaultConfig = {
interval: 180000,
tokens: [],
};
this.defaultState = { contractBalances: {} };
this.initialize();
onTokensStateChange(({ tokens, detectedTokens }) => {
this.configure({ tokens: [...tokens, ...detectedTokens] });
this.updateBalances();
});
this.getSelectedAddress = getSelectedAddress;
this.getERC20BalanceOf = getERC20BalanceOf;
this.poll();
}
/**
* Starts a new polling interval.
*
* @param interval - Polling interval used to fetch new token balances.
*/
async poll(interval?: number): Promise<void> {
interval && this.configure({ interval }, false, false);
this.handle && clearTimeout(this.handle);
await safelyExecute(() => this.updateBalances());
this.handle = setTimeout(() => {
this.poll(this.config.interval);
}, this.config.interval);
}
/**
* Updates balances for all tokens.
*/
async updateBalances() {
if (this.disabled) {
return;
}
const { tokens } = this.config;
const newContractBalances: { [address: string]: BN } = {};
for (const i in tokens) {
const { address } = tokens[i];
try {
newContractBalances[address] = await this.getERC20BalanceOf(
address,
this.getSelectedAddress(),
);
tokens[i].balanceError = null;
} catch (error) {
newContractBalances[address] = new BN(0);
tokens[i].balanceError = error;
}
}
this.update({ contractBalances: newContractBalances });
}
}
export default TokenBalancesController;