Compare commits

...

17 Commits

Author SHA1 Message Date
Danil Kovtonyuk 6eb71c18e1
update example 2022-06-13 21:06:23 +10:00
Danil Kovtonyuk 41777a0e67 bump tx-manager 2022-05-30 23:51:21 +10:00
smart_ex a935bea718 middleware for setting security headers 2022-05-30 23:51:21 +10:00
dependabot[bot] 32af0c955b chore(deps): bump ansi-regex from 3.0.0 to 3.0.1
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-30 23:51:08 +10:00
Danil Kovtonyuk a7be72cea5
update readme 2022-04-29 18:58:00 +10:00
Alexey Pertsev ac2e142cf0
Update readme 2022-03-01 11:39:19 +01:00
Danil Kovtonyuk e060df18f7 update contract 2022-02-27 04:44:07 +10:00
dependabot[bot] 3dce6947a9 chore(deps): bump node-fetch from 2.6.1 to 2.6.7
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-27 02:13:33 +10:00
dependabot[bot] c613e74169 chore(deps): bump follow-redirects from 1.14.1 to 1.14.8
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.1 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.1...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-27 02:13:18 +10:00
Alexey Pertsev 35e48a0c53
Merge pull request #17 from feshchenkod/master
added discord notification
2022-02-25 20:21:42 +01:00
Alexey 5be9b8ad72
calculate fee percent 2022-02-25 20:15:56 +01:00
_den 8e7ccd7b22 updated discord notification message 2022-02-04 21:24:20 +10:00
_den bafde64336 added discord notification 2022-02-01 14:25:04 +10:00
Danil Kovtonyuk 767c87b4d2 fix: catch service errors 2021-12-15 20:29:53 +10:00
Danil Kovtonyuk 036fe4f8ea
fix: compose environment 2021-12-15 14:33:40 +10:00
_den f38bd71666 fixed volumes config for compose v3 2021-12-12 18:02:35 +10:00
Danil Kovtonyuk 6b791d7ec9
fix: build 2021-12-10 19:24:11 +10:00
18 changed files with 265 additions and 104 deletions

View File

@ -11,7 +11,7 @@ REDIS_URL=redis://redis/0
# REDIS_URL=localhost
CHAIN_ID=100
# RPC_URL=https://rpc.xdaichain.com/tornado
# RPC_URL=https://rpc.gnosischain.com/tornado
# ORACLE_RPC_URL should always point to the mainnet
# ORACLE_RPC_URL=https://mainnet.infura.io

View File

@ -18,7 +18,7 @@ jobs:
- run: yarn install
- run: yarn lint
- name: Telegram Failure Notification
uses: appleboy/telegram-action@v0.1.1
uses: appleboy/telegram-action@master
if: failure()
with:
message: ❗ Build failed for [${{ github.repository }}](https://github.com/${{ github.repository }}/actions) because of ${{ github.actor }}
@ -48,12 +48,12 @@ jobs:
dockerfile: Dockerfile
repository: tornadocash/nova-relayer
tag_with_ref: true
tags: nova,candidate
tags: latest,nova,candidate
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Telegram Message Notify
uses: appleboy/telegram-action@v0.1.1
uses: appleboy/telegram-action@master
with:
to: ${{ secrets.TELEGRAM_CHAT_ID }}
token: ${{ secrets.TELEGRAM_BOT_TOKEN }}
@ -61,8 +61,32 @@ jobs:
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@v0.1.1
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 }}

View File

