Skip to content

Commit

Permalink
feat: suggested fees consumer (#176)
Browse files Browse the repository at this point in the history
* feat: add `suggestedRelayerFeePct` column to deposit

* feat: add `SuggestedFeesConsumer`

* refactor: review requests

- move suggested fees service into nearer located file
- remove if-clause to terminate suggested relayer fee population if already exists
  • Loading branch information
dohaki authored and amateima committed Dec 13, 2022
1 parent 77ca6a7 commit bd1676f
Show file tree
Hide file tree
Showing 11 changed files with 181 additions and 1 deletion.
13 changes: 13 additions & 0 deletions migrations/1670847543409-Deposit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from "typeorm";

export class Deposit1670847543409 implements MigrationInterface {
name = "Deposit1670847543409";

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "deposit" ADD "suggestedRelayerFeePct" numeric`);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "deposit" DROP COLUMN "suggestedRelayerFeePct"`);
}
}
4 changes: 4 additions & 0 deletions src/modules/configuration/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ export const configValues = () => ({
clientSecret: process.env.DISCORD_CLIENT_SECRET,
redirectUri: process.env.DISCORD_REDIRECT_URI,
},
suggestedFees: {
apiUrl: process.env.SUGGESTED_FEES_API_URL || "https://across.to/api/suggested-fees",
fallbackThresholdHours: Number(process.env.SUGGESTED_FEES_FALLBACK_THRESHOLD_HOURS || "24"),
},
});

export default registerAs("config", () => configValues());
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { HttpService } from "@nestjs/axios";
import { Injectable } from "@nestjs/common";

import { AppConfig } from "../../../configuration/configuration.service";

type SuggestedFeesApiParams = {
amount: string;
token: string;
destinationChainId: number;
originChainId: number;
};

type SuggestedFeesApiResponse = {
data: {
capitalFeePct: string;
capitalFeeTotal: string;
relayGasFeePct: string;
relayGasFeeTotal: string;
relayFeePct: string;
relayFeeTotal: string;
lpFeePct: string;
timestamp: string;
isAmountTooLow: boolean;
};
};

@Injectable()
export class SuggestedFeesService {
constructor(private appConfig: AppConfig, private httpService: HttpService) {}

public async getFromApi(params: SuggestedFeesApiParams) {
const response = await this.httpService.axiosRef.get<SuggestedFeesApiParams, SuggestedFeesApiResponse>(
this.appConfig.values.suggestedFees.apiUrl,
{ params },
);
return response?.data;
}
}
11 changes: 10 additions & 1 deletion src/modules/scraper/adapter/messaging/BlockNumberConsumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,13 @@ import { Job } from "bull";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { EthProvidersService } from "../../../web3/services/EthProvidersService";
import { BlockNumberQueueMessage, DepositReferralQueueMessage, ScraperQueue, TokenDetailsQueueMessage } from ".";
import {
BlockNumberQueueMessage,
DepositReferralQueueMessage,
ScraperQueue,
TokenDetailsQueueMessage,
SuggestedFeesQueueMessage,
} from ".";
import { Deposit } from "../../model/deposit.entity";
import { ScraperQueuesService } from "../../service/ScraperQueuesService";

Expand All @@ -31,6 +37,9 @@ export class BlockNumberConsumer {
await this.scraperQueuesService.publishMessage<DepositReferralQueueMessage>(ScraperQueue.DepositReferral, {
depositId: deposit.id,
});
await this.scraperQueuesService.publishMessage<SuggestedFeesQueueMessage>(ScraperQueue.SuggestedFees, {
depositId: deposit.id,
});
}

@OnQueueFailed()
Expand Down
66 changes: 66 additions & 0 deletions src/modules/scraper/adapter/messaging/SuggestedFeesConsumer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { OnQueueFailed, Process, Processor } from "@nestjs/bull";
import { Logger } from "@nestjs/common";
import { Job } from "bull";
import { InjectRepository } from "@nestjs/typeorm";
import { Repository } from "typeorm";
import { DateTime } from "luxon";
import { utils } from "ethers";

import { SuggestedFeesService } from "../across-serverless-api/suggested-fees-service";
import { SuggestedFeesQueueMessage, ScraperQueue } from ".";
import { Deposit } from "../../model/deposit.entity";
import { AppConfig } from "../../../configuration/configuration.service";

