Skip to content

Commit

Permalink
Add initial SmartTransactionsController (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
wachunei authored Sep 1, 2021
1 parent 5c511c6 commit ab8dd85
Show file tree
Hide file tree
Showing 7 changed files with 2,303 additions and 23 deletions.
13 changes: 10 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@metamask/module-template",
"name": "@metamask/smart-transactions-controller",
"version": "0.0.0",
"description": "The MetaMask Node module template.",
"description": "MetaMask controller for Smart Transactions.",
"repository": {
"type": "git",
"url": "https://github.com/MetaMask/metamask-module-template.git"
Expand All @@ -23,6 +23,9 @@
"build:clean": "rimraf dist && yarn build",
"build": "tsc --project ."
},
"dependencies": {
"@metamask/controllers": "^15.0.0"
},
"devDependencies": {
"@lavamoat/allow-scripts": "^1.0.5",
"@metamask/auto-changelog": "^2.3.0",
Expand All @@ -31,6 +34,7 @@
"@metamask/eslint-config-nodejs": "^8.0.0",
"@metamask/eslint-config-typescript": "^8.0.0",
"@types/jest": "^26.0.13",
"@types/node": "^16.7.8",
"@typescript-eslint/eslint-plugin": "^4.21.0",
"@typescript-eslint/parser": "^4.21.0",
"eslint": "^7.23.0",
Expand All @@ -55,7 +59,10 @@
},
"lavamoat": {
"allowScripts": {
"@lavamoat/preinstall-always-fail": false
"@lavamoat/preinstall-always-fail": false,
"keccak": false,
"secp256k1": false,
"core-js": false
}
}
}
98 changes: 98 additions & 0 deletions src/SmartTransactionsController.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { NetworkState } from '@metamask/controllers';
import SmartTransactionsController, {
DEFAULT_INTERVAL,
} from './SmartTransactionsController';

describe('SmartTransactionsController', () => {
let smartTransactionsController: SmartTransactionsController;
let networkListener: (networkState: NetworkState) => void;

beforeEach(() => {
smartTransactionsController = new SmartTransactionsController({
onNetworkStateChange: (listener) => {
networkListener = listener;
},
});
});

afterEach(async () => {
jest.clearAllMocks();
await smartTransactionsController.stop();
});

it('should initialize with default config', () => {
expect(smartTransactionsController.config).toStrictEqual({
interval: DEFAULT_INTERVAL,
allowedNetworks: ['1'],
chainId: '',
});
});

it('should initialize with default state', () => {
expect(smartTransactionsController.state).toStrictEqual({
smartTransactions: {},
userOptIn: undefined,
});
});

describe('onNetworkChange', () => {
it('should be triggered', () => {
networkListener({ provider: { chainId: '52' } } as NetworkState);
expect(smartTransactionsController.config.chainId).toBe('52');
});

it('should call poll', () => {
const pollSpy = jest.spyOn(smartTransactionsController, 'poll');
networkListener({ provider: { chainId: '2' } } as NetworkState);
expect(pollSpy).toHaveBeenCalled();
});
});

describe('poll', () => {
it('should poll with interval', async () => {
const interval = 35000;
const pollSpy = jest.spyOn(smartTransactionsController, 'poll');
const updateSmartTransactionsSpy = jest.spyOn(
smartTransactionsController,
'updateSmartTransactions',
);
expect(pollSpy).toHaveBeenCalledTimes(0);
expect(updateSmartTransactionsSpy).toHaveBeenCalledTimes(0);
networkListener({ provider: { chainId: '1' } } as NetworkState);
expect(pollSpy).toHaveBeenCalledTimes(1);
expect(updateSmartTransactionsSpy).toHaveBeenCalledTimes(1);
await smartTransactionsController.stop();
jest.useFakeTimers();
await smartTransactionsController.poll(interval);
expect(pollSpy).toHaveBeenCalledTimes(2);
expect(updateSmartTransactionsSpy).toHaveBeenCalledTimes(2);
jest.advanceTimersByTime(interval);
expect(pollSpy).toHaveBeenCalledTimes(3);
expect(updateSmartTransactionsSpy).toHaveBeenCalledTimes(3);
await smartTransactionsController.stop();
jest.clearAllTimers();
jest.useRealTimers();
});

it('should not updateSmartTransactions on unsupported networks', async () => {
const updateSmartTransactionsSpy = jest.spyOn(
smartTransactionsController,
'updateSmartTransactions',
);
expect(updateSmartTransactionsSpy).not.toHaveBeenCalled();
networkListener({ provider: { chainId: '56' } } as NetworkState);
expect(updateSmartTransactionsSpy).not.toHaveBeenCalled();
});
});

describe('setOptInState', () => {
it('should set optIn state', () => {
smartTransactionsController.setOptInState(true);
expect(smartTransactionsController.state.userOptIn).toBe(true);
smartTransactionsController.setOptInState(false);
expect(smartTransactionsController.state.userOptIn).toBe(false);
smartTransactionsController.setOptInState(undefined);
expect(smartTransactionsController.state.userOptIn).toBeUndefined();
});
});
});
83 changes: 83 additions & 0 deletions src/SmartTransactionsController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
BaseConfig,
BaseController,
BaseState,
NetworkState,
util,
} from '@metamask/controllers';

