aboutsummaryrefslogtreecommitdiffstats
path: root/packages/testnet-faucets
diff options
context:
space:
mode:
authorFabio Berger <me@fabioberger.com>2018-01-30 21:00:35 +0800
committerFabio Berger <me@fabioberger.com>2018-01-30 21:00:35 +0800
commit69151c06e42fd588506db3aa62b95df5b4399607 (patch)
tree878532483594627a5283f784ced0a2c0cfc68d59 /packages/testnet-faucets
parent86cc011212088801a778d947ae925cc0b1ddadf8 (diff)
parent2e3c02887efc24de35ce82b3662d9c47a0056a8c (diff)
downloaddexon-sol-tools-69151c06e42fd588506db3aa62b95df5b4399607.tar
dexon-sol-tools-69151c06e42fd588506db3aa62b95df5b4399607.tar.gz
dexon-sol-tools-69151c06e42fd588506db3aa62b95df5b4399607.tar.bz2
dexon-sol-tools-69151c06e42fd588506db3aa62b95df5b4399607.tar.lz
dexon-sol-tools-69151c06e42fd588506db3aa62b95df5b4399607.tar.xz
dexon-sol-tools-69151c06e42fd588506db3aa62b95df5b4399607.tar.zst
dexon-sol-tools-69151c06e42fd588506db3aa62b95df5b4399607.zip
Merge branch 'development' into feature/portal-ledger-support
* development: Publish Add PR number Add config file specifically in prettier command and fix files Fix prettier Fix prettier Add shouldAddPersonalMessagePrefix param to signOrderHashAsync instead of trying to infer whether to add it or not from the nodeVersion Publish Move @0xproject/types to dependencies Updated web3-typescript-typings changelog Fixed getTransactionReceipt not returning null Run prettier Update changelog Add Rinkeby addresses to artifacts Fix bad merge on package.json Respond to GH comments and add /info endpoint Change package name to @0xproject/testnet-faucets Implement testnet faucets for any testnet available via infura Rename to testnet-faucets Add to the Pull Request Template Create an ISSUE TEMPLATE
Diffstat (limited to 'packages/testnet-faucets')
-rw-r--r--packages/testnet-faucets/Dockerfile13
-rw-r--r--packages/testnet-faucets/README.md129
-rw-r--r--packages/testnet-faucets/gulpfile.js92
-rw-r--r--packages/testnet-faucets/package.json43
-rw-r--r--packages/testnet-faucets/scripts/postpublish.js15
-rw-r--r--packages/testnet-faucets/src/ts/configs.ts7
-rw-r--r--packages/testnet-faucets/src/ts/error_reporter.ts40
-rw-r--r--packages/testnet-faucets/src/ts/ether_request_queue.ts27
-rw-r--r--packages/testnet-faucets/src/ts/global.d.ts26
-rw-r--r--packages/testnet-faucets/src/ts/handler.ts114
-rw-r--r--packages/testnet-faucets/src/ts/id_management.ts21
-rw-r--r--packages/testnet-faucets/src/ts/request_queue.ts56
-rw-r--r--packages/testnet-faucets/src/ts/rpc_urls.ts13
-rw-r--r--packages/testnet-faucets/src/ts/server.ts29
-rw-r--r--packages/testnet-faucets/src/ts/utils.ts7
-rw-r--r--packages/testnet-faucets/src/ts/zrx_request_queue.ts46
-rw-r--r--packages/testnet-faucets/tsconfig.json7
-rw-r--r--packages/testnet-faucets/tslint.json3
18 files changed, 688 insertions, 0 deletions
diff --git a/packages/testnet-faucets/Dockerfile b/packages/testnet-faucets/Dockerfile
new file mode 100644
index 000000000..6d6ddc192
--- /dev/null
+++ b/packages/testnet-faucets/Dockerfile
@@ -0,0 +1,13 @@
+FROM node
+
+WORKDIR /src
+
+COPY package.json .
+RUN npm i
+RUN npm install forever -g
+
+COPY . .
+
+EXPOSE 3000
+
+CMD ["forever", "./bin/server.js"]
diff --git a/packages/testnet-faucets/README.md b/packages/testnet-faucets/README.md
new file mode 100644
index 000000000..a52d16a00
--- /dev/null
+++ b/packages/testnet-faucets/README.md
@@ -0,0 +1,129 @@
+## @0xproject/testnet-faucets
+
+This faucet dispenses 0.1 test ether to one recipient per second and 0.1 test ZRX every 5 seconds. It has a max queue size of 1000.
+
+## Installation
+
+This is a private package and therefore is not published to npm. In order to build and run this package locally, see the [Install Dependencies](#Install-Dependencies) section and onwards below.
+
+## Contributing
+
+We strongly recommend that the community help us make improvements and determine the future direction of the protocol. To report bugs within this package, please create an issue in this repository.
+
+Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
+
+### Install Dependencies
+
+If you don't have yarn workspaces enabled (Yarn < v1.0) - enable them:
+
+```bash
+yarn config set workspaces-experimental true
+```
+
+Then install dependencies
+
+```bash
+yarn install
+```
+
+### Start
+
+Set the following environment variables:
+
+```bash
+export DISPENSER_ADDRESS=0x5409ed021d9299bf6814279a6a1411a7e866a631
+export DISPENSER_PRIVATE_KEY=f2f48ee19680706196e2e339e5da3491186e0c4c5030670656b0e0164837257d
+export FAUCET_ROLLBAR_ACCESS_KEY={GET_THIS_FROM_ROLLBAR_ACCOUNT_SETTINGS}
+export INFURA_API_KEY={GET_THIS_FROM_INFURA}
+```
+
+If you want to talk to testrpc, set the following environment variable:
+
+```bash
+export FAUCET_ENVIRONMENT=development
+```
+
+Infura API Key can be requested here: https://infura.io/signup
+
+Note: The above public/private keys exist when running `testrpc` with the following option `--mnemonic concert load couple harbor equip island argue ramp clarify fence smart topic`.
+
+```bash
+yarn dev
+```
+
+### Endpoints
+
+`GET /ping`
+
+Returns `pong`
+
+`GET /info`
+
+Returns a JSON payload describing the state of the queues for each network. For example:
+
+```json
+{
+ "3": {
+ "ether": {
+ "full": false,
+ "size": 4
+ },
+ "zrx": {
+ "full": false,
+ "size": 6
+ }
+ },
+ "42": {
+ "ether": {
+ "full": false,
+ "size": 8
+ },
+ "zrx": {
+ "full": false,
+ "size": 20
+ }
+ }
+}
+```
+
+`GET /ether/:recipient`
+
+Where recipient is a hex encoded Ethereum address prefixed with `0x`.
+
+`GET /zrx/:recipient`
+
+Where recipient is a hex encoded Ethereum address prefixed with `0x`.
+
+#### Parameters
+
+The endpoints `/ether` and `/zrx` take a query parameter named `networkId` to specify the desired network where you would like to receive the ETH or ZRX. For example:
+
+```bash
+curl -i http://localhost:3000/ether/0x14e2F1F157E7DD4057D02817436D628A37120FD1\?networkId=3
+```
+
+This command will request the local server to initiate a transfer of 0.1 ETH from the dispensing address to `0x14e2F1F157E7DD4057D02817436D628A37120FD1` on the Ropsten testnet.
+
+If no `networkId` is provided via query parameters the faucet will default to network 42 (Kovan)
+
+### Docker configs
+
+```
+docker run -d \
+-p 80:3000 \
+--name testnet-faucets \
+--log-opt max-size=100m \
+--log-opt max-file=20 \
+-e DISPENSER_ADDRESS=$DISPENSER_ADDRESS \
+-e DISPENSER_PRIVATE_KEY=$DISPENSER_PRIVATE_KEY \
+-e FAUCET_ROLLBAR_ACCESS_KEY=$FAUCET_ROLLBAR_ACCESS_KEY \
+-e FAUCET_ENVIRONMENT=production \
+-e INFURA_API_KEY=$INFURA_API_KEY \
+testnet-faucets
+```
+
+### Lint
+
+```bash
+yarn lint
+```
diff --git a/packages/testnet-faucets/gulpfile.js b/packages/testnet-faucets/gulpfile.js
new file mode 100644
index 000000000..773faf33a
--- /dev/null
+++ b/packages/testnet-faucets/gulpfile.js
@@ -0,0 +1,92 @@
+const gulp = require('gulp');
+const nodemon = require('nodemon');
+const path = require('path');
+const webpack = require('webpack');
+const fs = require('fs');
+const nodeExternals = require('webpack-node-externals');
+
+const config = {
+ target: 'node',
+ entry: [path.join(__dirname, '/src/ts/server.ts')],
+ output: {
+ path: path.join(__dirname, '/bin'),
+ filename: 'server.js',
+ },
+ devtool: 'source-map',
+ resolve: {
+ modules: [
+ path.join(__dirname, '/src/ts'),
+ 'node_modules',
+ ],
+ extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'],
+ alias: {
+ ts: path.join(__dirname, '/src/ts'),
+ contract_artifacts: path.join(__dirname, '/src/contract_artifacts'),
+ },
+ },
+ module: {
+ rules: [
+ {
+ test: /\.js$/,
+ loader: 'source-map-loader',
+ },
+ {
+ test: /\.tsx?$/,
+ loader: 'awesome-typescript-loader',
+ },
+ ],
+ },
+ plugins: [
+ new webpack.BannerPlugin({
+ banner: 'require("source-map-support").install();',
+ raw: true,
+ entryOnly: false,
+ }),
+ ],
+ externals: nodeExternals({
+ modulesDir: path.join(__dirname, '../../node_modules')
+ }),
+ watchOptions: {
+ ignored: /bin|node_modules|transpiled/
+ },
+};
+
+gulp.task('build', function(done) {
+ webpack(config).run(onBuild(done));
+});
+
+gulp.task('watch', function() {
+ webpack(config).watch(100, function(err, stats) {
+ onBuild()(err, stats);
+ nodemon.restart();
+ });
+});
+
+gulp.task('run', ['watch'], function() {
+ nodemon({
+ execMap: {
+ js: 'node',
+ },
+ script: path.join(__dirname, 'bin/server'),
+ ignore: ['*'],
+ watch: ['foo/'],
+ ext: 'noop',
+ }).on('restart', function() {
+ console.log('Restarted!');
+ });
+});
+
+function onBuild(done) {
+ return function(err, stats) {
+ if(err) {
+ console.log('Error', err);
+ }
+ else {
+ console.log(stats.toString());
+ }
+
+ if(done) {
+ done();
+ }
+ }
+}
diff --git a/packages/testnet-faucets/package.json b/packages/testnet-faucets/package.json
new file mode 100644
index 000000000..3b42ebb3e
--- /dev/null
+++ b/packages/testnet-faucets/package.json
@@ -0,0 +1,43 @@
+{
+ "private": true,
+ "name": "@0xproject/testnet-faucets",
+ "version": "1.0.8",
+ "description": "A faucet micro-service that dispenses test ERC20 tokens or Ether",
+ "main": "server.js",
+ "scripts": {
+ "build": "node ../../node_modules/gulp/bin/gulp.js build",
+ "dev": "node ../../node_modules/gulp/bin/gulp.js run",
+ "start": "node ./bin/server.js",
+ "lint": "tslint --project . 'src/**/*.ts'",
+ "clean": "shx rm -rf bin"
+ },
+ "author": "Fabio Berger",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "0x.js": "^0.31.0",
+ "@0xproject/utils": "^0.2.3",
+ "body-parser": "^1.17.1",
+ "ethereumjs-tx": "^1.3.3",
+ "express": "^4.15.2",
+ "lodash": "^4.17.4",
+ "rollbar": "^0.6.5",
+ "web3": "^0.20.0",
+ "web3-provider-engine": "^13.0.1"
+ },
+ "devDependencies": {
+ "@0xproject/tslint-config": "^0.4.5",
+ "@types/body-parser": "^1.16.1",
+ "@types/express": "^4.0.35",
+ "@types/lodash": "^4.14.86",
+ "awesome-typescript-loader": "^3.1.3",
+ "gulp": "^3.9.1",
+ "nodemon": "^1.11.0",
+ "shx": "^0.2.2",
+ "source-map-loader": "^0.1.6",
+ "tslint": "5.8.0",
+ "typescript": "~2.6.1",
+ "web3-typescript-typings": "^0.9.7",
+ "webpack": "^3.1.0",
+ "webpack-node-externals": "^1.6.0"
+ }
+}
diff --git a/packages/testnet-faucets/scripts/postpublish.js b/packages/testnet-faucets/scripts/postpublish.js
new file mode 100644
index 000000000..16d67e03f
--- /dev/null
+++ b/packages/testnet-faucets/scripts/postpublish.js
@@ -0,0 +1,15 @@
+const postpublish_utils = require('../../../scripts/postpublish_utils');
+const packageJSON = require('../package.json');
+
+const subPackageName = packageJSON.name;
+
+postpublish_utils
+ .getLatestTagAndVersionAsync(subPackageName)
+ .then(function(result) {
+ const releaseName = postpublish_utils.getReleaseName(subPackageName, result.version);
+ const assets = [];
+ return postpublish_utils.publishReleaseNotes(result.tag, releaseName, assets);
+ })
+ .catch(function(err) {
+ throw err;
+ });
diff --git a/packages/testnet-faucets/src/ts/configs.ts b/packages/testnet-faucets/src/ts/configs.ts
new file mode 100644
index 000000000..038c8e22a
--- /dev/null
+++ b/packages/testnet-faucets/src/ts/configs.ts
@@ -0,0 +1,7 @@
+export const configs = {
+ DISPENSER_ADDRESS: (process.env.DISPENSER_ADDRESS as string).toLowerCase(),
+ DISPENSER_PRIVATE_KEY: process.env.DISPENSER_PRIVATE_KEY,
+ ENVIRONMENT: process.env.FAUCET_ENVIRONMENT,
+ INFURA_API_KEY: process.env.INFURA_API_KEY,
+ ROLLBAR_ACCESS_KEY: process.env.FAUCET_ROLLBAR_ACCESS_KEY,
+};
diff --git a/packages/testnet-faucets/src/ts/error_reporter.ts b/packages/testnet-faucets/src/ts/error_reporter.ts
new file mode 100644
index 000000000..6865d3893
--- /dev/null
+++ b/packages/testnet-faucets/src/ts/error_reporter.ts
@@ -0,0 +1,40 @@
+import * as express from 'express';
+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', async (err: Error) => {
+ utils.consoleLog(`Uncaught exception ${err}. Stack: ${err.stack}`);
+ await this.reportAsync(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/testnet-faucets/src/ts/ether_request_queue.ts b/packages/testnet-faucets/src/ts/ether_request_queue.ts
new file mode 100644
index 000000000..1c4b19ab9
--- /dev/null
+++ b/packages/testnet-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/testnet-faucets/src/ts/global.d.ts b/packages/testnet-faucets/src/ts/global.d.ts
new file mode 100644
index 000000000..97cd35680
--- /dev/null
+++ b/packages/testnet-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/testnet-faucets/src/ts/handler.ts b/packages/testnet-faucets/src/ts/handler.ts
new file mode 100644
index 000000000..bf5b3e81e
--- /dev/null
+++ b/packages/testnet-faucets/src/ts/handler.ts
@@ -0,0 +1,114 @@
+import { addressUtils } from '@0xproject/utils';
+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 { EtherRequestQueue } from './ether_request_queue';
+import { idManagement } from './id_management';
+import { RequestQueue } from './request_queue';
+import { rpcUrls } from './rpc_urls';
+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.
+// Filed issue: https://github.com/ethereum/web3.js/issues/844
+// tslint:disable-next-line:ordered-imports
+import * as Web3 from 'web3';
+
+interface RequestQueueByNetworkId {
+ [networkId: string]: RequestQueue;
+}
+
+enum QueueType {
+ ETH = 'ETH',
+ ZRX = 'ZRX',
+}
+
+const DEFAULT_NETWORK_ID = 42; // kovan
+
+export class Handler {
+ private _etherRequestQueueByNetworkId: RequestQueueByNetworkId = {};
+ private _zrxRequestQueueByNetworkId: RequestQueueByNetworkId = {};
+ constructor() {
+ _.forIn(rpcUrls, (rpcUrl: string, networkId: string) => {
+ const providerObj = this._createProviderEngine(rpcUrl);
+ const web3 = new Web3(providerObj);
+ this._etherRequestQueueByNetworkId[networkId] = new EtherRequestQueue(web3);
+ this._zrxRequestQueueByNetworkId[networkId] = new ZRXRequestQueue(web3, +networkId);
+ });
+ }
+ public getQueueInfo(req: express.Request, res: express.Response) {
+ res.setHeader('Content-Type', 'application/json');
+ const queueInfo = _.mapValues(rpcUrls, (rpcUrl: string, networkId: string) => {
+ utils.consoleLog(networkId);
+ const etherRequestQueue = this._etherRequestQueueByNetworkId[networkId];
+ const zrxRequestQueue = this._zrxRequestQueueByNetworkId[networkId];
+ return {
+ ether: {
+ full: etherRequestQueue.isFull(),
+ size: etherRequestQueue.size(),
+ },
+ zrx: {
+ full: zrxRequestQueue.isFull(),
+ size: zrxRequestQueue.size(),
+ },
+ };
+ });
+ const payload = JSON.stringify(queueInfo);
+ res.status(200).send(payload);
+ }
+ public dispenseEther(req: express.Request, res: express.Response) {
+ this._dispense(req, res, this._etherRequestQueueByNetworkId, QueueType.ETH);
+ }
+ public dispenseZRX(req: express.Request, res: express.Response) {
+ this._dispense(req, res, this._zrxRequestQueueByNetworkId, QueueType.ZRX);
+ }
+ private _dispense(
+ req: express.Request,
+ res: express.Response,
+ requestQueueByNetworkId: RequestQueueByNetworkId,
+ queueType: QueueType,
+ ) {
+ const recipientAddress = req.params.recipient;
+ if (_.isUndefined(recipientAddress) || !this._isValidEthereumAddress(recipientAddress)) {
+ res.status(400).send('INVALID_RECIPIENT_ADDRESS');
+ return;
+ }
+ const networkId = _.get(req.query, 'networkId', DEFAULT_NETWORK_ID);
+ const requestQueue = _.get(requestQueueByNetworkId, networkId);
+ if (_.isUndefined(requestQueue)) {
+ res.status(400).send('INVALID_NETWORK_ID');
+ return;
+ }
+ const lowerCaseRecipientAddress = recipientAddress.toLowerCase();
+ const didAddToQueue = requestQueue.add(lowerCaseRecipientAddress);
+ if (!didAddToQueue) {
+ res.status(503).send('QUEUE_IS_FULL');
+ return;
+ }
+ utils.consoleLog(`Added ${lowerCaseRecipientAddress} to queue: ${queueType} networkId: ${networkId}`);
+ res.status(200).end();
+ }
+ // 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;
+ }
+ // tslint:disable-next-line:prefer-function-over-method
+ private _isValidEthereumAddress(address: string): boolean {
+ const lowercaseAddress = address.toLowerCase();
+ return addressUtils.isAddress(lowercaseAddress);
+ }
+}
diff --git a/packages/testnet-faucets/src/ts/id_management.ts b/packages/testnet-faucets/src/ts/id_management.ts
new file mode 100644
index 000000000..db9b610a3
--- /dev/null
+++ b/packages/testnet-faucets/src/ts/id_management.ts
@@ -0,0 +1,21 @@
+import EthereumTx = require('ethereumjs-tx');
+
+import { configs } from './configs';
+
+type Callback = (err: Error | null, accounts: any) => void;
+
+export const idManagement = {
+ getAccounts(callback: Callback) {
+ 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 as string, 'hex');
+ tx.sign(privateKeyBuffer);
+ const rawTx = `0x${tx.serialize().toString('hex')}`;
+ callback(null, rawTx);
+ },
+};
diff --git a/packages/testnet-faucets/src/ts/request_queue.ts b/packages/testnet-faucets/src/ts/request_queue.ts
new file mode 100644
index 000000000..20f2833a1
--- /dev/null
+++ b/packages/testnet-faucets/src/ts/request_queue.ts
@@ -0,0 +1,56 @@
+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.
+// Filed issue: https://github.com/ethereum/web3.js/issues/844
+// 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(() => {
+ const recipientAddress = this.queue.shift();
+ if (_.isUndefined(recipientAddress)) {
+ return;
+ }
+ // 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 subclass');
+ }
+}
diff --git a/packages/testnet-faucets/src/ts/rpc_urls.ts b/packages/testnet-faucets/src/ts/rpc_urls.ts
new file mode 100644
index 000000000..25a3b938f
--- /dev/null
+++ b/packages/testnet-faucets/src/ts/rpc_urls.ts
@@ -0,0 +1,13 @@
+import { configs } from './configs';
+
+const productionRpcUrls = {
+ '2': `https://ropsten.infura.io/${configs.INFURA_API_KEY}`,
+ '3': `https://rinkeby.infura.io/${configs.INFURA_API_KEY}`,
+ '42': `https://kovan.infura.io/${configs.INFURA_API_KEY}`,
+};
+
+const developmentRpcUrls = {
+ '50': 'http://127.0.0.1:8545',
+};
+
+export const rpcUrls = configs.ENVIRONMENT === 'development' ? developmentRpcUrls : productionRpcUrls;
diff --git a/packages/testnet-faucets/src/ts/server.ts b/packages/testnet-faucets/src/ts/server.ts
new file mode 100644
index 000000000..26edfff5a
--- /dev/null
+++ b/packages/testnet-faucets/src/ts/server.ts
@@ -0,0 +1,29 @@
+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('/info', handler.getQueueInfo.bind(handler));
+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/testnet-faucets/src/ts/utils.ts b/packages/testnet-faucets/src/ts/utils.ts
new file mode 100644
index 000000000..893f82ca3
--- /dev/null
+++ b/packages/testnet-faucets/src/ts/utils.ts
@@ -0,0 +1,7 @@
+export const utils = {
+ consoleLog(message: string) {
+ /* tslint:disable */
+ console.log(message);
+ /* tslint:enable */
+ },
+};
diff --git a/packages/testnet-faucets/src/ts/zrx_request_queue.ts b/packages/testnet-faucets/src/ts/zrx_request_queue.ts
new file mode 100644
index 000000000..3d73f9dd2
--- /dev/null
+++ b/packages/testnet-faucets/src/ts/zrx_request_queue.ts
@@ -0,0 +1,46 @@
+import { ZeroEx } from '0x.js';
+import { BigNumber } 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';
+
+// HACK: web3 leaks XMLHttpRequest into the global scope and causes requests to hang
+// because they are using the wrong XHR package.
+// Filed issue: https://github.com/ethereum/web3.js/issues/844
+// 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, networkId: number) {
+ super(web3);
+ this.queueIntervalMs = QUEUE_INTERVAL_MS;
+ const zeroExConfig = {
+ networkId,
+ };
+ 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 {
+ const zrxTokenAddress = this._zeroEx.exchange.getZRXTokenAddress();
+ const txHash = await this._zeroEx.token.transferAsync(
+ zrxTokenAddress,
+ configs.DISPENSER_ADDRESS,
+ recipientAddress,
+ baseUnitAmount,
+ );
+ utils.consoleLog(`Sent ${DISPENSE_AMOUNT_ZRX} ZRX to ${recipientAddress} tx: ${txHash}`);
+ } catch (err) {
+ utils.consoleLog(`Unexpected err: ${err} - ${JSON.stringify(err)}`);
+ await errorReporter.reportAsync(err);
+ }
+ }
+}
diff --git a/packages/testnet-faucets/tsconfig.json b/packages/testnet-faucets/tsconfig.json
new file mode 100644
index 000000000..7f0c084ff
--- /dev/null
+++ b/packages/testnet-faucets/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig",
+ "compilerOptions": {
+ "outDir": "lib"
+ },
+ "include": ["./src/ts/**/*", "../../node_modules/web3-typescript-typings/index.d.ts"]
+}
diff --git a/packages/testnet-faucets/tslint.json b/packages/testnet-faucets/tslint.json
new file mode 100644
index 000000000..ffaefe83a
--- /dev/null
+++ b/packages/testnet-faucets/tslint.json
@@ -0,0 +1,3 @@
+{
+ "extends": ["@0xproject/tslint-config"]
+}