Merge pull request #229 from 0xProject/feature/addKovanFaucets
Add kovan faucet project into the mono repo
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -68,3 +68,6 @@ generated_docs/
|
||||
TODO.md
|
||||
|
||||
packages/website/public/bundle*
|
||||
|
||||
# generated binaries
|
||||
bin/
|
||||
|
@@ -34,3 +34,4 @@ This repository contains all the 0x developer tools written in TypeScript. Our h
|
||||
| [`@0xproject/types`](/packages/types) | [](https://www.npmjs.com/package/@0xproject/types) | Shared types |
|
||||
| [`@0xproject/utils`](/packages/utils) | [](https://www.npmjs.com/package/@0xproject/utils) | Shared utils |
|
||||
| [`@0xproject/web3-wrapper`](/packages/web3-wrapper) | [](https://www.npmjs.com/package/@0xproject/web3-wrapper) | Web3 wrapper |
|
||||
| [`@0xproject/kovan_faucets`](/packages/kovan-faucets) | | A faucet micro-service that dispenses test ERC20 tokens or Ether |
|
||||
|
@@ -54,7 +54,7 @@
|
||||
"yargs": "^10.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"0x.js": "^0.27.1",
|
||||
"0x.js": "~0.27.2",
|
||||
"@0xproject/json-schemas": "^0.6.10",
|
||||
"@0xproject/utils": "^0.1.0",
|
||||
"@0xproject/web3-wrapper": "^0.1.0",
|
||||
|
13
packages/kovan-faucets/Dockerfile
Normal file
13
packages/kovan-faucets/Dockerfile
Normal file
@@ -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"]
|
66
packages/kovan-faucets/README.md
Normal file
66
packages/kovan-faucets/README.md
Normal file
@@ -0,0 +1,66 @@
|
||||
Test Ether Faucet
|
||||
----------------------
|
||||
|
||||
This faucet dispenses 0.1 test ether to one recipient per second. It has a max queue size of 1000.
|
||||
|
||||
|
||||
## Install
|
||||
|
||||
Install project dependencies:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
## Start
|
||||
|
||||
Set the following environment variables:
|
||||
|
||||
```
|
||||
export FAUCET_ENVIRONMENT=development
|
||||
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}
|
||||
```
|
||||
|
||||
Infura API Key can be requested here: https://infura.io/register.html
|
||||
|
||||
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`.
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
||||
|
||||
## Endpoints
|
||||
|
||||
```GET /ether/:recipient```
|
||||
|
||||
Where recipient_address is a hex encoded Ethereum address prefixed with `0x`.
|
||||
|
||||
```GET /zrx/:recipient```
|
||||
|
||||
Where recipient_address is a hex encoded Ethereum address prefixed with `0x`.
|
||||
|
||||
|
||||
```javascript
|
||||
{
|
||||
"full": false,
|
||||
"size": 0
|
||||
}
|
||||
```
|
||||
|
||||
## Docker configs
|
||||
|
||||
```
|
||||
docker run -d \
|
||||
-p 80:3000 \
|
||||
--name kovan-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 \
|
||||
kovan-faucets
|
||||
```
|
92
packages/kovan-faucets/gulpfile.js
Normal file
92
packages/kovan-faucets/gulpfile.js
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
44
packages/kovan-faucets/package.json
Normal file
44
packages/kovan-faucets/package.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "@0xproject/kovan_faucets",
|
||||
"version": "1.0.0",
|
||||
"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": {
|
||||
"@0xproject/utils": "^0.1.0",
|
||||
"0x.js": "~0.27.2",
|
||||
"bignumber.js": "~4.1.0",
|
||||
"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.2.1",
|
||||
"@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.7.2",
|
||||
"webpack": "^3.1.0",
|
||||
"webpack-node-externals": "^1.6.0"
|
||||
}
|
||||
}
|
11
packages/kovan-faucets/src/ts/configs.ts
Normal file
11
packages/kovan-faucets/src/ts/configs.ts
Normal file
@@ -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,
|
||||
};
|
41
packages/kovan-faucets/src/ts/error_reporter.ts
Normal file
41
packages/kovan-faucets/src/ts/error_reporter.ts
Normal file
@@ -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);
|
||||
},
|
||||
};
|
27
packages/kovan-faucets/src/ts/ether_request_queue.ts
Normal file
27
packages/kovan-faucets/src/ts/ether_request_queue.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
26
packages/kovan-faucets/src/ts/global.d.ts
vendored
Normal file
26
packages/kovan-faucets/src/ts/global.d.ts
vendored
Normal file
@@ -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;
|
||||
}
|
91
packages/kovan-faucets/src/ts/handler.ts
Normal file
91
packages/kovan-faucets/src/ts/handler.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
25
packages/kovan-faucets/src/ts/id_management.ts
Normal file
25
packages/kovan-faucets/src/ts/id_management.ts
Normal file
@@ -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);
|
||||
},
|
||||
};
|
57
packages/kovan-faucets/src/ts/request_queue.ts
Normal file
57
packages/kovan-faucets/src/ts/request_queue.ts
Normal file
@@ -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');
|
||||
}
|
||||
}
|
26
packages/kovan-faucets/src/ts/server.ts
Normal file
26
packages/kovan-faucets/src/ts/server.ts
Normal file
@@ -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);
|
10
packages/kovan-faucets/src/ts/utils.ts
Normal file
10
packages/kovan-faucets/src/ts/utils.ts
Normal file
@@ -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 */
|
||||
},
|
||||
};
|
43
packages/kovan-faucets/src/ts/zrx_request_queue.ts
Normal file
43
packages/kovan-faucets/src/ts/zrx_request_queue.ts
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
16
packages/kovan-faucets/tsconfig.json
Normal file
16
packages/kovan-faucets/tsconfig.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [ "es2015", "dom" ],
|
||||
"outDir": "lib",
|
||||
"sourceMap": true,
|
||||
"declaration": true,
|
||||
"noImplicitAny": true,
|
||||
"experimentalDecorators": true,
|
||||
},
|
||||
"include": [
|
||||
"../../node_modules/web3-typescript-typings/index.d.ts",
|
||||
"./src/ts/**/*"
|
||||
]
|
||||
}
|
5
packages/kovan-faucets/tslint.json
Normal file
5
packages/kovan-faucets/tslint.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"extends": [
|
||||
"@0xproject/tslint-config"
|
||||
]
|
||||
}
|
@@ -18,7 +18,7 @@
|
||||
"author": "Fabio Berger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"0x.js": "0.27.1",
|
||||
"0x.js": "~0.27.2",
|
||||
"@0xproject/subproviders": "^0.1.0",
|
||||
"accounting": "^0.4.1",
|
||||
"basscss": "^8.0.3",
|
||||
|
Reference in New Issue
Block a user