@ -1,25 +1,5 @@
# 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)
## Getting listed on nova.tornado.cash
If you would like to be listed in nova.tornado.cash UI relayer's dropdown option, please do the following:
1. Setup tornado.cash relayer node(see below for docker-compose.yml example)
2. Setup ENS subdomain(`nova.xxx.eth`) with TEXT record and URL key that points to your DNS or IP address.
3. Test your relayer setup at https://nova.tornado.cash by choosing custom relayer's option on withdraw/transfer tab. Enter your ens name or url and initiate an operation.
4. Open new Github issue in https://github.com/tornadocash/tornado-pool-relayer/issues and specify the following:
- your mainnet ens url
- your telegram handle
- withdrawal tx on mainnet and xdai
- transfer tx on xdai
Please choose your testnet relayer's fee wisely.
Disclaimer: Please consult with legal and tax advisors regarding the compliance of running a relayer service in your jurisdiction. The authors of this project bear no responsibility.
USE AT YOUR OWN RISK.
## Deploy with docker-compose
docker-compose.yml contains a stack that will automatically provision SSL certificates for your domain name and will add a https redirect to port 80.
@ -44,13 +24,11 @@ wget https://raw.githubusercontent.com/tornadocash/tornado-pool-relayer/master/.
- 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
If you want to use more than 1 eth address for relaying transactions, please add as many `workers` as you want. For example, you can comment out `worker2` in docker-compose.yml file, but please use a different `PRIVATE_KEY` for each worker.
3. Run `docker-compose up -d`
## Run locally
1. `npm i`
1. `yarn`
2. `cp .env.example .env`
3. Modify `.env` as needed
4. `yarn start:dev`

View File

@ -31,7 +31,7 @@ services:
- conf:/etc/nginx/conf.d
- vhost:/etc/nginx/vhost.d
- html:/usr/share/nginx/html
- certs:/etc/nginx/certs
- certs:/etc/nginx/certs:ro
logging:
driver: none
@ -40,9 +40,11 @@ services:
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:
- 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:
@ -51,9 +53,13 @@ services:
restart: always
environment:
NGINX_DOCKER_GEN_CONTAINER: dockergen
volumes_from:
- nginx
- 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
volumes:
conf:

View File

@ -1,6 +1,6 @@
{
"name": "pool-relayer",
"version": "0.0.1",
"version": "0.0.5",
"description": "Relayer for Tornado.cash Nova privacy solution. https://tornado.cash",
"author": "tornado.cash",
"license": "MIT",
@ -33,12 +33,12 @@
"bull": "^3.22.11",
"class-validator": "^0.13.1",
"ethers": "^5.4.6",
"gas-price-oracle": "^0.4.4",
"gas-price-oracle": "^0.4.7",
"redis": "^3.1.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"tx-manager": "^0.4.5",
"tx-manager": "^0.4.8",
"uuid": "^8.3.2"
},
"devDependencies": {

View File

@ -151,6 +151,19 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MIN_EXT_AMOUNT_LIMIT",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "ROOT_HISTORY_SIZE",
@ -604,6 +617,11 @@
"internalType": "bool",
"name": "isL1Withdrawal",
"type": "bool"
},
{
"internalType": "uint256",
"name": "l1Fee",
"type": "uint256"
}
],
"internalType": "struct TornadoPool.ExtData",
@ -759,6 +777,11 @@
"internalType": "bool",
"name": "isL1Withdrawal",
"type": "bool"
},
{
"internalType": "uint256",
"name": "l1Fee",
"type": "uint256"
}
],
"internalType": "struct TornadoPool.ExtData",
@ -901,6 +924,11 @@
"internalType": "bool",
"name": "isL1Withdrawal",
"type": "bool"
},
{
"internalType": "uint256",
"name": "l1Fee",
"type": "uint256"
}
],
"internalType": "struct TornadoPool.ExtData",

View File

@ -1,8 +1,9 @@
import { Module } from '@nestjs/common';
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: [
@ -14,4 +15,8 @@ import { QueueModule, ApiModule } from '@/modules';
QueueModule,
],
})
export class AppModule {}
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(setHeadersMiddleware).forRoutes('/');
}
}

View File