@Processor(ScraperQueue.SuggestedFees)
export class SuggestedFeesConsumer {
private logger = new Logger(SuggestedFeesConsumer.name);

constructor(
private appConfig: AppConfig,
private suggestedFeesService: SuggestedFeesService,
@InjectRepository(Deposit) private depositRepository: Repository<Deposit>,
) {}

@Process()
private async process(job: Job<SuggestedFeesQueueMessage>) {
const { depositId } = job.data;

const deposit = await this.depositRepository.findOne({
where: { id: depositId },
});

if (!deposit) {
this.logger.verbose(`${ScraperQueue.SuggestedFees} deposit with id '${depositId}' does not exist in db`);
return;
}

if (!deposit.depositDate) {
throw new Error(`Deposit with id '${depositId}' needs 'depositDate' entry in order to fetch suggested fees`);
}

let suggestedRelayerFeePct: string;

const diffToNowHours = DateTime.fromJSDate(deposit.depositDate).diffNow().as("hours");
if (Math.abs(diffToNowHours) >= this.appConfig.values.suggestedFees.fallbackThresholdHours) {
// Due to the inability to retrieve historic suggested fees, we fallback
// to 1bp for deposits that were made more than configured hours ago.
suggestedRelayerFeePct = utils.parseEther("0.0001").toString();
} else {
// For deposits that were made tolerable hours ago, we assume somewhat constant suggested fees.
const suggestedFeesFromApi = await this.suggestedFeesService.getFromApi({
amount: deposit.amount,
token: deposit.tokenAddr,
destinationChainId: deposit.destinationChainId,
originChainId: deposit.sourceChainId,
});
suggestedRelayerFeePct = suggestedFeesFromApi.relayFeePct;
}

await this.depositRepository.update({ id: depositId }, { suggestedRelayerFeePct });
}

@OnQueueFailed()
private onQueueFailed(job: Job, error: Error) {
this.logger.error(`${ScraperQueue.SuggestedFees} ${JSON.stringify(job.data)} failed: ${error}`);
}
}
5 changes: 5 additions & 0 deletions src/modules/scraper/adapter/messaging/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export enum ScraperQueue {
DepositFilledDate = "DepositFilledDate",
MerkleDistributorBlocksEvents = "MerkleDistributorBlocksEvents",
DepositAcxPrice = "DepositAcxPrice",
SuggestedFees = "SuggestedFees",
}

export type BlocksEventsQueueMessage = {
Expand Down Expand Up @@ -55,3 +56,7 @@ export type DepositFilledDateQueueMessage = {
export type DepositAcxPriceQueueMessage = {
depositId: number;
};

export type SuggestedFeesQueueMessage = {
depositId: number;
};
17 changes: 17 additions & 0 deletions src/modules/scraper/entry-point/http/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
TokenPriceQueueMessage,
TokenDetailsQueueMessage,
DepositAcxPriceQueueMessage,
SuggestedFeesQueueMessage,
} from "../../adapter/messaging";
import { ScraperService } from "../../service";
import { ScraperQueuesService } from "../../service/ScraperQueuesService";
Expand All @@ -23,6 +24,7 @@ import {
SubmitDepositFilledDateBody,
ProcessBlockNumberBody,
SubmitDepositAcxPriceBody,
SubmitSuggestedFeesBody,
} from "./dto";

@Controller()
Expand Down Expand Up @@ -146,4 +148,19 @@ export class ScraperController {
});
}
}

@Post("scraper/suggested-fees")
@ApiTags("scraper")
@Roles(Role.Admin)
@UseGuards(JwtAuthGuard, RolesGuard)
@ApiBearerAuth()
async submitSuggestedFeesJob(@Body() body: SubmitSuggestedFeesBody) {
const { fromDepositId, toDepositId } = body;

for (let depositId = fromDepositId; depositId <= toDepositId; depositId++) {
await this.scraperQueuesService.publishMessage<SuggestedFeesQueueMessage>(ScraperQueue.SuggestedFees, {
depositId,
});
}
}
}
10 changes: 10 additions & 0 deletions src/modules/scraper/entry-point/http/dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,13 @@ export class ProcessBlockNumberBody {
@ApiProperty({ example: 2 })
toDepositId: number;
}

