Skip to content

Commit 55f247d

Browse files
committed
ShapeShiftController implementation
1 parent 0b5705f commit 55f247d

File tree

5 files changed

+290
-1
lines changed

5 files changed

+290
-1
lines changed

package-lock.json

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,14 @@
4747
]
4848
},
4949
"devDependencies": {
50+
"@types/fetch-mock": "^6.0.3",
5051
"@types/jest": "^22.2.3",
5152
"@types/node": "^10.1.4",
5253
"@types/sinon": "^4.3.3",
5354
"eth-json-rpc-infura": "^3.1.2",
5455
"eth-phishing-detect": "^1.1.13",
5556
"eth-query": "^2.1.2",
57+
"fetch-mock": "^6.4.3",
5658
"husky": "^0.14.3",
5759
"isomorphic-fetch": "^2.2.1",
5860
"jest": "^22.1.4",

src/PhishingController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export class PhishingController extends BaseController<PhishingState, PhishingCo
6969
/**
7070
* Sets a new polling interval
7171
*
72-
* @param interval - Polling interval used to fetch new exchange rates
72+
* @param interval - Polling interval used to fetch new approval lists
7373
*/
7474
set interval(interval: number) {
7575
this.handle && clearInterval(this.handle);

src/ShapeShiftController.test.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import 'isomorphic-fetch';
2+
3+
import { getOnce } from 'fetch-mock';
4+
import { stub } from 'sinon';
5+
import ShapeShiftController from './ShapeShiftController';
6+
7+
const PENDING_TX = {
8+
depositAddress: 'foo',
9+
depositType: 'bar',
10+
key: 'shapeshift',
11+
response: undefined,
12+
time: Date.now()
13+
};
14+
15+
const PENDING_TX_ALT = {
16+
depositAddress: 'uh',
17+
depositType: 'oh',
18+
key: 'shapeshift',
19+
response: undefined,
20+
time: Date.now()
21+
};
22+
23+
describe('NetworkStatusController', () => {
24+
it('should set default state', () => {
25+
const controller = new ShapeShiftController();
26+
expect(controller.state).toEqual({ shapeShiftTxList: [] });
27+
});
28+
29+
it('should set default config', () => {
30+
const controller = new ShapeShiftController();
31+
expect(controller.config).toEqual({ interval: 3000 });
32+
});
33+
34+
it('should poll on correct interval', () => {
35+
const mock = stub(global, 'setInterval');
36+
/* tslint:disable-next-line:no-unused-expression */
37+
new ShapeShiftController(undefined, { interval: 1337 });
38+
expect(mock.getCall(0).args[1]).toBe(1337);
39+
mock.restore();
40+
});
41+
42+
it('should update transaction list on interval', () => {
43+
return new Promise((resolve) => {
44+
const controller = new ShapeShiftController(undefined, { interval: 10 });
45+
const mock = stub(controller, 'updateTransactionList');
46+
setTimeout(() => {
47+
expect(mock.called).toBe(true);
48+
mock.restore();
49+
resolve();
50+
}, 20);
51+
});
52+
});
53+
54+
it('should not update infura rate if disabled', async () => {
55+
const controller = new ShapeShiftController({ shapeShiftTxList: [PENDING_TX] }, { disabled: true });
56+
controller.update = stub();
57+
await controller.updateTransactionList();
58+
expect((controller.update as any).called).toBe(false);
59+
});
60+
61+
it('should clear previous interval', () => {
62+
const clearInterval = stub(global, 'clearInterval');
63+
const controller = new ShapeShiftController(undefined, { interval: 1337 });
64+
controller.interval = 1338;
65+
expect(clearInterval.called).toBe(true);
66+
clearInterval.restore();
67+
});
68+
69+
it('should update lists', async () => {
70+
const controller = new ShapeShiftController({ shapeShiftTxList: [PENDING_TX] });
71+
getOnce('begin:https://shapeshift.io', () => ({
72+
body: JSON.stringify({ status: 'pending' })
73+
}));
74+
await controller.updateTransactionList();
75+
getOnce(
76+
'begin:https://shapeshift.io',
77+
() => ({
78+
body: JSON.stringify({ status: 'complete' })
79+
}),
80+
{ overwriteRoutes: true, method: 'GET' }
81+
);
82+
await controller.updateTransactionList();
83+
expect(controller.state.shapeShiftTxList[0].response!.status).toBe('complete');
84+
});
85+
86+
it('should create transaction', () => {
87+
const controller = new ShapeShiftController();
88+
controller.createTransaction('foo', 'bar');
89+
const tx = controller.state.shapeShiftTxList[0];
90+
expect(tx.depositAddress).toBe('foo');
91+
expect(tx.depositType).toBe('bar');
92+
expect(tx.response).toBeUndefined();
93+
});
94+
});

src/ShapeShiftController.ts

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import BaseController, { BaseConfig, BaseState } from './BaseController';
2+
3+
/**
4+
* @type ShapeShiftTransaction
5+
*
6+
* ShapeShift transaction object
7+
*
8+
* @property depositAddress - Address where coins should be deposited
9+
* @property depositType - Abbreviation of the type of crypto currency to be deposited
10+
* @property key - Unique string to identify this transaction as a ShapeShift transaction
11+
* @property response - Populated with a ShapeShiftResponse object upon transaction completion
12+
* @property time - Timestamp when this transction was last updated
13+
*/
14+
export interface ShapeShiftTransaction {
15+
depositAddress: string;
16+
depositType: string;
17+
key: string;
18+
response?: ShapeShiftResponse;
19+
time: number;
20+
}
21+
22+
/**
23+
* @type ShapeShiftResponse
24+
*
25+
* ShapeShift transaction response object
26+
*
27+
* @property status - String indicating transactional status
28+
*/
29+
export interface ShapeShiftResponse {
30+
status: 'complete' | 'failed' | 'no_deposits' | 'received';
31+
}
32+
33+
/**
34+
* @type ShapeShiftConfig
35+
*
36+
* ShapeShift controller configuration
37+
*
38+
* @property interval - Polling interval used to fetch ShapeShift transactions
39+
*/
40+
export interface ShapeShiftConfig extends BaseConfig {
41+
interval: number;
42+
}
43+
44+
/**
45+
* @type ShapeShiftState
46+
*
47+
* ShapeShift controller state
48+
*
49+
* @property shapeShiftTxList - List of ShapeShift transactions
50+
*/
51+
export interface ShapeShiftState extends BaseState {
52+
shapeShiftTxList: ShapeShiftTransaction[];
53+
}
54+
55+
/**
56+
* Controller that passively polls on a set interval for ShapeShift transactions
57+
*/
58+
export class ShapeShiftController extends BaseController<ShapeShiftState, ShapeShiftConfig> {
59+
private handle?: NodeJS.Timer;
60+
61+
private getPendingTransactions() {
62+
return this.state.shapeShiftTxList.filter((tx) => !tx.response || tx.response.status !== 'complete');
63+
}
64+
65+
private getUpdateURL(transaction: ShapeShiftTransaction) {
66+
return `https://shapeshift.io/txStat/${transaction.depositAddress}`;
67+
}
68+
69+
private async updateTransaction(transaction: ShapeShiftTransaction) {
70+
try {
71+
const response = await fetch(this.getUpdateURL(transaction));
72+
transaction.response = await response.json();
73+
if (transaction.response && transaction.response.status === 'complete') {
74+
transaction.time = Date.now();
75+
}
76+
return transaction;
77+
} catch (error) {
78+
/* tslint:disable-next-line:no-empty */
79+
}
80+
}
81+
82+
/**
83+
* Creates a ShapeShiftController instance
84+
*
85+
* @param state - Initial state to set on this controller
86+
* @param config - Initial options used to configure this controller
87+
*/
88+
constructor(state?: Partial<ShapeShiftState>, config?: Partial<ShapeShiftConfig>) {
89+
super(state, config);
90+
this.defaultConfig = { interval: 3000 };
91+
this.defaultState = { shapeShiftTxList: [] };
92+
this.initialize();
93+
}
94+
95+
/**
96+
* Sets a new polling interval
97+
*
98+
* @param interval - Polling interval used to fetch new ShapeShift transactions
99+
*/
100+
set interval(interval: number) {
101+
this.handle && clearInterval(this.handle);
102+
this.updateTransactionList();
103+
this.handle = setInterval(() => {
104+
this.updateTransactionList();
105+
}, interval);
106+
}
107+
108+
/**
109+
* Creates a new ShapeShift transaction
110+
*
111+
* @param depositAddress - Address where coins should be deposited
112+
* @param depositType - Abbreviation of the type of crypto currency to be deposited
113+
*/
114+
createTransaction(depositAddress: string, depositType: string) {
115+
const { shapeShiftTxList } = this.state;
116+
const transaction = {
117+
depositAddress,
118+
depositType,
119+
key: 'shapeshift',
120+
response: undefined,
121+
time: Date.now()
122+
};
123+
124+
shapeShiftTxList.push(transaction);
125+
this.update({ shapeShiftTxList });
126+
}
127+
128+
/**
129+
* Updates list of ShapeShift transactions
130+
*/
131+
async updateTransactionList() {
132+
const { shapeShiftTxList } = this.state;
133+
const pendingTx = this.getPendingTransactions();
134+
135+
if (this.disabled || pendingTx.length === 0) {
136+
return;
137+
}
138+
await Promise.all(pendingTx.map((tx) => this.updateTransaction(tx)));
139+
this.update({ shapeShiftTxList });
140+
}
141+
}
142+
143+
export default ShapeShiftController;

0 commit comments

Comments
 (0)