From 13ee92909cce93b37eec2092757e4aab174a970e Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 3 Feb 2017 20:45:20 -0800 Subject: [PATCH 1/9] Mostly got shapeshift tx management into its own controller Rendering the list is still having issues, so this isn't done yet. --- app/scripts/lib/config-manager.js | 34 -------- app/scripts/lib/controllers/shapeshift.js | 100 ++++++++++++++++++++++ app/scripts/metamask-controller.js | 15 +++- ui/app/actions.js | 3 +- 4 files changed, 114 insertions(+), 38 deletions(-) create mode 100644 app/scripts/lib/controllers/shapeshift.js diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index a9b86ca8c..7ae2d4400 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -250,40 +250,6 @@ ConfigManager.prototype.getTOSHash = function () { return data.TOSHash } -ConfigManager.prototype.getShapeShiftTxList = function () { - var data = this.getData() - var shapeShiftTxList = data.shapeShiftTxList ? data.shapeShiftTxList : [] - shapeShiftTxList.forEach((tx) => { - if (tx.response.status !== 'complete') { - var requestListner = function (request) { - tx.response = JSON.parse(this.responseText) - if (tx.response.status === 'complete') { - tx.time = new Date().getTime() - } - } - - var shapShiftReq = new XMLHttpRequest() - shapShiftReq.addEventListener('load', requestListner) - shapShiftReq.open('GET', `https://shapeshift.io/txStat/${tx.depositAddress}`, true) - shapShiftReq.send() - } - }) - this.setData(data) - return shapeShiftTxList -} - -ConfigManager.prototype.createShapeShiftTx = function (depositAddress, depositType) { - var data = this.getData() - - var shapeShiftTx = {depositAddress, depositType, key: 'shapeshift', time: new Date().getTime(), response: {}} - if (!data.shapeShiftTxList) { - data.shapeShiftTxList = [shapeShiftTx] - } else { - data.shapeShiftTxList.push(shapeShiftTx) - } - this.setData(data) -} - ConfigManager.prototype.getGasMultiplier = function () { var data = this.getData() return data.gasMultiplier diff --git a/app/scripts/lib/controllers/shapeshift.js b/app/scripts/lib/controllers/shapeshift.js new file mode 100644 index 000000000..6c02dc3eb --- /dev/null +++ b/app/scripts/lib/controllers/shapeshift.js @@ -0,0 +1,100 @@ +const ObservableStore = require('obs-store') +const extend = require('xtend') + +// every three seconds when an incomplete tx is waiting +const POLLING_INTERVAL = 3000 + +class ShapeshiftController { + + constructor (opts = {}) { + const initState = extend({ + shapeShiftTxList: [], + }, opts) + this.store = new ObservableStore(initState) + } + + // + // PUBLIC METHODS + // + + getShapeShiftTxList () { + const shapeShiftTxList = this.store.getState().shapeShiftTxList + + shapeShiftTxList.forEach((tx) => { + if (tx.response.status === 'no_deposits') { + this.updateTx(tx) + } + }) + console.dir({shapeShiftTxList}) + return shapeShiftTxList + } + + getPendingTxs () { + const txs = this.getShapeShiftTxList() + const pending = txs.filter(tx => tx.response.status !== 'complete') + return pending + } + + pollForUpdates () { + const pendingTxs = this.getPendingTxs() + + if (pendingTxs.length === 0) { + return + } + + Promise.all(pendingTxs.map((tx) => { + return this.updateTx(tx) + })) + .then((results) => { + results.forEach(tx => this.saveTx(tx)) + setTimeout(this.pollForUpdates.bind(this), POLLING_INTERVAL) + }) + } + + updateTx (tx) { + const url = `https://shapeshift.io/txStat/${tx.depositAddress}` + return fetch(url) + .then((response) => { + tx.response = response.json() + if (tx.response.status === 'complete') { + tx.time = new Date().getTime() + } + return tx + }) + } + + saveTx (tx) { + const { shapeShiftTxList } = this.store.getState() + const index = shapeShiftTxList.indexOf(tx) + if (index !== -1) { + shapeShiftTxList[index] = tx + this.store.updateState({ shapeShiftTxList }) + } + } + + createShapeShiftTx (depositAddress, depositType) { + const state = this.store.getState() + let { shapeShiftTxList } = state + + var shapeShiftTx = { + depositAddress, + depositType, + key: 'shapeshift', + time: new Date().getTime(), + response: {}, + } + + if (!shapeShiftTxList) { + shapeShiftTxList = [shapeShiftTx] + } else { + shapeShiftTxList.push(shapeShiftTx) + } + console.dir({ shapeShiftTxList }) + + this.store.updateState({ shapeShiftTxList }) + this.pollForUpdates() + } + +} + +module.exports = ShapeshiftController diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 066e389e2..fb2040c63 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -14,6 +14,7 @@ const KeyringController = require('./keyring-controller') const PreferencesController = require('./lib/controllers/preferences') const CurrencyController = require('./lib/controllers/currency') const NoticeController = require('./notice-controller') +const ShapeShiftController = require('./lib/controllers/shapeshift') const MessageManager = require('./lib/message-manager') const TxManager = require('./transaction-manager') const ConfigManager = require('./lib/config-manager') @@ -98,6 +99,10 @@ module.exports = class MetamaskController extends EventEmitter { // to be uncommented when retrieving notices from a remote server. // this.noticeController.startPolling() + this.shapeshiftController = new ShapeShiftController({ + initState: initState.ShapeShiftController, + }) + this.lookupNetwork() this.messageManager = new MessageManager() this.publicConfigStore = this.initPublicConfigStore() @@ -125,6 +130,9 @@ module.exports = class MetamaskController extends EventEmitter { this.noticeController.store.subscribe((state) => { this.store.updateState({ NoticeController: state }) }) + this.shapeshiftController.store.subscribe((state) => { + this.store.updateState({ ShapeShiftController: state }) + }) // manual mem state subscriptions this.networkStore.subscribe(this.sendUpdate.bind(this)) @@ -135,6 +143,7 @@ module.exports = class MetamaskController extends EventEmitter { this.preferencesController.store.subscribe(this.sendUpdate.bind(this)) this.currencyController.store.subscribe(this.sendUpdate.bind(this)) this.noticeController.memStore.subscribe(this.sendUpdate.bind(this)) + this.shapeshiftController.store.subscribe(this.sendUpdate.bind(this)) } // @@ -207,8 +216,8 @@ module.exports = class MetamaskController extends EventEmitter { this.noticeController.memStore.getState(), // config manager this.configManager.getConfig(), + this.shapeshiftController.store.getState(), { - shapeShiftTxList: this.configManager.getShapeShiftTxList(), lostAccounts: this.configManager.getLostAccounts(), isDisclaimerConfirmed: this.configManager.getConfirmedDisclaimer(), seedWords: this.configManager.getSeedWords(), @@ -327,7 +336,7 @@ module.exports = class MetamaskController extends EventEmitter { ) } - sendUpdate () { + sendUpdate () { this.emit('update', this.getState()) } @@ -597,7 +606,7 @@ module.exports = class MetamaskController extends EventEmitter { } createShapeShiftTx (depositAddress, depositType) { - this.configManager.createShapeShiftTx(depositAddress, depositType) + this.shapeshiftController.createShapeShiftTx(depositAddress, depositType) } setGasMultiplier (gasMultiplier, cb) { diff --git a/ui/app/actions.js b/ui/app/actions.js index c153a55a6..0c6e40552 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -843,6 +843,7 @@ function coinShiftRquest (data, marketData) { return (dispatch) => { dispatch(actions.showLoadingIndication()) shapeShiftRequest('shift', { method: 'POST', data}, (response) => { + dispatch(actions.hideLoadingIndication()) if (response.error) return dispatch(actions.displayWarning(response.error)) var message = ` Deposit your ${response.depositType} to the address bellow:` @@ -933,4 +934,4 @@ function forceUpdateMetamaskState(dispatch){ } dispatch(actions.updateMetamaskState(newState)) }) -} \ No newline at end of file +} From 4dc71ed57bab3e8310d37e2fb2a4c495ed3ca5d0 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 3 Feb 2017 21:12:18 -0800 Subject: [PATCH 2/9] Got ShapeShiftController back to working --- app/scripts/lib/controllers/shapeshift.js | 19 +++++------- app/scripts/migrations/010.js | 36 +++++++++++++++++++++++ 2 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 app/scripts/migrations/010.js diff --git a/app/scripts/lib/controllers/shapeshift.js b/app/scripts/lib/controllers/shapeshift.js index 6c02dc3eb..bcfe3e14c 100644 --- a/app/scripts/lib/controllers/shapeshift.js +++ b/app/scripts/lib/controllers/shapeshift.js @@ -9,8 +9,9 @@ class ShapeshiftController { constructor (opts = {}) { const initState = extend({ shapeShiftTxList: [], - }, opts) + }, opts.initState) this.store = new ObservableStore(initState) + this.pollForUpdates() } // @@ -19,19 +20,12 @@ class ShapeshiftController { getShapeShiftTxList () { const shapeShiftTxList = this.store.getState().shapeShiftTxList - - shapeShiftTxList.forEach((tx) => { - if (tx.response.status === 'no_deposits') { - this.updateTx(tx) - } - }) - console.dir({shapeShiftTxList}) return shapeShiftTxList } getPendingTxs () { const txs = this.getShapeShiftTxList() - const pending = txs.filter(tx => tx.response.status !== 'complete') + const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete') return pending } @@ -47,7 +41,7 @@ class ShapeshiftController { })) .then((results) => { results.forEach(tx => this.saveTx(tx)) - setTimeout(this.pollForUpdates.bind(this), POLLING_INTERVAL) + this.timeout = setTimeout(this.pollForUpdates.bind(this), POLLING_INTERVAL) }) } @@ -55,7 +49,9 @@ class ShapeshiftController { const url = `https://shapeshift.io/txStat/${tx.depositAddress}` return fetch(url) .then((response) => { - tx.response = response.json() + return response.json() + }).then((json) => { + tx.response = json if (tx.response.status === 'complete') { tx.time = new Date().getTime() } @@ -89,7 +85,6 @@ class ShapeshiftController { } else { shapeShiftTxList.push(shapeShiftTx) } - console.dir({ shapeShiftTxList }) this.store.updateState({ shapeShiftTxList }) this.pollForUpdates() diff --git a/app/scripts/migrations/010.js b/app/scripts/migrations/010.js new file mode 100644 index 000000000..d41c63fcd --- /dev/null +++ b/app/scripts/migrations/010.js @@ -0,0 +1,36 @@ +const version = 10 + +/* + +This migration breaks out the CurrencyController substate + +*/ + +const merge = require('deep-extend') + +module.exports = { + version, + + migrate: function (versionedData) { + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = merge({}, state, { + ShapeShiftController: { + shapeShiftTxList: state.shapeShiftTxList || [], + }, + }) + delete newState.shapeShiftTxList + + return newState +} From c9024655d3f4fa4d86735556e9e25f0eb63dfdb8 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 3 Feb 2017 21:35:54 -0800 Subject: [PATCH 3/9] Add migration to index --- app/scripts/migrations/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 22bf008ba..2db8646b0 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -1,5 +1,5 @@ /* The migrator has two methods the user should be concerned with: - * + * * getData(), which returns the app-consumable data object * saveData(), which persists the app-consumable data object. */ @@ -20,4 +20,5 @@ module.exports = [ require('./007'), require('./008'), require('./009'), + require('./010'), ] From 5d37f90787cdeec130537e61626f92d6f8a7b5e3 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 3 Feb 2017 21:36:04 -0800 Subject: [PATCH 4/9] Automatically remove shapeshift txs over 11 minutes old with no payment --- app/scripts/lib/controllers/shapeshift.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/app/scripts/lib/controllers/shapeshift.js b/app/scripts/lib/controllers/shapeshift.js index bcfe3e14c..6d1c95323 100644 --- a/app/scripts/lib/controllers/shapeshift.js +++ b/app/scripts/lib/controllers/shapeshift.js @@ -4,6 +4,9 @@ const extend = require('xtend') // every three seconds when an incomplete tx is waiting const POLLING_INTERVAL = 3000 +// drop txs that haven't been paid to in 11 mins +const TIMEOUT_LIMIT = 660000 + class ShapeshiftController { constructor (opts = {}) { @@ -24,11 +27,21 @@ class ShapeshiftController { } getPendingTxs () { + this.removeOldTxs() const txs = this.getShapeShiftTxList() const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete') return pending } + removeOldTxs() { + const { shapeShiftTxList } = this.store.getState() + const now = new Date().getTime() + const old = shapeShiftTxList.find((tx) => { + return tx.time + TIMEOUT_LIMIT < now + }) + old.forEach(tx => this.removeShapeShiftTx(tx)) + } + pollForUpdates () { const pendingTxs = this.getPendingTxs() @@ -68,6 +81,15 @@ class ShapeshiftController { } } + removeShapeShiftTx (tx) { + const { shapeShiftTxList } = this.store.getState() + const index = shapeShiftTxList.indexOf(index) + if (index !== -1) { + shapeShiftTxList.splice(index, 1) + } + this.updateState({ shapeShiftTxList }) + } + createShapeShiftTx (depositAddress, depositType) { const state = this.store.getState() let { shapeShiftTxList } = state From 901eeb5c102dbd8e42d5835e4d35c10fe0301086 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 3 Feb 2017 21:39:22 -0800 Subject: [PATCH 5/9] Fix bug when clearing old shapeshift txs --- app/scripts/lib/controllers/shapeshift.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/controllers/shapeshift.js b/app/scripts/lib/controllers/shapeshift.js index 6d1c95323..ec27b37a0 100644 --- a/app/scripts/lib/controllers/shapeshift.js +++ b/app/scripts/lib/controllers/shapeshift.js @@ -39,7 +39,9 @@ class ShapeshiftController { const old = shapeShiftTxList.find((tx) => { return tx.time + TIMEOUT_LIMIT < now }) - old.forEach(tx => this.removeShapeShiftTx(tx)) + if (old) { + old.forEach(tx => this.removeShapeShiftTx(tx)) + } } pollForUpdates () { From af439cc6cf10fa0387f7e2196d5fefaf84f2f0a2 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 3 Feb 2017 21:40:27 -0800 Subject: [PATCH 6/9] Do not remove completed shapeshift deposits --- app/scripts/lib/controllers/shapeshift.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/scripts/lib/controllers/shapeshift.js b/app/scripts/lib/controllers/shapeshift.js index ec27b37a0..fbdc2c180 100644 --- a/app/scripts/lib/controllers/shapeshift.js +++ b/app/scripts/lib/controllers/shapeshift.js @@ -37,7 +37,8 @@ class ShapeshiftController { const { shapeShiftTxList } = this.store.getState() const now = new Date().getTime() const old = shapeShiftTxList.find((tx) => { - return tx.time + TIMEOUT_LIMIT < now + return tx.time + TIMEOUT_LIMIT < now && + tx.response && tx.response.status === 'no_deposits' }) if (old) { old.forEach(tx => this.removeShapeShiftTx(tx)) From 89bbccb09cbb29dd4b1f6a8c7ef3be137da4b243 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sat, 4 Feb 2017 15:15:50 -0800 Subject: [PATCH 7/9] Stop removing old shapeshift txs --- app/scripts/lib/controllers/shapeshift.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/app/scripts/lib/controllers/shapeshift.js b/app/scripts/lib/controllers/shapeshift.js index fbdc2c180..3d955c01f 100644 --- a/app/scripts/lib/controllers/shapeshift.js +++ b/app/scripts/lib/controllers/shapeshift.js @@ -4,9 +4,6 @@ const extend = require('xtend') // every three seconds when an incomplete tx is waiting const POLLING_INTERVAL = 3000 -// drop txs that haven't been paid to in 11 mins -const TIMEOUT_LIMIT = 660000 - class ShapeshiftController { constructor (opts = {}) { @@ -27,24 +24,11 @@ class ShapeshiftController { } getPendingTxs () { - this.removeOldTxs() const txs = this.getShapeShiftTxList() const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete') return pending } - removeOldTxs() { - const { shapeShiftTxList } = this.store.getState() - const now = new Date().getTime() - const old = shapeShiftTxList.find((tx) => { - return tx.time + TIMEOUT_LIMIT < now && - tx.response && tx.response.status === 'no_deposits' - }) - if (old) { - old.forEach(tx => this.removeShapeShiftTx(tx)) - } - } - pollForUpdates () { const pendingTxs = this.getPendingTxs() From 0c0c0051e4cd351c2ffc5bd05a527364fd1445aa Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sat, 4 Feb 2017 15:20:31 -0800 Subject: [PATCH 8/9] Remove shapeshift tx list from idStore --- app/scripts/lib/idStore.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index ac395440d..c320a46e9 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -95,8 +95,7 @@ IdentityStore.prototype.getState = function () { isUnlocked: this._isUnlocked(), seedWords: seedWords, isDisclaimerConfirmed: configManager.getConfirmedDisclaimer(), - selectedAddress: configManager.getSelectedAccount(), - shapeShiftTxList: configManager.getShapeShiftTxList(), + tselectedAddress: configManager.getSelectedAccount(), gasMultiplier: configManager.getGasMultiplier(), })) } From c0637f8d6ad969f16b7e8b582f462a7f9c480537 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Sat, 4 Feb 2017 16:32:09 -0800 Subject: [PATCH 9/9] Fix typo --- app/scripts/lib/idStore.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index c320a46e9..1afe5f651 100644 --- a/app/scripts/lib/idStore.js +++ b/app/scripts/lib/idStore.js @@ -95,7 +95,7 @@ IdentityStore.prototype.getState = function () { isUnlocked: this._isUnlocked(), seedWords: seedWords, isDisclaimerConfirmed: configManager.getConfirmedDisclaimer(), - tselectedAddress: configManager.getSelectedAccount(), + selectedAddress: configManager.getSelectedAccount(), gasMultiplier: configManager.getGasMultiplier(), })) }