aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/controllers/computed-balances.js
blob: e04ce2ef7a849059859fceff59dc9db49eeff1c2 (plain) (blame)
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
const ObservableStore = require('obs-store')
const extend = require('xtend')
const BalanceController = require('./balance')

/**
 * @typedef {Object} ComputedBalancesOptions
 * @property {Object} accountTracker Account tracker store reference
 * @property {Object} txController Token controller reference
 * @property {Object} blockTracker Block tracker reference
 * @property {Object} initState Initial state to populate this internal store with
 */

/**
 * Background controller responsible for syncing
 * and computing ETH balances for all accounts
 */
class ComputedbalancesController {
  /**
   * Creates a new controller instance
   *
   * @param {ComputedBalancesOptions} [opts] Controller configuration parameters
   */
  constructor (opts = {}) {
    const { accountTracker, txController, blockTracker } = opts
    this.accountTracker = accountTracker
    this.txController = txController
    this.blockTracker = blockTracker

    const initState = extend({
      computedBalances: {},
    }, opts.initState)
    this.store = new ObservableStore(initState)
    this.balances = {}

    this._initBalanceUpdating()
  }

  /**
   * Updates balances associated with each internal address
   */
  updateAllBalances () {
    Object.keys(this.balances).forEach((balance) => {
      const address = balance.address
      this.balances[address].updateBalance()
    })
  }

  /**
   * Initializes internal address tracking
   *
   * @private
   */
  _initBalanceUpdating () {
    const store = this.accountTracker.store.getState()
    this.syncAllAccountsFromStore(store)
    this.accountTracker.store.subscribe(this.syncAllAccountsFromStore.bind(this))
  }

  /**
   * Uses current account state to sync and track all
   * addresses associated with the current account
   *
   * @param {{ accounts: Object }} store Account tracking state
   */
  syncAllAccountsFromStore (store) {
    const upstream = Object.keys(store.accounts)
    const balances = Object.keys(this.balances)
    .map(address => this.balances[address])

    // Follow new addresses
    for (const address in balances) {
      this.trackAddressIfNotAlready(address)
    }

    // Unfollow old ones
    balances.forEach(({ address }) => {
      if (!upstream.includes(address)) {
        delete this.balances[address]
      }
    })
  }

  /**
   * Conditionally establishes a new subscription
   * to track an address associated with the current
   * account
   *
   * @param {string} address Address to conditionally subscribe to
   */
  trackAddressIfNotAlready (address) {
    const state = this.store.getState()
    if (!(address in state.computedBalances)) {
      this.trackAddress(address)
    }
  }

  /**
   * Establishes a new subscription to track an
   * address associated with the current account
   *
   * @param {string} address Address to conditionally subscribe to
   */
  trackAddress (address) {
    const updater = new BalanceController({
      address,
      accountTracker: this.accountTracker,
      txController: this.txController,
      blockTracker: this.blockTracker,
    })
    updater.store.subscribe((accountBalance) => {
      const newState = this.store.getState()
      newState.computedBalances[address] = accountBalance
      this.store.updateState(newState)
    })
    this.balances[address] = updater
    updater.updateBalance()
  }
}

module.exports = ComputedbalancesController