diff --git a/src/modules/deposit/entry-point/http/controller.ts b/src/modules/deposit/entry-point/http/controller.ts index e01a4b9e..2a42ee33 100644 --- a/src/modules/deposit/entry-point/http/controller.ts +++ b/src/modules/deposit/entry-point/http/controller.ts @@ -1,7 +1,7 @@ import { Controller, Get, Query } from "@nestjs/common"; import { ApiResponse, ApiTags } from "@nestjs/swagger"; import { DepositService } from "../../service"; -import { GetDepositsQuery, GetDepositsStatsResponse } from "./dto"; +import { GetDepositsQuery, GetDepositDetailsQuery, GetDepositsStatsResponse } from "./dto"; @Controller() export class DepositController { @@ -20,6 +20,12 @@ export class DepositController { return this.depositService.getDeposits(query.status, limit, offset); } + @Get("deposits/details") + @ApiTags("deposits") + getDepositsDetails(@Query() query: GetDepositDetailsQuery) { + return this.depositService.getDepositDetails(query.depositTxHash, parseInt(query.originChainId)); + } + @Get("deposits/stats") @ApiTags("deposits") @ApiResponse({ type: GetDepositsStatsResponse }) diff --git a/src/modules/deposit/entry-point/http/dto.ts b/src/modules/deposit/entry-point/http/dto.ts index 575c2105..09edcc09 100644 --- a/src/modules/deposit/entry-point/http/dto.ts +++ b/src/modules/deposit/entry-point/http/dto.ts @@ -1,6 +1,6 @@ import { ApiProperty } from "@nestjs/swagger"; import { Type } from "class-transformer"; -import { IsDateString, IsEnum, IsInt, IsOptional, IsString, Max, Min } from "class-validator"; +import { IsDateString, IsEnum, IsInt, IsOptional, IsString, Max, Min, IsPositive } from "class-validator"; export class GetDepositsQuery { @IsOptional() @@ -38,6 +38,19 @@ export class GetDepositsQuery { address: string; } +export class GetDepositDetailsQuery { + @IsString() + @Type(() => String) + @ApiProperty({ example: "0x", required: true }) + depositTxHash: string; + + @IsInt() + @IsPositive() + @Type(() => Number) + @ApiProperty({ example: 1, required: true }) + originChainId: string; +} + export class GetDepositsStatsResponse { @ApiProperty({ example: 100, description: "Total number of deposits" }) totalDeposits: number; diff --git a/src/modules/deposit/exceptions.ts b/src/modules/deposit/exceptions.ts index a187ddfe..9f45e0cc 100644 --- a/src/modules/deposit/exceptions.ts +++ b/src/modules/deposit/exceptions.ts @@ -11,3 +11,15 @@ export class InvalidAddressException extends HttpException { ); } } + +export class DepositNotFoundException extends HttpException { + constructor() { + super( + { + error: DepositNotFoundException.name, + message: "Deposit not found", + }, + HttpStatus.NOT_FOUND, + ); + } +} diff --git a/src/modules/deposit/service.ts b/src/modules/deposit/service.ts index b075bb03..1057e288 100644 --- a/src/modules/deposit/service.ts +++ b/src/modules/deposit/service.ts @@ -12,7 +12,7 @@ import { getTotalVolumeQuery, } from "./adapter/db/queries"; import { AppConfig } from "../configuration/configuration.service"; -import { InvalidAddressException } from "./exceptions"; +import { InvalidAddressException, DepositNotFoundException } from "./exceptions"; export const DEPOSITS_STATS_CACHE_KEY = "deposits:stats"; @@ -132,6 +132,16 @@ export class DepositService { }; } + public async getDepositDetails(depositTxHash: string, sourceChainId: number) { + const deposit = await this.depositRepository.findOne({ where: { depositTxHash, sourceChainId } }); + + if (!deposit) { + throw new DepositNotFoundException(); + } + + return deposit; + } + public async getEtlReferralDeposits(date: string) { // delete time from date in case of datetime const parsedDate = new Date(date).toISOString().split("T")[0]; diff --git a/test/deposit.e2e-spec.ts b/test/deposit.e2e-spec.ts index 0b0916c9..be7b57e8 100644 --- a/test/deposit.e2e-spec.ts +++ b/test/deposit.e2e-spec.ts @@ -3,7 +3,11 @@ import { INestApplication } from "@nestjs/common"; import { Test } from "@nestjs/testing"; import { constants, ethers } from "ethers"; -import { DepositFixture, mockManyDepositEntities } from "../src/modules/deposit/adapter/db/deposit-fixture"; +import { + DepositFixture, + mockManyDepositEntities, + mockDepositEntity, +} from "../src/modules/deposit/adapter/db/deposit-fixture"; import { ValidationPipe } from "../src/validation.pipe"; import { AppModule } from "../src/app.module"; import { RunMode } from "../src/dynamic-module"; @@ -120,6 +124,58 @@ describe("GET /deposits", () => { }); }); +describe("GET /deposits/details", () => { + const depositorAddress = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; + const depositTxHash = "0x91616c035fe2b7432d1549b9a204e29fd7cf3f5d5d9170cd418e5cc7dcc4e3a0"; + const sourceChainId = 1; + + const FILLED_DEPOSIT = mockDepositEntity({ + status: "filled", + depositorAddr: depositorAddress, + sourceChainId, + depositTxHash, + amount: "10", + filled: "10", + }); + + beforeAll(async () => { + await app.get(DepositFixture).insertManyDeposits([FILLED_DEPOSIT]); + }); + + it("200 with for correct params", async () => { + const response = await request(app.getHttpServer()).get( + `/deposits/details?depositTxHash=${depositTxHash}&originChainId=${sourceChainId}`, + ); + expect(response.status).toBe(200); + expect(response.body.status).toBe("filled"); + }); + + it("404 for non existent depositTxHash", async () => { + const response = await request(app.getHttpServer()).get( + `/deposits/details?depositTxHash=0x&originChainId=${sourceChainId}`, + ); + expect(response.status).toBe(404); + }); + + it("404 for non existent originChainId", async () => { + const response = await request(app.getHttpServer()).get( + `/deposits/details?depositTxHash=${depositTxHash}&originChainId=10`, + ); + expect(response.status).toBe(404); + }); + + it("400 for invalid originChainId", async () => { + const response = await request(app.getHttpServer()).get( + `/deposits/details?depositTxHash=${depositTxHash}&originChainId=invalid`, + ); + expect(response.status).toBe(400); + }); + + afterAll(async () => { + await app.get(DepositFixture).deleteAllDeposits(); + }); +}); + describe("GET /etl/referral-deposits", () => { const date = "2022-01-01";