Skip to content

Commit

Permalink
ShapeShiftController implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
bitpshr committed Jun 5, 2018
1 parent 0b5705f commit 55f247d
Show file tree
Hide file tree
Showing 5 changed files with 290 additions and 1 deletion.
50 changes: 50 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,14 @@
]
},
"devDependencies": {
"@types/fetch-mock": "^6.0.3",
"@types/jest": "^22.2.3",
"@types/node": "^10.1.4",
"@types/sinon": "^4.3.3",
"eth-json-rpc-infura": "^3.1.2",
"eth-phishing-detect": "^1.1.13",
"eth-query": "^2.1.2",
"fetch-mock": "^6.4.3",
"husky": "^0.14.3",
"isomorphic-fetch": "^2.2.1",
"jest": "^22.1.4",
Expand Down
2 changes: 1 addition & 1 deletion src/PhishingController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export class PhishingController extends BaseController<PhishingState, PhishingCo
/**
* Sets a new polling interval
*
* @param interval - Polling interval used to fetch new exchange rates
* @param interval - Polling interval used to fetch new approval lists
*/
set interval(interval: number) {
this.handle && clearInterval(this.handle);
Expand Down
94 changes: 94 additions & 0 deletions src/ShapeShiftController.test.ts
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();
});
});
143 changes: 143 additions & 0 deletions src/ShapeShiftController.ts
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;

0 comments on commit 55f247d

Please sign in to comment.