-
-
Notifications
You must be signed in to change notification settings - Fork 205
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
290 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import 'isomorphic-fetch'; | ||
|
||
import { getOnce } from 'fetch-mock'; | ||
import { stub } from 'sinon'; | ||
import ShapeShiftController from './ShapeShiftController'; | ||
|
||
const PENDING_TX = { | ||
depositAddress: 'foo', | ||
depositType: 'bar', | ||
key: 'shapeshift', | ||
response: undefined, | ||
time: Date.now() | ||
}; | ||
|
||
const PENDING_TX_ALT = { | ||
depositAddress: 'uh', | ||
depositType: 'oh', | ||
key: 'shapeshift', | ||
response: undefined, | ||
time: Date.now() | ||
}; | ||
|
||
describe('NetworkStatusController', () => { | ||
it('should set default state', () => { | ||
const controller = new ShapeShiftController(); | ||
expect(controller.state).toEqual({ shapeShiftTxList: [] }); | ||
}); | ||
|
||
it('should set default config', () => { | ||
const controller = new ShapeShiftController(); | ||
expect(controller.config).toEqual({ interval: 3000 }); | ||
}); | ||
|
||
it('should poll on correct interval', () => { | ||
const mock = stub(global, 'setInterval'); | ||
/* tslint:disable-next-line:no-unused-expression */ | ||
new ShapeShiftController(undefined, { interval: 1337 }); | ||
expect(mock.getCall(0).args[1]).toBe(1337); | ||
mock.restore(); | ||
}); | ||
|
||
it('should update transaction list on interval', () => { | ||
return new Promise((resolve) => { | ||
const controller = new ShapeShiftController(undefined, { interval: 10 }); | ||
const mock = stub(controller, 'updateTransactionList'); | ||
setTimeout(() => { | ||
expect(mock.called).toBe(true); | ||
mock.restore(); | ||
resolve(); | ||
}, 20); | ||
}); | ||
}); | ||
|
||
it('should not update infura rate if disabled', async () => { | ||
const controller = new ShapeShiftController({ shapeShiftTxList: [PENDING_TX] }, { disabled: true }); | ||
controller.update = stub(); | ||
await controller.updateTransactionList(); | ||
expect((controller.update as any).called).toBe(false); | ||
}); | ||
|
||
it('should clear previous interval', () => { | ||
const clearInterval = stub(global, 'clearInterval'); | ||
const controller = new ShapeShiftController(undefined, { interval: 1337 }); | ||
controller.interval = 1338; | ||
expect(clearInterval.called).toBe(true); | ||
clearInterval.restore(); | ||
}); | ||
|
||
it('should update lists', async () => { | ||
const controller = new ShapeShiftController({ shapeShiftTxList: [PENDING_TX] }); | ||
getOnce('begin:https://shapeshift.io', () => ({ | ||
body: JSON.stringify({ status: 'pending' }) | ||
})); | ||
await controller.updateTransactionList(); | ||
getOnce( | ||
'begin:https://shapeshift.io', | ||
() => ({ | ||
body: JSON.stringify({ status: 'complete' }) | ||
}), | ||
{ overwriteRoutes: true, method: 'GET' } | ||
); | ||
await controller.updateTransactionList(); | ||
expect(controller.state.shapeShiftTxList[0].response!.status).toBe('complete'); | ||
}); | ||
|
||
it('should create transaction', () => { | ||
const controller = new ShapeShiftController(); | ||
controller.createTransaction('foo', 'bar'); | ||
const tx = controller.state.shapeShiftTxList[0]; | ||
expect(tx.depositAddress).toBe('foo'); | ||
expect(tx.depositType).toBe('bar'); | ||
expect(tx.response).toBeUndefined(); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
import BaseController, { BaseConfig, BaseState } from './BaseController'; | ||
|
||
/** | ||
* @type ShapeShiftTransaction | ||
* | ||
* ShapeShift transaction object | ||
* | ||
* @property depositAddress - Address where coins should be deposited | ||
* @property depositType - Abbreviation of the type of crypto currency to be deposited | ||
* @property key - Unique string to identify this transaction as a ShapeShift transaction | ||
* @property response - Populated with a ShapeShiftResponse object upon transaction completion | ||
* @property time - Timestamp when this transction was last updated | ||
*/ | ||
export interface ShapeShiftTransaction { | ||
depositAddress: string; | ||
depositType: string; | ||
key: string; | ||
response?: ShapeShiftResponse; | ||
time: number; | ||
} | ||
|
||
/** | ||
* @type ShapeShiftResponse | ||
* | ||
* ShapeShift transaction response object | ||
* | ||
* @property status - String indicating transactional status | ||
*/ | ||
export interface ShapeShiftResponse { | ||
status: 'complete' | 'failed' | 'no_deposits' | 'received'; | ||
} | ||
|
||
/** | ||
* @type ShapeShiftConfig | ||
* | ||
* ShapeShift controller configuration | ||
* | ||
* @property interval - Polling interval used to fetch ShapeShift transactions | ||
*/ | ||
export interface ShapeShiftConfig extends BaseConfig { | ||
interval: number; | ||
} | ||
|
||
/** | ||
* @type ShapeShiftState | ||
* | ||
* ShapeShift controller state | ||
* | ||
* @property shapeShiftTxList - List of ShapeShift transactions | ||
*/ | ||
export interface ShapeShiftState extends BaseState { | ||
shapeShiftTxList: ShapeShiftTransaction[]; | ||
} | ||
|
||
/** | ||
* Controller that passively polls on a set interval for ShapeShift transactions | ||
*/ | ||
export class ShapeShiftController extends BaseController<ShapeShiftState, ShapeShiftConfig> { | ||
private handle?: NodeJS.Timer; | ||
|
||
private getPendingTransactions() { | ||
return this.state.shapeShiftTxList.filter((tx) => !tx.response || tx.response.status !== 'complete'); | ||
} | ||
|
||
private getUpdateURL(transaction: ShapeShiftTransaction) { | ||
return `https://shapeshift.io/txStat/${transaction.depositAddress}`; | ||
} | ||
|
||
private async updateTransaction(transaction: ShapeShiftTransaction) { | ||
try { | ||
const response = await fetch(this.getUpdateURL(transaction)); | ||
transaction.response = await response.json(); | ||
if (transaction.response && transaction.response.status === 'complete') { | ||
transaction.time = Date.now(); | ||
} | ||
return transaction; | ||
} catch (error) { | ||
/* tslint:disable-next-line:no-empty */ | ||
} | ||
} | ||
|
||
/** | ||
* Creates a ShapeShiftController instance | ||
* | ||
* @param state - Initial state to set on this controller | ||
* @param config - Initial options used to configure this controller | ||
*/ | ||
constructor(state?: Partial<ShapeShiftState>, config?: Partial<ShapeShiftConfig>) { | ||
super(state, config); | ||
this.defaultConfig = { interval: 3000 }; | ||
this.defaultState = { shapeShiftTxList: [] }; | ||
this.initialize(); | ||
} | ||
|
||
/** | ||
* Sets a new polling interval | ||
* | ||
* @param interval - Polling interval used to fetch new ShapeShift transactions | ||
*/ | ||
set interval(interval: number) { | ||
this.handle && clearInterval(this.handle); | ||
this.updateTransactionList(); | ||
this.handle = setInterval(() => { | ||
this.updateTransactionList(); | ||
}, interval); | ||
} | ||
|
||
/** | ||
* Creates a new ShapeShift transaction | ||
* | ||
* @param depositAddress - Address where coins should be deposited | ||
* @param depositType - Abbreviation of the type of crypto currency to be deposited | ||
*/ | ||
createTransaction(depositAddress: string, depositType: string) { | ||
const { shapeShiftTxList } = this.state; | ||
const transaction = { | ||
depositAddress, | ||
depositType, | ||
key: 'shapeshift', | ||
response: undefined, | ||
time: Date.now() | ||
}; | ||
|
||
shapeShiftTxList.push(transaction); | ||
this.update({ shapeShiftTxList }); | ||
} | ||
|
||
/** | ||
* Updates list of ShapeShift transactions | ||
*/ | ||
async updateTransactionList() { | ||
const { shapeShiftTxList } = this.state; | ||
const pendingTx = this.getPendingTransactions(); | ||
|
||
if (this.disabled || pendingTx.length === 0) { | ||
return; | ||
} | ||
await Promise.all(pendingTx.map((tx) => this.updateTransaction(tx))); | ||
this.update({ shapeShiftTxList }); | ||
} | ||
} | ||
|
||
export default ShapeShiftController; |