tornado-pool-relayer/src/modules/queue/transaction.processor.ts

173 lines
4.9 KiB
TypeScript
Raw Normal View History

import { BigNumber } from 'ethers';
2021-07-13 19:06:36 +02:00
import { TxManager } from 'tx-manager';
import { Job, Queue, DoneCallback } from 'bull';
2021-07-13 19:06:36 +02:00
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { InjectQueue, Process, Processor, OnQueueActive, OnQueueCompleted, OnQueueFailed } from '@nestjs/bull';
2021-10-12 11:37:24 +02:00
import { Transaction } from '@/types';
2021-10-13 17:18:24 +02:00
import { getToIntegerMultiplier, toWei } from '@/utilities';
2021-12-15 11:24:09 +01:00
import { CONTRACT_ERRORS, SERVICE_ERRORS, jobStatus } from '@/constants';
2021-10-13 17:18:24 +02:00
import { GasPriceService, ProviderService, OffchainPriceService } from '@/services';
2021-07-14 15:56:28 +02:00
import txMangerConfig from '@/config/txManager.config';
2021-07-13 19:06:36 +02:00
import { BaseProcessor } from './base.processor';
2021-10-12 11:37:24 +02:00
2021-07-13 19:06:36 +02:00
@Injectable()
2021-09-13 12:38:28 +02:00
@Processor('transaction')
export class TransactionProcessor extends BaseProcessor<Transaction> {
2021-07-13 19:06:36 +02:00
constructor(
2021-09-13 12:38:28 +02:00
@InjectQueue('transaction') public transactionQueue: Queue,
2021-10-13 17:18:24 +02:00
private configService: ConfigService,
private gasPriceService: GasPriceService,
private providerService: ProviderService,
2021-10-13 17:18:24 +02:00
private offChainPriceService: OffchainPriceService,
2021-07-13 19:06:36 +02:00
) {
super();
2021-09-13 12:38:28 +02:00
this.queueName = 'transaction';
this.queue = transactionQueue;
2021-07-13 19:06:36 +02:00
}
@Process()
async processTransactions(job: Job<Transaction>, cb: DoneCallback) {
2021-07-13 19:06:36 +02:00
try {
2021-09-27 11:10:19 +02:00
const { extData } = job.data;
2021-07-13 19:06:36 +02:00
2021-09-27 11:10:19 +02:00
await this.checkFee({ fee: extData.fee, externalAmount: extData.extAmount });
2021-10-01 06:15:28 +02:00
const txHash = await this.submitTx(job);
2021-10-01 06:15:28 +02:00
cb(null, txHash);
2021-07-13 19:06:36 +02:00
} catch (err) {
cb(err);
2021-07-13 19:06:36 +02:00
}
}
@OnQueueActive()
async onActive(job: Job) {
2021-10-12 11:37:24 +02:00
job.data.status = jobStatus.ACCEPTED;
await this.updateTask(job);
}
2021-10-01 08:07:19 +02:00
@OnQueueCompleted()
async onCompleted(job: Job) {
2021-10-12 11:37:24 +02:00
job.data.status = jobStatus.CONFIRMED;
await this.updateTask(job);
2021-10-01 08:07:19 +02:00
}
@OnQueueFailed()
async onFailed(job: Job) {
2021-10-12 11:37:24 +02:00
job.data.status = jobStatus.FAILED;
await this.updateTask(job);
}
2021-09-13 12:38:28 +02:00
async submitTx(job: Job<Transaction>) {
try {
const txManager = new TxManager(txMangerConfig());
2021-07-13 19:06:36 +02:00
const prepareTx = await this.prepareTransaction(job.data);
const tx = await txManager.createTx(prepareTx);
2021-07-13 19:06:36 +02:00
const receipt = await tx
.send()
.on('transactionHash', async (txHash: string) => {
job.data.txHash = txHash;
2021-10-12 11:37:24 +02:00
job.data.status = jobStatus.SENT;
2021-07-13 19:06:36 +02:00
2021-10-12 11:37:24 +02:00
await this.updateTask(job);
2021-07-13 19:06:36 +02:00
})
.on('mined', async () => {
2021-10-12 11:37:24 +02:00
job.data.status = jobStatus.MINED;
2021-07-13 19:06:36 +02:00
2021-10-12 11:37:24 +02:00
await this.updateTask(job);
2021-07-13 19:06:36 +02:00
})
.on('confirmations', async (confirmations) => {
job.data.confirmations = confirmations;
2021-10-12 11:37:24 +02:00
await this.updateTask(job);
2021-07-13 19:06:36 +02:00
});
2021-10-01 06:15:28 +02:00
if (BigNumber.from(receipt.status).eq(1)) {
return receipt.transactionHash;
} else {
2021-07-13 19:06:36 +02:00
throw new Error('Submitted transaction failed');
}
} catch (err) {
return this.handleError(err);
2021-07-13 19:06:36 +02:00
}
}
2021-07-23 14:46:35 +02:00
async prepareTransaction({ extData, args }) {
const contract = this.providerService.getTornadoPool();
2021-07-13 19:06:36 +02:00
2021-10-11 18:06:41 +02:00
const data = contract.interface.encodeFunctionData('transact', [args, extData]);
const gasLimit = this.configService.get<BigNumber>('base.gasLimit');
const { fast } = await this.gasPriceService.getGasPrice();
2021-07-13 19:06:36 +02:00
return {
data,
gasLimit,
to: contract.address,
2021-10-11 18:06:41 +02:00
gasPrice: fast,
value: BigNumber.from(0)._hex,
};
2021-07-13 19:06:36 +02:00
}
2021-09-27 11:10:19 +02:00
getServiceFee(externalAmount) {
const amount = BigNumber.from(externalAmount);
const { serviceFee } = this.configService.get('base');
// for withdrawals the amount is negative
if (amount.isNegative()) {
2021-12-01 15:49:33 +01:00
const oneEther = getToIntegerMultiplier();
2021-10-11 18:06:41 +02:00
2021-12-01 15:49:33 +01:00
return amount.mul(toWei(serviceFee.withdrawal)).div(oneEther);
2021-09-27 11:10:19 +02:00
}
return serviceFee.transfer;
}
async checkFee({ fee, externalAmount }) {
2021-12-15 11:24:09 +01:00
try {
const { gasLimit } = this.configService.get('base');
const { fast } = await this.gasPriceService.getGasPrice();
2021-07-13 19:06:36 +02:00
2021-12-15 11:24:09 +01:00
const operationFee = BigNumber.from(fast).mul(gasLimit);
2021-07-13 19:06:36 +02:00
2021-12-15 11:24:09 +01:00
const feePercent = this.getServiceFee(externalAmount);
2021-07-13 19:06:36 +02:00
2021-12-15 11:24:09 +01:00
const ethPrice = await this.offChainPriceService.getDaiEthPrice();
2021-10-13 17:18:24 +02:00
2021-12-15 11:24:09 +01:00
const expense = operationFee.mul(ethPrice).div(toWei('1'));
const desiredFee = expense.add(feePercent);
2021-07-13 19:06:36 +02:00
2021-12-15 11:24:09 +01:00
if (BigNumber.from(fee).lt(desiredFee)) {
throw new Error(SERVICE_ERRORS.GAS_SPIKE);
}
} catch (err) {
this.handleError(err);
2021-07-13 19:06:36 +02:00
}
}
2021-10-07 08:22:36 +02:00
handleError({ message }: Error) {
2021-12-15 11:24:09 +01:00
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));
2021-12-15 11:24:09 +01:00
if (serviceError) {
throw new Error(`Relayer internal error: ${serviceError}`);
}
2021-10-27 15:17:35 +02:00
console.log('handleError:', message);
throw new Error('Relayer did not send your transaction. Please choose a different relayer.');
}
2021-07-13 19:06:36 +02:00
}