aboutsummaryrefslogtreecommitdiffstats
path: root/packages/kovan-faucets/src/ts
diff options
context:
space:
mode:
authorBrandon Millman <brandon@0xproject.com>2017-12-14 07:09:39 +0800
committerGitHub <noreply@github.com>2017-12-14 07:09:39 +0800
commit2ef763a19555b85814a80aa5d93a14c90c51c512 (patch)
tree5b41b8911e12569f669fc7b30f3ea2cc19a33611 /packages/kovan-faucets/src/ts
parent6bda6a22e3551707e7069e8ad6ea34d1ba0abd4d (diff)
parent6ba4ed99c42b4976902046625790698b490c3e0e (diff)
downloaddexon-sol-tools-2ef763a19555b85814a80aa5d93a14c90c51c512.tar.gz
dexon-sol-tools-2ef763a19555b85814a80aa5d93a14c90c51c512.tar.zst
dexon-sol-tools-2ef763a19555b85814a80aa5d93a14c90c51c512.zip
Merge pull request #229 from 0xProject/feature/addKovanFaucets
Add kovan faucet project into the mono repo
Diffstat (limited to 'packages/kovan-faucets/src/ts')
-rw-r--r--packages/kovan-faucets/src/ts/configs.ts11
-rw-r--r--packages/kovan-faucets/src/ts/error_reporter.ts41
-rw-r--r--packages/kovan-faucets/src/ts/ether_request_queue.ts27
-rw-r--r--packages/kovan-faucets/src/ts/global.d.ts26
-rw-r--r--packages/kovan-faucets/src/ts/handler.ts91
-rw-r--r--packages/kovan-faucets/src/ts/id_management.ts25
-rw-r--r--packages/kovan-faucets/src/ts/request_queue.ts57
-rw-r--r--packages/kovan-faucets/src/ts/server.ts26
-rw-r--r--packages/kovan-faucets/src/ts/utils.ts10
-rw-r--r--packages/kovan-faucets/src/ts/zrx_request_queue.ts43
10 files changed, 357 insertions, 0 deletions
diff --git a/packages/kovan-faucets/src/ts/configs.ts b/packages/kovan-faucets/src/ts/configs.ts
new file mode 100644
index 000000000..f432ba66f
--- /dev/null
+++ b/packages/kovan-faucets/src/ts/configs.ts
@@ -0,0 +1,11 @@
+export const configs = {
+ DISPENSER_ADDRESS: process.env.DISPENSER_ADDRESS.toLowerCase(),
+ DISPENSER_PRIVATE_KEY: process.env.DISPENSER_PRIVATE_KEY,
+ ENVIRONMENT: process.env.FAUCET_ENVIRONMENT,
+ ROLLBAR_ACCESS_KEY: process.env.FAUCET_ROLLBAR_ACCESS_KEY,
+ RPC_URL: process.env.FAUCET_ENVIRONMENT === 'development' ?
+ 'http://127.0.0.1:8545' :
+ `https://kovan.infura.io/${process.env.INFURA_API_KEY}`,
+ ZRX_TOKEN_ADDRESS: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570',
+ KOVAN_NETWORK_ID: 42,
+};
diff --git a/packages/kovan-faucets/src/ts/error_reporter.ts b/packages/kovan-faucets/src/ts/error_reporter.ts
new file mode 100644
index 000000000..db21647c0
--- /dev/null
+++ b/packages/kovan-faucets/src/ts/error_reporter.ts
@@ -0,0 +1,41 @@
+import * as express from 'express';
+import * as fs from 'fs';
+import rollbar = require('rollbar');
+
+import {configs} from './configs';
+import {utils} from './utils';
+
+export const errorReporter = {
+ setup() {
+ rollbar.init(configs.ROLLBAR_ACCESS_KEY, {
+ environment: configs.ENVIRONMENT,
+ });
+
+ rollbar.handleUncaughtExceptions(configs.ROLLBAR_ACCESS_KEY);
+
+ process.on('unhandledRejection', (err: Error) => {
+ utils.consoleLog(`Uncaught exception ${err}. Stack: ${err.stack}`);
+ this.report(err);
+ process.exit(1);
+ });
+ },
+ async reportAsync(err: Error, req?: express.Request): Promise<any> {
+ if (configs.ENVIRONMENT === 'development') {
+ return; // Do not log development environment errors
+ }
+
+ return new Promise((resolve, reject) => {
+ rollbar.handleError(err, req, (rollbarErr: Error) => {
+ if (rollbarErr) {
+ utils.consoleLog(`Error reporting to rollbar, ignoring: ${rollbarErr}`);
+ reject(rollbarErr);
+ } else {
+ resolve();
+ }
+ });
+ });
+ },
+ errorHandler() {
+ return rollbar.errorHandler(configs.ROLLBAR_ACCESS_KEY);
+ },
+};
diff --git a/packages/kovan-faucets/src/ts/ether_request_queue.ts b/packages/kovan-faucets/src/ts/ether_request_queue.ts
new file mode 100644
index 000000000..0750e6170
--- /dev/null
+++ b/packages/kovan-faucets/src/ts/ether_request_queue.ts
@@ -0,0 +1,27 @@
+import {promisify} from '@0xproject/utils';
+import * as _ from 'lodash';
+
+import {configs} from './configs';
+import {errorReporter} from './error_reporter';
+import {RequestQueue} from './request_queue';
+import {utils} from './utils';
+
+const DISPENSE_AMOUNT_ETHER = 0.1;
+
+export class EtherRequestQueue extends RequestQueue {
+ protected async processNextRequestFireAndForgetAsync(recipientAddress: string) {
+ utils.consoleLog(`Processing ETH ${recipientAddress}`);
+ const sendTransactionAsync = promisify(this.web3.eth.sendTransaction);
+ try {
+ const txHash = await sendTransactionAsync({
+ from: configs.DISPENSER_ADDRESS,
+ to: recipientAddress,
+ value: this.web3.toWei(DISPENSE_AMOUNT_ETHER, 'ether'),
+ });
+ utils.consoleLog(`Sent ${DISPENSE_AMOUNT_ETHER} ETH to ${recipientAddress} tx: ${txHash}`);
+ } catch (err) {
+ utils.consoleLog(`Unexpected err: ${err} - ${JSON.stringify(err)}`);
+ await errorReporter.reportAsync(err);
+ }
+ }
+}
diff --git a/packages/kovan-faucets/src/ts/global.d.ts b/packages/kovan-faucets/src/ts/global.d.ts
new file mode 100644
index 000000000..c698207dd
--- /dev/null
+++ b/packages/kovan-faucets/src/ts/global.d.ts
@@ -0,0 +1,26 @@
+declare module 'rollbar';
+declare module 'web3-provider-engine';
+declare module 'web3-provider-engine/subproviders/rpc';
+declare module 'web3-provider-engine/subproviders/nonce-tracker';
+declare module 'web3-provider-engine/subproviders/hooked-wallet';
+
+declare module '*.json' {
+ const json: any;
+ /* tslint:disable */
+ export default json;
+ /* tslint:enable */
+}
+
+// Ethereumjs-tx declarations
+declare module 'ethereumjs-tx' {
+ class EthereumTx {
+ public raw: Buffer[];
+ public r: Buffer;
+ public s: Buffer;
+ public v: Buffer;
+ public serialize(): Buffer;
+ public sign(buffer: Buffer): void;
+ constructor(txParams: any);
+ }
+ export = EthereumTx;
+}
diff --git a/packages/kovan-faucets/src/ts/handler.ts b/packages/kovan-faucets/src/ts/handler.ts
new file mode 100644
index 000000000..1c6866a1c
--- /dev/null
+++ b/packages/kovan-faucets/src/ts/handler.ts
@@ -0,0 +1,91 @@
+import * as express from 'express';
+import * as _ from 'lodash';
+import ProviderEngine = require('web3-provider-engine');
+import HookedWalletSubprovider = require('web3-provider-engine/subproviders/hooked-wallet');
+import NonceSubprovider = require('web3-provider-engine/subproviders/nonce-tracker');
+import RpcSubprovider = require('web3-provider-engine/subproviders/rpc');
+
+import {configs} from './configs';
+import {EtherRequestQueue} from './ether_request_queue';
+import {idManagement} from './id_management';
+import {utils} from './utils';
+import {ZRXRequestQueue} from './zrx_request_queue';
+
+// HACK: web3 leaks XMLHttpRequest into the global scope and causes requests to hang
+// because they are using the wrong XHR package.
+// Issue: https://github.com/trufflesuite/truffle-contract/issues/14
+// tslint:disable-next-line:ordered-imports
+import * as Web3 from 'web3';
+
+export class Handler {
+ private etherRequestQueue: EtherRequestQueue;
+ private zrxRequestQueue: ZRXRequestQueue;
+ private web3: Web3;
+ constructor() {
+ // Setup provider engine to talk with RPC node
+ const providerObj = this.createProviderEngine(configs.RPC_URL);
+ this.web3 = new Web3(providerObj);
+
+ this.etherRequestQueue = new EtherRequestQueue(this.web3);
+ this.zrxRequestQueue = new ZRXRequestQueue(this.web3);
+ }
+ public dispenseEther(req: express.Request, res: express.Response) {
+ const recipientAddress = req.params.recipient;
+ if (_.isUndefined(recipientAddress) || !this.isValidEthereumAddress(recipientAddress)) {
+ res.status(400).send('INVALID_REQUEST');
+ return;
+ }
+ const lowerCaseRecipientAddress = recipientAddress.toLowerCase();
+ const didAddToQueue = this.etherRequestQueue.add(lowerCaseRecipientAddress);
+ if (!didAddToQueue) {
+ res.status(503).send('QUEUE_IS_FULL');
+ return;
+ }
+ utils.consoleLog(`Added ${lowerCaseRecipientAddress} to the ETH queue`);
+ res.status(200).end();
+ }
+ public dispenseZRX(req: express.Request, res: express.Response) {
+ const recipientAddress = req.params.recipient;
+ if (_.isUndefined(recipientAddress) || !this.isValidEthereumAddress(recipientAddress)) {
+ res.status(400).send('INVALID_REQUEST');
+ return;
+ }
+ const lowerCaseRecipientAddress = recipientAddress.toLowerCase();
+ const didAddToQueue = this.zrxRequestQueue.add(lowerCaseRecipientAddress);
+ if (!didAddToQueue) {
+ res.status(503).send('QUEUE_IS_FULL');
+ return;
+ }
+ utils.consoleLog(`Added ${lowerCaseRecipientAddress} to the ZRX queue`);
+ res.status(200).end();
+ }
+ public getQueueInfo(req: express.Request, res: express.Response) {
+ res.setHeader('Content-Type', 'application/json');
+ const payload = JSON.stringify({
+ ether: {
+ full: this.etherRequestQueue.isFull(),
+ size: this.etherRequestQueue.size(),
+ },
+ zrx: {
+ full: this.zrxRequestQueue.isFull(),
+ size: this.zrxRequestQueue.size(),
+ },
+ });
+ res.status(200).send(payload);
+ }
+ // tslint:disable-next-line:prefer-function-over-method
+ private createProviderEngine(rpcUrl: string) {
+ const engine = new ProviderEngine();
+ engine.addProvider(new NonceSubprovider());
+ engine.addProvider(new HookedWalletSubprovider(idManagement));
+ engine.addProvider(new RpcSubprovider({
+ rpcUrl,
+ }));
+ engine.start();
+ return engine;
+ }
+ private isValidEthereumAddress(address: string): boolean {
+ const lowercaseAddress = address.toLowerCase();
+ return this.web3.isAddress(lowercaseAddress);
+ }
+}
diff --git a/packages/kovan-faucets/src/ts/id_management.ts b/packages/kovan-faucets/src/ts/id_management.ts
new file mode 100644
index 000000000..89d6f6fd1
--- /dev/null
+++ b/packages/kovan-faucets/src/ts/id_management.ts
@@ -0,0 +1,25 @@
+import EthereumTx = require('ethereumjs-tx');
+
+import {configs} from './configs';
+import {utils} from './utils';
+
+type Callback = (err: Error, accounts: any) => void;
+
+export const idManagement = {
+ getAccounts(callback: Callback) {
+ utils.consoleLog(`configs.DISPENSER_ADDRESS: ${configs.DISPENSER_ADDRESS}`);
+ callback(null, [
+ configs.DISPENSER_ADDRESS,
+ ]);
+ },
+ approveTransaction(txData: object, callback: Callback) {
+ callback(null, true);
+ },
+ signTransaction(txData: object, callback: Callback) {
+ const tx = new EthereumTx(txData);
+ const privateKeyBuffer = new Buffer(configs.DISPENSER_PRIVATE_KEY, 'hex');
+ tx.sign(privateKeyBuffer);
+ const rawTx = `0x${tx.serialize().toString('hex')}`;
+ callback(null, rawTx);
+ },
+};
diff --git a/packages/kovan-faucets/src/ts/request_queue.ts b/packages/kovan-faucets/src/ts/request_queue.ts
new file mode 100644
index 000000000..0b5e66ae9
--- /dev/null
+++ b/packages/kovan-faucets/src/ts/request_queue.ts
@@ -0,0 +1,57 @@
+import * as _ from 'lodash';
+import * as timers from 'timers';
+
+// HACK: web3 leaks XMLHttpRequest into the global scope and causes requests to hang
+// because they are using the wrong XHR package.
+// Issue: https://github.com/trufflesuite/truffle-contract/issues/14
+// tslint:disable-next-line:ordered-imports
+import * as Web3 from 'web3';
+
+const MAX_QUEUE_SIZE = 500;
+const DEFAULT_QUEUE_INTERVAL_MS = 1000;
+
+export class RequestQueue {
+ protected queueIntervalMs: number;
+ protected queue: string[];
+ protected queueIntervalId: NodeJS.Timer;
+ protected web3: Web3;
+ constructor(web3: any) {
+ this.queueIntervalMs = DEFAULT_QUEUE_INTERVAL_MS;
+ this.queue = [];
+
+ this.web3 = web3;
+
+ this.start();
+ }
+ public add(recipientAddress: string): boolean {
+ if (this.isFull()) {
+ return false;
+ }
+ this.queue.push(recipientAddress);
+ return true;
+ }
+ public size(): number {
+ return this.queue.length;
+ }
+ public isFull(): boolean {
+ return this.size() >= MAX_QUEUE_SIZE;
+ }
+ protected start() {
+ this.queueIntervalId = timers.setInterval(() => {
+ if (this.queue.length === 0) {
+ return;
+ }
+ const recipientAddress = this.queue.shift();
+ // tslint:disable-next-line:no-floating-promises
+ this.processNextRequestFireAndForgetAsync(recipientAddress);
+
+ }, this.queueIntervalMs);
+ }
+ protected stop() {
+ clearInterval(this.queueIntervalId);
+ }
+ // tslint:disable-next-line:prefer-function-over-method
+ protected async processNextRequestFireAndForgetAsync(recipientAddress: string) {
+ throw new Error('Expected processNextRequestFireAndForgetAsync to be implemented by a superclass');
+ }
+}
diff --git a/packages/kovan-faucets/src/ts/server.ts b/packages/kovan-faucets/src/ts/server.ts
new file mode 100644
index 000000000..fbb9caf1e
--- /dev/null
+++ b/packages/kovan-faucets/src/ts/server.ts
@@ -0,0 +1,26 @@
+import * as bodyParser from 'body-parser';
+import * as express from 'express';
+
+import {errorReporter} from './error_reporter';
+import {Handler} from './handler';
+
+// Setup the errorReporter to catch uncaught exceptions and unhandled rejections
+errorReporter.setup();
+
+const app = express();
+app.use(bodyParser.json()); // for parsing application/json
+app.use((req, res, next) => {
+ res.header('Access-Control-Allow-Origin', '*');
+ res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
+ next();
+});
+
+const handler = new Handler();
+app.get('/ping', (req: express.Request, res: express.Response) => { res.status(200).send('pong'); });
+app.get('/ether/:recipient', handler.dispenseEther.bind(handler));
+app.get('/zrx/:recipient', handler.dispenseZRX.bind(handler));
+
+// Log to rollbar any errors unhandled by handlers
+app.use(errorReporter.errorHandler());
+const port = process.env.PORT || 3000;
+app.listen(port);
diff --git a/packages/kovan-faucets/src/ts/utils.ts b/packages/kovan-faucets/src/ts/utils.ts
new file mode 100644
index 000000000..6a0b262e6
--- /dev/null
+++ b/packages/kovan-faucets/src/ts/utils.ts
@@ -0,0 +1,10 @@
+import * as express from 'express';
+import * as _ from 'lodash';
+
+export const utils = {
+ consoleLog(message: string) {
+ /* tslint:disable */
+ console.log(message);
+ /* tslint:enable */
+ },
+};
diff --git a/packages/kovan-faucets/src/ts/zrx_request_queue.ts b/packages/kovan-faucets/src/ts/zrx_request_queue.ts
new file mode 100644
index 000000000..6e6590e7c
--- /dev/null
+++ b/packages/kovan-faucets/src/ts/zrx_request_queue.ts
@@ -0,0 +1,43 @@
+import {ZeroEx} from '0x.js';
+import {promisify} from '@0xproject/utils';
+import BigNumber from 'bignumber.js';
+import * as _ from 'lodash';
+
+import {configs} from './configs';
+import {errorReporter} from './error_reporter';
+import {RequestQueue} from './request_queue';
+import {utils} from './utils';
+
+// HACK: web3 leaks XMLHttpRequest into the global scope and causes requests to hang
+// because they are using the wrong XHR package.
+// Issue: https://github.com/trufflesuite/truffle-contract/issues/14
+// tslint:disable-next-line:ordered-imports
+import * as Web3 from 'web3';
+
+const DISPENSE_AMOUNT_ZRX = new BigNumber(0.1);
+const QUEUE_INTERVAL_MS = 5000;
+
+export class ZRXRequestQueue extends RequestQueue {
+ private zeroEx: ZeroEx;
+ constructor(web3: Web3) {
+ super(web3);
+ this.queueIntervalMs = QUEUE_INTERVAL_MS;
+ const zeroExConfig = {
+ networkId: configs.KOVAN_NETWORK_ID,
+ };
+ this.zeroEx = new ZeroEx(web3.currentProvider, zeroExConfig);
+ }
+ protected async processNextRequestFireAndForgetAsync(recipientAddress: string) {
+ utils.consoleLog(`Processing ZRX ${recipientAddress}`);
+ const baseUnitAmount = ZeroEx.toBaseUnitAmount(DISPENSE_AMOUNT_ZRX, 18);
+ try {
+ await this.zeroEx.token.transferAsync(
+ configs.ZRX_TOKEN_ADDRESS, configs.DISPENSER_ADDRESS, recipientAddress, baseUnitAmount,
+ );
+ utils.consoleLog(`Sent ${DISPENSE_AMOUNT_ZRX} ZRX to ${recipientAddress}`);
+ } catch (err) {
+ utils.consoleLog(`Unexpected err: ${err} - ${JSON.stringify(err)}`);
+ await errorReporter.reportAsync(err);
+ }
+ }
+}