aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts')
-rw-r--r--app/scripts/controllers/balance.js60
-rw-r--r--app/scripts/controllers/balances.js64
-rw-r--r--app/scripts/controllers/transactions.js1
-rw-r--r--app/scripts/lib/pending-balance-calculator.js53
-rw-r--r--app/scripts/metamask-controller.js16
5 files changed, 193 insertions, 1 deletions
diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js
new file mode 100644
index 000000000..b4e72e751
--- /dev/null
+++ b/app/scripts/controllers/balance.js
@@ -0,0 +1,60 @@
+const ObservableStore = require('obs-store')
+const PendingBalanceCalculator = require('../lib/pending-balance-calculator')
+const BN = require('ethereumjs-util').BN
+
+class BalanceController {
+
+ constructor (opts = {}) {
+ const { address, ethStore, txController } = opts
+ this.address = address
+ this.ethStore = ethStore
+ this.txController = txController
+
+ const initState = {
+ ethBalance: undefined,
+ }
+ this.store = new ObservableStore(initState)
+
+ this.balanceCalc = new PendingBalanceCalculator({
+ getBalance: () => Promise.resolve(this._getBalance()),
+ getPendingTransactions: this._getPendingTransactions.bind(this),
+ })
+
+ this.registerUpdates()
+ }
+
+ async updateBalance () {
+ const balance = await this.balanceCalc.getBalance()
+ this.store.updateState({
+ ethBalance: balance,
+ })
+ }
+
+ registerUpdates () {
+ const update = this.updateBalance.bind(this)
+ this.txController.on('submitted', update)
+ this.txController.on('confirmed', update)
+ this.txController.on('failed', update)
+ this.txController.blockTracker.on('block', update)
+ }
+
+ _getBalance () {
+ const store = this.ethStore.getState()
+ const balances = store.accounts
+ const entry = balances[this.address]
+ const balance = entry.balance
+ return balance ? new BN(balance.substring(2), 16) : undefined
+ }
+
+ _getPendingTransactions () {
+ const pending = this.txController.getFilteredTxList({
+ from: this.address,
+ status: 'submitted',
+ err: undefined,
+ })
+ return Promise.resolve(pending)
+ }
+
+}
+
+module.exports = BalanceController
diff --git a/app/scripts/controllers/balances.js b/app/scripts/controllers/balances.js
new file mode 100644
index 000000000..89c2ca95d
--- /dev/null
+++ b/app/scripts/controllers/balances.js
@@ -0,0 +1,64 @@
+const ObservableStore = require('obs-store')
+const extend = require('xtend')
+const BalanceController = require('./balance')
+
+class BalancesController {
+
+ constructor (opts = {}) {
+ const { ethStore, txController } = opts
+ this.ethStore = ethStore
+ this.txController = txController
+
+ const initState = extend({
+ computedBalances: {},
+ }, opts.initState)
+ this.store = new ObservableStore(initState)
+ this.balances = {}
+
+ this._initBalanceUpdating()
+ }
+
+ updateAllBalances () {
+ for (let address in this.balances) {
+ this.balances[address].updateBalance()
+ }
+ }
+
+ _initBalanceUpdating () {
+ const store = this.ethStore.getState()
+ this.addAnyAccountsFromStore(store)
+ this.ethStore.subscribe(this.addAnyAccountsFromStore.bind(this))
+ }
+
+ addAnyAccountsFromStore(store) {
+ const balances = store.accounts
+
+ for (let address in balances) {
+ this.trackAddressIfNotAlready(address)
+ }
+ }
+
+ trackAddressIfNotAlready (address) {
+ const state = this.store.getState()
+ if (!(address in state.computedBalances)) {
+ this.trackAddress(address)
+ }
+ }
+
+ trackAddress (address) {
+ let updater = new BalanceController({
+ address,
+ ethStore: this.ethStore,
+ txController: this.txController,
+ })
+ updater.store.subscribe((accountBalance) => {
+ let newState = this.store.getState()
+ newState.computedBalances[address] = accountBalance
+ this.store.updateState(newState)
+ })
+ this.balances[address] = updater
+ updater.updateBalance()
+ }
+}
+
+module.exports = BalancesController
diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js
index fb3be6073..59a3f5329 100644
--- a/app/scripts/controllers/transactions.js
+++ b/app/scripts/controllers/transactions.js
@@ -434,6 +434,7 @@ module.exports = class TransactionController extends EventEmitter {
const txMeta = this.getTx(txId)
txMeta.status = status
this.emit(`${txMeta.id}:${status}`, txId)
+ this.emit(`${status}`, txId)
if (status === 'submitted' || status === 'rejected') {
this.emit(`${txMeta.id}:finished`, txMeta)
}
diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js
new file mode 100644
index 000000000..c66bffbbb
--- /dev/null
+++ b/app/scripts/lib/pending-balance-calculator.js
@@ -0,0 +1,53 @@
+const BN = require('ethereumjs-util').BN
+const normalize = require('eth-sig-util').normalize
+
+class PendingBalanceCalculator {
+
+ // Must be initialized with two functions:
+ // getBalance => Returns a promise of a BN of the current balance in Wei
+ // getPendingTransactions => Returns an array of TxMeta Objects,
+ // which have txParams properties, which include value, gasPrice, and gas,
+ // all in a base=16 hex format.
+ constructor ({ getBalance, getPendingTransactions }) {
+ this.getPendingTransactions = getPendingTransactions
+ this.getNetworkBalance = getBalance
+ }
+
+ async getBalance() {
+ const results = await Promise.all([
+ this.getNetworkBalance(),
+ this.getPendingTransactions(),
+ ])
+
+ const balance = results[0]
+ const pending = results[1]
+
+ if (!balance) return undefined
+
+ const pendingValue = pending.reduce((total, tx) => {
+ return total.add(this.valueFor(tx))
+ }, new BN(0))
+
+ return `0x${balance.sub(pendingValue).toString(16)}`
+ }
+
+ valueFor (tx) {
+ const txValue = tx.txParams.value
+ const value = this.hexToBn(txValue)
+ const gasPrice = this.hexToBn(tx.txParams.gasPrice)
+
+ const gas = tx.txParams.gas
+ const gasLimit = tx.txParams.gasLimit
+ const gasLimitBn = this.hexToBn(gas || gasLimit)
+
+ const gasCost = gasPrice.mul(gasLimitBn)
+ return value.add(gasCost)
+ }
+
+ hexToBn (hex) {
+ return new BN(normalize(hex).substring(2), 16)
+ }
+
+}
+
+module.exports = PendingBalanceCalculator
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index a007d6fc5..02c06ead2 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -20,6 +20,7 @@ const BlacklistController = require('./controllers/blacklist')
const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager')
const TransactionController = require('./controllers/transactions')
+const BalancesController = require('./controllers/balances')
const ConfigManager = require('./lib/config-manager')
const nodeify = require('./lib/nodeify')
const accountImporter = require('./account-import-strategies')
@@ -115,6 +116,16 @@ module.exports = class MetamaskController extends EventEmitter {
})
this.txController.on('newUnaprovedTx', opts.showUnapprovedTx.bind(opts))
+ // computed balances (accounting for pending transactions)
+ this.balancesController = new BalancesController({
+ ethStore: this.ethStore,
+ txController: this.txController,
+ })
+ this.networkController.on('networkDidChange', () => {
+ this.balancesController.updateAllBalances()
+ })
+ this.balancesController.updateAllBalances()
+
// notices
this.noticeController = new NoticeController({
initState: initState.NoticeController,
@@ -168,6 +179,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.networkController.store.subscribe(this.sendUpdate.bind(this))
this.ethStore.subscribe(this.sendUpdate.bind(this))
this.txController.memStore.subscribe(this.sendUpdate.bind(this))
+ this.balancesController.store.subscribe(this.sendUpdate.bind(this))
this.messageManager.memStore.subscribe(this.sendUpdate.bind(this))
this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this))
this.keyringController.memStore.subscribe(this.sendUpdate.bind(this))
@@ -242,6 +254,7 @@ module.exports = class MetamaskController extends EventEmitter {
const wallet = this.configManager.getWallet()
const vault = this.keyringController.store.getState().vault
const isInitialized = (!!wallet || !!vault)
+
return extend(
{
isInitialized,
@@ -252,6 +265,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.messageManager.memStore.getState(),
this.personalMessageManager.memStore.getState(),
this.keyringController.memStore.getState(),
+ this.balancesController.store.getState(),
this.preferencesController.store.getState(),
this.addressBookController.store.getState(),
this.currencyController.store.getState(),
@@ -647,4 +661,4 @@ module.exports = class MetamaskController extends EventEmitter {
return Promise.resolve(rpcTarget)
})
}
-} \ No newline at end of file
+}