Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into 1.9.0
Browse files Browse the repository at this point in the history
  • Loading branch information
thedoublejay committed Mar 23, 2023
2 parents 96d29d6 + 4e18dc3 commit 2397387
Show file tree
Hide file tree
Showing 26 changed files with 773 additions and 446 deletions.
6 changes: 3 additions & 3 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@
"@nestjs/terminus": "^9.2.1",
"@nestjs/throttler": "^4.0.0",
"@prisma/client": "^4.11.0",
"@waveshq/walletkit-core": "^0.40.1",
"@waveshq/walletkit-ui": "^0.40.1",
"@waveshq/walletkit-core": "^0.42.3",
"@waveshq/walletkit-ui": "^0.42.3",
"async-mutex": "^0.4.0",
"bignumber.js": "^9.1.1",
"cache-manager": "^5.1.7",
"cache-manager": "^5.2.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"ethers": "^5.7.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- AlterTable
ALTER TABLE "BridgeEventTransactions" ADD COLUMN "blockHash" TEXT,
ADD COLUMN "blockHeight" TEXT,
ADD COLUMN "unconfirmedSendTransactionHash" TEXT;
47 changes: 25 additions & 22 deletions apps/server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -23,31 +23,34 @@ enum BotStatus {
}

model DeFiChainAddressIndex {
id BigInt @id @default(autoincrement())
index Int
address String @unique
refundAddress String
claimNonce String?
claimDeadline String?
claimSignature String?
claimAmount String?
tokenSymbol String?
createdAt DateTime @default(now())
updatedAt DateTime? @updatedAt
hotWalletAddress String
ethReceiverAddress String?
botStatus BotStatus?
id BigInt @id @default(autoincrement())
index Int
address String @unique
refundAddress String
claimNonce String?
claimDeadline String?
claimSignature String?
claimAmount String?
tokenSymbol String?
createdAt DateTime @default(now())
updatedAt DateTime? @updatedAt
hotWalletAddress String
ethReceiverAddress String?
botStatus BotStatus?
@@unique([hotWalletAddress, index])
}

model BridgeEventTransactions {
id BigInt @id @default(autoincrement())
transactionHash String @unique
status EthereumTransactionStatus
sendTransactionHash String?
createdAt DateTime @default(now())
updatedAt DateTime? @updatedAt
amount String?
tokenSymbol String?
id BigInt @id @default(autoincrement())
transactionHash String @unique
status EthereumTransactionStatus
sendTransactionHash String?
unconfirmedSendTransactionHash String?
createdAt DateTime @default(now())
updatedAt DateTime? @updatedAt
amount String?
tokenSymbol String?
blockHash String?
blockHeight String?
}
27 changes: 15 additions & 12 deletions apps/server/src/defichain/DefichainInterface.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
import BigNumber from 'bignumber.js';
import { IsDateString, IsOptional } from 'class-validator';

import { SupportedDFCTokenSymbols } from '../AppConfig';
import { Iso8601DateOnlyString } from '../utils/StatsUtils';

const defaultValue = new BigNumber(0).toFixed(6);

