Skip to content

Commit

Permalink
feat: Faucet (#2856)
Browse files Browse the repository at this point in the history
This PR creates a small faucet application for retrieving eth.

# Checklist:
Remove the checklist to signal you've completed it. Enable auto-merge if
the PR is ready to merge.
- [ ] If the pull request requires a cryptography review (e.g.
cryptographic algorithm implementations) I have added the 'crypto' tag.
- [ ] I have reviewed my diff in github, line by line and removed
unexpected formatting changes, testing logs, or commented-out code.
- [ ] Every change is related to the PR description.
- [ ] I have
[linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
this pull request to relevant issues (if any exist).
  • Loading branch information
PhilWindle authored Oct 17, 2023
1 parent c74311d commit 5bad35f
Show file tree
Hide file tree
Showing 20 changed files with 615 additions and 5 deletions.
16 changes: 16 additions & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,17 @@ jobs:
name: "Build and test"
command: build aztec-node | add_timestamps

aztec-faucet:
machine:
image: ubuntu-2204:2023.07.2
resource_class: large
steps:
- *checkout
- *setup_env
- run:
name: "Build and test"
command: build aztec-faucet | add_timestamps

pxe-x86_64:
machine:
image: ubuntu-2204:2023.07.2
Expand Down Expand Up @@ -1263,6 +1274,11 @@ workflows:
- yarn-project
<<: *defaults

- aztec-faucet:
requires:
- yarn-project
<<: *defaults

- pxe-x86_64:
requires:
- yarn-project
Expand Down
6 changes: 6 additions & 0 deletions build_manifest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,12 @@ aztec-sandbox:
dependencies:
- yarn-project

aztec-faucet:
buildDir: yarn-project
projectDir: yarn-project/aztec-faucet
dependencies:
- yarn-project

pxe:
buildDir: yarn-project
projectDir: yarn-project/pxe
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/aztec-faucet/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
data
dest
node_modules
Dockerfile
1 change: 1 addition & 0 deletions yarn-project/aztec-faucet/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@aztec/foundation/eslint');
1 change: 1 addition & 0 deletions yarn-project/aztec-faucet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/data*
14 changes: 14 additions & 0 deletions yarn-project/aztec-faucet/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM 278380418400.dkr.ecr.eu-west-2.amazonaws.com/yarn-project AS builder

WORKDIR /usr/src/yarn-project/aztec-faucet

# Productionify. See comment in yarn-project-base/Dockerfile.
RUN yarn cache clean && yarn workspaces focus --production

# Create final, minimal size image.
FROM node:18-alpine
COPY --from=builder /usr/src/ /usr/src/
WORKDIR /usr/src/yarn-project/aztec-faucet
ENTRYPOINT ["yarn"]
CMD [ "start" ]
EXPOSE 8080
3 changes: 3 additions & 0 deletions yarn-project/aztec-faucet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Aztec Faucet

This application allows someone to obtain a small amount of eth via a http endpoint.
61 changes: 61 additions & 0 deletions yarn-project/aztec-faucet/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"name": "@aztec/aztec-faucet",
"version": "0.1.0",
"main": "dest/bin/index.js",
"type": "module",
"bin": "./dest/bin/index.js",
"typedocOptions": {
"entryPoints": [
"./src/bin/index.ts"
],
"name": "Aztec Faucet",
"tsconfig": "./tsconfig.json"
},
"scripts": {
"start": "node --no-warnings ./dest/bin",
"build": "yarn clean && tsc -b",
"build:dev": "tsc -b --watch",
"clean": "rm -rf ./dest .tsbuildinfo",
"formatting": "run -T prettier --check ./src && run -T eslint ./src",
"formatting:fix": "run -T prettier -w ./src",
"test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules $(yarn bin jest) --passWithNoTests"
},
"inherits": [
"../package.common.json"
],
"jest": {
"preset": "ts-jest/presets/default-esm",
"moduleNameMapper": {
"^(\\.{1,2}/.*)\\.m?js$": "$1"
},
"testRegex": "./src/.*\\.test\\.(js|mjs|ts)$",
"rootDir": "./src"
},
"dependencies": {
"@aztec/ethereum": "workspace:^",
"@aztec/foundation": "workspace:^",
"koa": "^2.14.2",
"koa-cors": "^0.0.16",
"koa-router": "^12.0.0",
"viem": "^1.2.5"
},
"devDependencies": {
"@jest/globals": "^29.5.0",
"@rushstack/eslint-patch": "^1.1.4",
"@types/jest": "^29.5.0",
"@types/node": "^18.7.23",
"jest": "^29.5.0",
"ts-jest": "^29.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.4"
},
"files": [
"dest",
"src",
"!*.test.*"
],
"types": "./dest/index.d.ts",
"engines": {
"node": ">=18"
}
}
153 changes: 153 additions & 0 deletions yarn-project/aztec-faucet/src/bin/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
#!/usr/bin/env -S node --no-warnings
import { NULL_KEY, createEthereumChain } from '@aztec/ethereum';
import { EthAddress } from '@aztec/foundation/eth-address';
import { createDebugLogger } from '@aztec/foundation/log';

import http from 'http';
import Koa from 'koa';
import cors from 'koa-cors';
import Router from 'koa-router';
import { Hex, http as ViemHttp, createWalletClient, parseEther } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';

const {
FAUCET_PORT = 8082,
API_PREFIX = '',
API_KEY = '',
RPC_URL = '',
CHAIN_ID = '',
PRIVATE_KEY = '',
INTERVAL = '',
ETH_AMOUNT = '',
} = process.env;

const logger = createDebugLogger('aztec:faucet');

const rpcUrl = RPC_URL;
const apiKey = API_KEY;
const chainId = +CHAIN_ID;
const privateKey: Hex = PRIVATE_KEY ? createHex(PRIVATE_KEY) : NULL_KEY;
const interval = +INTERVAL;
const mapping: { [key: Hex]: Date } = {};

/**
* Helper function to convert a string to a Hex value
* @param hex - The string to convert
* @returns The converted value
*/
function createHex(hex: string) {
return `0x${hex.replace('0x', '')}` as Hex;
}

/**
* Function to throttle drips on a per address basis
* @param address - Address requesting some ETH
*/
function checkThrottle(address: Hex) {
if (mapping[address] === undefined) {
return;
}
const last = mapping[address];
const current = new Date();
const diff = (current.getTime() - last.getTime()) / 1000;
if (diff < interval) {
throw new Error(`Not funding address ${address}, please try again later`);
}
}

/**
* Helper function to send some ETH to the given address
* @param address - Address to receive some ETH
*/
async function transferEth(address: string) {
const chain = createEthereumChain(rpcUrl, apiKey);

const account = privateKeyToAccount(privateKey);
const walletClient = createWalletClient({
account: account,
chain: chain.chainInfo,
transport: ViemHttp(chain.rpcUrl),
});
const hexAddress = createHex(address);
checkThrottle(hexAddress);
try {
const hash = await walletClient.sendTransaction({
account,
to: hexAddress,
value: parseEther(ETH_AMOUNT),
});
mapping[hexAddress] = new Date();
logger.info(`Sent ${ETH_AMOUNT} ETH to ${hexAddress} in tx ${hash}`);
} catch (error) {
logger.error(`Failed to send eth to ${hexAddress}`);
throw error;
}
}

/**
* Creates a router for the faucet.
* @param apiPrefix - The prefix to use for all api requests
* @returns - The router for handling status requests.
*/
function createRouter(apiPrefix: string) {
logger.info(`Creating router with prefix ${apiPrefix}`);
const router = new Router({ prefix: `${apiPrefix}` });
router.get('/status', (ctx: Koa.Context) => {
ctx.status = 200;
});
router.get('/drip/:address', async (ctx: Koa.Context) => {
const { address } = ctx.params;
await transferEth(EthAddress.fromString(address).toChecksumString());
ctx.status = 200;
});
return router;
}

/**
* Create and start a new Aztec Node HTTP Server
*/
async function main() {
logger.info(`Setting up Aztec Faucet...`);

const chain = createEthereumChain(rpcUrl, apiKey);
if (chain.chainInfo.id !== chainId) {
throw new Error(`Incorrect chain id, expected ${chain.chainInfo.id}`);
}

const shutdown = () => {
logger.info('Shutting down...');
process.exit(0);
};

process.once('SIGINT', shutdown);
process.once('SIGTERM', shutdown);

const app = new Koa();
app.on('error', error => {
logger.error(`Error on API handler: ${error}`);
});
const exceptionHandler = async (ctx: Koa.Context, next: () => Promise<void>) => {
try {
await next();
} catch (err: any) {
logger.error(err);
ctx.status = 400;
ctx.body = { error: err.message };
}
};
app.use(exceptionHandler);
app.use(cors());
const apiRouter = createRouter(API_PREFIX);
app.use(apiRouter.routes());
app.use(apiRouter.allowedMethods());

const httpServer = http.createServer(app.callback());
httpServer.listen(+FAUCET_PORT);
logger.info(`Aztec Faucet listening on port ${FAUCET_PORT}`);
await Promise.resolve();
}

main().catch(err => {
logger.error(err);
process.exit(1);
});
Loading

0 comments on commit 5bad35f

Please sign in to comment.