Remove testnet-faucet from monorepo
This commit is contained in:
parent
35099d9b2f
commit
4990c4903d
2
.github/autolabeler.yml
vendored
2
.github/autolabeler.yml
vendored
@ -32,5 +32,3 @@ contracts: ['contracts']
|
||||
@0x/json-schemas: ['packages/json-schemas']
|
||||
@0x/ethereum-types: ['ethereum-types']
|
||||
@0x/connect: ['packages/connect']
|
||||
@0x/testnet-faucets: ['packages/testnet-faucets']
|
||||
@0x/monorepo-scripts: ['packages/monorepo-scripts']
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@ -78,9 +78,6 @@ TODO.md
|
||||
# VSCode file
|
||||
.vscode
|
||||
|
||||
# server cli
|
||||
packages/testnet-faucets/server/
|
||||
|
||||
# generated contract artifacts/
|
||||
contracts/integrations/generated-artifacts/
|
||||
contracts/staking/generated-artifacts/
|
||||
|
@ -97,9 +97,8 @@ These packages are all under development. See [/contracts/README.md](/contracts/
|
||||
#### Private Packages
|
||||
|
||||
| Package | Description |
|
||||
| -------------------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| ---------------------------------- | -------------------------------------------------------------------------------- |
|
||||
| [`@0x/instant`](/packages/instant) | A free and flexible way to offer simple crypto purchasing in any app or website. |
|
||||
| [`@0x/testnet-faucets`](/packages/testnet-faucets) | A faucet micro-service that dispenses test ERC20 tokens or Ether |
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -1,13 +0,0 @@
|
||||
FROM node
|
||||
|
||||
WORKDIR /src
|
||||
|
||||
COPY package.json .
|
||||
RUN npm i
|
||||
RUN npm install forever -g
|
||||
|
||||
COPY . .
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["forever", "./server/server.js"]
|
@ -1,153 +0,0 @@
|
||||
## @0x/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 contributing instructions below.
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome improvements and fixes from the wider community! 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
|
||||
```
|
||||
|
||||
### Build
|
||||
|
||||
To build this package and all other monorepo packages that it depends on, run the following from the monorepo root directory:
|
||||
|
||||
```bash
|
||||
PKG=@0x/testnet-faucets yarn build
|
||||
```
|
||||
|
||||
Or continuously rebuild on change:
|
||||
|
||||
```bash
|
||||
PKG=@0x/testnet-faucets yarn watch
|
||||
```
|
||||
|
||||
### Clean
|
||||
|
||||
```bash
|
||||
yarn clean
|
||||
```
|
||||
|
||||
### Lint
|
||||
|
||||
```bash
|
||||
yarn lint
|
||||
```
|
||||
|
||||
### 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
|
||||
PKG=0x.js yarn watch
|
||||
```
|
||||
|
||||
### 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?networkId=:networkId`
|
||||
|
||||
Schedules a transaction that sends 0.1 ETH to the `recipient` on the network specified by `networkId` where `recipient` is a hex encoded Ethereum address prefixed with `0x`. If no `networkId` is provided via query parameters the faucet will default to network 42 (Kovan).
|
||||
|
||||
`GET /zrx/:recipient?networkId=:networkId`
|
||||
|
||||
Schedules a transaction that sends 0.1 ZRX to the `recipient` on the network specified by `networkId` where `recipient` is a hex encoded Ethereum address prefixed with `0x`. If no `networkId` is provided via query parameters the faucet will default to network 42 (Kovan).
|
||||
|
||||
`GET /order/weth/:recipient?networkId=:networkId`
|
||||
|
||||
Returns a JSON payload describing an order for 0.1 WETH in exchange for 0.1 ZRX signed by the dispenser address on the network specified by `networkId`. The taker is specified by `recipient` where `recipient` is a hex encoded Ethereum address prefixed with `0x`. If no `networkId` is provided via query parameters the faucet will default to network 42 (Kovan).
|
||||
|
||||
`GET /order/zrx/:recipient?networkId=:networkId`
|
||||
|
||||
Returns a JSON payload describing an order for 0.1 ZRX in exchange for 0.1 WETH signed by the dispenser address on the network specified by `networkId`. The taker is specified by `recipient` where `recipient` is a hex encoded Ethereum address prefixed with `0x`. If no `networkId` is provided via query parameters the faucet will default to network 42 (Kovan).
|
||||
|
||||
#### Example request
|
||||
|
||||
```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.
|
||||
|
||||
### 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
|
||||
```
|
@ -1,91 +0,0 @@
|
||||
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, '/server'),
|
||||
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: /server|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, 'server/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);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(stats.toString());
|
||||
}
|
||||
if (done) {
|
||||
if (stats.compilation.errors && stats.compilation.errors.length > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
done();
|
||||
}
|
||||
};
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "@0x/testnet-faucets",
|
||||
"version": "1.0.89",
|
||||
"engines": {
|
||||
"node": ">=6.12"
|
||||
},
|
||||
"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",
|
||||
"build:ci": "yarn build",
|
||||
"dev": "node ../../node_modules/gulp/bin/gulp.js run",
|
||||
"start": "node ./server/server.js",
|
||||
"lint": "tslint --format stylish --project .",
|
||||
"fix": "tslint --fix --format stylish --project .",
|
||||
"clean": "shx rm -rf server"
|
||||
},
|
||||
"author": "Fabio Berger",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"0x.js": "^8.0.0-beta.0",
|
||||
"@0x/contract-addresses": "^3.3.0-beta.2",
|
||||
"@0x/contract-wrappers": "^12.2.0-beta.1",
|
||||
"@0x/subproviders": "^5.1.0-beta.1",
|
||||
"@0x/typescript-typings": "^4.4.0-beta.1",
|
||||
"@0x/utils": "^4.6.0-beta.1",
|
||||
"@0x/web3-wrapper": "^6.1.0-beta.1",
|
||||
"body-parser": "^1.17.1",
|
||||
"ethereum-types": "^2.2.0-beta.1",
|
||||
"ethereumjs-tx": "^1.3.5",
|
||||
"ethereumjs-util": "^5.1.1",
|
||||
"express": "^4.15.2",
|
||||
"lodash": "^4.17.11",
|
||||
"rollbar": "^2.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@0x/tslint-config": "^3.1.0-beta.1",
|
||||
"@types/body-parser": "^1.16.1",
|
||||
"@types/express": "^4.0.35",
|
||||
"@types/lodash": "4.14.104",
|
||||
"awesome-typescript-loader": "^5.2.1",
|
||||
"gulp": "^3.9.1",
|
||||
"make-promises-safe": "^1.1.0",
|
||||
"nodemon": "^1.11.0",
|
||||
"shx": "^0.2.2",
|
||||
"source-map-loader": "^0.2.4",
|
||||
"tslint": "5.11.0",
|
||||
"typescript": "3.0.1",
|
||||
"webpack": "^4.20.2",
|
||||
"webpack-node-externals": "^1.6.0"
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
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,
|
||||
};
|
@ -1,5 +0,0 @@
|
||||
export const constants = {
|
||||
SUCCESS_STATUS: 200,
|
||||
SERVICE_UNAVAILABLE_STATUS: 503,
|
||||
BAD_REQUEST_STATUS: 400,
|
||||
};
|
@ -1,52 +0,0 @@
|
||||
import { intervalUtils, logUtils } from '@0x/utils';
|
||||
|
||||
import { errorReporter } from './error_reporter';
|
||||
|
||||
const MAX_QUEUE_SIZE = 500;
|
||||
const DEFAULT_QUEUE_INTERVAL_MS = 1000;
|
||||
|
||||
export class DispatchQueue {
|
||||
private readonly _queueIntervalMs: number;
|
||||
private readonly _queue: Array<() => Promise<void>>;
|
||||
private _queueIntervalIdIfExists?: NodeJS.Timer;
|
||||
constructor() {
|
||||
this._queueIntervalMs = DEFAULT_QUEUE_INTERVAL_MS;
|
||||
this._queue = [];
|
||||
this._start();
|
||||
}
|
||||
public add(taskAsync: () => Promise<void>): boolean {
|
||||
if (this.isFull()) {
|
||||
return false;
|
||||
}
|
||||
this._queue.push(taskAsync);
|
||||
return true;
|
||||
}
|
||||
public size(): number {
|
||||
return this._queue.length;
|
||||
}
|
||||
public isFull(): boolean {
|
||||
return this.size() >= MAX_QUEUE_SIZE;
|
||||
}
|
||||
public stop(): void {
|
||||
if (this._queueIntervalIdIfExists !== undefined) {
|
||||
intervalUtils.clearAsyncExcludingInterval(this._queueIntervalIdIfExists);
|
||||
}
|
||||
}
|
||||
private _start(): void {
|
||||
this._queueIntervalIdIfExists = intervalUtils.setAsyncExcludingInterval(
|
||||
async () => {
|
||||
const taskAsync = this._queue.shift();
|
||||
if (taskAsync === undefined) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
await taskAsync();
|
||||
},
|
||||
this._queueIntervalMs,
|
||||
(err: Error) => {
|
||||
logUtils.log(`Unexpected err: ${err} - ${JSON.stringify(err)}`);
|
||||
// tslint:disable-next-line:no-floating-promises
|
||||
errorReporter.reportAsync(err);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -1,68 +0,0 @@
|
||||
import { ERC20TokenContract, SupportedProvider } from '0x.js';
|
||||
import { BigNumber, logUtils } from '@0x/utils';
|
||||
import { Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { configs } from './configs';
|
||||
import { TOKENS_BY_CHAIN } from './tokens';
|
||||
|
||||
const DISPENSE_AMOUNT_ETHER = 0.1;
|
||||
const DISPENSE_AMOUNT_TOKEN = 1;
|
||||
const DISPENSE_MAX_AMOUNT_TOKEN = 100;
|
||||
const DISPENSE_MAX_AMOUNT_ETHER = 2;
|
||||
|
||||
type AsyncTask = () => Promise<void>;
|
||||
|
||||
export const dispenseAssetTasks = {
|
||||
dispenseEtherTask(recipientAddress: string, web3Wrapper: Web3Wrapper): AsyncTask {
|
||||
return async () => {
|
||||
logUtils.log(`Processing ETH ${recipientAddress}`);
|
||||
const userBalance = await web3Wrapper.getBalanceInWeiAsync(recipientAddress);
|
||||
const maxAmountInWei = Web3Wrapper.toWei(new BigNumber(DISPENSE_MAX_AMOUNT_ETHER));
|
||||
if (userBalance.isGreaterThanOrEqualTo(maxAmountInWei)) {
|
||||
logUtils.log(
|
||||
`User exceeded ETH balance maximum (${maxAmountInWei}) ${recipientAddress} ${userBalance} `,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const txHash = await web3Wrapper.sendTransactionAsync({
|
||||
from: configs.DISPENSER_ADDRESS,
|
||||
to: recipientAddress,
|
||||
value: Web3Wrapper.toWei(new BigNumber(DISPENSE_AMOUNT_ETHER)),
|
||||
});
|
||||
logUtils.log(`Sent ${DISPENSE_AMOUNT_ETHER} ETH to ${recipientAddress} tx: ${txHash}`);
|
||||
};
|
||||
},
|
||||
dispenseTokenTask(
|
||||
recipientAddress: string,
|
||||
tokenSymbol: string,
|
||||
chainId: number,
|
||||
provider: SupportedProvider,
|
||||
): AsyncTask {
|
||||
return async () => {
|
||||
logUtils.log(`Processing ${tokenSymbol} ${recipientAddress}`);
|
||||
const amountToDispense = new BigNumber(DISPENSE_AMOUNT_TOKEN);
|
||||
const tokenIfExists = _.get(TOKENS_BY_CHAIN, [chainId, tokenSymbol]);
|
||||
if (tokenIfExists === undefined) {
|
||||
throw new Error(`Unsupported asset type: ${tokenSymbol}`);
|
||||
}
|
||||
const baseUnitAmount = Web3Wrapper.toBaseUnitAmount(amountToDispense, tokenIfExists.decimals);
|
||||
const erc20Token = new ERC20TokenContract(tokenIfExists.address, provider);
|
||||
const userBalanceBaseUnits = await erc20Token.balanceOf.callAsync(recipientAddress);
|
||||
const maxAmountBaseUnits = Web3Wrapper.toBaseUnitAmount(
|
||||
new BigNumber(DISPENSE_MAX_AMOUNT_TOKEN),
|
||||
tokenIfExists.decimals,
|
||||
);
|
||||
if (userBalanceBaseUnits.isGreaterThanOrEqualTo(maxAmountBaseUnits)) {
|
||||
logUtils.log(
|
||||
`User exceeded token balance maximum (${maxAmountBaseUnits}) ${recipientAddress} ${userBalanceBaseUnits} `,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const txHash = await erc20Token.transfer.sendTransactionAsync(recipientAddress, baseUnitAmount, {
|
||||
from: configs.DISPENSER_ADDRESS,
|
||||
});
|
||||
logUtils.log(`Sent ${amountToDispense} ${tokenSymbol} to ${recipientAddress} tx: ${txHash}`);
|
||||
};
|
||||
},
|
||||
};
|
@ -1,37 +0,0 @@
|
||||
import { logUtils } from '@0x/utils';
|
||||
import * as express from 'express';
|
||||
import rollbar = require('rollbar');
|
||||
|
||||
import { configs } from './configs';
|
||||
|
||||
export const errorReporter = {
|
||||
setup(): void {
|
||||
rollbar.init(configs.ROLLBAR_ACCESS_KEY, {
|
||||
environment: configs.ENVIRONMENT,
|
||||
});
|
||||
rollbar.handleUncaughtExceptions(configs.ROLLBAR_ACCESS_KEY);
|
||||
process.on('unhandledRejection', async (err: Error) => {
|
||||
logUtils.log(`Uncaught exception ${err}. Stack: ${err.stack}`);
|
||||
await errorReporter.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<any>((resolve, reject) => {
|
||||
rollbar.handleError(err, req, (rollbarErr: Error) => {
|
||||
if (rollbarErr) {
|
||||
logUtils.log(`Error reporting to rollbar, ignoring: ${rollbarErr}`);
|
||||
reject(rollbarErr);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
errorHandler(): any {
|
||||
return rollbar.errorHandler(configs.ROLLBAR_ACCESS_KEY);
|
||||
},
|
||||
};
|
6
packages/testnet-faucets/src/ts/global.d.ts
vendored
6
packages/testnet-faucets/src/ts/global.d.ts
vendored
@ -1,6 +0,0 @@
|
||||
declare module '*.json' {
|
||||
const json: any;
|
||||
/* tslint:disable */
|
||||
export default json;
|
||||
/* tslint:enable */
|
||||
}
|
@ -1,199 +0,0 @@
|
||||
import {
|
||||
assetDataUtils,
|
||||
BigNumber,
|
||||
generatePseudoRandomSalt,
|
||||
Order,
|
||||
orderHashUtils,
|
||||
RPCSubprovider,
|
||||
signatureUtils,
|
||||
SignedOrder,
|
||||
Web3ProviderEngine,
|
||||
} from '0x.js';
|
||||
import { getContractAddressesForChainOrThrow } from '@0x/contract-addresses';
|
||||
import { NonceTrackerSubprovider, PrivateKeyWalletSubprovider } from '@0x/subproviders';
|
||||
import { logUtils } from '@0x/utils';
|
||||
import { SupportedProvider, Web3Wrapper } from '@0x/web3-wrapper';
|
||||
import * as express from 'express';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { configs } from './configs';
|
||||
import { constants } from './constants';
|
||||
import { DispatchQueue } from './dispatch_queue';
|
||||
import { dispenseAssetTasks } from './dispense_asset_tasks';
|
||||
import { rpcUrls } from './rpc_urls';
|
||||
import { TOKENS_BY_CHAIN } from './tokens';
|
||||
|
||||
interface ChainConfig {
|
||||
dispatchQueue: DispatchQueue;
|
||||
web3Wrapper: Web3Wrapper;
|
||||
provider: SupportedProvider;
|
||||
chainId: number;
|
||||
}
|
||||
|
||||
interface ItemByChainId<T> {
|
||||
[chainId: string]: T;
|
||||
}
|
||||
|
||||
enum RequestedAssetType {
|
||||
ETH = 'ETH', // tslint:disable-line:enum-naming
|
||||
WETH = 'WETH', // tslint:disable-line:enum-naming
|
||||
ZRX = 'ZRX', // tslint:disable-line:enum-naming
|
||||
}
|
||||
|
||||
const FIVE_DAYS_IN_MS = 4.32e8; // TODO: make this configurable
|
||||
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||
const ZERO = new BigNumber(0);
|
||||
const ASSET_AMOUNT = new BigNumber(0.1);
|
||||
|
||||
export class Handler {
|
||||
private readonly _chainConfigByChainId: ItemByChainId<ChainConfig> = {};
|
||||
private static _createProviderEngine(rpcUrl: string): Web3ProviderEngine {
|
||||
if (configs.DISPENSER_PRIVATE_KEY === undefined) {
|
||||
throw new Error('Dispenser Private key not found');
|
||||
}
|
||||
const engine = new Web3ProviderEngine();
|
||||
engine.addProvider(new NonceTrackerSubprovider());
|
||||
engine.addProvider(new PrivateKeyWalletSubprovider(configs.DISPENSER_PRIVATE_KEY));
|
||||
engine.addProvider(new RPCSubprovider(rpcUrl));
|
||||
engine.start();
|
||||
return engine;
|
||||
}
|
||||
constructor() {
|
||||
_.forIn(rpcUrls, (rpcUrl: string, chainIdString: string) => {
|
||||
const providerObj = Handler._createProviderEngine(rpcUrl);
|
||||
const web3Wrapper = new Web3Wrapper(providerObj);
|
||||
// tslint:disable-next-line:custom-no-magic-numbers
|
||||
const chainId = parseInt(chainIdString, 10);
|
||||
const dispatchQueue = new DispatchQueue();
|
||||
this._chainConfigByChainId[chainId] = {
|
||||
dispatchQueue,
|
||||
web3Wrapper,
|
||||
provider: providerObj,
|
||||
chainId,
|
||||
};
|
||||
});
|
||||
}
|
||||
public getQueueInfo(_req: express.Request, res: express.Response): void {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
const queueInfo = _.mapValues(rpcUrls, (_rpcUrl: string, chainId: string) => {
|
||||
const dispatchQueue = this._chainConfigByChainId[chainId].dispatchQueue;
|
||||
return {
|
||||
full: dispatchQueue.isFull(),
|
||||
size: dispatchQueue.size(),
|
||||
};
|
||||
});
|
||||
const payload = JSON.stringify(queueInfo);
|
||||
res.status(constants.SUCCESS_STATUS).send(payload);
|
||||
}
|
||||
public dispenseEther(req: express.Request, res: express.Response): void {
|
||||
this._dispenseAsset(req, res, RequestedAssetType.ETH);
|
||||
}
|
||||
public dispenseZRX(req: express.Request, res: express.Response): void {
|
||||
this._dispenseAsset(req, res, RequestedAssetType.ZRX);
|
||||
}
|
||||
public async dispenseWETHOrderAsync(req: express.Request, res: express.Response): Promise<void> {
|
||||
await this._dispenseOrderAsync(req, res, RequestedAssetType.WETH);
|
||||
}
|
||||
public async dispenseZRXOrderAsync(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
_next: express.NextFunction,
|
||||
): Promise<void> {
|
||||
await this._dispenseOrderAsync(req, res, RequestedAssetType.ZRX);
|
||||
}
|
||||
private _dispenseAsset(req: express.Request, res: express.Response, requestedAssetType: RequestedAssetType): void {
|
||||
const chainId = req.params.chainId;
|
||||
const recipient = req.params.recipient;
|
||||
const chainConfig = _.get(this._chainConfigByChainId, chainId);
|
||||
if (chainConfig === undefined) {
|
||||
res.status(constants.BAD_REQUEST_STATUS).send('UNSUPPORTED_CHAIN_ID');
|
||||
return;
|
||||
}
|
||||
let dispenserTask;
|
||||
switch (requestedAssetType) {
|
||||
case RequestedAssetType.ETH:
|
||||
dispenserTask = dispenseAssetTasks.dispenseEtherTask(recipient, chainConfig.web3Wrapper);
|
||||
break;
|
||||
case RequestedAssetType.WETH:
|
||||
case RequestedAssetType.ZRX:
|
||||
dispenserTask = dispenseAssetTasks.dispenseTokenTask(
|
||||
recipient,
|
||||
requestedAssetType,
|
||||
chainConfig.chainId,
|
||||
chainConfig.provider,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported asset type: ${requestedAssetType}`);
|
||||
}
|
||||
const didAddToQueue = chainConfig.dispatchQueue.add(dispenserTask);
|
||||
if (!didAddToQueue) {
|
||||
res.status(constants.SERVICE_UNAVAILABLE_STATUS).send('QUEUE_IS_FULL');
|
||||
return;
|
||||
}
|
||||
logUtils.log(`Added ${recipient} to queue: ${requestedAssetType} chainId: ${chainId}`);
|
||||
res.status(constants.SUCCESS_STATUS).end();
|
||||
}
|
||||
private async _dispenseOrderAsync(
|
||||
req: express.Request,
|
||||
res: express.Response,
|
||||
requestedAssetType: RequestedAssetType,
|
||||
): Promise<void> {
|
||||
const chainConfig = _.get(this._chainConfigByChainId, req.params.chainId);
|
||||
if (chainConfig === undefined) {
|
||||
res.status(constants.BAD_REQUEST_STATUS).send('UNSUPPORTED_CHAIN_ID');
|
||||
return;
|
||||
}
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
const makerTokenIfExists = _.get(TOKENS_BY_CHAIN, [chainConfig.chainId, requestedAssetType]);
|
||||
if (makerTokenIfExists === undefined) {
|
||||
throw new Error(`Unsupported asset type: ${requestedAssetType}`);
|
||||
}
|
||||
const takerTokenSymbol =
|
||||
requestedAssetType === RequestedAssetType.WETH ? RequestedAssetType.ZRX : RequestedAssetType.WETH;
|
||||
const takerTokenIfExists = _.get(TOKENS_BY_CHAIN, [chainConfig.chainId, takerTokenSymbol]);
|
||||
if (takerTokenIfExists === undefined) {
|
||||
throw new Error(`Unsupported asset type: ${takerTokenSymbol}`);
|
||||
}
|
||||
|
||||
const makerAssetAmount = Web3Wrapper.toBaseUnitAmount(ASSET_AMOUNT, makerTokenIfExists.decimals);
|
||||
const takerAssetAmount = Web3Wrapper.toBaseUnitAmount(ASSET_AMOUNT, takerTokenIfExists.decimals);
|
||||
const makerAssetData = assetDataUtils.encodeERC20AssetData(makerTokenIfExists.address);
|
||||
const takerAssetData = assetDataUtils.encodeERC20AssetData(takerTokenIfExists.address);
|
||||
const contractAddresses = getContractAddressesForChainOrThrow(chainConfig.chainId);
|
||||
const order: Order = {
|
||||
makerAddress: configs.DISPENSER_ADDRESS,
|
||||
takerAddress: req.params.recipient as string,
|
||||
makerFee: ZERO,
|
||||
takerFee: ZERO,
|
||||
makerAssetAmount,
|
||||
takerAssetAmount,
|
||||
makerAssetData,
|
||||
takerAssetData,
|
||||
salt: generatePseudoRandomSalt(),
|
||||
makerFeeAssetData: makerAssetData,
|
||||
takerFeeAssetData: takerAssetData,
|
||||
feeRecipientAddress: NULL_ADDRESS,
|
||||
senderAddress: NULL_ADDRESS,
|
||||
expirationTimeSeconds: new BigNumber(Date.now() + FIVE_DAYS_IN_MS)
|
||||
// tslint:disable-next-line:custom-no-magic-numbers
|
||||
.div(1000)
|
||||
.integerValue(BigNumber.ROUND_FLOOR),
|
||||
exchangeAddress: contractAddresses.exchange,
|
||||
chainId: chainConfig.chainId,
|
||||
};
|
||||
const orderHash = orderHashUtils.getOrderHashHex(order);
|
||||
const signature = await signatureUtils.ecSignHashAsync(
|
||||
chainConfig.web3Wrapper.getProvider(),
|
||||
orderHash,
|
||||
configs.DISPENSER_ADDRESS,
|
||||
);
|
||||
const signedOrder: SignedOrder = {
|
||||
...order,
|
||||
signature,
|
||||
};
|
||||
const payload = JSON.stringify(signedOrder);
|
||||
logUtils.log(`Dispensed signed order: ${payload}`);
|
||||
res.status(constants.SUCCESS_STATUS).send(payload);
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
import { addressUtils } from '@0x/utils';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import * as _ from 'lodash';
|
||||
|
||||
import { constants } from './constants';
|
||||
import { rpcUrls } from './rpc_urls';
|
||||
|
||||
const DEFAULT_CHAIN_ID = 42; // kovan
|
||||
|
||||
export const parameterTransformer = {
|
||||
transform(req: Request, res: Response, next: NextFunction): void {
|
||||
const recipientAddress = req.params.recipient;
|
||||
if (recipientAddress === undefined || !addressUtils.isAddress(recipientAddress)) {
|
||||
res.status(constants.BAD_REQUEST_STATUS).send('INVALID_RECIPIENT_ADDRESS');
|
||||
return;
|
||||
}
|
||||
const lowerCaseRecipientAddress = recipientAddress.toLowerCase();
|
||||
req.params.recipient = lowerCaseRecipientAddress;
|
||||
const chainId = _.get(req.query, 'chainId', DEFAULT_CHAIN_ID);
|
||||
const rpcUrlIfExists = _.get(rpcUrls, chainId);
|
||||
if (rpcUrlIfExists === undefined) {
|
||||
res.status(constants.BAD_REQUEST_STATUS).send('UNSUPPORTED_CHAIN_ID');
|
||||
return;
|
||||
}
|
||||
req.params.chainId = chainId;
|
||||
next();
|
||||
},
|
||||
};
|
@ -1,12 +0,0 @@
|
||||
import { configs } from './configs';
|
||||
|
||||
const productionRpcUrls = {
|
||||
'3': `https://ropsten.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;
|
@ -1,52 +0,0 @@
|
||||
import * as bodyParser from 'body-parser';
|
||||
import * as express from 'express';
|
||||
|
||||
import { constants } from './constants';
|
||||
import { errorReporter } from './error_reporter';
|
||||
import { Handler } from './handler';
|
||||
import { parameterTransformer } from './parameter_transformer';
|
||||
|
||||
// Setup the errorReporter to catch uncaught exceptions and unhandled rejections
|
||||
errorReporter.setup();
|
||||
|
||||
const app = express();
|
||||
app.use(bodyParser.json()); // for parsing application/json
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
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();
|
||||
// tslint:disable-next-line:no-unused-variable
|
||||
app.get('/ping', (req: express.Request, res: express.Response) => {
|
||||
res.status(constants.SUCCESS_STATUS).send('pong');
|
||||
});
|
||||
app.get('/info', handler.getQueueInfo.bind(handler));
|
||||
app.get(
|
||||
'/ether/:recipient',
|
||||
parameterTransformer.transform.bind(parameterTransformer),
|
||||
handler.dispenseEther.bind(handler),
|
||||
);
|
||||
app.get(
|
||||
'/zrx/:recipient',
|
||||
parameterTransformer.transform.bind(parameterTransformer),
|
||||
handler.dispenseZRX.bind(handler),
|
||||
);
|
||||
app.get(
|
||||
'/order/weth/:recipient',
|
||||
parameterTransformer.transform.bind(parameterTransformer),
|
||||
handler.dispenseWETHOrderAsync.bind(handler),
|
||||
);
|
||||
app.get(
|
||||
'/order/zrx/:recipient',
|
||||
parameterTransformer.transform.bind(parameterTransformer),
|
||||
handler.dispenseZRXOrderAsync.bind(handler),
|
||||
);
|
||||
|
||||
// Log to rollbar any errors unhandled by handlers
|
||||
app.use(errorReporter.errorHandler());
|
||||
const DEFAULT_PORT = 3000;
|
||||
const port = process.env.PORT || DEFAULT_PORT;
|
||||
app.listen(port);
|
@ -1,44 +0,0 @@
|
||||
interface TokensByChain {
|
||||
[chainId: number]: { [tokenSymbol: string]: { address: string; decimals: number } };
|
||||
}
|
||||
|
||||
export const tokens = {
|
||||
ZRX: {
|
||||
decimals: 18,
|
||||
},
|
||||
WETH: {
|
||||
decimals: 18,
|
||||
},
|
||||
};
|
||||
export const TOKENS_BY_CHAIN: TokensByChain = {
|
||||
3: {
|
||||
ZRX: {
|
||||
...tokens.ZRX,
|
||||
address: '0xff67881f8d12f372d91baae9752eb3631ff0ed00',
|
||||
},
|
||||
WETH: {
|
||||
...tokens.WETH,
|
||||
address: '0xc778417e063141139fce010982780140aa0cd5ab',
|
||||
},
|
||||
},
|
||||
42: {
|
||||
ZRX: {
|
||||
...tokens.ZRX,
|
||||
address: '0x2002d3812f58e35f0ea1ffbf80a75a38c32175fa',
|
||||
},
|
||||
WETH: {
|
||||
...tokens.WETH,
|
||||
address: '0xd0a1e359811322d97991e03f863a0c30c2cf029c',
|
||||
},
|
||||
},
|
||||
50: {
|
||||
ZRX: {
|
||||
...tokens.ZRX,
|
||||
address: '0x871dd7c2b4b25e1aa18728e9d5f2af4c4e431f5c',
|
||||
},
|
||||
WETH: {
|
||||
...tokens.WETH,
|
||||
address: '0x0b1ba0af832d7c05fd64161e0db78e85978e8082',
|
||||
},
|
||||
},
|
||||
};
|
@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig",
|
||||
"compilerOptions": {
|
||||
"outDir": "lib",
|
||||
"rootDir": "src/ts",
|
||||
"strictPropertyInitialization": false
|
||||
},
|
||||
"include": ["./src/ts/**/*"]
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": ["@0x/tslint-config"]
|
||||
}
|
@ -60,7 +60,6 @@
|
||||
{ "path": "./packages/sol-resolver" },
|
||||
{ "path": "./packages/sra-spec" },
|
||||
{ "path": "./packages/subproviders" },
|
||||
{ "path": "./packages/testnet-faucets" },
|
||||
{ "path": "./packages/tslint-config" },
|
||||
{ "path": "./packages/types" },
|
||||
{ "path": "./packages/typescript-typings" },
|
||||
|
Loading…
x
Reference in New Issue
Block a user