export const DEFAULT_INTERVAL = 5 * 60 * 1000;

export interface SmartTransactionsConfig extends BaseConfig {
interval: number;
chainId: string;
allowedNetworks: string[];
}

export interface SmartTransactionsState extends BaseState {
smartTransactions: Record<string, any>;
userOptIn: boolean | undefined;
}

export default class SmartTransactionsController extends BaseController<
SmartTransactionsConfig,
SmartTransactionsState
> {
private handle?: NodeJS.Timeout;

constructor(
{
onNetworkStateChange,
}: {
onNetworkStateChange: (
listener: (networkState: NetworkState) => void,
) => void;
},
config?: Partial<SmartTransactionsConfig>,
state?: Partial<SmartTransactionsState>,
) {
super(config, state);
this.defaultConfig = {
interval: DEFAULT_INTERVAL,
chainId: '',
allowedNetworks: ['1'],
};

this.defaultState = {
smartTransactions: {},
userOptIn: undefined,
};
this.initialize();
onNetworkStateChange(({ provider }) => {
const { chainId } = provider;
this.configure({ chainId });
this.poll();
});
this.poll();
}

setOptInState(state: boolean | undefined): void {
this.update({ userOptIn: state });
}

async poll(interval?: number): Promise<void> {
const { chainId, allowedNetworks } = this.config;
interval && this.configure({ interval }, false, false);
this.handle && clearTimeout(this.handle);
if (!allowedNetworks.includes(chainId)) {
return;
}
await util.safelyExecute(() => this.updateSmartTransactions());
this.handle = setTimeout(() => {
this.poll(this.config.interval);
}, this.config.interval);
}

async stop() {
this.handle && clearTimeout(this.handle);
}

async updateSmartTransactions() {
//
}
}
15 changes: 9 additions & 6 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import greeter from '.';
import SmartTransactionsController from './SmartTransactionsController';
import DefaultExport from '.';

describe('Test', () => {
it('greets', () => {
const name = 'Huey';
const result = greeter(name);
expect(result).toStrictEqual('Hello, Huey!');
describe('default export', () => {
it('exports SmartTransactionsController', () => {
expect(
new DefaultExport({
onNetworkStateChange: jest.fn(),
}),
).toBeInstanceOf(SmartTransactionsController);
});
});
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export default function greeter(name: string): string {
return `Hello, ${name}!`;
}
import SmartTransactionsController from './SmartTransactionsController';

export default SmartTransactionsController;
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"declaration": true,
"esModuleInterop": true,
"inlineSources": true,
"lib": ["ES2020"],
"lib": ["DOM", "ES2020"],
"module": "CommonJS",
"moduleResolution": "Node",
"outDir": "dist",
Expand Down
Loading

0 comments on commit ab8dd85

Please sign in to comment.