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
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
const ObservableStore = require('obs-store')
const extend = require('xtend')
const log = require('loglevel')
// every three seconds when an incomplete tx is waiting
const POLLING_INTERVAL = 3000
class ShapeshiftController {
/**
* 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
* @param {object} opts Overrides the defaults for the initial state of this.store
* @property {array} opts.initState initializes the the state of the ShapeshiftController. Can contain an
* shapeShiftTxList array.
* @property {array} shapeShiftTxList An array of ShapeShiftTx objects
*
*/
constructor (opts = {}) {
const initState = extend({
shapeShiftTxList: [],
}, opts.initState)
this.store = new ObservableStore(initState)
this.pollForUpdates()
}
/**
* 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.
* @property {string} key - The 'shapeshift' key differentiates this from other types of txs in Metamask
* @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}
*/
//
// PUBLIC METHODS
//
/**
* A getter for the shapeShiftTxList property
*
* @returns {array<ShapeShiftTx>}
*
*/
getShapeShiftTxList () {
const shapeShiftTxList = this.store.getState().shapeShiftTxList
return shapeShiftTxList
}
/**
* 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
*
*/
getPendingTxs () {
const txs = this.getShapeShiftTxList()
const pending = txs.filter(tx => tx.response && tx.response.status !== 'complete')
return pending
}
/**
* A poll that exists as long as there are pending transactions. Each call attempts to update the data of any
* 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.
*
*/
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))
this.timeout = setTimeout(this.pollForUpdates.bind(this), POLLING_INTERVAL)
})
}
/**
* 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
*
*/
async updateTx (tx) {
try {
const url = `https://shapeshift.io/txStat/${tx.depositAddress}`
const response = await fetch(url)
const json = await response.json()
tx.response = json
if (tx.response.status === 'complete') {
tx.time = new Date().getTime()
}
return tx
} catch (err) {
log.warn(err)
}
}
/**
* Saves an updated to a ShapeShiftTx in the shapeShiftTxList. If the passed ShapeShiftTx is not in the
* shapeShiftTxList, nothing happens.
*
* @param {ShapeShiftTx} tx The updated tx to save, if it exists in the current shapeShiftTxList
*
*/
saveTx (tx) {
const { shapeShiftTxList } = this.store.getState()
const index = shapeShiftTxList.indexOf(tx)
if (index !== -1) {
shapeShiftTxList[index] = tx
this.store.updateState({ shapeShiftTxList })
}
}
/**
* Removes a ShapeShiftTx from the shapeShiftTxList
*
* @param {ShapeShiftTx} tx The tx to remove
*
*/
removeShapeShiftTx (tx) {
const { shapeShiftTxList } = this.store.getState()
const index = shapeShiftTxList.indexOf(index)
if (index !== -1) {
shapeShiftTxList.splice(index, 1)
}
this.updateState({ shapeShiftTxList })
}
/**
* Creates a new ShapeShiftTx, adds it to the shapeShiftTxList, and initiates a new poll for updates of pending txs
*
* @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.
*
*/
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
|