@ -24,6 +24,7 @@ interface TornadoPoolInterface extends ethers.utils.Interface {
"FIELD_SIZE()": FunctionFragment;
"MAX_EXT_AMOUNT()": FunctionFragment;
"MAX_FEE()": FunctionFragment;
"MIN_EXT_AMOUNT_LIMIT()": FunctionFragment;
"ROOT_HISTORY_SIZE()": FunctionFragment;
"ZERO_VALUE()": FunctionFragment;
"ambBridge()": FunctionFragment;
@ -72,6 +73,10 @@ interface TornadoPoolInterface extends ethers.utils.Interface {
values?: undefined
): string;
encodeFunctionData(functionFragment: "MAX_FEE", values?: undefined): string;
encodeFunctionData(
functionFragment: "MIN_EXT_AMOUNT_LIMIT",
values?: undefined
): string;
encodeFunctionData(
functionFragment: "ROOT_HISTORY_SIZE",
values?: undefined
@ -169,6 +174,7 @@ interface TornadoPoolInterface extends ethers.utils.Interface {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
}
]
): string;
@ -201,6 +207,7 @@ interface TornadoPoolInterface extends ethers.utils.Interface {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
}
]
): string;
@ -229,6 +236,7 @@ interface TornadoPoolInterface extends ethers.utils.Interface {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
}
]
): string;
@ -258,6 +266,10 @@ interface TornadoPoolInterface extends ethers.utils.Interface {
data: BytesLike
): Result;
decodeFunctionResult(functionFragment: "MAX_FEE", data: BytesLike): Result;
decodeFunctionResult(
functionFragment: "MIN_EXT_AMOUNT_LIMIT",
data: BytesLike
): Result;
decodeFunctionResult(
functionFragment: "ROOT_HISTORY_SIZE",
data: BytesLike
@ -414,6 +426,8 @@ export class TornadoPool extends BaseContract {
MAX_FEE(overrides?: CallOverrides): Promise<[BigNumber]>;
MIN_EXT_AMOUNT_LIMIT(overrides?: CallOverrides): Promise<[BigNumber]>;
ROOT_HISTORY_SIZE(overrides?: CallOverrides): Promise<[number]>;
ZERO_VALUE(overrides?: CallOverrides): Promise<[BigNumber]>;
@ -514,6 +528,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: Overrides & { from?: string | Promise<string> }
): Promise<ContractTransaction>;
@ -545,6 +560,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: Overrides & { from?: string | Promise<string> }
): Promise<ContractTransaction>;
@ -577,6 +593,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: Overrides & { from?: string | Promise<string> }
): Promise<ContractTransaction>;
@ -606,6 +623,8 @@ export class TornadoPool extends BaseContract {
MAX_FEE(overrides?: CallOverrides): Promise<BigNumber>;
MIN_EXT_AMOUNT_LIMIT(overrides?: CallOverrides): Promise<BigNumber>;
ROOT_HISTORY_SIZE(overrides?: CallOverrides): Promise<number>;
ZERO_VALUE(overrides?: CallOverrides): Promise<BigNumber>;
@ -700,6 +719,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: Overrides & { from?: string | Promise<string> }
): Promise<ContractTransaction>;
@ -731,6 +751,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: Overrides & { from?: string | Promise<string> }
): Promise<ContractTransaction>;
@ -763,6 +784,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: Overrides & { from?: string | Promise<string> }
): Promise<ContractTransaction>;
@ -792,6 +814,8 @@ export class TornadoPool extends BaseContract {
MAX_FEE(overrides?: CallOverrides): Promise<BigNumber>;
MIN_EXT_AMOUNT_LIMIT(overrides?: CallOverrides): Promise<BigNumber>;
ROOT_HISTORY_SIZE(overrides?: CallOverrides): Promise<number>;
ZERO_VALUE(overrides?: CallOverrides): Promise<BigNumber>;
@ -887,6 +911,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: CallOverrides
): Promise<void>;
@ -918,6 +943,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: CallOverrides
): Promise<void>;
@ -950,6 +976,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: CallOverrides
): Promise<void>;
@ -1000,6 +1027,8 @@ export class TornadoPool extends BaseContract {
MAX_FEE(overrides?: CallOverrides): Promise<BigNumber>;
MIN_EXT_AMOUNT_LIMIT(overrides?: CallOverrides): Promise<BigNumber>;
ROOT_HISTORY_SIZE(overrides?: CallOverrides): Promise<BigNumber>;
ZERO_VALUE(overrides?: CallOverrides): Promise<BigNumber>;
@ -1100,6 +1129,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: Overrides & { from?: string | Promise<string> }
): Promise<BigNumber>;
@ -1131,6 +1161,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: Overrides & { from?: string | Promise<string> }
): Promise<BigNumber>;
@ -1163,6 +1194,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: Overrides & { from?: string | Promise<string> }
): Promise<BigNumber>;
@ -1193,6 +1225,10 @@ export class TornadoPool extends BaseContract {
MAX_FEE(overrides?: CallOverrides): Promise<PopulatedTransaction>;
MIN_EXT_AMOUNT_LIMIT(
overrides?: CallOverrides
): Promise<PopulatedTransaction>;
ROOT_HISTORY_SIZE(overrides?: CallOverrides): Promise<PopulatedTransaction>;
ZERO_VALUE(overrides?: CallOverrides): Promise<PopulatedTransaction>;
@ -1297,6 +1333,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: Overrides & { from?: string | Promise<string> }
): Promise<PopulatedTransaction>;
@ -1328,6 +1365,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: Overrides & { from?: string | Promise<string> }
): Promise<PopulatedTransaction>;
@ -1363,6 +1401,7 @@ export class TornadoPool extends BaseContract {
encryptedOutput1: BytesLike;
encryptedOutput2: BytesLike;
isL1Withdrawal: boolean;
l1Fee: BigNumberish;
},
overrides?: Overrides & { from?: string | Promise<string> }
): Promise<PopulatedTransaction>;

