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/json-schemas: ['packages/json-schemas']
|
||||||
@0x/ethereum-types: ['ethereum-types']
|
@0x/ethereum-types: ['ethereum-types']
|
||||||
@0x/connect: ['packages/connect']
|
@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 file
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
# server cli
|
|
||||||
packages/testnet-faucets/server/
|
|
||||||
|
|
||||||
# generated contract artifacts/
|
# generated contract artifacts/
|
||||||
contracts/integrations/generated-artifacts/
|
contracts/integrations/generated-artifacts/
|
||||||
contracts/staking/generated-artifacts/
|
contracts/staking/generated-artifacts/
|
||||||
|
@ -96,10 +96,9 @@ These packages are all under development. See [/contracts/README.md](/contracts/
|
|||||||
|
|
||||||
#### Private Packages
|
#### Private Packages
|
||||||
|
|
||||||
| Package | Description |
|
| Package | Description |
|
||||||
| -------------------------------------------------- | -------------------------------------------------------------------------------- |
|
| ---------------------------------- | -------------------------------------------------------------------------------- |
|
||||||
| [`@0x/instant`](/packages/instant) | A free and flexible way to offer simple crypto purchasing in any app or website. |
|
| [`@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
|
## 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/sol-resolver" },
|
||||||
{ "path": "./packages/sra-spec" },
|
{ "path": "./packages/sra-spec" },
|
||||||
{ "path": "./packages/subproviders" },
|
{ "path": "./packages/subproviders" },
|
||||||
{ "path": "./packages/testnet-faucets" },
|
|
||||||
{ "path": "./packages/tslint-config" },
|
{ "path": "./packages/tslint-config" },
|
||||||
{ "path": "./packages/types" },
|
{ "path": "./packages/types" },
|
||||||
{ "path": "./packages/typescript-typings" },
|
{ "path": "./packages/typescript-typings" },
|
||||||
|
Loading…
x
Reference in New Issue
Block a user