Compare commits
No commits in common. "nova" and "main" have entirely different histories.
@ -1,3 +0,0 @@
|
||||
node_modules
|
||||
.env
|
||||
.git
|
30
.env.example
30
.env.example
@ -1,30 +0,0 @@
|
||||
# DNS settings
|
||||
VIRTUAL_HOST=
|
||||
LETSENCRYPT_HOST=
|
||||
|
||||
# server settings
|
||||
PORT=8000
|
||||
|
||||
# for production
|
||||
REDIS_URL=redis://redis/0
|
||||
# for development
|
||||
# REDIS_URL=redis://127.0.0.1:6379
|
||||
|
||||
CHAIN_ID=100
|
||||
|
||||
RPC_URL=https://rpc.gnosischain.com
|
||||
# ORACLE_RPC_URL should always point to the mainnet
|
||||
ORACLE_RPC_URL=https://rpc.payload.de
|
||||
|
||||
REWARD_ADDRESS=
|
||||
|
||||
PRIVATE_KEY=
|
||||
CONFIRMATIONS=4
|
||||
MAX_GAS_PRICE=100
|
||||
|
||||
# commission for service
|
||||
# transfer fee is a fixed value in ether, 0.01 means 0.01 ether
|
||||
TRANSFER_SERVICE_FEE=0.00000001
|
||||
# withdrawal fee is a percentage of the amount, 0.05 means 0.05%
|
||||
WITHDRAWAL_SERVICE_FEE=0.05
|
||||
|
24
.eslintrc.js
24
.eslintrc.js
@ -1,24 +0,0 @@
|
||||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
sourceType: 'module',
|
||||
},
|
||||
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||
extends: [
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:prettier/recommended',
|
||||
],
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
jest: true,
|
||||
},
|
||||
ignorePatterns: ['.eslintrc.js'],
|
||||
rules: {
|
||||
'@typescript-eslint/interface-name-prefix': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
},
|
||||
};
|
95
.github/workflows/build.yml
vendored
95
.github/workflows/build.yml
vendored
@ -1,95 +0,0 @@
|
||||
name: build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ['*']
|
||||
tags: ['v[0-9]+.[0-9]+.[0-9]+']
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
- run: yarn install
|
||||
- run: yarn lint
|
||||
- name: Telegram Failure Notification
|
||||
uses: appleboy/telegram-action@master
|
||||
if: failure()
|
||||
with:
|
||||
message: ❗ Build failed for [${{ github.repository }}](https://github.com/${{ github.repository }}/actions) because of ${{ github.actor }}
|
||||
format: markdown
|
||||
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
needs: build
|
||||
if: startsWith(github.ref, 'refs/tags')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set vars
|
||||
id: vars
|
||||
run: |
|
||||
echo "::set-output name=version::$(echo ${GITHUB_REF#refs/tags/v})"
|
||||
echo "::set-output name=repo_name::$(echo ${GITHUB_REPOSITORY#*/})"
|
||||
- name: Check package.json version vs tag
|
||||
run: |
|
||||
[ ${{ steps.vars.outputs.version }} = $(grep '"version":' package.json | grep -o "[0-9.]*") ] || (echo "Git tag doesn't match version in package.json" && false)
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v1.1.0
|
||||
with:
|
||||
dockerfile: Dockerfile
|
||||
repository: tornadocash/nova-relayer
|
||||
tag_with_ref: true
|
||||
tags: latest,nova,candidate
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
|
||||
- name: Telegram Message Notify
|
||||
uses: appleboy/telegram-action@master
|
||||
with:
|
||||
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
message: 🚀 Published a [${{ steps.vars.outputs.repo_name }}](https://github.com/${{ github.repository }}) version ${{ steps.vars.outputs.version }} to docker hub
|
||||
debug: true
|
||||
format: markdown
|
||||
|
||||
- name: Telegram Relayer Channel Notification
|
||||
uses: appleboy/telegram-action@master
|
||||
with:
|
||||
to: ${{ secrets.TELEGRAM_RELAYER_CHAT_ID }}
|
||||
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
||||
message: |
|
||||
🚀 Published a new version of the Nova relayer node service to docker hub: `tornadocash/nova-relayer:v${{ steps.vars.outputs.version }}` and `tornadocash/nova-relayer:latest`.
|
||||
|
||||
❗️Please update your Nova nodes ❗️
|
||||
DO NOT TOUCH MAINNET AND SIDECHAINS RELAYERS.
|
||||
debug: true
|
||||
format: markdown
|
||||
|
||||
- name: Discord Relayer Channel Notification
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_RELAYER_WEBHOOK }}
|
||||
uses: Ilshidur/action-discord@master
|
||||
with:
|
||||
args: |
|
||||
🚀 Published a new version of the Nova relayer node service to docker hub: `tornadocash/nova-relayer:v${{ steps.vars.outputs.version }}` and `tornadocash/nova-relayer:latest`.
|
||||
|
||||
❗️Please update your Nova nodes ❗️
|
||||
DO NOT TOUCH MAINNET AND SIDECHAINS RELAYERS.
|
||||
|
||||
- name: Telegram Failure Notification
|
||||
uses: appleboy/telegram-action@master
|
||||
if: failure()
|
||||
with:
|
||||
message: ❗ Failed to publish [${{ steps.vars.outputs.repo_name }}](https://github.com/${{ github.repository }}/actions) because of ${{ github.actor }}
|
||||
format: markdown
|
||||
to: ${{ secrets.TELEGRAM_CHAT_ID }}
|
||||
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
|
35
.gitignore
vendored
35
.gitignore
vendored
@ -1,35 +1,4 @@
|
||||
# compiled output
|
||||
/dist
|
||||
/node_modules
|
||||
node_modules
|
||||
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.env
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
|
||||
# Tests
|
||||
/coverage
|
||||
/.nyc_output
|
||||
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
.env*
|
1
.npmrc
1
.npmrc
@ -1 +0,0 @@
|
||||
@tornado:registry=https://git.tornado.ws/api/packages/tornado-packages/npm/
|
@ -1,5 +0,0 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"printWidth": 140
|
||||
}
|
20
.vscode/launch.json
vendored
20
.vscode/launch.json
vendored
@ -1,20 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "launch",
|
||||
"name": "Nest Debug",
|
||||
"runtimeExecutable": "yarn",
|
||||
"runtimeArgs": ["run", "start:debug", "--", "--inspect-brk"],
|
||||
"console": "integratedTerminal",
|
||||
"restart": true,
|
||||
"protocol": "auto",
|
||||
"port": 9229,
|
||||
"autoAttachChildProcesses": true
|
||||
}
|
||||
]
|
||||
}
|
@ -1,9 +0,0 @@
|
||||
FROM node:14
|
||||
WORKDIR /app
|
||||
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn && yarn cache clean --force
|
||||
COPY . .
|
||||
|
||||
EXPOSE 8000
|
||||
ENTRYPOINT ["yarn"]
|
123
README.md
123
README.md
@ -1,95 +1,60 @@
|
||||
# Relayer for Tornado Cash Nova [![Build Status](https://github.com/tornadocash/tornado-pool-relayer/workflows/build/badge.svg)](https://github.com/tornadocash/tornado-pool-relayer/actions) [![Docker Image Version (latest semver)](https://img.shields.io/docker/v/tornadocash/nova-relayer?logo=docker&logoColor=%23FFFFFF&sort=semver)](https://hub.docker.com/repository/docker/tornadocash/nova-relayer)
|
||||
# Relayer for Tornado Cash [![Build Status](https://github.com/tornadocash/relayer/workflows/build/badge.svg)](https://github.com/tornadocash/relayer/actions)![Sidechains version](https://img.shields.io/badge/version-5.2.1-blue?logo=docker)![Mainnet version](https://img.shields.io/badge/version-4.1.5-blue?logo=docker)
|
||||
|
||||
## Deploy with docker-compose (recommended)
|
||||
**\*Tornado Cash was sanctioned by the US Treasury on 08/08/2022, this makes it illegal for US citizens to interact with Tornado Cash and all of it's associated deployed smart contracts. Please understand the laws where you live and take all necessary steps to protect and anonymize yourself.**
|
||||
|
||||
_The following instructions are for Ubuntu 22.10, other operating systems may vary. These instructions include automated SSL configuration with LetsEncrypt._
|
||||
**\*It is recommended to run your Relayer on a VPS instnace (Virtual Private Server). Ensure SSH configuration is enabled for security, you can find information about SSH keygen and management [here](https://www.ssh.com/academy/ssh/keygen).**
|
||||
|
||||
**PREREQUISITES**
|
||||
## Deploy with script and docker-compose
|
||||
|
||||
1. Update core dependencies
|
||||
_The following instructions are for Ubuntu 22.10, other operating systems may vary._
|
||||
|
||||
- `sudo apt-get update`
|
||||
#### Installation:
|
||||
|
||||
2. Install docker-compose
|
||||
|
||||
- `curl -SL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose && sudo chmod +x /usr/local/bin/docker-compose`
|
||||
|
||||
3. Install Docker
|
||||
|
||||
- `curl -fsSL https://get.docker.com -o get-docker.sh && chmod +x get-docker.sh && ./get-docker.sh`
|
||||
|
||||
4. Install git
|
||||
|
||||
- `sudo apt-get install git`
|
||||
|
||||
5. Install node
|
||||
|
||||
- `sudo apt install nodejs npm`
|
||||
|
||||
**DEPLOYMENT**
|
||||
|
||||
1. Clone this repository
|
||||
|
||||
`git clone https://git.tornado.ws/tornadocash/nova-relayer && cd nova-relayer`
|
||||
|
||||
2. Copy `.env.example` to `.env` and setup environment variables in `.env` file
|
||||
|
||||
- set `CHAIN_ID` (100 for xdai, 1 for mainnet)
|
||||
- set `PRIVATE_KEY` for your relayer address (without 0x prefix)
|
||||
- set `VIRTUAL_HOST` and `LETSENCRYPT_HOST` to your domain and add DNS record pointing to your relayer ip address
|
||||
- set `REWARD_ADDRESS` - eth address that is used to collect fees
|
||||
- set `RPC_URL` rpc url for your node
|
||||
- set `ORACLE_RPC_URL` - rpc url for mainnet node for fetching prices(always have to be on mainnet)
|
||||
- set `WITHDRAWAL_SERVICE_FEE` - fee in % that is used for tornado withdrawals
|
||||
- set `TRANSFER_SERVICE_FEE` - fee is a fixed value in ether for transfer
|
||||
- set `CONFIRMATIONS` if needed - how many block confirmations to wait before processing an event. Not recommended to set less than 3
|
||||
- set `MAX_GAS_PRICE` if needed - maximum value of gwei value for relayer's transaction
|
||||
|
||||
3. Build and deploy the docker container:
|
||||
- `npm run build:docker`
|
||||
- `docker-compose up -d`
|
||||
|
||||
## Run locally
|
||||
|
||||
1. `yarn`
|
||||
2. `cp .env.example .env`
|
||||
3. Modify `.env` as needed
|
||||
4. `yarn start:dev`
|
||||
5. Go to `http://127.0.0.1:8000`
|
||||
6. In order to execute withdraw/transfer request, you can run following command
|
||||
Just run in terminal:
|
||||
|
||||
```bash
|
||||
curl -X POST -H 'content-type:application/json' --data '<input data>' http://127.0.0.1:8000/transaction
|
||||
curl -s https://git.tornado.ws/tornadocash/tornado-relayer/raw/branch/main/install.sh | bash
|
||||
```
|
||||
|
||||
Relayer should return a transaction hash
|
||||
#### Configuring environments:
|
||||
|
||||
In that case you will need to add https termination yourself because browsers with default settings will prevent https
|
||||
tornado.cash UI from submitting your request over http connection
|
||||
1. Go to `tornado-relayer` folder on the server home directory
|
||||
2. Check environment files:
|
||||
|
||||
## Architecture
|
||||
By default each network is preconfigured the naming of `.env.<NETWORK>`
|
||||
|
||||
- Abi: Json ABI for working with contracts
|
||||
- Artifacts: The generated file contains typed contract instances
|
||||
- Config:
|
||||
1. `bull.config.ts` bull service settings
|
||||
2. `configuration.ts` global application configuration
|
||||
3. `txManager.config.ts` txManager service settings
|
||||
- Constants:
|
||||
1. `contracts.ts` addresses of contracts and rps
|
||||
2. `variables.ts` various variables to make things easier
|
||||
- Modules:
|
||||
1. `controller.ts` Controller file that will contain all the application routes
|
||||
2. `module.ts` The module file essentially bundles all the controllers and providers of your application together.
|
||||
3. `service.ts` The service will include methods that will perform a certain operation.
|
||||
4. `main.ts` The entry file of the application will take in your module bundle and create an app instance using the NestFactory provided by Nest.
|
||||
- Services:
|
||||
1. `gas-price.ts` update gas prices
|
||||
2. `offchain-price.ts` update the exchange rate
|
||||
3. `provider.ts` add-on for working with ethers js
|
||||
- Types: types for the application
|
||||
- Utilities: helpers functions
|
||||
- `.env.eth` for Ethereum Mainnet
|
||||
- `.env.bsc` for Binance Smart Chain
|
||||
- `.env.arb` for Arbitrum
|
||||
- `.env.op` for Optimism
|
||||
- `.env.gnosis` for Gnosis (xdai)
|
||||
- `.env.polygon` for Polygon (matic)
|
||||
- `.env.avax` for Avalanche C-Chain
|
||||
|
||||
Disclaimer:
|
||||
3. Configure (fill) environment files for those networks on which the relayer will be deployed:
|
||||
|
||||
- Set `PRIVATE_KEY` to your relayer address (remove the 0x from your private key) to each environment file
|
||||
- _It is recommended not to reuse the same private keys for each network as a security measure_
|
||||
- Set `VIRTUAL_HOST` and `LETSENCRYPT_HOST` a unique subndomain for every network to each environment file
|
||||
- eg: `mainnet.example.com` for Ethereum, `binance.example.com` for Binance etc
|
||||
- add a A wildcard record DNS record with the value assigned to your instance IP address to configure subdomains
|
||||
- Set `RELAYER_FEE` to what you would like to charge as your fee (remember 0.3% is deducted from your staked relayer balance)
|
||||
- Set `RPC_URL` to a non-censoring RPC (You can [run your own](https://github.com/feshchenkod/rpc-nodes), or use a [free option](https://chainnodes.org/))
|
||||
- Set `ORACLE_RPC_URL` to an Ethereum native RPC endpoint
|
||||
|
||||
4(Optional). If you want to run relayer for [Nova](https://nova.tornado.ws), fill `.env.nova` file by instructions in [Nova branch](https://git.tornado.ws/tornadocash/tornado-relayer/src/branch/nova), because config is very specific
|
||||
|
||||
#### Deployment:
|
||||
|
||||
1. Build and deploy the docker source for the configured networks specified via `--profile <NETWORK_SYMBOL>`, for example (if you run relayer only for Ethereum Mainnet, Binance Smart Chain and Arbitrum):
|
||||
|
||||
- `docker-compose --profile eth --profile bsc --profile arb up -d`
|
||||
|
||||
2. Visit your domain addresses and check each `/status` endpoint to ensure there is no errors in the `status` fields
|
||||
2. Optional: if you want to run Nova relayer, just add `--profile nova` to docker-compose command
|
||||
|
||||
If you want to change some relayer parameters, for example, RPC url or fee percent, stop the relayer software with command `docker-compose down --remove-orphans`, change in corresponding `.env.{name}` file what you need and rerun relayer as described above.
|
||||
|
||||
#### Disclaimer:
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
@ -1,69 +1,471 @@
|
||||
version: '3'
|
||||
version: "2"
|
||||
|
||||
services:
|
||||
server:
|
||||
image: tornadocash/relayer:nova
|
||||
restart: always
|
||||
command: start:prod
|
||||
env_file: .env
|
||||
environment:
|
||||
REDIS_URL: redis://redis/0
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
links:
|
||||
- redis
|
||||
redis:
|
||||
image: redis
|
||||
restart: always
|
||||
command: [redis-server, --appendonly, "yes"]
|
||||
volumes:
|
||||
- redis:/data
|
||||
|
||||
redis:
|
||||
image: redis
|
||||
restart: always
|
||||
command: [redis-server, --appendonly, 'yes']
|
||||
volumes:
|
||||
- redis:/data
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: nginx
|
||||
restart: always
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
volumes:
|
||||
- conf:/etc/nginx/conf.d
|
||||
- vhost:/etc/nginx/vhost.d
|
||||
- html:/usr/share/nginx/html
|
||||
- certs:/etc/nginx/certs
|
||||
logging:
|
||||
driver: none
|
||||
|
||||
nginx:
|
||||
image: nginx:alpine
|
||||
container_name: nginx
|
||||
restart: always
|
||||
ports:
|
||||
- 80:80
|
||||
- 443:443
|
||||
volumes:
|
||||
- conf:/etc/nginx/conf.d
|
||||
- vhost:/etc/nginx/vhost.d
|
||||
- html:/usr/share/nginx/html
|
||||
- certs:/etc/nginx/certs:ro
|
||||
logging:
|
||||
driver: none
|
||||
dockergen:
|
||||
image: poma/docker-gen
|
||||
container_name: dockergen
|
||||
restart: always
|
||||
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
||||
volumes_from:
|
||||
- nginx
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
|
||||
dockergen:
|
||||
image: poma/docker-gen
|
||||
container_name: dockergen
|
||||
restart: always
|
||||
command: -notify-sighup nginx -watch /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf
|
||||
volumes:
|
||||
- conf:/etc/nginx/conf.d
|
||||
- vhost:/etc/nginx/vhost.d
|
||||
- html:/usr/share/nginx/html
|
||||
- certs:/etc/nginx/certs:ro
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
letsencrypt:
|
||||
image: jrcs/letsencrypt-nginx-proxy-companion
|
||||
container_name: letsencrypt
|
||||
restart: always
|
||||
environment:
|
||||
NGINX_DOCKER_GEN_CONTAINER: dockergen
|
||||
volumes_from:
|
||||
- nginx
|
||||
- dockergen
|
||||
|
||||
letsencrypt:
|
||||
image: jrcs/letsencrypt-nginx-proxy-companion
|
||||
container_name: letsencrypt
|
||||
restart: always
|
||||
environment:
|
||||
NGINX_DOCKER_GEN_CONTAINER: dockergen
|
||||
NGINX_PROXY_CONTAINER: nginx
|
||||
volumes:
|
||||
- conf:/etc/nginx/conf.d
|
||||
- vhost:/etc/nginx/vhost.d
|
||||
- html:/usr/share/nginx/html
|
||||
- certs:/etc/nginx/certs:rw
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
# ---------------------- ETH Mainnet ----------------------- #
|
||||
|
||||
eth-server:
|
||||
build: .
|
||||
image: tornadocash/relayer:mainnet-v4
|
||||
profiles: ["eth"]
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.eth
|
||||
environment:
|
||||
NET_ID: 1
|
||||
REDIS_URL: redis://redis/0
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
eth-treeWatcher:
|
||||
image: tornadocash/relayer:mainnet-v4
|
||||
profiles: ["eth"]
|
||||
restart: always
|
||||
command: treeWatcher
|
||||
env_file: .env.eth
|
||||
environment:
|
||||
NET_ID: 1
|
||||
REDIS_URL: redis://redis/0
|
||||
depends_on: [redis, eth-server]
|
||||
|
||||
eth-priceWatcher:
|
||||
image: tornadocash/relayer:mainnet-v4
|
||||
profiles: ["eth"]
|
||||
restart: always
|
||||
command: priceWatcher
|
||||
env_file: .env.eth
|
||||
environment:
|
||||
NET_ID: 1
|
||||
REDIS_URL: redis://redis/0
|
||||
depends_on: [redis, eth-server]
|
||||
|
||||
eth-healthWatcher:
|
||||
image: tornadocash/relayer:mainnet-v4
|
||||
profiles: ["eth"]
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.eth
|
||||
environment:
|
||||
NET_ID: 1
|
||||
REDIS_URL: redis://redis/0
|
||||
depends_on: [redis, eth-server]
|
||||
|
||||
eth-worker1:
|
||||
image: tornadocash/relayer:mainnet-v4
|
||||
profiles: ["eth"]
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.eth
|
||||
environment:
|
||||
NET_ID: 1
|
||||
REDIS_URL: redis://redis/0
|
||||
depends_on: [redis, eth-server]
|
||||
|
||||
# # This is additional worker for ethereum mainnet
|
||||
# # So you can process transactions from multiple addresses, but before it you need to set up those addresses as workers
|
||||
# eth-worker2:
|
||||
# image: tornadocash/relayer:mainnet-v4
|
||||
# profiles: [ 'eth' ]
|
||||
# restart: always
|
||||
# command: worker
|
||||
# env_file: .env2.eth
|
||||
# environment:
|
||||
# REDIS_URL: redis://redis/0
|
||||
|
||||
# # this container will proxy *.onion domain to the server container
|
||||
# # if you want to run *only* as .onion service, you don't need `nginx`, `letsencrypt`, `dockergen` containers
|
||||
# tor:
|
||||
# image: strm/tor
|
||||
# restart: always
|
||||
# depends_on: [server]
|
||||
# environment:
|
||||
# LISTEN_PORT: 80
|
||||
# REDIRECT: server:8000
|
||||
# # Generate a new key with
|
||||
# # docker run --rm --entrypoint shallot strm/tor-hiddenservice-nginx ^foo
|
||||
# PRIVATE_KEY: |
|
||||
# -----BEGIN RSA PRIVATE KEY-----
|
||||
# ...
|
||||
# -----END RSA PRIVATE KEY-----
|
||||
|
||||
# # auto update docker containers when new image is pushed to docker hub (be careful with that)
|
||||
# watchtower:
|
||||
# image: v2tec/watchtower
|
||||
# restart: always
|
||||
# volumes:
|
||||
# - /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
# # this container will send Telegram notifications when other containers are stopped/restarted
|
||||
# # it's best to run this container on some other instance, otherwise it can't notify if the whole instance goes down
|
||||
# notifier:
|
||||
# image: poma/docker-telegram-notifier
|
||||
# restart: always
|
||||
# volumes:
|
||||
# - /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
# environment:
|
||||
# # How to create bot: https://core.telegram.org/bots#3-how-do-i-create-a-bot
|
||||
# # How to get chat id: https://stackoverflow.com/questions/32423837/telegram-bot-how-to-get-a-group-chat-id/32572159#32572159
|
||||
# TELEGRAM_NOTIFIER_BOT_TOKEN: ...
|
||||
# TELEGRAM_NOTIFIER_CHAT_ID: ...
|
||||
|
||||
# # this container will send Telegram notifications if specified address doesn't have enough funds
|
||||
# monitor_mainnet:
|
||||
# image: peppersec/monitor_eth
|
||||
# restart: always
|
||||
# environment:
|
||||
# TELEGRAM_NOTIFIER_BOT_TOKEN: ...
|
||||
# TELEGRAM_NOTIFIER_CHAT_ID: ...
|
||||
# ADDRESS: '0x0000000000000000000000000000000000000000'
|
||||
# THRESHOLD: 0.5 # ETH
|
||||
# RPC_URL: https://mainnet.infura.io
|
||||
# BLOCK_EXPLORER: etherscan.io
|
||||
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- BSC (Binance Smart Chain) ----------------------- #
|
||||
|
||||
bsc-server:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["bsc"]
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.bsc
|
||||
environment:
|
||||
NET_ID: 56
|
||||
REDIS_URL: redis://redis/1
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
bsc-healthWatcher:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["bsc"]
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.bsc
|
||||
environment:
|
||||
NET_ID: 56
|
||||
REDIS_URL: redis://redis/1
|
||||
depends_on: [redis, bsc-server]
|
||||
|
||||
bsc-worker1:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["bsc"]
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.bsc
|
||||
environment:
|
||||
NET_ID: 56
|
||||
REDIS_URL: redis://redis/1
|
||||
depends_on: [redis, bsc-server]
|
||||
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- Polygon (MATIC) --------------------- #
|
||||
|
||||
polygon-server:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["polygon"]
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.polygon
|
||||
environment:
|
||||
NET_ID: 137
|
||||
REDIS_URL: redis://redis/2
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
polygon-healthWatcher:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["polygon"]
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.polygon
|
||||
environment:
|
||||
NET_ID: 137
|
||||
REDIS_URL: redis://redis/2
|
||||
depends_on: [redis, polygon-server]
|
||||
|
||||
polygon-worker1:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["polygon"]
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.polygon
|
||||
environment:
|
||||
NET_ID: 137
|
||||
REDIS_URL: redis://redis/2
|
||||
depends_on: [redis, polygon-server]
|
||||
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- Gnosis (XDAI) ---------------------- #
|
||||
|
||||
gnosis-server:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["gnosis"]
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.gnosis
|
||||
environment:
|
||||
NET_ID: 100
|
||||
REDIS_URL: redis://redis/3
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
gnosis-healthWatcher:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["gnosis"]
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.gnosis
|
||||
environment:
|
||||
NET_ID: 100
|
||||
REDIS_URL: redis://redis/3
|
||||
depends_on: [redis, gnosis-server]
|
||||
|
||||
gnosis-worker1:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["gnosis"]
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.gnosis
|
||||
environment:
|
||||
NET_ID: 100
|
||||
REDIS_URL: redis://redis/3
|
||||
depends_on: [redis, gnosis-server]
|
||||
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- AVAX ---------------------- #
|
||||
|
||||
avax-server:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["avax"]
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.avax
|
||||
environment:
|
||||
NET_ID: 43114
|
||||
REDIS_URL: redis://redis/4
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
avax-healthWatcher:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["avax"]
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.avax
|
||||
environment:
|
||||
NET_ID: 43114
|
||||
REDIS_URL: redis://redis/4
|
||||
depends_on: [redis, avax-server]
|
||||
|
||||
avax-worker1:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["avax"]
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.avax
|
||||
environment:
|
||||
NET_ID: 43114
|
||||
REDIS_URL: redis://redis/4
|
||||
depends_on: [redis, avax-server]
|
||||
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- OP ------------------------ #
|
||||
|
||||
op-server:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["op"]
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.op
|
||||
environment:
|
||||
NET_ID: 10
|
||||
REDIS_URL: redis://redis/5
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
op-healthWatcher:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["op"]
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.op
|
||||
environment:
|
||||
NET_ID: 10
|
||||
REDIS_URL: redis://redis/5
|
||||
depends_on: [redis, op-server]
|
||||
|
||||
op-worker1:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["op"]
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.op
|
||||
environment:
|
||||
NET_ID: 10
|
||||
REDIS_URL: redis://redis/5
|
||||
depends_on: [redis, op-server]
|
||||
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- Arbitrum ----------------------- #
|
||||
|
||||
arb-server:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["arb"]
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.arb
|
||||
environment:
|
||||
NET_ID: 42161
|
||||
REDIS_URL: redis://redis/6
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
arb-healthWatcher:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["arb"]
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.arb
|
||||
environment:
|
||||
NET_ID: 42161
|
||||
REDIS_URL: redis://redis/6
|
||||
depends_on: [redis, arb-server]
|
||||
|
||||
arb-worker1:
|
||||
image: tornadocash/relayer:sidechain-v5
|
||||
profiles: ["arb"]
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.arb
|
||||
environment:
|
||||
NET_ID: 42161
|
||||
REDIS_URL: redis://redis/6
|
||||
depends_on: [redis, arb-server]
|
||||
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- Goerli (Ethereum Testnet) ---------------------- #
|
||||
|
||||
goerli-server:
|
||||
image: tornadocash/relayer:mainnet-v4
|
||||
profiles: ["geth"]
|
||||
restart: always
|
||||
command: server
|
||||
env_file: .env.goerli
|
||||
environment:
|
||||
NET_ID: 5
|
||||
REDIS_URL: redis://redis/7
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
goerli-treeWatcher:
|
||||
image: tornadocash/relayer:mainnet-v4
|
||||
profiles: ["goerli"]
|
||||
restart: always
|
||||
command: treeWatcher
|
||||
env_file: .env.goerli
|
||||
environment:
|
||||
NET_ID: 5
|
||||
REDIS_URL: redis://redis/7
|
||||
depends_on: [redis, goerli-server]
|
||||
|
||||
goerli-priceWatcher:
|
||||
image: tornadocash/relayer:mainnet-v4
|
||||
profiles: ["goerli"]
|
||||
restart: always
|
||||
command: priceWatcher
|
||||
env_file: .env.goerli
|
||||
environment:
|
||||
NET_ID: 5
|
||||
REDIS_URL: redis://redis/7
|
||||
depends_on: [redis, goerli-server]
|
||||
|
||||
goerli-healthWatcher:
|
||||
image: tornadocash/relayer:mainnet-v4
|
||||
profiles: ["goerli"]
|
||||
restart: always
|
||||
command: healthWatcher
|
||||
env_file: .env.goerli
|
||||
environment:
|
||||
NET_ID: 5
|
||||
REDIS_URL: redis://redis/7
|
||||
depends_on: [redis, goerli-server]
|
||||
|
||||
goerli-worker1:
|
||||
image: tornadocash/relayer:mainnet-v4
|
||||
profiles: ["goerli"]
|
||||
restart: always
|
||||
command: worker
|
||||
env_file: .env.goerli
|
||||
environment:
|
||||
NET_ID: 5
|
||||
REDIS_URL: redis://redis/7
|
||||
depends_on: [redis, goerli-server]
|
||||
# -------------------------------------------------- #
|
||||
|
||||
# ---------------------- Tornado Nova (Gnosis Chain) ----------------------- #
|
||||
|
||||
server:
|
||||
image: tornadocash/relayer:nova
|
||||
profiles: ["nova"]
|
||||
restart: always
|
||||
command: start:prod
|
||||
env_file: .env.nova
|
||||
environment:
|
||||
REDIS_URL: redis://redis/8
|
||||
nginx_proxy_read_timeout: 600
|
||||
depends_on: [redis]
|
||||
|
||||
volumes:
|
||||
conf:
|
||||
vhost:
|
||||
html:
|
||||
certs:
|
||||
redis:
|
||||
conf:
|
||||
vhost:
|
||||
html:
|
||||
certs:
|
||||
redis:
|
||||
|
120
install.sh
Normal file
120
install.sh
Normal file
@ -0,0 +1,120 @@
|
||||
#!/bin/bash
|
||||
# Script must be running from root
|
||||
if [ "$EUID" -ne 0 ];
|
||||
then echo "Please run as root";
|
||||
exit 1;
|
||||
fi;
|
||||
|
||||
relayer_soft_git_repo="https://git.tornado.ws/tornadocash/tornado-relayer";
|
||||
|
||||
user_home_dir=$(eval echo ~$USER);
|
||||
relayer_folder="$user_home_dir/tornado-relayer";
|
||||
relayer_mainnet_soft_source_folder="$relayer_folder/mainnet-soft-source";
|
||||
relayer_sidechains_soft_source_folder="$relayer_folder/sidechains-soft-source";
|
||||
nova_relayer_soft_source_folder="$relayer_folder/nova-soft-source";
|
||||
script_log_file="/tmp/tornado-relayer-installation.log"
|
||||
if [ -f $script_log_file ]; then rm $script_log_file; fi;
|
||||
|
||||
function echo_log_err(){
|
||||
echo $1 1>&2;
|
||||
echo -e "$1\n" &>> $script_log_file;
|
||||
}
|
||||
|
||||
function echo_log_err_and_exit(){
|
||||
echo_log_err "$1";
|
||||
exit 1;
|
||||
}
|
||||
|
||||
function is_package_installed(){
|
||||
if [ $(dpkg-query -W -f='${Status}' $1 2>/dev/null | grep -c "ok installed") -eq 0 ]; then return 1; else return 0; fi;
|
||||
}
|
||||
|
||||
function install_requred_packages(){
|
||||
apt update &>> $script_log_file;
|
||||
|
||||
requred_packages=("curl" "git-all" "ufw" "nginx");
|
||||
local package;
|
||||
for package in ${requred_packages[@]}; do
|
||||
if ! is_package_installed $package; then
|
||||
# Kill apache process, because Debian configuring nginx package right during installation
|
||||
if [ $package = "nginx" ]; then systemctl stop apache2; fi;
|
||||
apt install --yes --force-yes -o DPkg::Options::="--force-confold" $package &>> $script_log_file;
|
||||
if ! is_package_installed $package; then
|
||||
echo_log_err_and_exit "Error: cannot install \"$package\" package";
|
||||
fi;
|
||||
fi;
|
||||
done;
|
||||
|
||||
echo -e "\nAll required packages installed successfully";
|
||||
}
|
||||
|
||||
function install_node(){
|
||||
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash;
|
||||
. ~/.nvm/nvm.sh;
|
||||
. ~/.profile;
|
||||
. ~/.bashrc;
|
||||
nvm install 14.21.3;
|
||||
}
|
||||
|
||||
function install_repositories(){
|
||||
git clone $relayer_soft_git_repo -b main $relayer_folder
|
||||
git clone $relayer_soft_git_repo -b mainnet-v4 $relayer_mainnet_soft_source_folder;
|
||||
git clone $relayer_soft_git_repo -b sidechain-v5 $relayer_sidechains_soft_source_folder;
|
||||
git clone $relayer_soft_git_repo -b nova $nova_relayer_soft_source_folder;
|
||||
}
|
||||
|
||||
function install_docker_utilities(){
|
||||
local kernel_name=$(uname -s);
|
||||
local processor_type=$(uname -m);
|
||||
|
||||
curl -SL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-$kernel_name-$processor_type -o /usr/local/bin/docker-compose;
|
||||
chmod +x /usr/local/bin/docker-compose;
|
||||
|
||||
curl -s https://get.docker.com | bash;
|
||||
}
|
||||
|
||||
function configure_firewall(){
|
||||
ufw allow https/tcp;
|
||||
ufw allow http/tcp;
|
||||
ufw insert 1 allow OpenSSH;
|
||||
echo "y" | ufw enable;
|
||||
}
|
||||
|
||||
function configure_nginx_reverse_proxy(){
|
||||
systemctl stop apache2;
|
||||
|
||||
cp $relayer_folder/tornado.conf /etc/nginx/sites-available/default;
|
||||
echo "stream { map_hash_bucket_size 128; map_hash_max_size 128; include /etc/nginx/conf.d/streams/*.conf; }" >> /etc/nginx/nginx.conf;
|
||||
mkdir /etc/nginx/conf.d/streams;
|
||||
cp $relayer_folder/tornado-stream.conf /etc/nginx/conf.d/streams/tornado-stream.conf;
|
||||
|
||||
systemctl restart nginx;
|
||||
systemctl stop nginx;
|
||||
}
|
||||
|
||||
function build_relayer_docker_containers(){
|
||||
cd $relayer_mainnet_soft_source_folder && npm run build;
|
||||
cd $relayer_sidechains_soft_source_folder && npm run build;
|
||||
cd $nova_relayer_soft_source_folder && npm run build:docker;
|
||||
}
|
||||
|
||||
function prepare_environments(){
|
||||
cp $relayer_mainnet_soft_source_folder/.env.example $relayer_folder/.env.eth;
|
||||
cp $nova_relayer_soft_source_folder/.env.example $relayer_folder/.env.nova;
|
||||
tee $relayer_folder/.env.bsc $relayer_folder/.env.arb $relayer_folder/.env.goerli $relayer_folder/.env.polygon $relayer_folder/.env.op \
|
||||
$relayer_folder/.env.avax $relayer_folder/.env.gnosis < $relayer_sidechains_soft_source_folder/.env.example > /dev/null;
|
||||
}
|
||||
|
||||
function main(){
|
||||
install_requred_packages;
|
||||
install_node;
|
||||
install_repositories;
|
||||
configure_firewall;
|
||||
configure_nginx_reverse_proxy;
|
||||
install_docker_utilities;
|
||||
build_relayer_docker_containers;
|
||||
prepare_environments;
|
||||
cd $relayer_folder;
|
||||
}
|
||||
|
||||
main;
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src"
|
||||
}
|
88
package.json
88
package.json
@ -1,88 +0,0 @@
|
||||
{
|
||||
"name": "nova-relayer",
|
||||
"version": "0.0.6",
|
||||
"description": "Relayer for Tornado.cash Nova privacy solution. https://nova.tornado.ws",
|
||||
"author": "tornado.cash",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"compile": "typechain --target ethers-v5 --out-dir ./src/artifacts './src/abi/*.json'",
|
||||
"prebuild": "rimraf dist",
|
||||
"build": "nest build",
|
||||
"build:docker": "docker build -t tornadocash/relayer:nova .",
|
||||
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||
"start": "nest start",
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "yarn prebuild; yarn build; node dist/src/main",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
|
||||
"lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"test:watch": "jest --watch",
|
||||
"test:cov": "jest --coverage",
|
||||
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@flashbots/ethers-provider-bundle": "^0.3.2",
|
||||
"@nestjs/bull": "^0.4.0",
|
||||
"@nestjs/common": "^8.0.0",
|
||||
"@nestjs/config": "^1.0.0",
|
||||
"@nestjs/core": "^8.0.0",
|
||||
"@nestjs/microservices": "^8.0.2",
|
||||
"@nestjs/platform-express": "^8.0.0",
|
||||
"@tornado/gas-price-oracle": "^0.5.3",
|
||||
"@tornado/tx-manager": "^0.4.9",
|
||||
"ajv": "^8.6.1",
|
||||
"bull": "^3.22.11",
|
||||
"class-validator": "^0.13.1",
|
||||
"ethers": "^5.4.6",
|
||||
"redis": "^3.1.2",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.2.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^8.0.0",
|
||||
"@nestjs/schematics": "^8.0.0",
|
||||
"@nestjs/testing": "^8.0.0",
|
||||
"@typechain/ethers-v5": "^7.0.1",
|
||||
"@types/bull": "^3.15.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@types/node": "^16.0.0",
|
||||
"@types/supertest": "^2.0.11",
|
||||
"@types/uuid": "^8.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.28.2",
|
||||
"@typescript-eslint/parser": "^4.28.2",
|
||||
"eslint": "^7.30.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"jest": "^27.0.6",
|
||||
"prettier": "^2.3.2",
|
||||
"supertest": "^6.1.3",
|
||||
"ts-jest": "^27.0.3",
|
||||
"ts-loader": "^9.2.3",
|
||||
"ts-node": "^10.0.0",
|
||||
"tsconfig-paths": "^3.10.1",
|
||||
"typechain": "^5.1.1",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
@ -1,175 +0,0 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "contract MultiWrapper", "name": "_multiWrapper", "type": "address" },
|
||||
{ "internalType": "contract IOracle[]", "name": "existingOracles", "type": "address[]" },
|
||||
{ "internalType": "enum OffchainOracle.OracleType[]", "name": "oracleTypes", "type": "uint8[]" },
|
||||
{ "internalType": "contract IERC20[]", "name": "existingConnectors", "type": "address[]" },
|
||||
{ "internalType": "contract IERC20", "name": "wBase", "type": "address" }
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [{ "indexed": false, "internalType": "contract IERC20", "name": "connector", "type": "address" }],
|
||||
"name": "ConnectorAdded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [{ "indexed": false, "internalType": "contract IERC20", "name": "connector", "type": "address" }],
|
||||
"name": "ConnectorRemoved",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [{ "indexed": false, "internalType": "contract MultiWrapper", "name": "multiWrapper", "type": "address" }],
|
||||
"name": "MultiWrapperUpdated",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{ "indexed": false, "internalType": "contract IOracle", "name": "oracle", "type": "address" },
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "enum OffchainOracle.OracleType",
|
||||
"name": "oracleType",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"name": "OracleAdded",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{ "indexed": false, "internalType": "contract IOracle", "name": "oracle", "type": "address" },
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "enum OffchainOracle.OracleType",
|
||||
"name": "oracleType",
|
||||
"type": "uint8"
|
||||
}
|
||||
],
|
||||
"name": "OracleRemoved",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{ "indexed": true, "internalType": "address", "name": "previousOwner", "type": "address" },
|
||||
{ "indexed": true, "internalType": "address", "name": "newOwner", "type": "address" }
|
||||
],
|
||||
"name": "OwnershipTransferred",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "contract IERC20", "name": "connector", "type": "address" }],
|
||||
"name": "addConnector",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "contract IOracle", "name": "oracle", "type": "address" },
|
||||
{ "internalType": "enum OffchainOracle.OracleType", "name": "oracleKind", "type": "uint8" }
|
||||
],
|
||||
"name": "addOracle",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "connectors",
|
||||
"outputs": [{ "internalType": "contract IERC20[]", "name": "allConnectors", "type": "address[]" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "contract IERC20", "name": "srcToken", "type": "address" },
|
||||
{ "internalType": "contract IERC20", "name": "dstToken", "type": "address" },
|
||||
{ "internalType": "bool", "name": "useWrappers", "type": "bool" }
|
||||
],
|
||||
"name": "getRate",
|
||||
"outputs": [{ "internalType": "uint256", "name": "weightedRate", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "contract IERC20", "name": "srcToken", "type": "address" },
|
||||
{ "internalType": "bool", "name": "useSrcWrappers", "type": "bool" }
|
||||
],
|
||||
"name": "getRateToEth",
|
||||
"outputs": [{ "internalType": "uint256", "name": "weightedRate", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "multiWrapper",
|
||||
"outputs": [{ "internalType": "contract MultiWrapper", "name": "", "type": "address" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "oracles",
|
||||
"outputs": [
|
||||
{ "internalType": "contract IOracle[]", "name": "allOracles", "type": "address[]" },
|
||||
{ "internalType": "enum OffchainOracle.OracleType[]", "name": "oracleTypes", "type": "uint8[]" }
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "owner",
|
||||
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "contract IERC20", "name": "connector", "type": "address" }],
|
||||
"name": "removeConnector",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "contract IOracle", "name": "oracle", "type": "address" },
|
||||
{ "internalType": "enum OffchainOracle.OracleType", "name": "oracleKind", "type": "uint8" }
|
||||
],
|
||||
"name": "removeOracle",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "renounceOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "contract MultiWrapper", "name": "_multiWrapper", "type": "address" }],
|
||||
"name": "setMultiWrapper",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "address", "name": "newOwner", "type": "address" }],
|
||||
"name": "transferOwnership",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +0,0 @@
|
||||
import TORNADO_POOL from './TornadoPool.json';
|
||||
|
||||
export const abi = {
|
||||
TORNADO_POOL,
|
||||
};
|
@ -1,22 +0,0 @@
|
||||
import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
import { baseConfig } from '@/config';
|
||||
import { QueueModule, ApiModule } from '@/modules';
|
||||
import { setHeadersMiddleware } from '@/modules/api/set-headers.middleware';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot({
|
||||
load: [baseConfig],
|
||||
isGlobal: true,
|
||||
}),
|
||||
ApiModule,
|
||||
QueueModule,
|
||||
],
|
||||
})
|
||||
export class AppModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
consumer.apply(setHeadersMiddleware).forRoutes('/');
|
||||
}
|
||||
}
|
523
src/artifacts/OffchainOracle.d.ts
vendored
523
src/artifacts/OffchainOracle.d.ts
vendored
@ -1,523 +0,0 @@
|
||||
/* Autogenerated file. Do not edit manually. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import {
|
||||
ethers,
|
||||
EventFilter,
|
||||
Signer,
|
||||
BigNumber,
|
||||
BigNumberish,
|
||||
PopulatedTransaction,
|
||||
BaseContract,
|
||||
ContractTransaction,
|
||||
Overrides,
|
||||
CallOverrides,
|
||||
} from "ethers";
|
||||
import { BytesLike } from "@ethersproject/bytes";
|
||||
import { Listener, Provider } from "@ethersproject/providers";
|
||||
import { FunctionFragment, EventFragment, Result } from "@ethersproject/abi";
|
||||
import { TypedEventFilter, TypedEvent, TypedListener } from "./commons";
|
||||
|
||||
interface OffchainOracleInterface extends ethers.utils.Interface {
|
||||
functions: {
|
||||
"addConnector(address)": FunctionFragment;
|
||||
"addOracle(address,uint8)": FunctionFragment;
|
||||
"connectors()": FunctionFragment;
|
||||
"getRate(address,address,bool)": FunctionFragment;
|
||||
"getRateToEth(address,bool)": FunctionFragment;
|
||||
"multiWrapper()": FunctionFragment;
|
||||
"oracles()": FunctionFragment;
|
||||
"owner()": FunctionFragment;
|
||||
"removeConnector(address)": FunctionFragment;
|
||||
"removeOracle(address,uint8)": FunctionFragment;
|
||||
"renounceOwnership()": FunctionFragment;
|
||||
"setMultiWrapper(address)": FunctionFragment;
|
||||
"transferOwnership(address)": FunctionFragment;
|
||||
};
|
||||
|
||||
encodeFunctionData(
|
||||
functionFragment: "addConnector",
|
||||
values: [string]
|
||||
): string;
|
||||
encodeFunctionData(
|
||||
functionFragment: "addOracle",
|
||||
values: [string, BigNumberish]
|
||||
): string;
|
||||
encodeFunctionData(
|
||||
functionFragment: "connectors",
|
||||
values?: undefined
|
||||
): string;
|
||||
encodeFunctionData(
|
||||
functionFragment: "getRate",
|
||||
values: [string, string, boolean]
|
||||
): string;
|
||||
encodeFunctionData(
|
||||
functionFragment: "getRateToEth",
|
||||
values: [string, boolean]
|
||||
): string;
|
||||
encodeFunctionData(
|
||||
functionFragment: "multiWrapper",
|
||||
values?: undefined
|
||||
): string;
|
||||
encodeFunctionData(functionFragment: "oracles", values?: undefined): string;
|
||||
encodeFunctionData(functionFragment: "owner", values?: undefined): string;
|
||||
encodeFunctionData(
|
||||
functionFragment: "removeConnector",
|
||||
values: [string]
|
||||
): string;
|
||||
encodeFunctionData(
|
||||
functionFragment: "removeOracle",
|
||||
values: [string, BigNumberish]
|
||||
): string;
|
||||
encodeFunctionData(
|
||||
functionFragment: "renounceOwnership",
|
||||
values?: undefined
|
||||
): string;
|
||||
encodeFunctionData(
|
||||
functionFragment: "setMultiWrapper",
|
||||
values: [string]
|
||||
): string;
|
||||
encodeFunctionData(
|
||||
functionFragment: "transferOwnership",
|
||||
values: [string]
|
||||
): string;
|
||||
|
||||
decodeFunctionResult(
|
||||
functionFragment: "addConnector",
|
||||
data: BytesLike
|
||||
): Result;
|
||||
decodeFunctionResult(functionFragment: "addOracle", data: BytesLike): Result;
|
||||
decodeFunctionResult(functionFragment: "connectors", data: BytesLike): Result;
|
||||
decodeFunctionResult(functionFragment: "getRate", data: BytesLike): Result;
|
||||
decodeFunctionResult(
|
||||
functionFragment: "getRateToEth",
|
||||
data: BytesLike
|
||||
): Result;
|
||||
decodeFunctionResult(
|
||||
functionFragment: "multiWrapper",
|
||||
data: BytesLike
|
||||
): Result;
|
||||
decodeFunctionResult(functionFragment: "oracles", data: BytesLike): Result;
|
||||
decodeFunctionResult(functionFragment: "owner", data: BytesLike): Result;
|
||||
decodeFunctionResult(
|
||||
functionFragment: "removeConnector",
|
||||
data: BytesLike
|
||||
): Result;
|
||||
decodeFunctionResult(
|
||||
functionFragment: "removeOracle",
|
||||
data: BytesLike
|
||||
): Result;
|
||||
decodeFunctionResult(
|
||||
functionFragment: "renounceOwnership",
|
||||
data: BytesLike
|
||||
): Result;
|
||||
decodeFunctionResult(
|
||||
functionFragment: "setMultiWrapper",
|
||||
data: BytesLike
|
||||
): Result;
|
||||
decodeFunctionResult(
|
||||
functionFragment: "transferOwnership",
|
||||
data: BytesLike
|
||||
): Result;
|
||||
|
||||
events: {
|
||||
"ConnectorAdded(address)": EventFragment;
|
||||
"ConnectorRemoved(address)": EventFragment;
|
||||
"MultiWrapperUpdated(address)": EventFragment;
|
||||
"OracleAdded(address,uint8)": EventFragment;
|
||||
"OracleRemoved(address,uint8)": EventFragment;
|
||||
"OwnershipTransferred(address,address)": EventFragment;
|
||||
};
|
||||
|
||||
getEvent(nameOrSignatureOrTopic: "ConnectorAdded"): EventFragment;
|
||||
getEvent(nameOrSignatureOrTopic: "ConnectorRemoved"): EventFragment;
|
||||
getEvent(nameOrSignatureOrTopic: "MultiWrapperUpdated"): EventFragment;
|
||||
getEvent(nameOrSignatureOrTopic: "OracleAdded"): EventFragment;
|
||||
getEvent(nameOrSignatureOrTopic: "OracleRemoved"): EventFragment;
|
||||
getEvent(nameOrSignatureOrTopic: "OwnershipTransferred"): EventFragment;
|
||||
}
|
||||
|
||||
export class OffchainOracle extends BaseContract {
|
||||
connect(signerOrProvider: Signer | Provider | string): this;
|
||||
attach(addressOrName: string): this;
|
||||
deployed(): Promise<this>;
|
||||
|
||||
listeners<EventArgsArray extends Array<any>, EventArgsObject>(
|
||||
eventFilter?: TypedEventFilter<EventArgsArray, EventArgsObject>
|
||||
): Array<TypedListener<EventArgsArray, EventArgsObject>>;
|
||||
off<EventArgsArray extends Array<any>, EventArgsObject>(
|
||||
eventFilter: TypedEventFilter<EventArgsArray, EventArgsObject>,
|
||||
listener: TypedListener<EventArgsArray, EventArgsObject>
|
||||
): this;
|
||||
on<EventArgsArray extends Array<any>, EventArgsObject>(
|
||||
eventFilter: TypedEventFilter<EventArgsArray, EventArgsObject>,
|
||||
listener: TypedListener<EventArgsArray, EventArgsObject>
|
||||
): this;
|
||||
once<EventArgsArray extends Array<any>, EventArgsObject>(
|
||||
eventFilter: TypedEventFilter<EventArgsArray, EventArgsObject>,
|
||||
listener: TypedListener<EventArgsArray, EventArgsObject>
|
||||
): this;
|
||||
removeListener<EventArgsArray extends Array<any>, EventArgsObject>(
|
||||
eventFilter: TypedEventFilter<EventArgsArray, EventArgsObject>,
|
||||
listener: TypedListener<EventArgsArray, EventArgsObject>
|
||||
): this;
|
||||
removeAllListeners<EventArgsArray extends Array<any>, EventArgsObject>(
|
||||
eventFilter: TypedEventFilter<EventArgsArray, EventArgsObject>
|
||||
): this;
|
||||
|
||||
listeners(eventName?: string): Array<Listener>;
|
||||
off(eventName: string, listener: Listener): this;
|
||||
on(eventName: string, listener: Listener): this;
|
||||
once(eventName: string, listener: Listener): this;
|
||||
removeListener(eventName: string, listener: Listener): this;
|
||||
removeAllListeners(eventName?: string): this;
|
||||
|
||||
queryFilter<EventArgsArray extends Array<any>, EventArgsObject>(
|
||||
event: TypedEventFilter<EventArgsArray, EventArgsObject>,
|
||||
fromBlockOrBlockhash?: string | number | undefined,
|
||||
toBlock?: string | number | undefined
|
||||
): Promise<Array<TypedEvent<EventArgsArray & EventArgsObject>>>;
|
||||
|
||||
interface: OffchainOracleInterface;
|
||||
|
||||
functions: {
|
||||
addConnector(
|
||||
connector: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
addOracle(
|
||||
oracle: string,
|
||||
oracleKind: BigNumberish,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
connectors(
|
||||
overrides?: CallOverrides
|
||||
): Promise<[string[]] & { allConnectors: string[] }>;
|
||||
|
||||
getRate(
|
||||
srcToken: string,
|
||||
dstToken: string,
|
||||
useWrappers: boolean,
|
||||
overrides?: CallOverrides
|
||||
): Promise<[BigNumber] & { weightedRate: BigNumber }>;
|
||||
|
||||
getRateToEth(
|
||||
srcToken: string,
|
||||
useSrcWrappers: boolean,
|
||||
overrides?: CallOverrides
|
||||
): Promise<[BigNumber] & { weightedRate: BigNumber }>;
|
||||
|
||||
multiWrapper(overrides?: CallOverrides): Promise<[string]>;
|
||||
|
||||
oracles(
|
||||
overrides?: CallOverrides
|
||||
): Promise<
|
||||
[string[], number[]] & { allOracles: string[]; oracleTypes: number[] }
|
||||
>;
|
||||
|
||||
owner(overrides?: CallOverrides): Promise<[string]>;
|
||||
|
||||
removeConnector(
|
||||
connector: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
removeOracle(
|
||||
oracle: string,
|
||||
oracleKind: BigNumberish,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
renounceOwnership(
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
setMultiWrapper(
|
||||
_multiWrapper: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
transferOwnership(
|
||||
newOwner: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
};
|
||||
|
||||
addConnector(
|
||||
connector: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
addOracle(
|
||||
oracle: string,
|
||||
oracleKind: BigNumberish,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
connectors(overrides?: CallOverrides): Promise<string[]>;
|
||||
|
||||
getRate(
|
||||
srcToken: string,
|
||||
dstToken: string,
|
||||
useWrappers: boolean,
|
||||
overrides?: CallOverrides
|
||||
): Promise<BigNumber>;
|
||||
|
||||
getRateToEth(
|
||||
srcToken: string,
|
||||
useSrcWrappers: boolean,
|
||||
overrides?: CallOverrides
|
||||
): Promise<BigNumber>;
|
||||
|
||||
multiWrapper(overrides?: CallOverrides): Promise<string>;
|
||||
|
||||
oracles(
|
||||
overrides?: CallOverrides
|
||||
): Promise<
|
||||
[string[], number[]] & { allOracles: string[]; oracleTypes: number[] }
|
||||
>;
|
||||
|
||||
owner(overrides?: CallOverrides): Promise<string>;
|
||||
|
||||
removeConnector(
|
||||
connector: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
removeOracle(
|
||||
oracle: string,
|
||||
oracleKind: BigNumberish,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
renounceOwnership(
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
setMultiWrapper(
|
||||
_multiWrapper: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
transferOwnership(
|
||||
newOwner: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<ContractTransaction>;
|
||||
|
||||
callStatic: {
|
||||
addConnector(connector: string, overrides?: CallOverrides): Promise<void>;
|
||||
|
||||
addOracle(
|
||||
oracle: string,
|
||||
oracleKind: BigNumberish,
|
||||
overrides?: CallOverrides
|
||||
): Promise<void>;
|
||||
|
||||
connectors(overrides?: CallOverrides): Promise<string[]>;
|
||||
|
||||
getRate(
|
||||
srcToken: string,
|
||||
dstToken: string,
|
||||
useWrappers: boolean,
|
||||
overrides?: CallOverrides
|
||||
): Promise<BigNumber>;
|
||||
|
||||
getRateToEth(
|
||||
srcToken: string,
|
||||
useSrcWrappers: boolean,
|
||||
overrides?: CallOverrides
|
||||
): Promise<BigNumber>;
|
||||
|
||||
multiWrapper(overrides?: CallOverrides): Promise<string>;
|
||||
|
||||
oracles(
|
||||
overrides?: CallOverrides
|
||||
): Promise<
|
||||
[string[], number[]] & { allOracles: string[]; oracleTypes: number[] }
|
||||
>;
|
||||
|
||||
owner(overrides?: CallOverrides): Promise<string>;
|
||||
|
||||
removeConnector(
|
||||
connector: string,
|
||||
overrides?: CallOverrides
|
||||
): Promise<void>;
|
||||
|
||||
removeOracle(
|
||||
oracle: string,
|
||||
oracleKind: BigNumberish,
|
||||
overrides?: CallOverrides
|
||||
): Promise<void>;
|
||||
|
||||
renounceOwnership(overrides?: CallOverrides): Promise<void>;
|
||||
|
||||
setMultiWrapper(
|
||||
_multiWrapper: string,
|
||||
overrides?: CallOverrides
|
||||
): Promise<void>;
|
||||
|
||||
transferOwnership(
|
||||
newOwner: string,
|
||||
overrides?: CallOverrides
|
||||
): Promise<void>;
|
||||
};
|
||||
|
||||
filters: {
|
||||
ConnectorAdded(
|
||||
connector?: null
|
||||
): TypedEventFilter<[string], { connector: string }>;
|
||||
|
||||
ConnectorRemoved(
|
||||
connector?: null
|
||||
): TypedEventFilter<[string], { connector: string }>;
|
||||
|
||||
MultiWrapperUpdated(
|
||||
multiWrapper?: null
|
||||
): TypedEventFilter<[string], { multiWrapper: string }>;
|
||||
|
||||
OracleAdded(
|
||||
oracle?: null,
|
||||
oracleType?: null
|
||||
): TypedEventFilter<
|
||||
[string, number],
|
||||
{ oracle: string; oracleType: number }
|
||||
>;
|
||||
|
||||
OracleRemoved(
|
||||
oracle?: null,
|
||||
oracleType?: null
|
||||
): TypedEventFilter<
|
||||
[string, number],
|
||||
{ oracle: string; oracleType: number }
|
||||
>;
|
||||
|
||||
OwnershipTransferred(
|
||||
previousOwner?: string | null,
|
||||
newOwner?: string | null
|
||||
): TypedEventFilter<
|
||||
[string, string],
|
||||
{ previousOwner: string; newOwner: string }
|
||||
>;
|
||||
};
|
||||
|
||||
estimateGas: {
|
||||
addConnector(
|
||||
connector: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<BigNumber>;
|
||||
|
||||
addOracle(
|
||||
oracle: string,
|
||||
oracleKind: BigNumberish,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<BigNumber>;
|
||||
|
||||
connectors(overrides?: CallOverrides): Promise<BigNumber>;
|
||||
|
||||
getRate(
|
||||
srcToken: string,
|
||||
dstToken: string,
|
||||
useWrappers: boolean,
|
||||
overrides?: CallOverrides
|
||||
): Promise<BigNumber>;
|
||||
|
||||
getRateToEth(
|
||||
srcToken: string,
|
||||
useSrcWrappers: boolean,
|
||||
overrides?: CallOverrides
|
||||
): Promise<BigNumber>;
|
||||
|
||||
multiWrapper(overrides?: CallOverrides): Promise<BigNumber>;
|
||||
|
||||
oracles(overrides?: CallOverrides): Promise<BigNumber>;
|
||||
|
||||
owner(overrides?: CallOverrides): Promise<BigNumber>;
|
||||
|
||||
removeConnector(
|
||||
connector: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<BigNumber>;
|
||||
|
||||
removeOracle(
|
||||
oracle: string,
|
||||
oracleKind: BigNumberish,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<BigNumber>;
|
||||
|
||||
renounceOwnership(
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<BigNumber>;
|
||||
|
||||
setMultiWrapper(
|
||||
_multiWrapper: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<BigNumber>;
|
||||
|
||||
transferOwnership(
|
||||
newOwner: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<BigNumber>;
|
||||
};
|
||||
|
||||
populateTransaction: {
|
||||
addConnector(
|
||||
connector: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<PopulatedTransaction>;
|
||||
|
||||
addOracle(
|
||||
oracle: string,
|
||||
oracleKind: BigNumberish,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<PopulatedTransaction>;
|
||||
|
||||
connectors(overrides?: CallOverrides): Promise<PopulatedTransaction>;
|
||||
|
||||
getRate(
|
||||
srcToken: string,
|
||||
dstToken: string,
|
||||
useWrappers: boolean,
|
||||
overrides?: CallOverrides
|
||||
): Promise<PopulatedTransaction>;
|
||||
|
||||
getRateToEth(
|
||||
srcToken: string,
|
||||
useSrcWrappers: boolean,
|
||||
overrides?: CallOverrides
|
||||
): Promise<PopulatedTransaction>;
|
||||
|
||||
multiWrapper(overrides?: CallOverrides): Promise<PopulatedTransaction>;
|
||||
|
||||
oracles(overrides?: CallOverrides): Promise<PopulatedTransaction>;
|
||||
|
||||
owner(overrides?: CallOverrides): Promise<PopulatedTransaction>;
|
||||
|
||||
removeConnector(
|
||||
connector: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<PopulatedTransaction>;
|
||||
|
||||
removeOracle(
|
||||
oracle: string,
|
||||
oracleKind: BigNumberish,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<PopulatedTransaction>;
|
||||
|
||||
renounceOwnership(
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<PopulatedTransaction>;
|
||||
|
||||
setMultiWrapper(
|
||||
_multiWrapper: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<PopulatedTransaction>;
|
||||
|
||||
transferOwnership(
|
||||
newOwner: string,
|
||||
overrides?: Overrides & { from?: string | Promise<string> }
|
||||
): Promise<PopulatedTransaction>;
|
||||
};
|
||||
}
|
1430
src/artifacts/TornadoPool.d.ts
vendored
1430
src/artifacts/TornadoPool.d.ts
vendored
File diff suppressed because it is too large
Load Diff
@ -1,36 +0,0 @@
|
||||
/* Autogenerated file. Do not edit manually. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import { EventFilter, Event } from "ethers";
|
||||
import { Result } from "@ethersproject/abi";
|
||||
|
||||
export interface TypedEventFilter<_EventArgsArray, _EventArgsObject>
|
||||
extends EventFilter {}
|
||||
|
||||
export interface TypedEvent<EventArgs extends Result> extends Event {
|
||||
args: EventArgs;
|
||||
}
|
||||
|
||||
export type TypedListener<
|
||||
EventArgsArray extends Array<any>,
|
||||
EventArgsObject
|
||||
> = (
|
||||
...listenerArg: [
|
||||
...EventArgsArray,
|
||||
TypedEvent<EventArgsArray & EventArgsObject>
|
||||
]
|
||||
) => void;
|
||||
|
||||
export type MinEthersFactory<C, ARGS> = {
|
||||
deploy(...a: ARGS[]): Promise<C>;
|
||||
};
|
||||
export type GetContractTypeFromFactory<F> = F extends MinEthersFactory<
|
||||
infer C,
|
||||
any
|
||||
>
|
||||
? C
|
||||
: never;
|
||||
export type GetARGsTypeFromFactory<F> = F extends MinEthersFactory<any, any>
|
||||
? Parameters<F["deploy"]>
|
||||
: never;
|
@ -1,358 +0,0 @@
|
||||
/* Autogenerated file. Do not edit manually. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
|
||||
import { Contract, Signer, utils } from "ethers";
|
||||
import { Provider } from "@ethersproject/providers";
|
||||
import type {
|
||||
OffchainOracle,
|
||||
OffchainOracleInterface,
|
||||
} from "../OffchainOracle";
|
||||
|
||||
const _abi = [
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "contract MultiWrapper",
|
||||
name: "_multiWrapper",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "contract IOracle[]",
|
||||
name: "existingOracles",
|
||||
type: "address[]",
|
||||
},
|
||||
{
|
||||
internalType: "enum OffchainOracle.OracleType[]",
|
||||
name: "oracleTypes",
|
||||
type: "uint8[]",
|
||||
},
|
||||
{
|
||||
internalType: "contract IERC20[]",
|
||||
name: "existingConnectors",
|
||||
type: "address[]",
|
||||
},
|
||||
{
|
||||
internalType: "contract IERC20",
|
||||
name: "wBase",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
stateMutability: "nonpayable",
|
||||
type: "constructor",
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
internalType: "contract IERC20",
|
||||
name: "connector",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
name: "ConnectorAdded",
|
||||
type: "event",
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
internalType: "contract IERC20",
|
||||
name: "connector",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
name: "ConnectorRemoved",
|
||||
type: "event",
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
internalType: "contract MultiWrapper",
|
||||
name: "multiWrapper",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
name: "MultiWrapperUpdated",
|
||||
type: "event",
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
internalType: "contract IOracle",
|
||||
name: "oracle",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: "enum OffchainOracle.OracleType",
|
||||
name: "oracleType",
|
||||
type: "uint8",
|
||||
},
|
||||
],
|
||||
name: "OracleAdded",
|
||||
type: "event",
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: false,
|
||||
internalType: "contract IOracle",
|
||||
name: "oracle",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
indexed: false,
|
||||
internalType: "enum OffchainOracle.OracleType",
|
||||
name: "oracleType",
|
||||
type: "uint8",
|
||||
},
|
||||
],
|
||||
name: "OracleRemoved",
|
||||
type: "event",
|
||||
},
|
||||
{
|
||||
anonymous: false,
|
||||
inputs: [
|
||||
{
|
||||
indexed: true,
|
||||
internalType: "address",
|
||||
name: "previousOwner",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
indexed: true,
|
||||
internalType: "address",
|
||||
name: "newOwner",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
name: "OwnershipTransferred",
|
||||
type: "event",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "contract IERC20",
|
||||
name: "connector",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
name: "addConnector",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "contract IOracle",
|
||||
name: "oracle",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "enum OffchainOracle.OracleType",
|
||||
name: "oracleKind",
|
||||
type: "uint8",
|
||||
},
|
||||
],
|
||||
name: "addOracle",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "connectors",
|
||||
outputs: [
|
||||
{
|
||||
internalType: "contract IERC20[]",
|
||||
name: "allConnectors",
|
||||
type: "address[]",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "contract IERC20",
|
||||
name: "srcToken",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "contract IERC20",
|
||||
name: "dstToken",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "bool",
|
||||
name: "useWrappers",
|
||||
type: "bool",
|
||||
},
|
||||
],
|
||||
name: "getRate",
|
||||
outputs: [
|
||||
{
|
||||
internalType: "uint256",
|
||||
name: "weightedRate",
|
||||
type: "uint256",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "contract IERC20",
|
||||
name: "srcToken",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "bool",
|
||||
name: "useSrcWrappers",
|
||||
type: "bool",
|
||||
},
|
||||
],
|
||||
name: "getRateToEth",
|
||||
outputs: [
|
||||
{
|
||||
internalType: "uint256",
|
||||
name: "weightedRate",
|
||||
type: "uint256",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "multiWrapper",
|
||||
outputs: [
|
||||
{
|
||||
internalType: "contract MultiWrapper",
|
||||
name: "",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "oracles",
|
||||
outputs: [
|
||||
{
|
||||
internalType: "contract IOracle[]",
|
||||
name: "allOracles",
|
||||
type: "address[]",
|
||||
},
|
||||
{
|
||||
internalType: "enum OffchainOracle.OracleType[]",
|
||||
name: "oracleTypes",
|
||||
type: "uint8[]",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "owner",
|
||||
outputs: [
|
||||
{
|
||||
internalType: "address",
|
||||
name: "",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
stateMutability: "view",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "contract IERC20",
|
||||
name: "connector",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
name: "removeConnector",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "contract IOracle",
|
||||
name: "oracle",
|
||||
type: "address",
|
||||
},
|
||||
{
|
||||
internalType: "enum OffchainOracle.OracleType",
|
||||
name: "oracleKind",
|
||||
type: "uint8",
|
||||
},
|
||||
],
|
||||
name: "removeOracle",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [],
|
||||
name: "renounceOwnership",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "contract MultiWrapper",
|
||||
name: "_multiWrapper",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
name: "setMultiWrapper",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function",
|
||||
},
|
||||
{
|
||||
inputs: [
|
||||
{
|
||||
internalType: "address",
|
||||
name: "newOwner",
|
||||
type: "address",
|
||||
},
|
||||
],
|
||||
name: "transferOwnership",
|
||||
outputs: [],
|
||||
stateMutability: "nonpayable",
|
||||
type: "function",
|
||||
},
|
||||
];
|
||||
|
||||
export class OffchainOracle__factory {
|
||||
static readonly abi = _abi;
|
||||
static createInterface(): OffchainOracleInterface {
|
||||
return new utils.Interface(_abi) as OffchainOracleInterface;
|
||||
}
|
||||
static connect(
|
||||
address: string,
|
||||
signerOrProvider: Signer | Provider
|
||||
): OffchainOracle {
|
||||
return new Contract(address, _abi, signerOrProvider) as OffchainOracle;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,8 +0,0 @@
|
||||
/* Autogenerated file. Do not edit manually. */
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
export type { OffchainOracle } from "./OffchainOracle";
|
||||
export type { TornadoPool } from "./TornadoPool";
|
||||
|
||||
export { OffchainOracle__factory } from "./factories/OffchainOracle__factory";
|
||||
export { TornadoPool__factory } from "./factories/TornadoPool__factory";
|
@ -1,14 +0,0 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('bull', () => ({
|
||||
redis: process.env.REDIS_URL || 'localhost',
|
||||
settings: {
|
||||
lockDuration: 300000,
|
||||
lockRenewTime: 30000,
|
||||
stalledInterval: 30000,
|
||||
maxStalledCount: 3,
|
||||
guardInterval: 5000,
|
||||
retryProcessDelay: 5000,
|
||||
drainDelay: 5,
|
||||
},
|
||||
}));
|
@ -1,26 +0,0 @@
|
||||
import { Wallet } from 'ethers';
|
||||
|
||||
import { ChainId } from '@/types';
|
||||
|
||||
import { toWei } from '@/utilities';
|
||||
import { NETWORKS_INFO, RPC_LIST } from '@/constants';
|
||||
|
||||
import { version } from '../../package.json';
|
||||
|
||||
export const baseConfig = () => ({
|
||||
base: {
|
||||
version,
|
||||
port: process.env.PORT,
|
||||
chainId: Number(process.env.CHAIN_ID),
|
||||
serviceFee: {
|
||||
transfer: toWei(process.env.TRANSFER_SERVICE_FEE).toString(),
|
||||
withdrawal: Number(process.env.WITHDRAWAL_SERVICE_FEE),
|
||||
},
|
||||
rpcUrl: process.env.RPC_URL || RPC_LIST[process.env.CHAIN_ID],
|
||||
oracleRpcUrl: process.env.ORACLE_RPC_URL || RPC_LIST[ChainId.MAINNET],
|
||||
rewardAddress: process.env.REWARD_ADDRESS,
|
||||
address: new Wallet(process.env.PRIVATE_KEY).address,
|
||||
gasLimit: NETWORKS_INFO[process.env.CHAIN_ID].gasLimit,
|
||||
minimumBalance: NETWORKS_INFO[process.env.CHAIN_ID].minimumBalance,
|
||||
},
|
||||
});
|
@ -1,4 +0,0 @@
|
||||
export * from './configuration';
|
||||
|
||||
export * from './bull.config';
|
||||
export * from './txManager.config';
|
@ -1,15 +0,0 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
import { RPC_LIST } from '@/constants';
|
||||
|
||||
export default registerAs('txManager', () => ({
|
||||
privateKey: process.env.PRIVATE_KEY,
|
||||
rpcUrl: process.env.RPC_URL || RPC_LIST[process.env.CHAIN_ID],
|
||||
config: {
|
||||
THROW_ON_REVERT: false,
|
||||
CONFIRMATIONS: Number(process.env.CONFIRMATIONS),
|
||||
MAX_GAS_PRICE: Number(process.env.MAX_GAS_PRICE),
|
||||
},
|
||||
gasPriceOracleConfig: {
|
||||
chainId: Number(process.env.CHAIN_ID),
|
||||
},
|
||||
}));
|
@ -1,12 +0,0 @@
|
||||
import { ChainId } from '@/types';
|
||||
|
||||
export const CONTRACT_NETWORKS: { [chainId in ChainId]: string } = {
|
||||
[ChainId.XDAI]: '0xD692Fd2D0b2Fbd2e52CFa5B5b9424bC981C30696', // ETH
|
||||
};
|
||||
|
||||
export const RPC_LIST: { [chainId in ChainId]: string } = {
|
||||
[ChainId.MAINNET]: 'https://api.mycryptoapi.com/eth',
|
||||
[ChainId.XDAI]: 'https://rpc.gnosischain.com/tornado',
|
||||
};
|
||||
|
||||
export const OFF_CHAIN_ORACLE = '0x07D91f5fb9Bf7798734C3f606dB065549F6893bb';
|
@ -1,2 +0,0 @@
|
||||
export * from './variables';
|
||||
export * from './contracts';
|
@ -1,55 +0,0 @@
|
||||
import { BigNumber } from 'ethers';
|
||||
import { ChainId } from '@/types';
|
||||
|
||||
const NETWORKS_INFO: { [chainId in ChainId] } = {
|
||||
[ChainId.XDAI]: {
|
||||
symbol: 'xDAI',
|
||||
gasLimit: BigNumber.from(2000000),
|
||||
minimumBalance: '0.5',
|
||||
},
|
||||
};
|
||||
|
||||
const numbers = {
|
||||
ZERO: 0,
|
||||
ONE: 1,
|
||||
TWO: 2,
|
||||
TEN: 10,
|
||||
ONE_HUNDRED: 100,
|
||||
SECOND: 1000,
|
||||
ETH_DECIMALS: 18,
|
||||
MERKLE_TREE_HEIGHT: 23,
|
||||
};
|
||||
|
||||
export const jobStatus = {
|
||||
QUEUED: 'QUEUED',
|
||||
ACCEPTED: 'ACCEPTED',
|
||||
CONFIRMED: 'CONFIRMED',
|
||||
FAILED: 'FAILED',
|
||||
MINED: 'MINED',
|
||||
SENT: 'SENT',
|
||||
};
|
||||
|
||||
const BG_ZERO = BigNumber.from(numbers.ZERO);
|
||||
const FIELD_SIZE = BigNumber.from('21888242871839275222246405745257275088548364400416034343698204186575808495617');
|
||||
|
||||
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
|
||||
const DAI_ADDRESS = '0x6b175474e89094c44da98b954eedeac495271d0f';
|
||||
|
||||
export { numbers, NETWORKS_INFO, DAI_ADDRESS, FIELD_SIZE, BG_ZERO, ZERO_ADDRESS };
|
||||
|
||||
export const CONTRACT_ERRORS = [
|
||||
'Invalid merkle root',
|
||||
'Input is already spent',
|
||||
'Incorrect external data hash',
|
||||
'Invalid fee',
|
||||
'Invalid ext amount',
|
||||
'Invalid public amount',
|
||||
'Invalid transaction proof',
|
||||
"Can't withdraw to zero address",
|
||||
];
|
||||
|
||||
export const SERVICE_ERRORS = {
|
||||
GAS_PRICE: 'Could not get gas price',
|
||||
TOKEN_RATES: 'Could not get token rates',
|
||||
GAS_SPIKE: 'Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.',
|
||||
};
|
18
src/main.ts
18
src/main.ts
@ -1,18 +0,0 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
try {
|
||||
const app = await NestFactory.create<NestExpressApplication>(AppModule, { cors: true });
|
||||
|
||||
const configService = app.get(ConfigService);
|
||||
await app.listen(configService.get('base.port'));
|
||||
} catch (err) {
|
||||
console.log('err', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
bootstrap();
|
@ -1,45 +0,0 @@
|
||||
import { Body, Controller, Get, HttpStatus, Param, Post, Res } from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
|
||||
import { ApiService } from './api.service';
|
||||
import { validateTransactionRequest } from './api.validator';
|
||||
|
||||
@Controller()
|
||||
export class ApiController {
|
||||
constructor(private readonly service: ApiService) {}
|
||||
|
||||
@Get('/status')
|
||||
async status(@Res() res: Response): Promise<Response<Status>> {
|
||||
return res.json(await this.service.status());
|
||||
}
|
||||
|
||||
@Get('/')
|
||||
root(@Res() res: Response): Response<string> {
|
||||
return res.send(this.service.root());
|
||||
}
|
||||
|
||||
@Get('/job/:jobId')
|
||||
async getJob(@Res() res: Response, @Param('jobId') jobId: string) {
|
||||
const job = await this.service.getJob(jobId);
|
||||
|
||||
if (!job) {
|
||||
return res.status(HttpStatus.BAD_REQUEST).json({ error: "The job doesn't exist" });
|
||||
}
|
||||
return res.json(job);
|
||||
}
|
||||
|
||||
@Post('/transaction')
|
||||
async transaction(@Res() res: Response, @Body() { body }: any) {
|
||||
const params = JSON.parse(body);
|
||||
const inputError = validateTransactionRequest(params);
|
||||
|
||||
if (inputError) {
|
||||
console.log('Invalid input:', inputError);
|
||||
return res.status(HttpStatus.BAD_REQUEST).json({ error: inputError });
|
||||
}
|
||||
|
||||
const jobId = await this.service.transaction(params);
|
||||
|
||||
return res.send(jobId);
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
import { ApiService } from './api.service';
|
||||
import { ApiController } from './api.controller';
|
||||
|
||||
import { QueueModule } from '@/modules';
|
||||
import { ProviderService } from '@/services';
|
||||
|
||||
@Module({
|
||||
imports: [ConfigModule, QueueModule],
|
||||
providers: [ApiService, ProviderService],
|
||||
controllers: [ApiController],
|
||||
exports: [],
|
||||
})
|
||||
export class ApiModule {}
|
@ -1,71 +0,0 @@
|
||||
import { Queue } from 'bull';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { InjectQueue } from '@nestjs/bull';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { ProviderService } from '@/services';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { jobStatus, NETWORKS_INFO } from '@/constants';
|
||||
|
||||
import { Transaction } from '@/types';
|
||||
|
||||
@Injectable()
|
||||
class ApiService {
|
||||
constructor(
|
||||
private configService: ConfigService,
|
||||
private providerService: ProviderService,
|
||||
@InjectQueue('transaction') private transactionQueue: Queue,
|
||||
) {}
|
||||
|
||||
async status(): Promise<Status> {
|
||||
const { rewardAddress, version, chainId, serviceFee } = this.configService.get('base');
|
||||
|
||||
const health = await this.healthCheck();
|
||||
|
||||
return {
|
||||
health,
|
||||
version,
|
||||
chainId,
|
||||
serviceFee,
|
||||
rewardAddress,
|
||||
};
|
||||
}
|
||||
|
||||
root(): string {
|
||||
return `This is <a href=https://tornado.cash>tornado.cash</a> Relayer service. Check the <a href=/status>/status</a> for settings`;
|
||||
}
|
||||
|
||||
async transaction(data: any): Promise<string> {
|
||||
const jobId = uuid();
|
||||
|
||||
await this.transactionQueue.add({ ...data, status: jobStatus.QUEUED }, { jobId });
|
||||
|
||||
return jobId;
|
||||
}
|
||||
|
||||
async getJob(id: string): Promise<Transaction | null> {
|
||||
const job = await this.transactionQueue.getJob(id);
|
||||
|
||||
if (!job) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...job.data,
|
||||
failedReason: job.failedReason,
|
||||
};
|
||||
}
|
||||
|
||||
private async healthCheck(): Promise<Health> {
|
||||
const status = await this.providerService.checkSenderBalance();
|
||||
|
||||
const { chainId, minimumBalance } = this.configService.get('base');
|
||||
|
||||
return {
|
||||
status,
|
||||
error: status ? '' : `Not enough balance, less than ${minimumBalance} ${NETWORKS_INFO[chainId].symbol}`,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export { ApiService };
|
@ -1,74 +0,0 @@
|
||||
import Ajv, { ValidateFunction } from 'ajv';
|
||||
import { isAddress } from '@/utilities';
|
||||
|
||||
const ajv = new Ajv();
|
||||
|
||||
ajv.addKeyword({
|
||||
keyword: 'isAddress',
|
||||
validate: (schema: any, address: string) => {
|
||||
return isAddress(address);
|
||||
},
|
||||
errors: true,
|
||||
});
|
||||
|
||||
const addressType = {
|
||||
type: 'string',
|
||||
pattern: '^0x[a-fA-F0-9]{40}$',
|
||||
isAddress: true,
|
||||
};
|
||||
|
||||
const proofType = { type: 'string', pattern: '^0x[a-fA-F0-9]{512}$' };
|
||||
const bytes32Type = { type: 'string', pattern: '^0x[a-fA-F0-9]{64}$' };
|
||||
const externalAmountType = { type: 'string', pattern: '^(0x[a-fA-F0-9]{64}|-0x[a-fA-F0-9]{63})$' };
|
||||
const encryptedOutputType = { type: 'string', pattern: '^0x[a-fA-F0-9]{312}$' };
|
||||
const arrayType = { type: 'array', items: bytes32Type };
|
||||
const booleanType = { type: 'boolean' };
|
||||
|
||||
const transactionSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
extData: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
encryptedOutput1: encryptedOutputType,
|
||||
encryptedOutput2: encryptedOutputType,
|
||||
extAmount: externalAmountType,
|
||||
fee: bytes32Type,
|
||||
recipient: addressType,
|
||||
relayer: addressType,
|
||||
isL1Withdrawal: booleanType,
|
||||
l1Fee: bytes32Type,
|
||||
},
|
||||
},
|
||||
args: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
extDataHash: bytes32Type,
|
||||
inputNullifiers: arrayType,
|
||||
outputCommitments: arrayType,
|
||||
proof: proofType,
|
||||
publicAmount: bytes32Type,
|
||||
root: bytes32Type,
|
||||
},
|
||||
},
|
||||
},
|
||||
additionalProperties: false,
|
||||
required: ['extData', 'args'],
|
||||
};
|
||||
|
||||
const validateTornadoTransaction = ajv.compile(transactionSchema);
|
||||
|
||||
function getInputError(validator: ValidateFunction, data: typeof transactionSchema) {
|
||||
validator(data);
|
||||
if (validator.errors) {
|
||||
const [error] = validator.errors;
|
||||
return error.message;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function validateTransactionRequest(data: typeof transactionSchema) {
|
||||
return getInputError(validateTornadoTransaction, data);
|
||||
}
|
||||
|
||||
export { validateTransactionRequest };
|
@ -1,4 +0,0 @@
|
||||
export class CreateApiDto {
|
||||
error: boolean;
|
||||
status: string;
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './create-subscribe.dto';
|
@ -1 +0,0 @@
|
||||
export { ApiModule } from './api.module';
|
@ -1,11 +0,0 @@
|
||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
|
||||
@Injectable()
|
||||
export class setHeadersMiddleware implements NestMiddleware {
|
||||
use(req: Request, res: Response, next: NextFunction) {
|
||||
res.setHeader('X-Frame-Options', 'DENY');
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
next();
|
||||
}
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
type Health = {
|
||||
status: boolean;
|
||||
error: string;
|
||||
};
|
||||
|
||||
type ServiceFee = {
|
||||
transfer: string;
|
||||
withdrawal: number;
|
||||
};
|
||||
|
||||
type Status = {
|
||||
health: Health;
|
||||
chainId: number;
|
||||
version: string;
|
||||
rewardAddress: string;
|
||||
serviceFee: ServiceFee;
|
||||
};
|
@ -1,2 +0,0 @@
|
||||
export * from './queue';
|
||||
export * from './api';
|
@ -1,66 +0,0 @@
|
||||
import {
|
||||
Processor,
|
||||
OnQueueActive,
|
||||
OnQueueFailed,
|
||||
OnQueueRemoved,
|
||||
OnQueueResumed,
|
||||
OnQueueStalled,
|
||||
OnQueueProgress,
|
||||
OnQueueCompleted,
|
||||
} from '@nestjs/bull';
|
||||
import { Injectable, OnModuleDestroy } from '@nestjs/common';
|
||||
import { Job, Queue } from 'bull';
|
||||
|
||||
@Injectable()
|
||||
@Processor()
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export class BaseProcessor<T = object> implements OnModuleDestroy {
|
||||
public queueName: string;
|
||||
public queue: Queue<T>;
|
||||
|
||||
@OnQueueActive()
|
||||
async onQueueActive(job: Job<T>) {
|
||||
return this.updateTask(job);
|
||||
}
|
||||
|
||||
@OnQueueFailed()
|
||||
async onQueueFailed(job: Job<T>) {
|
||||
return this.updateTask(job);
|
||||
}
|
||||
|
||||
@OnQueueCompleted()
|
||||
async onQueueCompleted(job: Job<T>) {
|
||||
return this.updateTask(job);
|
||||
}
|
||||
|
||||
@OnQueueProgress()
|
||||
async onQueueProgress(job: Job<T>) {
|
||||
return this.updateTask(job);
|
||||
}
|
||||
|
||||
@OnQueueRemoved()
|
||||
async onQueueRemoved(job: Job<T>) {
|
||||
return this.updateTask(job);
|
||||
}
|
||||
|
||||
@OnQueueResumed()
|
||||
async onQueueResumed(job: Job<T>) {
|
||||
return this.updateTask(job);
|
||||
}
|
||||
|
||||
@OnQueueStalled()
|
||||
async onQueueStalled(job: Job<T>) {
|
||||
return this.updateTask(job);
|
||||
}
|
||||
|
||||
protected async updateTask(job: Job<T>) {
|
||||
const currentJob = await this.queue.getJob(job.id);
|
||||
await currentJob.update(job.data);
|
||||
}
|
||||
|
||||
async onModuleDestroy() {
|
||||
if (this.queue) {
|
||||
await this.queue.close();
|
||||
}
|
||||
}
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './queue.module';
|
@ -1,20 +0,0 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
|
||||
import { GasPriceService, ProviderService, OffchainPriceService } from '@/services';
|
||||
|
||||
import { TransactionProcessor } from './transaction.processor';
|
||||
|
||||
import bullConfig from '@/config/bull.config';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
BullModule.registerQueueAsync({
|
||||
name: 'transaction',
|
||||
useFactory: bullConfig,
|
||||
}),
|
||||
],
|
||||
providers: [GasPriceService, ProviderService, TransactionProcessor, OffchainPriceService],
|
||||
exports: [BullModule],
|
||||
})
|
||||
export class QueueModule {}
|
@ -1,173 +0,0 @@
|
||||
import { BigNumber } from 'ethers';
|
||||
import { TxManager } from '@tornado/tx-manager';
|
||||
import { Job, Queue, DoneCallback } from 'bull';
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { InjectQueue, Process, Processor, OnQueueActive, OnQueueCompleted, OnQueueFailed } from '@nestjs/bull';
|
||||
|
||||
import { Transaction } from '@/types';
|
||||
import { getToIntegerMultiplier, toWei } from '@/utilities';
|
||||
import { CONTRACT_ERRORS, SERVICE_ERRORS, jobStatus } from '@/constants';
|
||||
import { GasPriceService, ProviderService, OffchainPriceService } from '@/services';
|
||||
|
||||
import txMangerConfig from '@/config/txManager.config';
|
||||
|
||||
import { BaseProcessor } from './base.processor';
|
||||
|
||||
@Injectable()
|
||||
@Processor('transaction')
|
||||
export class TransactionProcessor extends BaseProcessor<Transaction> {
|
||||
constructor(
|
||||
@InjectQueue('transaction') public transactionQueue: Queue,
|
||||
private configService: ConfigService,
|
||||
private gasPriceService: GasPriceService,
|
||||
private providerService: ProviderService,
|
||||
private offChainPriceService: OffchainPriceService,
|
||||
) {
|
||||
super();
|
||||
this.queueName = 'transaction';
|
||||
this.queue = transactionQueue;
|
||||
}
|
||||
|
||||
@Process()
|
||||
async processTransactions(job: Job<Transaction>, cb: DoneCallback) {
|
||||
try {
|
||||
const { extData } = job.data;
|
||||
|
||||
await this.checkFee({ fee: extData.fee, externalAmount: extData.extAmount });
|
||||
const txHash = await this.submitTx(job);
|
||||
|
||||
cb(null, txHash);
|
||||
} catch (err) {
|
||||
cb(err);
|
||||
}
|
||||
}
|
||||
|
||||
@OnQueueActive()
|
||||
async onActive(job: Job) {
|
||||
job.data.status = jobStatus.ACCEPTED;
|
||||
await this.updateTask(job);
|
||||
}
|
||||
|
||||
@OnQueueCompleted()
|
||||
async onCompleted(job: Job) {
|
||||
job.data.status = jobStatus.CONFIRMED;
|
||||
await this.updateTask(job);
|
||||
}
|
||||
|
||||
@OnQueueFailed()
|
||||
async onFailed(job: Job) {
|
||||
job.data.status = jobStatus.FAILED;
|
||||
await this.updateTask(job);
|
||||
}
|
||||
|
||||
async submitTx(job: Job<Transaction>) {
|
||||
try {
|
||||
const txManager = new TxManager(txMangerConfig());
|
||||
|
||||
const prepareTx = await this.prepareTransaction(job.data);
|
||||
const tx = await txManager.createTx(prepareTx);
|
||||
|
||||
const receipt = await tx
|
||||
.send()
|
||||
.on('transactionHash', async (txHash: string) => {
|
||||
job.data.txHash = txHash;
|
||||
job.data.status = jobStatus.SENT;
|
||||
|
||||
await this.updateTask(job);
|
||||
})
|
||||
.on('mined', async () => {
|
||||
job.data.status = jobStatus.MINED;
|
||||
|
||||
await this.updateTask(job);
|
||||
})
|
||||
.on('confirmations', async (confirmations) => {
|
||||
job.data.confirmations = confirmations;
|
||||
|
||||
await this.updateTask(job);
|
||||
});
|
||||
|
||||
if (BigNumber.from(receipt.status).eq(1)) {
|
||||
return receipt.transactionHash;
|
||||
} else {
|
||||
throw new Error('Submitted transaction failed');
|
||||
}
|
||||
} catch (err) {
|
||||
return this.handleError(err);
|
||||
}
|
||||
}
|
||||
|
||||
async prepareTransaction({ extData, args }) {
|
||||
const contract = this.providerService.getTornadoPool();
|
||||
|
||||
const data = contract.interface.encodeFunctionData('transact', [args, extData]);
|
||||
|
||||
const gasLimit = this.configService.get<BigNumber>('base.gasLimit');
|
||||
|
||||
const { fast } = await this.gasPriceService.getGasPrice();
|
||||
|
||||
return {
|
||||
data,
|
||||
gasLimit,
|
||||
to: contract.address,
|
||||
gasPrice: fast,
|
||||
value: BigNumber.from(0)._hex,
|
||||
};
|
||||
}
|
||||
|
||||
getServiceFee(externalAmount) {
|
||||
const amount = BigNumber.from(externalAmount);
|
||||
const { serviceFee } = this.configService.get('base');
|
||||
|
||||
// for withdrawals the amount is negative
|
||||
if (amount.isNegative()) {
|
||||
const oneEther = getToIntegerMultiplier();
|
||||
|
||||
const share = Number(serviceFee.withdrawal) / 100;
|
||||
return amount.mul(toWei(share.toString())).div(oneEther);
|
||||
}
|
||||
|
||||
return serviceFee.transfer;
|
||||
}
|
||||
|
||||
async checkFee({ fee, externalAmount }) {
|
||||
try {
|
||||
const { gasLimit } = this.configService.get('base');
|
||||
const { fast } = await this.gasPriceService.getGasPrice();
|
||||
|
||||
const operationFee = BigNumber.from(fast).mul(gasLimit);
|
||||
|
||||
const feePercent = this.getServiceFee(externalAmount);
|
||||
|
||||
const ethPrice = await this.offChainPriceService.getDaiEthPrice();
|
||||
|
||||
const expense = operationFee.mul(ethPrice).div(toWei('1'));
|
||||
const desiredFee = expense.add(feePercent);
|
||||
|
||||
if (BigNumber.from(fee).lt(desiredFee)) {
|
||||
throw new Error(SERVICE_ERRORS.GAS_SPIKE);
|
||||
}
|
||||
} catch (err) {
|
||||
this.handleError(err);
|
||||
}
|
||||
}
|
||||
|
||||
handleError({ message }: Error) {
|
||||
const contractError = CONTRACT_ERRORS.find((knownError) => message.includes(knownError));
|
||||
|
||||
if (contractError) {
|
||||
throw new Error(`Revert by smart contract: ${contractError}`);
|
||||
}
|
||||
|
||||
const serviceError = Object.values(SERVICE_ERRORS).find((knownError) => message.includes(knownError));
|
||||
|
||||
if (serviceError) {
|
||||
throw new Error(`Relayer internal error: ${serviceError}`);
|
||||
}
|
||||
|
||||
console.log('handleError:', message);
|
||||
|
||||
throw new Error('Relayer did not send your transaction. Please choose a different relayer.');
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { BigNumber } from 'ethers';
|
||||
import { GasPriceOracle } from '@tornado/gas-price-oracle';
|
||||
import type { GasPrice } from '@tornado/gas-price-oracle/lib/services';
|
||||
|
||||
import { toWei } from '@/utilities';
|
||||
import { SERVICE_ERRORS } from '@/constants';
|
||||
|
||||
const bump = (gas: BigNumber, percent: number) => gas.mul(percent).div(100).toHexString();
|
||||
const gweiToWei = (value: number) => toWei(String(value), 'gwei');
|
||||
|
||||
const percentBump = {
|
||||
INSTANT: 150,
|
||||
FAST: 130,
|
||||
STANDARD: 85,
|
||||
LOW: 50,
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class GasPriceService {
|
||||
private readonly chainId: number;
|
||||
private readonly rpcUrl: string;
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
this.chainId = this.configService.get<number>('base.chainId');
|
||||
this.rpcUrl = this.configService.get('base.rpcUrl');
|
||||
}
|
||||
|
||||
async getGasPrice() {
|
||||
try {
|
||||
const instance = new GasPriceOracle({
|
||||
chainId: this.chainId,
|
||||
defaultRpc: this.rpcUrl,
|
||||
});
|
||||
|
||||
const result = (await instance.gasPrices({ isLegacy: true })) as GasPrice;
|
||||
|
||||
return {
|
||||
instant: bump(gweiToWei(result.instant), percentBump.INSTANT),
|
||||
fast: bump(gweiToWei(result.instant), percentBump.FAST),
|
||||
standard: bump(gweiToWei(result.standard), percentBump.STANDARD),
|
||||
low: bump(gweiToWei(result.low), percentBump.LOW),
|
||||
};
|
||||
} catch (err) {
|
||||
console.log('getGasPrice has error:', err.message);
|
||||
throw new Error(SERVICE_ERRORS.GAS_PRICE);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
export * from './provider.service';
|
||||
|
||||
export * from './gas-price.service';
|
||||
export * from './offchain-price.service';
|
@ -1,36 +0,0 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { BigNumber } from 'ethers';
|
||||
|
||||
import { ChainId } from '@/types';
|
||||
import { toWei } from '@/utilities';
|
||||
import { ProviderService } from '@/services';
|
||||
import { DAI_ADDRESS, SERVICE_ERRORS } from '@/constants';
|
||||
@Injectable()
|
||||
export class OffchainPriceService {
|
||||
private readonly chainId: number;
|
||||
private readonly rpcUrl: string;
|
||||
|
||||
constructor(private configService: ConfigService, private providerService: ProviderService) {
|
||||
this.chainId = ChainId.MAINNET;
|
||||
this.rpcUrl = this.configService.get('base.oracleRpcUrl');
|
||||
}
|
||||
|
||||
async getDaiEthPrice() {
|
||||
try {
|
||||
const contract = this.providerService.getOffChainOracle();
|
||||
|
||||
const rate = await contract.callStatic.getRateToEth(DAI_ADDRESS, false);
|
||||
|
||||
const numerator = BigNumber.from(toWei('1'));
|
||||
const denominator = BigNumber.from(toWei('1'));
|
||||
|
||||
// price = rate * "token decimals" / "eth decimals" (dai = eth decimals)
|
||||
return BigNumber.from(rate).mul(numerator).div(denominator);
|
||||
} catch (err) {
|
||||
console.log('getDaiEthPrice has error:', err.message);
|
||||
throw new Error(SERVICE_ERRORS.TOKEN_RATES);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
import { ethers } from 'ethers';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
import { ChainId } from '@/types';
|
||||
import { CONTRACT_NETWORKS, OFF_CHAIN_ORACLE } from '@/constants';
|
||||
import { TornadoPool__factory as TornadoPool, OffchainOracle__factory as OffchainOracle } from '@/artifacts';
|
||||
|
||||
@Injectable()
|
||||
export class ProviderService {
|
||||
private readonly chainId: number;
|
||||
private readonly rpcUrl: string;
|
||||
private readonly providers: Map<ChainId, ethers.providers.StaticJsonRpcProvider> = new Map();
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
this.chainId = this.configService.get<number>('base.chainId');
|
||||
this.rpcUrl = this.configService.get('base.rpcUrl');
|
||||
}
|
||||
|
||||
get provider() {
|
||||
return this.getProvider(this.chainId, this.rpcUrl);
|
||||
}
|
||||
|
||||
getProvider(chainId: ChainId, rpcUrl: string) {
|
||||
if (!this.providers.has(chainId)) {
|
||||
this.providers.set(chainId, new ethers.providers.StaticJsonRpcProvider(rpcUrl, chainId));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return this.providers.get(chainId)!;
|
||||
}
|
||||
|
||||
getTornadoPool() {
|
||||
return TornadoPool.connect(CONTRACT_NETWORKS[this.chainId], this.provider);
|
||||
}
|
||||
|
||||
getOffChainOracle() {
|
||||
const oracleRpcUrl = this.configService.get('base.oracleRpcUrl');
|
||||
const provider = this.getProvider(ChainId.MAINNET, oracleRpcUrl);
|
||||
return OffchainOracle.connect(OFF_CHAIN_ORACLE, provider);
|
||||
}
|
||||
|
||||
async checkSenderBalance() {
|
||||
try {
|
||||
const balance = await this.getBalance(this.configService.get<string>('base.address'));
|
||||
|
||||
return balance.gt(ethers.utils.parseEther(this.configService.get('base.minimumBalance')));
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async getBalance(address: string) {
|
||||
return await this.provider.getBalance(address);
|
||||
}
|
||||
}
|
@ -1,37 +0,0 @@
|
||||
import { BigNumberish } from 'ethers';
|
||||
import { BytesLike } from '@ethersproject/bytes';
|
||||
|
||||
const MAINNET_CHAIN_ID = 1;
|
||||
const XDAI_CHAIN_ID = 100;
|
||||
|
||||
export enum ChainId {
|
||||
MAINNET = MAINNET_CHAIN_ID,
|
||||
XDAI = XDAI_CHAIN_ID,
|
||||
}
|
||||
|
||||
export type ExtData = {
|
||||
recipient: string;
|
||||
relayer: string;
|
||||
fee: BigNumberish;
|
||||
extAmount: BigNumberish;
|
||||
encryptedOutput1: BytesLike;
|
||||
encryptedOutput2: BytesLike;
|
||||
};
|
||||
|
||||
export type ArgsProof = {
|
||||
proof: BytesLike;
|
||||
root: BytesLike;
|
||||
inputNullifiers: string[];
|
||||
outputCommitments: BytesLike[];
|
||||
publicAmount: string;
|
||||
extDataHash: string;
|
||||
};
|
||||
|
||||
export interface Transaction {
|
||||
extData: ExtData;
|
||||
args: ArgsProof;
|
||||
status: string;
|
||||
txHash?: string;
|
||||
confirmations?: number;
|
||||
failedReason?: string;
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
import { BigNumber, utils, BigNumberish } from 'ethers';
|
||||
|
||||
import { numbers } from '@/constants';
|
||||
|
||||
export function isAddress(value: string): boolean {
|
||||
return utils.isAddress(value);
|
||||
}
|
||||
|
||||
export function toChecksumAddress(value: string): string {
|
||||
return utils.getAddress(value);
|
||||
}
|
||||
|
||||
export function toWei(value: string, uintName = 'ether') {
|
||||
return utils.parseUnits(String(value), uintName);
|
||||
}
|
||||
|
||||
export function hexToNumber(hex: string) {
|
||||
return BigNumber.from(hex).toNumber();
|
||||
}
|
||||
|
||||
export function numberToHex(value: number) {
|
||||
return utils.hexlify(value);
|
||||
}
|
||||
|
||||
export function fromWei(balance: BigNumberish) {
|
||||
return utils.formatUnits(balance, numbers.ETH_DECIMALS);
|
||||
}
|
||||
|
||||
export function getToIntegerMultiplier(): BigNumber {
|
||||
return toWei('1', 'ether');
|
||||
}
|
@ -1 +0,0 @@
|
||||
export * from './crypto';
|
@ -1,24 +0,0 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import * as request from 'supertest';
|
||||
import { AppModule } from './../src/app.module';
|
||||
|
||||
describe('AppController (e2e)', () => {
|
||||
let app: INestApplication;
|
||||
|
||||
beforeEach(async () => {
|
||||
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||
imports: [AppModule],
|
||||
}).compile();
|
||||
|
||||
app = moduleFixture.createNestApplication();
|
||||
await app.init();
|
||||
});
|
||||
|
||||
it('/ (GET)', () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/')
|
||||
.expect(200)
|
||||
.expect('This is <a href=https://tornado.cash>tornado.cash</a> Relayer service. Check the <a href=/status>/status</a> for settings');
|
||||
});
|
||||
});
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"moduleFileExtensions": ["js", "json", "ts"],
|
||||
"rootDir": "../",
|
||||
"testEnvironment": "node",
|
||||
"testRegex": ".e2e-spec.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"moduleNameMapper": {
|
||||
"^@/(.*)$": "<rootDir>/src/$1"
|
||||
}
|
||||
}
|
15
tornado-stream.conf
Normal file
15
tornado-stream.conf
Normal file
@ -0,0 +1,15 @@
|
||||
map $ssl_preread_server_name $name {
|
||||
yourdomain.com tornado_mainnet;
|
||||
|
||||
default tornado_mainnet;
|
||||
}
|
||||
|
||||
upstream tornado_mainnet {
|
||||
server 127.0.0.1:4380;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 0.0.0.0:443;
|
||||
proxy_pass $name;
|
||||
ssl_preread on;
|
||||
}
|
87
tornado.conf
Normal file
87
tornado.conf
Normal file
@ -0,0 +1,87 @@
|
||||
# If we receive X-Forwarded-Proto, pass it through; otherwise, pass along the
|
||||
# scheme used to connect to this server
|
||||
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
|
||||
default $http_x_forwarded_proto;
|
||||
'' $scheme;
|
||||
}
|
||||
# If we receive X-Forwarded-Port, pass it through; otherwise, pass along the
|
||||
# server port the client connected to
|
||||
map $http_x_forwarded_port $proxy_x_forwarded_port {
|
||||
default $http_x_forwarded_port;
|
||||
'' $server_port;
|
||||
}
|
||||
# If we receive Upgrade, set Connection to "upgrade"; otherwise, delete any
|
||||
# Connection header that may have been passed to this server
|
||||
map $http_upgrade $proxy_connection {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
# Apply fix for very long server names
|
||||
server_names_hash_bucket_size 128;
|
||||
# Default dhparam
|
||||
# Set appropriate X-Forwarded-Ssl header based on $proxy_x_forwarded_proto
|
||||
map $proxy_x_forwarded_proto $proxy_x_forwarded_ssl {
|
||||
default off;
|
||||
https on;
|
||||
}
|
||||
gzip_types text/plain text/css application/javascript application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
log_format vhost '$host $remote_addr - $remote_user [$time_local] '
|
||||
'"$request" $status $body_bytes_sent '
|
||||
'"$http_referer" "$http_user_agent" '
|
||||
'"$upstream_addr"';
|
||||
# HTTP 1.1 support
|
||||
proxy_http_version 1.1;
|
||||
proxy_buffering off;
|
||||
proxy_set_header Host $http_host;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection $proxy_connection;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
|
||||
proxy_set_header X-Forwarded-Ssl $proxy_x_forwarded_ssl;
|
||||
proxy_set_header X-Forwarded-Port $proxy_x_forwarded_port;
|
||||
proxy_set_header X-Original-URI $request_uri;
|
||||
# Mitigate httpoxy attack (see README for details)
|
||||
proxy_set_header Proxy "";
|
||||
|
||||
# Request rate limiting per second, 2Mb zone @ 5 requests per second
|
||||
limit_req_zone $binary_remote_addr zone=one:2m rate=5r/s;
|
||||
# Connections per IP limited to 2
|
||||
limit_conn_zone $binary_remote_addr zone=two:2m;
|
||||
|
||||
server {
|
||||
server_name _; # This is just an invalid value which will never trigger on a real hostname.
|
||||
server_tokens off;
|
||||
listen 80;
|
||||
access_log /var/log/nginx/access.log vhost;
|
||||
return 503;
|
||||
}
|
||||
|
||||
server {
|
||||
server_name yourdomain.com;
|
||||
|
||||
# Connection timeouts
|
||||
client_body_timeout 10s;
|
||||
client_header_timeout 10s;
|
||||
|
||||
listen 80;
|
||||
access_log /var/log/nginx/access.log vhost;
|
||||
|
||||
# Do not HTTPS redirect LetsEncrypt ACME challenge
|
||||
location ^~ /.well-known/acme-challenge/ {
|
||||
limit_req zone=one;
|
||||
limit_conn two 1;
|
||||
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
location / {
|
||||
limit_req zone=one;
|
||||
limit_conn two 1;
|
||||
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +0,0 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"skipLibCheck": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es2017",
|
||||
"resolveJsonModule": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"./src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user