View File

@ -159,6 +159,19 @@ const _abi = [
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "MIN_EXT_AMOUNT_LIMIT",
outputs: [
{
internalType: "uint256",
name: "",
type: "uint256",
},
],
stateMutability: "view",
type: "function",
},
{
inputs: [],
name: "ROOT_HISTORY_SIZE",
@ -613,6 +626,11 @@ const _abi = [
name: "isL1Withdrawal",
type: "bool",
},
{
internalType: "uint256",
name: "l1Fee",
type: "uint256",
},
],
internalType: "struct TornadoPool.ExtData",
name: "_extData",
@ -768,6 +786,11 @@ const _abi = [
name: "isL1Withdrawal",
type: "bool",
},
{
internalType: "uint256",
name: "l1Fee",
type: "uint256",
},
],
internalType: "struct TornadoPool.ExtData",
name: "_extData",
@ -910,6 +933,11 @@ const _abi = [
name: "isL1Withdrawal",
type: "bool",
},
{
internalType: "uint256",
name: "l1Fee",
type: "uint256",
},
],
internalType: "struct TornadoPool.ExtData",
name: "_extData",

View File

@ -6,7 +6,7 @@ export const CONTRACT_NETWORKS: { [chainId in ChainId]: string } = {
export const RPC_LIST: { [chainId in ChainId]: string } = {
[ChainId.MAINNET]: 'https://api.mycryptoapi.com/eth',
[ChainId.XDAI]: 'https://rpc.xdaichain.com/tornado',
[ChainId.XDAI]: 'https://rpc.gnosischain.com/tornado',
};
export const OFF_CHAIN_ORACLE = '0x07D91f5fb9Bf7798734C3f606dB065549F6893bb';

View File

@ -47,3 +47,9 @@ export const CONTRACT_ERRORS = [
'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.',
};

View File

@ -1,4 +1,4 @@
import { Controller, Body, Param, Res, Get, Post, HttpStatus } from '@nestjs/common';
import { Body, Controller, Get, HttpStatus, Param, Post, Res } from '@nestjs/common';
import { Response } from 'express';
import { ApiService } from './api.service';
@ -9,13 +9,13 @@ export class ApiController {
constructor(private readonly service: ApiService) {}
@Get('/status')
async status(): Promise<Status> {
return await this.service.status();
async status(@Res() res: Response): Promise<Response<Status>> {
return res.json(await this.service.status());
}
@Get('/')
async root(): Promise<string> {
return this.service.root();
root(@Res() res: Response): Response<string> {
return res.send(this.service.root());
}
@Get('/job/:jobId')
@ -25,7 +25,6 @@ export class ApiController {
if (!job) {
return res.status(HttpStatus.BAD_REQUEST).json({ error: "The job doesn't exist" });
}
return res.json(job);
}

View File

@ -37,6 +37,7 @@ const transactionSchema = {
recipient: addressType,
relayer: addressType,
isL1Withdrawal: booleanType,
l1Fee: bytes32Type,
},
},
args: {

View File

@ -0,0 +1,11 @@
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();
}
}

View File

@ -7,8 +7,8 @@ import { ConfigService } from '@nestjs/config';
import { InjectQueue, Process, Processor, OnQueueActive, OnQueueCompleted, OnQueueFailed } from '@nestjs/bull';
import { Transaction } from '@/types';
import { CONTRACT_ERRORS, jobStatus } from '@/constants';
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';
@ -124,35 +124,46 @@ export class TransactionProcessor extends BaseProcessor<Transaction> {
if (amount.isNegative()) {
const oneEther = getToIntegerMultiplier();
return amount.mul(toWei(serviceFee.withdrawal)).div(oneEther);
const share = Number(serviceFee.withdrawal) / 100;
return amount.mul(toWei(share.toString())).div(oneEther);
}
return serviceFee.transfer;
}
async checkFee({ fee, externalAmount }) {
const { gasLimit } = this.configService.get('base');
const { fast } = await this.gasPriceService.getGasPrice();
try {
const { gasLimit } = this.configService.get('base');
const { fast } = await this.gasPriceService.getGasPrice();
const operationFee = BigNumber.from(fast).mul(gasLimit);
const operationFee = BigNumber.from(fast).mul(gasLimit);
const feePercent = this.getServiceFee(externalAmount);
const feePercent = this.getServiceFee(externalAmount);
const ethPrice = await this.offChainPriceService.getDaiEthPrice();
const ethPrice = await this.offChainPriceService.getDaiEthPrice();
const expense = operationFee.mul(ethPrice).div(toWei('1'));
const desiredFee = expense.add(feePercent);
const expense = operationFee.mul(ethPrice).div(toWei('1'));
const desiredFee = expense.add(feePercent);
if (BigNumber.from(fee).lt(desiredFee)) {
throw new Error('Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.');
if (BigNumber.from(fee).lt(desiredFee)) {
throw new Error(SERVICE_ERRORS.GAS_SPIKE);
}
} catch (err) {
this.handleError(err);
}
}
handleError({ message }: Error) {
const error = CONTRACT_ERRORS.find((knownError) => message.includes(knownError));
const contractError = CONTRACT_ERRORS.find((knownError) => message.includes(knownError));
if (error) {
throw new Error(`Revert by smart contract: ${error}`);
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);

View File

@ -5,6 +5,7 @@ import { BigNumber } from 'ethers';
import { GasPriceOracle } from 'gas-price-oracle';
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');
@ -27,18 +28,23 @@ export class GasPriceService {
}
async getGasPrice() {
const instance = new GasPriceOracle({
chainId: this.chainId,
defaultRpc: this.rpcUrl,
});
try {
const instance = new GasPriceOracle({
chainId: this.chainId,
defaultRpc: this.rpcUrl,
});
const result = await instance.gasPrices();
const result = await instance.gasPrices();
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),
};
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);
}
}
}