export class DeFiChainStats {
readonly totalTransactions: number;

readonly confirmedTransactions: number;

readonly amountBridged: BridgedDfcToEvm;

constructor(totalTransactions: number, confirmedTransactions: number, amountBridged: BridgedDfcToEvm) {
readonly totalBridgedAmount: BridgedDfcToEvm;

constructor(
totalTransactions: number,
confirmedTransactions: number,
amountBridged: BridgedDfcToEvm,
totalBridgedAmount: BridgedDfcToEvm,
) {
this.totalTransactions = totalTransactions;
this.confirmedTransactions = confirmedTransactions;
this.amountBridged = {
USDC: amountBridged[SupportedDFCTokenSymbols.USDC]?.toString() || defaultValue,
USDT: amountBridged[SupportedDFCTokenSymbols.USDT]?.toString() || defaultValue,
BTC: amountBridged[SupportedDFCTokenSymbols.BTC]?.toString() || defaultValue,
ETH: amountBridged[SupportedDFCTokenSymbols.ETH]?.toString() || defaultValue,
DFI: amountBridged[SupportedDFCTokenSymbols.DFI]?.toString() || defaultValue,
EUROC: amountBridged[SupportedDFCTokenSymbols.EUROC]?.toString() || defaultValue,
};
this.amountBridged = amountBridged;
this.totalBridgedAmount = totalBridgedAmount;
}
}

Expand All @@ -34,3 +32,8 @@ export class DFCStatsDto {
@IsOptional()
date?: Iso8601DateOnlyString;
}

export type BridgedDFCTokenSum = {
tokenSymbol: SupportedDFCTokenSymbols;
totalAmount: string;
};
41 changes: 7 additions & 34 deletions apps/server/src/defichain/controllers/StatsController.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { stats } from '@defichain/whale-api-client';
import { Controller, Get, HttpException, HttpStatus, Query } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { Controller, Get, Query } from '@nestjs/common';
import { SkipThrottle } from '@nestjs/throttler';
import { EnvironmentNetwork } from '@waveshq/walletkit-core';

import { SemaphoreCache } from '../../libs/caches/SemaphoreCache';
import { DeFiChainStats, DFCStatsDto } from '../DefichainInterface';
Expand All @@ -11,16 +9,11 @@ import { WhaleApiService } from '../services/WhaleApiService';

@Controller()
export class StatsController {
private network: EnvironmentNetwork;

constructor(
private readonly whaleClient: WhaleApiService,
private readonly configService: ConfigService,
protected readonly cache: SemaphoreCache,
private defichainStatsService: DeFiChainStatsService,
) {
this.network = configService.getOrThrow<EnvironmentNetwork>(`defichain.network`);
}
) {}

@SkipThrottle()
@Get('/whale/stats')
Expand All @@ -29,30 +22,10 @@ export class StatsController {
}

@Get('/stats')
async getDFCStats(@Query('date') date?: DFCStatsDto): Promise<DeFiChainStats | undefined> {
return this.cache.get(
`DFC_STATS_${date ?? 'TODAY'}`,
async () => {
try {
const { totalTransactions, confirmedTransactions, amountBridged } =
await this.defichainStatsService.getDefiChainStats(date);
return new DeFiChainStats(totalTransactions, confirmedTransactions, amountBridged);
} catch (e: any) {
throw new HttpException(
{
status: e.code || HttpStatus.INTERNAL_SERVER_ERROR,
error: `API call for DefiChain statistics was unsuccessful: ${e.message}`,
},
HttpStatus.INTERNAL_SERVER_ERROR,
{
cause: e,
},
);
}
},
{
ttl: 3600_000 * 24, // 1 day
},
);
async getDFCStats(@Query('date') date?: DFCStatsDto): Promise<DeFiChainStats> {
const cacheKey = `DFC_STATS_${date ?? 'TODAY'}`;
return (await this.cache.get(cacheKey, async () => this.defichainStatsService.getDefiChainStats(date), {
ttl: 3600_000 * 24, // 1 day
})) as DeFiChainStats;
}
}
174 changes: 95 additions & 79 deletions apps/server/src/defichain/services/DeFiChainStatsService.ts
Original file line number Diff line number Diff line change
@@ -1,100 +1,116 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { BadRequestException, HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import BigNumber from 'bignumber.js';

import { SupportedDFCTokenSymbols } from '../../AppConfig';
import { PrismaService } from '../../PrismaService';
import { BridgedDfcToEvm, DeFiChainStats, DFCStatsDto } from '../DefichainInterface';
import { initializeEnumKeys } from '../../utils/EnumUtils';
import { BridgedDfcToEvm, BridgedDFCTokenSum, DeFiChainStats, DFCStatsDto } from '../DefichainInterface';

@Injectable()
export class DeFiChainStatsService {
private readonly NUMERICAL_PLACEHOLDER = '0.000000';

constructor(private prisma: PrismaService) {}

async getDefiChainStats(date?: DFCStatsDto): Promise<DeFiChainStats> {
const dateOnly = date ?? new Date().toISOString().slice(0, 10);
const dateFrom = new Date(dateOnly as string);
const today = new Date();
if (dateFrom > today) {
throw new BadRequestException(`Cannot query future date.`);
}
dateFrom.setUTCHours(0, 0, 0, 0); // set to UTC +0
const dateTo = new Date(dateFrom);
dateTo.setDate(dateFrom.getDate() + 1);
try {
const dateOnly = date ?? new Date().toISOString().slice(0, 10);
const dateFrom = new Date(dateOnly as string);
const today = new Date();
if (dateFrom > today) {
throw new BadRequestException(`Cannot query future date.`);
}
dateFrom.setUTCHours(0, 0, 0, 0); // set to UTC +0
const dateTo = new Date(dateFrom);
dateTo.setDate(dateFrom.getDate() + 1);

const [totalTransactions, confirmedTransactions] = await Promise.all([
this.prisma.deFiChainAddressIndex.count({
where: {
createdAt: {
// new Date() creates date with current time and day and etc.
gte: dateFrom.toISOString(),
lt: dateTo.toISOString(),
const [totalTransactions, confirmedTransactions] = await Promise.all([
this.prisma.deFiChainAddressIndex.count({
where: {
createdAt: {
// new Date() creates date with current time and day and etc.
gte: dateFrom.toISOString(),
lt: dateTo.toISOString(),
},
},
},
}),
}),

this.prisma.deFiChainAddressIndex.findMany({
where: {
claimSignature: { not: null },
address: { not: undefined },
tokenSymbol: { not: null },
claimAmount: { not: null },
createdAt: {
// new Date() creates date with current time and day and etc.
gte: dateFrom.toISOString(),
lt: dateTo.toISOString(),
this.prisma.deFiChainAddressIndex.findMany({
where: {
claimSignature: { not: null },
address: { not: undefined },
tokenSymbol: { not: null },
claimAmount: { not: null },
createdAt: {
// new Date() creates date with current time and day and etc.
gte: dateFrom.toISOString(),
lt: dateTo.toISOString(),
},
},
},
select: {
tokenSymbol: true,
claimAmount: true,
},
}),
]);
select: {
tokenSymbol: true,
claimAmount: true,
},
}),
]);

const amountBridged = getAmountBridged(confirmedTransactions);
const amountBridged = this.getAmountBridged(confirmedTransactions);

return {
totalTransactions,
confirmedTransactions: confirmedTransactions.length,
amountBridged,
};
}
}
// Get overall total amount of tokens claimed
const totalBridgedAmount = initializeEnumKeys(SupportedDFCTokenSymbols, this.NUMERICAL_PLACEHOLDER);
const totalAmounts: BridgedDFCTokenSum[] = await this.prisma.$queryRaw(
Prisma.sql`
SELECT SUM("claimAmount"::DECIMAL) AS "totalAmount", "tokenSymbol"
FROM "DeFiChainAddressIndex" WHERE "claimAmount" IS NOT NULL GROUP BY "tokenSymbol";`,
);
for (const total of totalAmounts) {
totalBridgedAmount[total.tokenSymbol as SupportedDFCTokenSymbols] = BigNumber(total.totalAmount)
.decimalPlaces(6, BigNumber.ROUND_FLOOR)
.toFixed(6);
}

function getAmountBridged(
confirmedTransactions: Array<{
tokenSymbol: string | null;
claimAmount: string | null;
}>,
): BridgedDfcToEvm {
const amountBridgedBigN: Record<SupportedDFCTokenSymbols, BigNumber> = {
USDC: BigNumber(0),
USDT: BigNumber(0),
BTC: BigNumber(0),
ETH: BigNumber(0),
DFI: BigNumber(0),
EUROC: BigNumber(0),
};
for (const transaction of confirmedTransactions) {
const { tokenSymbol, claimAmount } = transaction;
amountBridgedBigN[tokenSymbol as SupportedDFCTokenSymbols] = amountBridgedBigN[
tokenSymbol as SupportedDFCTokenSymbols
].plus(BigNumber(claimAmount as string));
return {
totalTransactions,
confirmedTransactions: confirmedTransactions.length,
amountBridged,
totalBridgedAmount,
};
} catch (e: any) {
throw new HttpException(
{
status: e.code || HttpStatus.INTERNAL_SERVER_ERROR,
error: `API call for DefiChain statistics was unsuccessful: ${e.message}`,
},
HttpStatus.INTERNAL_SERVER_ERROR,
{
cause: e,
},
);
}
}
const numericalPlaceholder = '0.000000';
const amountBridgedToEVM: BridgedDfcToEvm = {
USDC: numericalPlaceholder,
USDT: numericalPlaceholder,
BTC: numericalPlaceholder,
ETH: numericalPlaceholder,
DFI: numericalPlaceholder,
EUROC: numericalPlaceholder,
};

Object.keys(amountBridgedBigN).forEach((key) => {
amountBridgedToEVM[key as SupportedDFCTokenSymbols] = amountBridgedBigN[key as SupportedDFCTokenSymbols]
.decimalPlaces(6, BigNumber.ROUND_FLOOR)
.toString();
});
getAmountBridged(
confirmedTransactions: Array<{
tokenSymbol: string | null;
claimAmount: string | null;
}>,
): BridgedDfcToEvm {
const amountBridgedBigN = initializeEnumKeys(SupportedDFCTokenSymbols, BigNumber(0));
for (const transaction of confirmedTransactions) {
const { tokenSymbol, claimAmount } = transaction;
amountBridgedBigN[tokenSymbol as SupportedDFCTokenSymbols] = amountBridgedBigN[
tokenSymbol as SupportedDFCTokenSymbols
].plus(BigNumber(claimAmount as string));
}
const amountBridgedToEVM = initializeEnumKeys(SupportedDFCTokenSymbols, this.NUMERICAL_PLACEHOLDER);

Object.keys(amountBridgedBigN).forEach((key) => {
amountBridgedToEVM[key as SupportedDFCTokenSymbols] = amountBridgedBigN[key as SupportedDFCTokenSymbols]
.decimalPlaces(6, BigNumber.ROUND_FLOOR)
.toFixed(6);
});

return amountBridgedToEVM;
return amountBridgedToEVM;
}
}
Loading

0 comments on commit 2397387

Please sign in to comment.