2017-02-04 05:45:20 +01:00
|
|
|
const ObservableStore = require('obs-store')
|
|
|
|
const extend = require('xtend')
|
2018-04-12 23:06:59 +02:00
|
|
|
const log = require('loglevel')
|
2017-02-04 05:45:20 +01:00
|
|
|
|
|
|
|
// every three seconds when an incomplete tx is waiting
|
|
|
|
const POLLING_INTERVAL = 3000
|
|
|
|
|
|
|
|
class ShapeshiftController {
|
|
|
|
|
2018-04-16 19:08:04 +02:00
|
|
|
/**
|
|
|
|
* Controller responsible for managing the list of shapeshift transactions. On construction, it initiates a poll
|
|
|
|
* that queries a shapeshift.io API for updates to any pending shapeshift transactions
|
|
|
|
*
|
|
|
|
* @typedef {Object} ShapeshiftController
|
2018-04-17 01:53:29 +02:00
|
|
|
* @param {object} opts Overrides the defaults for the initial state of this.store
|
2018-04-16 19:08:04 +02:00
|
|
|
* @property {array} opts.initState initializes the the state of the ShapeshiftController. Can contain an
|
|
|
|
* shapeShiftTxList array.
|
|
|
|
* @property {array} shapeShiftTxList An array of ShapeShiftTx objects
|
|
|
|
*
|
|
|
|
*/
|
2017-02-04 05:45:20 +01:00
|
|
|
constructor (opts = {}) {
|
|
|
|
const initState = extend({
|
|
|
|
shapeShiftTxList: [],
|
2017-02-04 06:12:18 +01:00
|
|
|
}, opts.initState)
|
2017-02-04 05:45:20 +01:00
|
|
|
this.store = new ObservableStore(initState)
|
2017-02-04 06:12:18 +01:00
|
|
|
this.pollForUpdates()
|
2017-02-04 05:45:20 +01:00
|
|
|
}
|
|
|
|
|
2018-04-16 19:08:04 +02:00
|
|
|
/**
|
|
|
|
* Represents, and contains data about, a single shapeshift transaction.
|
|
|
|
* @typedef {Object} ShapeShiftTx
|
|
|
|
* @property {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the
|
|
|
|
* user's Metamask account
|
|
|
|
* @property {string} depositType - An abbreviation of the type of crypto currency to be deposited.
|
2018-04-18 20:38:08 +02:00
|
|
|
* @property {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask
|
2018-04-16 19:08:04 +02:00
|
|
|
* @property {number} time - The time at which the tx was created
|
|
|
|
* @property {object} response - Initiated as an empty object, which will be replaced by a Response object. @see {@link
|
|
|
|
* https://developer.mozilla.org/en-US/docs/Web/API/Response}
|
|
|
|
*/
|
|
|
|
|
2017-02-04 05:45:20 +01:00
|
|
|
//
|
|
|
|
// PUBLIC METHODS
|
|
|
|
//
|
|
|
|
|
2018-04-18 20:41:39 +02:00
|
|
|
/**
|
|
|
|
* A getter for the shapeShiftTxList property
|
|
|
|
*
|
|
|
|
* @returns {array<ShapeShiftTx>}
|
|
|
|
*
|
|
|
|
*/
|
2017-02-04 05:45:20 +01:00
|
|
|
getShapeShiftTxList () {
|
|
|
|
const shapeShiftTxList = this.store.getState().shapeShiftTxList
|
|
|
|
return shapeShiftTxList
|
|
|
|
}
|
|
|
|
|
2018-04-18 20:41:39 +02:00
|
|
|
/**
|
|
|
|
* A getter for all ShapeShiftTx in the shapeShiftTxList that have not successfully completed a deposit.
|
|
|
|
*
|
|
|
|
* @returns {array<ShapeShiftTx>} Only includes ShapeShiftTx which has a response property with a status !== complete
|
|
|
|
*
|
|
|
|
*/
|
2017-02-04 05:45:20 +01:00
|
|
|
getPendingTxs () {
|
|
|
|
const txs = this.getShapeShiftTxList()
|
2017-02-04 06:12:18 +01:00
|
|
|
const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete')
|
2017-02-04 05:45:20 +01:00
|
|
|
return pending
|
|
|
|
}
|
|
|
|
|
2018-04-18 20:41:39 +02:00
|
|
|
/**
|
|
|
|
* A poll that exists as long as there are pending transactions. Each call attempts to update the data of any
|
2018-04-16 19:08:04 +02:00
|
|
|
* pendingTxs, and then calls itself again. If there are no pending txs, the recursive call is not made and
|
|
|
|
* the polling stops.
|
|
|
|
*
|
|
|
|
* this.updateTx is used to attempt the update to the pendingTxs in the ShapeShiftTxList, and that updated data
|
|
|
|
* is saved with saveTx.
|
2018-04-18 20:41:39 +02:00
|
|
|
*
|
|
|
|
*/
|
2017-02-04 05:45:20 +01:00
|
|
|
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))
|
2017-02-04 06:12:18 +01:00
|
|
|
this.timeout = setTimeout(this.pollForUpdates.bind(this), POLLING_INTERVAL)
|
2017-02-04 05:45:20 +01:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2018-04-16 19:08:04 +02:00
|
|
|
/**
|
|
|
|
* Attempts to update a ShapeShiftTx with data from a shapeshift.io API. Both the response and time properties
|
|
|
|
* can be updated. The response property is updated with every call, but the time property is only updated when
|
|
|
|
* the response status updates to 'complete'. This will occur once the user makes a deposit as the ShapeShiftTx
|
|
|
|
* depositAddress
|
|
|
|
*
|
|
|
|
* @param {ShapeShiftTx} tx The tx to update
|
|
|
|
*
|
|
|
|
*/
|
2018-04-03 19:33:10 +02:00
|
|
|
async updateTx (tx) {
|
|
|
|
try {
|
|
|
|
const url = `https://shapeshift.io/txStat/${tx.depositAddress}`
|
|
|
|
const response = await fetch(url)
|
|
|
|
const json = await response.json()
|
2017-02-04 06:12:18 +01:00
|
|
|
tx.response = json
|
2017-02-04 05:45:20 +01:00
|
|
|
if (tx.response.status === 'complete') {
|
|
|
|
tx.time = new Date().getTime()
|
|
|
|
}
|
|
|
|
return tx
|
2018-04-03 19:33:10 +02:00
|
|
|
} catch (err) {
|
|
|
|
log.warn(err)
|
|
|
|
}
|
2017-02-04 05:45:20 +01:00
|
|
|
}
|
|
|
|
|
2018-04-18 20:41:39 +02:00
|
|
|
/**
|
|
|
|
* Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the
|
2018-04-16 19:08:04 +02:00
|
|
|
* shapeShiftTxList, nothing happens.
|
2018-04-18 20:41:39 +02:00
|
|
|
*
|
|
|
|
* @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList
|
|
|
|
*
|
|
|
|
*/
|
2017-02-04 05:45:20 +01:00
|
|
|
saveTx (tx) {
|
|
|
|
const { shapeShiftTxList } = this.store.getState()
|
|
|
|
const index = shapeShiftTxList.indexOf(tx)
|
|
|
|
if (index !== -1) {
|
|
|
|
shapeShiftTxList[index] = tx
|
|
|
|
this.store.updateState({ shapeShiftTxList })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-18 20:41:39 +02:00
|
|
|
/**
|
|
|
|
* Removes a ShapeShiftTx from the shapeShiftTxList
|
|
|
|
*
|
|
|
|
* @param {ShapeShiftTx} tx The tx to remove
|
|
|
|
*
|
|
|
|
*/
|
2017-02-04 06:36:04 +01:00
|
|
|
removeShapeShiftTx (tx) {
|
|
|
|
const { shapeShiftTxList } = this.store.getState()
|
|
|
|
const index = shapeShiftTxList.indexOf(index)
|
|
|
|
if (index !== -1) {
|
|
|
|
shapeShiftTxList.splice(index, 1)
|
|
|
|
}
|
|
|
|
this.updateState({ shapeShiftTxList })
|
|
|
|
}
|
|
|
|
|
2018-04-18 20:41:39 +02:00
|
|
|
/**
|
|
|
|
* Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs
|
|
|
|
*
|
2018-04-16 19:08:04 +02:00
|
|
|
* @param {string} depositAddress - An address at which to send a crypto deposit, so that eth can be sent to the
|
|
|
|
* user's Metamask account
|
|
|
|
* @param {string} depositType - An abbreviation of the type of crypto currency to be deposited.
|
2018-04-18 20:41:39 +02:00
|
|
|
*
|
|
|
|
*/
|
2017-02-04 05:45:20 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
|
|
|
this.store.updateState({ shapeShiftTxList })
|
|
|
|
this.pollForUpdates()
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = ShapeshiftController
|