Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hatom liquidation events webhook #4

Open
wants to merge 17 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .env.devnet
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ NETWORK=mainnet
API_URL=https://api.multiversx.com
DATA_API_CEX_URL=https://data-api.multiversx.com/v1/quotes/cex
DATA_API_XEXCHANGE_URL=https://data-api.multiversx.com/v1/quotes/xexchange
DATA_API_HATOM_URL=https://data-api.multiversx.com/v1/quotes/hatom
# DUNE_API_URL=http://localhost:3001/api/v1/table
DUNE_API_URL=https://api.dune.com/api/v1/table
DUNE_NAMESPACE=stefanmvx
Expand Down
1 change: 1 addition & 0 deletions .env.mainnet
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ NETWORK=mainnet
API_URL=https://api.multiversx.com
DATA_API_CEX_URL=https://data-api.multiversx.com/v1/quotes/cex
DATA_API_XEXCHANGE_URL=https://data-api.multiversx.com/v1/quotes/xexchange
DATA_API_HATOM_URL=https://data-api.multiversx.com/v1/quotes/hatom
DUNE_API_URL=http://localhost:3001/api/v1/table
# DUNE_API_URL=https://api.dune.com/api/v1/table
DUNE_NAMESPACE=stefanmvx
Expand Down
1 change: 1 addition & 0 deletions .env.testnet
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ NETWORK=mainnet
API_URL=https://api.multiversx.com
DATA_API_CEX_URL=https://data-api.multiversx.com/v1/quotes/cex
DATA_API_XEXCHANGE_URL=https://data-api.multiversx.com/v1/quotes/xexchange
DATA_API_HATOM_URL=https://data-api.multiversx.com/v1/quotes/hatom
# DUNE_API_URL=http://localhost:3001/api/v1/table
DUNE_API_URL=https://api.dune.com/api/v1/table
DUNE_NAMESPACE=stefanmvx
Expand Down
3 changes: 2 additions & 1 deletion .multiversx/config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ libs:
api: ${API_URL}
dataApiCex: ${DATA_API_CEX_URL}
dataApiXexchange: ${DATA_API_XEXCHANGE_URL}
dataApiHatom: ${DATA_API_HATOM_URL}
duneApi: ${DUNE_API_URL}
database:
host: 'localhost'
port: 27017
# username: 'root'
# password: 'root'
name: 'example'
name: 'duneDB'
tlsAllowInvalidCertificates: true
redis:
host: '127.0.0.1'
Expand Down
4 changes: 4 additions & 0 deletions apps/api/src/config/app-config.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ export class AppConfigService {
return configuration().libs.common.urls.dataApiXexchange ?? "";
}

getDataApiHatomUrl(): string {
return configuration().libs.common.urls.dataApiHatom ?? "";
}

getDuneApiUrl(): string {
return configuration().libs.common.urls.duneApi ?? "";
}
Expand Down
12 changes: 10 additions & 2 deletions apps/api/src/endpoints/events/events.controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Body, Controller, Param, Post } from "@nestjs/common";
import { ApiTags } from "@nestjs/swagger";
import { EventLog } from "./entities/event.log";
import { HatomBorrowEventsService, LiquidityEventsService } from "@libs/services/events";
import { HatomBorrowEventsService, HatomLiquidationService, LiquidityEventsService } from "@libs/services/events";