export class SubmitSuggestedFeesBody {
@IsInt()
@ApiProperty({ example: 1 })
fromDepositId: number;

@IsInt()
@ApiProperty({ example: 2 })
toDepositId: number;
}
3 changes: 3 additions & 0 deletions src/modules/scraper/model/deposit.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ export class Deposit {
@Column({ type: "decimal" })
depositRelayerFeePct?: string;

@Column({ type: "decimal", nullable: true })
suggestedRelayerFeePct?: string;

@Column({ type: "decimal", default: 0 })
realizedLpFeePctCapped: string;

Expand Down
7 changes: 7 additions & 0 deletions src/modules/scraper/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ import { MerkleDistributorProcessedBlock } from "./model/MerkleDistributorProces
import { ScraperService } from "./service";
import { ScraperQueuesService } from "./service/ScraperQueuesService";
import { DepositAcxPriceConsumer } from "./adapter/messaging/DepositAcxPriceConsumer";
import { SuggestedFeesConsumer } from "./adapter/messaging/SuggestedFeesConsumer";
import { SuggestedFeesService } from "./adapter/across-serverless-api/suggested-fees-service";

@Module({
providers: [
ScraperService,
ScraperQueuesService,
SuggestedFeesService,
BlocksEventsConsumer,
MerkleDistributorBlocksEventsConsumer,
FillEventsConsumer,
Expand All @@ -41,6 +44,7 @@ import { DepositAcxPriceConsumer } from "./adapter/messaging/DepositAcxPriceCons
TokenPriceConsumer,
DepositFilledDateConsumer,
DepositAcxPriceConsumer,
SuggestedFeesConsumer,
DepositFixture,
ClaimFixture,
],
Expand Down Expand Up @@ -84,6 +88,9 @@ import { DepositAcxPriceConsumer } from "./adapter/messaging/DepositAcxPriceCons
BullModule.registerQueue({
name: ScraperQueue.DepositFilledDate,
}),
BullModule.registerQueue({
name: ScraperQueue.SuggestedFees,
}),
],
exports: [ScraperQueuesService],
controllers: [ScraperController],
Expand Down
8 changes: 8 additions & 0 deletions src/modules/scraper/service/ScraperQueuesService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class ScraperQueuesService {
@InjectQueue(ScraperQueue.TokenPrice) private tokenPriceQueue: Queue,
@InjectQueue(ScraperQueue.DepositFilledDate) private depositFilledDateQueue: Queue,
@InjectQueue(ScraperQueue.DepositAcxPrice) private depositAcxPriceQueue: Queue,
@InjectQueue(ScraperQueue.SuggestedFees) private suggestedFeesQueue: Queue,
) {
setInterval(() => {
this.blocksEventsQueue
Expand Down Expand Up @@ -46,6 +47,9 @@ export class ScraperQueuesService {
this.depositAcxPriceQueue
.getJobCounts()
.then((data) => this.logger.log(`${ScraperQueue.DepositAcxPrice} ${JSON.stringify(data)}`));
this.suggestedFeesQueue
.getJobCounts()
.then((data) => this.logger.log(`${ScraperQueue.SuggestedFees} ${JSON.stringify(data)}`));
}, 1000 * 60);
}

Expand All @@ -68,6 +72,8 @@ export class ScraperQueuesService {
await this.merkleDistributorBlocksEventsQueue.add(message);
} else if (queue === ScraperQueue.DepositAcxPrice) {
await this.depositAcxPriceQueue.add(message);
} else if (queue === ScraperQueue.SuggestedFees) {
await this.suggestedFeesQueue.add(message);
}
}

Expand All @@ -90,6 +96,8 @@ export class ScraperQueuesService {
await this.merkleDistributorBlocksEventsQueue.addBulk(messages.map((m) => ({ data: m })));
} else if (queue === ScraperQueue.DepositAcxPrice) {
await this.depositAcxPriceQueue.addBulk(messages.map((m) => ({ data: m })));
} else if (queue === ScraperQueue.SuggestedFees) {
await this.suggestedFeesQueue.addBulk(messages.map((m) => ({ data: m })));
}
}
}

0 comments on commit bd1676f

Please sign in to comment.