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
|
const ObservableStore = require('obs-store')
const PendingBalanceCalculator = require('../lib/pending-balance-calculator')
const BN = require('ethereumjs-util').BN
class BalanceController {
/**
* Controller responsible for storing and updating an account's balance.
*
* @typedef {Object} BalanceController
* @param {Object} opts Initialize various properties of the class.
* @property {string} address A base 16 hex string. The account address which has the balance managed by this
* BalanceController.
* @property {AccountTracker} accountTracker Stores and updates the users accounts
* for which this BalanceController manages balance.
* @property {TransactionController} txController Stores, tracks and manages transactions. Here used to create a listener for
* transaction updates.
* @property {BlockTracker} blockTracker Tracks updates to blocks. On new blocks, this BalanceController updates its balance
* @property {Object} store The store for the ethBalance
* @property {string} store.ethBalance A base 16 hex string. The balance for the current account.
* @property {PendingBalanceCalculator} balanceCalc Used to calculate the accounts balance with possible pending
* transaction costs taken into account.
*
*/
constructor (opts = {}) {
this._validateParams(opts)
const { address, accountTracker, txController, blockTracker } = opts
this.address = address
this.accountTracker = accountTracker
this.txController = txController
this.blockTracker = blockTracker
const initState = {
ethBalance: undefined,
}
this.store = new ObservableStore(initState)
this.balanceCalc = new PendingBalanceCalculator({
getBalance: () => this._getBalance(),
getPendingTransactions: this._getPendingTransactions.bind(this),
})
this._registerUpdates()
}
/**
* Updates the ethBalance property to the current pending balance
*
* @returns {Promise<void>} Promises undefined
*/
async updateBalance () {
const balance = await this.balanceCalc.getBalance()
this.store.updateState({
ethBalance: balance,
})
}
/**
* Sets up listeners and subscriptions which should trigger an update of ethBalance. These updates include:
* - when a transaction changes state to 'submitted', 'confirmed' or 'failed'
* - when the current account changes (i.e. a new account is selected)
* - when there is a block update
*
* @private
*
*/
_registerUpdates () {
const update = this.updateBalance.bind(this)
this.txController.on('tx:status-update', (txId, status) => {
switch (status) {
case 'submitted':
case 'confirmed':
case 'failed':
update()
return
default:
return
}
})
this.accountTracker.store.subscribe(update)
this.blockTracker.on('latest', update)
}
/**
* Gets the balance, as a base 16 hex string, of the account at this BalanceController's current address.
* If the current account has no balance, returns undefined.
*
* @returns {Promise<BN|void>} Promises a BN with a value equal to the balance of the current account, or undefined
* if the current account has no balance
*
*/
async _getBalance () {
const { accounts } = this.accountTracker.store.getState()
const entry = accounts[this.address]
const balance = entry.balance
return balance ? new BN(balance.substring(2), 16) : undefined
}
/**
* Gets the pending transactions (i.e. those with a 'submitted' status). These are accessed from the
* TransactionController passed to this BalanceController during construction.
*
* @private
* @returns {Promise<array>} Promises an array of transaction objects.
*
*/
async _getPendingTransactions () {
const pending = this.txController.getFilteredTxList({
from: this.address,
status: 'submitted',
err: undefined,
})
return pending
}
/**
* Validates that the passed options have all required properties.
*
* @param {Object} opts The options object to validate
* @throws {string} Throw a custom error indicating that address, accountTracker, txController and blockTracker are
* missing and at least one is required
*
*/
_validateParams (opts) {
const { address, accountTracker, txController, blockTracker } = opts
if (!address || !accountTracker || !txController || !blockTracker) {
const error = 'Cannot construct a balance checker without address, accountTracker, txController, and blockTracker.'
throw new Error(error)
}
}
}
module.exports = BalanceController
|