@Controller('/events')
@ApiTags('events')
export class EventsController {
constructor(
private readonly liquidityService: LiquidityEventsService,
private readonly hatomBorrowService: HatomBorrowEventsService,
private readonly hatomLiquidationService: HatomLiquidationService,
) { }

@Post("/liquidity-webhook")
Expand All @@ -18,12 +19,19 @@ export class EventsController {
await this.liquidityService.liquidityWebhook(body);
}

@Post("/hatom-webhook/:borrowed_token")
@Post("/hatom-borrow-webhook/:borrowed_token")
async hatomBorrowWebhook(
@Body() body: EventLog[],
@Param('borrowed_token') borrowedToken: string,
): Promise<void> {
await this.hatomBorrowService.hatomBorrowWebhook(body, borrowedToken);
}

@Post("/hatom-liquidation-webhook")
async hatomLiquidationWebhook(
@Body() body: EventLog[],
): Promise<void> {
await this.hatomLiquidationService.hatomLiquidationWebhook(body);
}

}
3 changes: 2 additions & 1 deletion config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,14 @@ libs:
api: ${API_URL}
dataApiCex: ${DATA_API_CEX_URL}
dataApiXexchange: ${DATA_API_XEXCHANGE_URL}
dataApiHatom: ${DATA_API_HATOM_URL}
duneApi: ${DUNE_API_URL}
database:
host: 'localhost'
port: 27017
# username: 'root'
# password: 'root'
name: 'example'
name: 'duneDB'
tlsAllowInvalidCertificates: true
redis:
host: '127.0.0.1'
Expand Down
1 change: 1 addition & 0 deletions config/schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ libs:
api: string
dataApiCex: string
dataApiXexchange: string
dataApiHatom: string
duneApi: string
database:
host: string
Expand Down
1 change: 1 addition & 0 deletions libs/common/src/entities/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface Config {
api: string;
dataApiCex: string;
dataApiXexchange: string;
dataApiHatom: string;
duneApi: string;
};
database: {
Expand Down
18 changes: 15 additions & 3 deletions libs/services/src/data/data.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,28 @@ export class DataService {
private readonly appConfigService: AppConfigService,
) { }

async getTokenPrice(tokenId: string, date: moment.Moment): Promise<BigNumber> {
async getTokenPrice(tokenId: string, date: moment.Moment, market?: string): Promise<BigNumber> {
return await this.cachingService.getOrSet(
CacheInfo.TokenPrice(tokenId, date).key,
async () => await this.getTokenPriceRaw(tokenId, date),
async () => await this.getTokenPriceRaw(tokenId, date, market),
CacheInfo.TokenPrice(tokenId, date).ttl
);
}

async getTokenPriceRaw(tokenId: string, date: moment.Moment): Promise<BigNumber> {
async getTokenPriceRaw(tokenId: string, date: moment.Moment, market?: string): Promise<BigNumber> {
try {
if (market) {
switch (market) {
case 'hatom':
return (await axios.get<TokenPrice>(`${this.appConfigService.getDataApiHatomUrl()}/${tokenId}?date=${date.format('YYYY-MM-DD')}`)).data.price;
case 'xexchange':
return (await axios.get<TokenPrice>(`${this.appConfigService.getDataApiXexchangeUrl()}/${tokenId}?date=${date.format('YYYY-MM-DD')}`)).data.price;
case 'cex':
return (await axios.get<TokenPrice>(`${this.appConfigService.getDataApiCexUrl()}/${tokenId}?date=${date.format('YYYY-MM-DD')}`)).data.price;
default:
throw Error('Invalid market !');
}
}
if (tokenId.startsWith('USD')) {
return (await axios.get<TokenPrice>(`${this.appConfigService.getDataApiCexUrl()}/${tokenId}?date=${date.format('YYYY-MM-DD')}`)).data.price;
}
Expand Down
29 changes: 6 additions & 23 deletions libs/services/src/events/hatom.borrow.events.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import { Injectable } from "@nestjs/common";
import { EventLog } from "apps/api/src/endpoints/events/entities";
import { Address } from "@multiversx/sdk-core";
import BigNumber from "bignumber.js";
import { CsvRecordsService } from "../records";
import moment from "moment";
import { DataService } from "../data";
import { TableSchema } from "apps/dune-simulator/src/endpoints/dune-simulator/entities";
import { joinCsvAttributes } from "libs/services/utils";
import { borrowEvent, decodeTopics, HatomEvent, joinCsvAttributes } from "libs/services/utils";

interface BorrowEvent {
interface BorrowEvent extends HatomEvent {
eventName: string;
borrowerAddress: string;
amount: BigNumber;
Expand All @@ -35,12 +34,11 @@ export class HatomBorrowEventsService {
) { }

public async hatomBorrowWebhook(eventsLog: EventLog[], borrowedToken: string): Promise<void> {

for (const eventLog of eventsLog) {
const borrowEventInHex = '626f72726f775f6576656e74'; // 'borrow_event'

if (eventLog.identifier === "borrow" && eventLog.topics[0] === borrowEventInHex) {
const currentEvent = this.decodeTopics(eventLog);
if (eventLog.identifier === "borrow" && eventLog.topics[0] === borrowEvent) {
const properties: string[] = ["eventName", "borrowerAddress", "amount", "newAccountBorrow", "newTotalBorrows", "newBorrowerIndex"];
const types: string[] = ["String", "Address", "BigNumber", "BigNumber", "BigNumber", "BigNumber"];
const currentEvent: BorrowEvent = decodeTopics(properties, eventLog.topics, types) as BorrowEvent;
const eventDate = moment.unix(eventLog.timestamp);

const [borrowedAmountInEGLD, borrowedAmountInUSD] = await this.convertBorrowedAmount(currentEvent, borrowedToken, eventDate);
Expand All @@ -66,19 +64,6 @@ export class HatomBorrowEventsService {
}
}

decodeTopics(eventLog: EventLog): BorrowEvent {
const currentEvent: BorrowEvent = {
eventName: Buffer.from(eventLog.topics[0], 'hex').toString(),
borrowerAddress: Address.newFromHex(Buffer.from(eventLog.topics[1], 'hex').toString('hex')).toBech32(),
amount: BigNumber(Buffer.from(eventLog.topics[2], 'hex').toString('hex'), 16),
newAccountBorrow: BigNumber(Buffer.from(eventLog.topics[3], 'hex').toString('hex'), 16),
newTotalBorrows: BigNumber(Buffer.from(eventLog.topics[4], 'hex').toString('hex'), 16),
newBorrowerIndex: BigNumber(Buffer.from(eventLog.topics[5], 'hex').toString('hex'), 16),
};

return currentEvent;
}

async convertBorrowedAmount(currentEvent: BorrowEvent, borrowedToken: string, date: moment.Moment): Promise<[BigNumber, BigNumber]> {
let borrowedAmountInEGLD, borrowedAmountInUSD;

Expand All @@ -93,5 +78,3 @@ export class HatomBorrowEventsService {
return [borrowedAmountInEGLD, borrowedAmountInUSD];
}
}


92 changes: 92 additions & 0 deletions libs/services/src/events/hatom.liquidation.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { Injectable, Logger } from "@nestjs/common";
import { TableSchema } from "apps/dune-simulator/src/endpoints/dune-simulator/entities";
import { EventLog } from "apps/api/src/endpoints/events/entities";
import { CsvRecordsService } from "../records";
import { liquidationBorrowEvent, decodeTopics, getTokenIdByMoneyMarket, HatomEvent, joinCsvAttributes } from "libs/services/utils";
import BigNumber from "bignumber.js";
import moment from "moment";
import { DataService } from "../data";

interface LiquidationEvent extends HatomEvent {
liquidator: string,
borrower: string,
amount: BigNumber,
collateral_mma: string,
tokens: BigNumber,
}

@Injectable()
export class HatomLiquidationService {
private readonly headers: TableSchema[] = [
{ name: "timestamp", type: "varchar" },
{ name: 'liquidator', type: 'varchar' },
{ name: 'account_liquidated', type: 'varchar' },
{ name: 'token', type: 'varchar' },
{ name: 'amount', type: 'double' },
{ name: 'amount_in_egld', type: 'double' },
{ name: 'amount_in_usd', type: 'double' },
{ name: 'total_liquidated_in_egld', type: 'double' },
{ name: 'total_liquidated_in_usd', type: 'double' },
];

constructor(
private readonly csvRecordsService: CsvRecordsService,
private readonly dataService: DataService,
) {}

public async hatomLiquidationWebhook(eventsLog: EventLog[]): Promise<void> {
const liquidationBorrowTopicsLength = 6;
const totalLiquidatedInEgld = new BigNumber(0);
const totalLiquidatedInUsd = new BigNumber(0);

for (const eventLog of eventsLog) {
console.log(eventLog);
if (eventLog.identifier === "liquidateBorrow" && eventLog.topics.length === liquidationBorrowTopicsLength && eventLog.topics[0] === liquidationBorrowEvent) {
const properties: string[] = ["liquidator", "borrower", "amount", "collateral_mma", "tokens"];
const types: string[] = ["Address", "Address", "BigNumber", "Address", "BigNumber"];
const currentEvent: LiquidationEvent = decodeTopics(properties, eventLog.topics.slice(1), types) as LiquidationEvent;
const eventDate = moment.unix(eventLog.timestamp);

const tokenId: string | undefined = getTokenIdByMoneyMarket(currentEvent.collateral_mma);

if (tokenId === undefined) {
Logger.warn(`Token ID not found for collateral MMA: ${currentEvent.collateral_mma}`);
continue;
}

const tokenPrecision = await this.dataService.getTokenPrecision(tokenId);
const [liquidatedAmountInEGLD, liquidatedAmountInUSD] = await this.convertTokenValue(currentEvent.tokens, tokenId, eventDate);
totalLiquidatedInEgld.plus(liquidatedAmountInEGLD);
totalLiquidatedInUsd.plus(liquidatedAmountInUSD);

await this.csvRecordsService.pushRecord(
"hatom_liquidation_events",
[
joinCsvAttributes(
eventDate.format('YYYY-MM-DD HH:mm:ss.SSS'),
currentEvent.liquidator,
currentEvent.borrower,
tokenId,
currentEvent.tokens.shiftedBy(-tokenPrecision).decimalPlaces(4),
liquidatedAmountInEGLD.shiftedBy(-tokenPrecision).decimalPlaces(4),
liquidatedAmountInUSD.shiftedBy(-tokenPrecision).decimalPlaces(4),
totalLiquidatedInEgld.shiftedBy(-tokenPrecision).decimalPlaces(4),
totalLiquidatedInUsd.shiftedBy(-tokenPrecision).decimalPlaces(4),
),
],
this.headers
);
}
}
}

async convertTokenValue(amount: BigNumber, tokenID: string, date: moment.Moment): Promise<[BigNumber, BigNumber]> {
const egldPrice = await this.dataService.getTokenPrice('WEGLD-bd4d79', date);
const tokenPrice = await this.dataService.getTokenPrice(tokenID, date, 'hatom');

const valueInUsd = amount.multipliedBy(tokenPrice);
const valueInEgld = valueInUsd.dividedBy(egldPrice);

return [valueInEgld, valueInUsd];
}
}
1 change: 1 addition & 0 deletions libs/services/src/events/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './liquidity.events.service';
export * from './hatom.borrow.events.service';
export * from './hatom.liquidation.service';
13 changes: 6 additions & 7 deletions libs/services/src/events/liquidity.events.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@ export class LiquidityEventsService {
let currentEvent: AddLiquidityEvent | RemoveLiquidityEvent;

for (const eventLog of eventsLog) {
// We need to parse an event only when we receive data from events-log-service

// eventLog.topics = eventLog.topics.map((topic) => Buffer.from(topic, 'hex').toString('base64'));
// eventLog.data = Buffer.from(eventLog.data, 'hex').toString('base64');
// eventLog.additionalData = eventLog.additionalData.map((data) => Buffer.from(data, 'hex').toString('base64'));

switch (eventLog.identifier) {
case "addLiquidity":
currentEvent = new AddLiquidityEvent(eventLog);
Expand All @@ -54,7 +48,12 @@ export class LiquidityEventsService {

for (let i = 0; i < diff; i++) {
this.lastDate[csvFileName].add(1, 'hour').startOf('hour');
const liquidity = await this.computeLiquidty(this.lastFirstTokenReserves[csvFileName], this.lastSecondTokenReserves[csvFileName], firstTokenId, secondTokenId, this.lastDate[csvFileName]);
const liquidity = await this.computeLiquidty(
this.lastFirstTokenReserves[csvFileName],
this.lastSecondTokenReserves[csvFileName],
firstTokenId,
secondTokenId,
this.lastDate[csvFileName]);
await this.csvRecordsService.pushRecord(
csvFileName,
[
Expand Down
4 changes: 3 additions & 1 deletion libs/services/src/services.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Global, Module } from '@nestjs/common';
import { DatabaseModule } from '@libs/database';
import { DynamicModuleUtils } from '@libs/common';
import { HatomBorrowEventsService, LiquidityEventsService } from './events';
import { HatomBorrowEventsService, HatomLiquidationService, LiquidityEventsService } from './events';
import { DataService } from './data';
import { DuneSenderService } from './dune-sender';
import { CsvRecordsService } from './records';
Expand All @@ -19,13 +19,15 @@ import { CsvRecordsService } from './records';
DuneSenderService,
CsvRecordsService,
HatomBorrowEventsService,
HatomLiquidationService,
],
exports: [
LiquidityEventsService,
DataService,
DuneSenderService,
CsvRecordsService,
HatomBorrowEventsService,
HatomLiquidationService,
],
})
export class ServicesModule { }
Loading
Loading