View File

@ -4,10 +4,9 @@ import { ConfigService } from '@nestjs/config';
import { BigNumber } from 'ethers';
import { ChainId } from '@/types';
import { DAI_ADDRESS } from '@/constants';
import { ProviderService } from '@/services';
import { toWei } from '@/utilities';
import { ProviderService } from '@/services';
import { DAI_ADDRESS, SERVICE_ERRORS } from '@/constants';
@Injectable()
export class OffchainPriceService {
private readonly chainId: number;
@ -19,14 +18,19 @@ export class OffchainPriceService {
}
async getDaiEthPrice() {
const contract = this.providerService.getOffChainOracle();
try {
const contract = this.providerService.getOffChainOracle();
const rate = await contract.callStatic.getRateToEth(DAI_ADDRESS, false);
const rate = await contract.callStatic.getRateToEth(DAI_ADDRESS, false);
const numerator = BigNumber.from(toWei('1'));
const denominator = BigNumber.from(toWei('1'));
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);
// 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);
}
}
}

View File

@ -1746,9 +1746,9 @@ ansi-escapes@^4.2.1:
type-fest "^0.21.3"
ansi-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
version "3.0.1"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.1.tgz#123d6479e92ad45ad897d4054e3c7ca7db4944e1"
integrity sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==
ansi-regex@^5.0.0:
version "5.0.0"
@ -3077,15 +3077,10 @@ flatted@^3.1.0:
resolved "https://registry.npmjs.org/flatted/-/flatted-3.2.1.tgz#bbef080d95fca6709362c73044a1634f7c6e7d05"
integrity sha512-OMQjaErSFHmHqZe+PSidH5n8j3O0F2DdnVh8JB4j4eUQ2k6KvB0qGfrKIhapvez5JerBbmWkaLYUYWISaESoXg==
follow-redirects@^1.10.0:
version "1.14.1"
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz#d9114ded0a1cfdd334e164e6662ad02bfd91ff43"
integrity sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==
follow-redirects@^1.14.0:
version "1.14.5"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.5.tgz#f09a5848981d3c772b5392309778523f8d85c381"
integrity sha512-wtphSXy7d4/OR+MvIFbCVBDzZ5520qV8XfPklSN5QtxuMUJZ+b0Wnst1e1lCDocfzuCkHqj8k0FpZqO+UIaKNA==
follow-redirects@^1.10.0, follow-redirects@^1.14.0:
version "1.14.8"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
for-each@^0.3.3:
version "0.3.3"
@ -3190,10 +3185,10 @@ functional-red-black-tree@^1.0.1:
resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
gas-price-oracle@^0.4.4:
version "0.4.4"
resolved "https://registry.yarnpkg.com/gas-price-oracle/-/gas-price-oracle-0.4.4.tgz#9b7e5583ed7126a68f9d230b9efbd9d75d864bad"
integrity sha512-alAHLiZmPJ+GxKvujZZzEY8NRPqgGGHmDQUPFTa6HAMFB4LR/T6ShTDbqQzsdeWLPSw/j8/Gux0ZSC2AsPK+Hg==
gas-price-oracle@^0.4.7:
version "0.4.7"
resolved "https://registry.yarnpkg.com/gas-price-oracle/-/gas-price-oracle-0.4.7.tgz#47406048083074bcab677efb9de08663e742153d"
integrity sha512-Ti8nhpATm83YebWU/Pz5xclZoTkzOblIhT504ZViZJUcd8jOxgj9pWtCasg8RYw+d0f19m0dJUPvdj04RC4o3A==
dependencies:
axios "^0.21.2"
bignumber.js "^9.0.0"
@ -4584,9 +4579,11 @@ node-emoji@1.10.0:
lodash.toarray "^4.4.0"
node-fetch@^2.6.1:
version "2.6.1"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
version "2.6.7"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad"
integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==
dependencies:
whatwg-url "^5.0.0"
node-int64@^0.4.0:
version "0.4.0"
@ -5687,6 +5684,11 @@ tr46@^2.1.0:
dependencies:
punycode "^2.1.1"
tr46@~0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a"
integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=
tree-kill@1.2.2:
version "1.2.2"
resolved "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc"
@ -5791,14 +5793,14 @@ tsutils@^3.21.0:
dependencies:
tslib "^1.8.1"
tx-manager@^0.4.5:
version "0.4.5"
resolved "https://registry.yarnpkg.com/tx-manager/-/tx-manager-0.4.5.tgz#a1fb64abc6f503ac1f6f43795e6915b70c028aba"
integrity sha512-5cG4YUzGG2hB8Sw1SQLbAIQVpMyPBWOrn1zpRe3QKo/gMUbf7JHT7fsK2Gr4m+O6wkd59c/hDgu6QZwZW9VKww==
tx-manager@^0.4.8:
version "0.4.8"
resolved "https://registry.yarnpkg.com/tx-manager/-/tx-manager-0.4.8.tgz#d354af422d6019629c4b80b0872e332e1ab8c338"
integrity sha512-gs7V8jWxAjMLl+HtkDI6U04fqqB/Coprz0xRCVYeuBh/5ZiVuC+txd2yP08HyQsSRgAiNMXAlSskrfxaJKsxZg==
dependencies:
async-mutex "^0.2.4"
ethers "^5.4.6"
gas-price-oracle "^0.4.4"
gas-price-oracle "^0.4.7"
web3-core-promievent "^1.3.0"
type-check@^0.4.0, type-check@~0.4.0:
@ -6001,6 +6003,11 @@ web3-core-promievent@^1.3.0:
dependencies:
eventemitter3 "4.0.4"
webidl-conversions@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=
webidl-conversions@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
@ -6065,6 +6072,14 @@ whatwg-mimetype@^2.3.0:
resolved "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
whatwg-url@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d"
integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0=
dependencies:
tr46 "~0.0.3"
webidl-conversions "^3.0.0"
whatwg-url@^8.0.0, whatwg-url@^8.5.0:
version "8.7.0"
resolved "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77"