diff --git a/packages/api/.env.example b/packages/api/.env.example index e9a1ce6464..ed85f208b3 100644 --- a/packages/api/.env.example +++ b/packages/api/.env.example @@ -13,3 +13,4 @@ DISABLE_BFF_API_SCHEMA_DOCS=false DISABLE_EXTERNAL_API=false DATABASE_STATEMENT_TIMEOUT_MS=90000 CONTRACT_VERIFICATION_API_URL=http://127.0.0.1:3070 +NETWORK_NAME=testnet-goerli diff --git a/packages/api/nest-cli.json b/packages/api/nest-cli.json index 256648114a..db78c9f4be 100644 --- a/packages/api/nest-cli.json +++ b/packages/api/nest-cli.json @@ -1,5 +1,10 @@ { "$schema": "https://json.schemastore.org/nest-cli", "collection": "@nestjs/schematics", - "sourceRoot": "src" -} + "sourceRoot": "src", + "compilerOptions": { + "assets": [ + "config/docs/constants.*.json" + ] + } +} \ No newline at end of file diff --git a/packages/api/src/address/address.controller.ts b/packages/api/src/address/address.controller.ts index 8b953062f5..d6b30e772e 100644 --- a/packages/api/src/address/address.controller.ts +++ b/packages/api/src/address/address.controller.ts @@ -25,6 +25,7 @@ import { ParseAddressPipe, ADDRESS_REGEX_PATTERN } from "../common/pipes/parseAd import { TransferService } from "../transfer/transfer.service"; import { TransferDto } from "../transfer/transfer.dto"; import { swagger } from "../config/featureFlags"; +import { constants } from "../config/docs"; const entityName = "address"; @@ -46,7 +47,7 @@ export class AddressController { @ApiParam({ name: "address", schema: { pattern: ADDRESS_REGEX_PATTERN }, - example: "0xd754ff5e8a6f257e162f72578a4bb0493c0681d8", + example: constants.address, description: "Valid hex address", }) @ApiExtraModels(AccountDto, ContractDto) @@ -112,7 +113,7 @@ export class AddressController { @ApiParam({ name: "address", schema: { pattern: ADDRESS_REGEX_PATTERN }, - example: "0xd754ff5e8a6f257e162f72578a4bb0493c0681d8", + example: constants.contractAddressWithLogs, description: "Valid hex address", }) @ApiListPageOkResponse(LogDto, { description: "Successfully returned address logs" }) @@ -136,7 +137,7 @@ export class AddressController { @ApiParam({ name: "address", schema: { pattern: ADDRESS_REGEX_PATTERN }, - example: "0xd754ff5e8a6f257e162f72578a4bb0493c0681d8", + example: constants.address, description: "Valid hex address", }) @ApiListPageOkResponse(TransferDto, { description: "Successfully returned address transfers" }) diff --git a/packages/api/src/api/api.controller.spec.ts b/packages/api/src/api/api.controller.spec.ts index fc2a8d7001..567eaa2876 100644 --- a/packages/api/src/api/api.controller.spec.ts +++ b/packages/api/src/api/api.controller.spec.ts @@ -122,6 +122,20 @@ describe("ApiController", () => { }); }); + describe("getInternalTransactionsByTxHash", () => { + it("returns null as it is defined only to appear in docs and cannot be called", async () => { + const result = await controller.getInternalTransactionsByTxHash( + { + page: 1, + offset: 10, + maxLimit: 10000, + }, + { sort: SortingOrder.Desc } + ); + expect(result).toBe(null); + }); + }); + describe("getAccountTokenTransfers", () => { it("returns null as it is defined only to appear in docs and cannot be called", async () => { const result = await controller.getAccountTokenTransfers( diff --git a/packages/api/src/api/api.controller.ts b/packages/api/src/api/api.controller.ts index ef950f075d..7694f04c7d 100644 --- a/packages/api/src/api/api.controller.ts +++ b/packages/api/src/api/api.controller.ts @@ -43,6 +43,7 @@ import { ParseModulePipe } from "./pipes/parseModule.pipe"; import { ParseActionPipe } from "./pipes/parseAction.pipe"; import { ApiExceptionFilter } from "./exceptionFilter"; import { LogsResponseDto, LogApiDto } from "./dtos/log/logs.dto"; +import { constants } from "../config/docs"; @Controller("") export class ApiController { @@ -82,7 +83,7 @@ export class ApiController { @ApiQuery({ name: "address", description: "The contract address that has a verified source code", - example: "0x8A63F953e19aA4Ce3ED90621EeF61E17A95c6594", + example: constants.verifiedContractAddress, required: true, }) @ApiOkResponse({ @@ -99,7 +100,7 @@ export class ApiController { @ApiQuery({ name: "address", description: "The contract address that has a verified source code", - example: "0x8A63F953e19aA4Ce3ED90621EeF61E17A95c6594", + example: constants.verifiedContractAddress, required: true, }) @ApiOkResponse({ @@ -118,7 +119,7 @@ export class ApiController { explode: false, name: "contractaddresses", description: "List of contract addresses, up to 5 at a time", - example: ["0x8A63F953e19aA4Ce3ED90621EeF61E17A95c6594", "0x0E03197d697B592E5AE49EC14E952cddc9b28e14"], + example: [constants.verifiedContractAddress, constants.verifiedContractAddress2], required: true, }) @ApiExtraModels(ContractCreationInfoDto) @@ -165,7 +166,7 @@ export class ApiController { @ApiQuery({ name: "txhash", description: "The transaction hash to check the execution status", - example: "0x04a4757cd59681b037c1e7bd2402cc45a23c66ed7497614879376719d34e020a", + example: constants.txHash, required: true, }) @ApiExtraModels(TransactionStatusDto) @@ -183,7 +184,7 @@ export class ApiController { @ApiQuery({ name: "txhash", description: "The transaction hash to check the execution status", - example: "0x04a4757cd59681b037c1e7bd2402cc45a23c66ed7497614879376719d34e020a", + example: constants.txHash, required: true, }) @ApiOkResponse({ @@ -200,7 +201,7 @@ export class ApiController { @ApiQuery({ name: "address", description: "The address to filter transactions by", - example: "0xFb7E0856e44Eff812A44A9f47733d7d55c39Aa28", + example: constants.address, required: true, }) @ApiQuery({ @@ -232,18 +233,49 @@ export class ApiController { } @ApiTags("Account API") - @Get("api?module=account&action=txlistinternal") - @ApiOperation({ summary: "Retrieve internal transactions for a given address or transaction hash" }) + @Get("api?module=account&action=txlistinternal&address=") + @ApiOperation({ summary: "Retrieve internal transactions for a given address" }) @ApiQuery({ name: "address", description: "The address to filter internal transactions by", - example: "0xFb7E0856e44Eff812A44A9f47733d7d55c39Aa28", + example: constants.addressWithInternalTx, required: false, }) + @ApiQuery({ + name: "startblock", + type: "integer", + description: "The block number to start searching for internal transactions", + example: 0, + required: false, + }) + @ApiQuery({ + name: "endblock", + type: "integer", + description: "The block number to stop searching for internal transactions", + example: 99999999, + required: false, + }) + @ApiExtraModels(AccountInternalTransactionDto) + @ApiOkResponse({ + description: "Internal transactions list", + type: AccountInternalTransactionsResponseDto, + }) + public async getAccountInternalTransactions( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + @Query() pagingOptions: PagingOptionsWithMaxItemsLimitDto, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + @Query() sortingOptions: SortingOptionsDto + ): Promise { + return null; + } + + @ApiTags("Account API") + @Get("api?module=account&action=txlistinternal&txhash=") + @ApiOperation({ summary: "Retrieve internal transactions for a given transaction hash" }) @ApiQuery({ name: "txhash", description: "The transaction hash to filter internal transaction by", - example: "0x04a4757cd59681b037c1e7bd2402cc45a23c66ed7497614879376719d34e020a", + example: constants.addressTxWithInternalTransfers, required: false, }) @ApiQuery({ @@ -265,7 +297,7 @@ export class ApiController { description: "Internal transactions list", type: AccountInternalTransactionsResponseDto, }) - public async getAccountInternalTransactions( + public async getInternalTransactionsByTxHash( // eslint-disable-next-line @typescript-eslint/no-unused-vars @Query() pagingOptions: PagingOptionsWithMaxItemsLimitDto, // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -280,7 +312,7 @@ export class ApiController { @ApiQuery({ name: "address", description: "The address to get Ether balance for", - example: "0xFb7E0856e44Eff812A44A9f47733d7d55c39Aa28", + example: constants.address, required: true, }) @ApiOkResponse({ @@ -299,7 +331,7 @@ export class ApiController { explode: false, name: "address", description: "List of addresses to get Ether balance for", - example: ["0xFb7E0856e44Eff812A44A9f47733d7d55c39Aa28", "0x0E03197d697B592E5AE49EC14E952cddc9b28e14"], + example: [constants.address, constants.addressWithInternalTx], required: true, }) @ApiOkResponse({ @@ -316,13 +348,13 @@ export class ApiController { @ApiQuery({ name: "address", description: "The address to get Token balance for", - example: "0xFb7E0856e44Eff812A44A9f47733d7d55c39Aa28", + example: constants.address, required: true, }) @ApiQuery({ name: "contractaddress", description: "The Token contract address to get balance for", - example: "0x0faF6df7054946141266420b43783387A78d82A9", + example: constants.tokenAddress, required: true, }) @ApiOkResponse({ @@ -339,13 +371,13 @@ export class ApiController { @ApiQuery({ name: "address", description: "The address to get transfers for", - example: "0xFb7E0856e44Eff812A44A9f47733d7d55c39Aa28", + example: constants.address, required: false, }) @ApiQuery({ name: "contractaddress", description: "The Token contract address to get transfers for", - example: "0x0faF6df7054946141266420b43783387A78d82A9", + example: constants.tokenAddress, required: false, }) @ApiQuery({ @@ -382,13 +414,13 @@ export class ApiController { @ApiQuery({ name: "address", description: "The address to get transfers for", - example: "0xFb7E0856e44Eff812A44A9f47733d7d55c39Aa28", + example: constants.address, required: false, }) @ApiQuery({ name: "contractaddress", description: "The Token contract address to get transfers for", - example: "0x0faF6df7054946141266420b43783387A78d82A9", + example: constants.tokenAddress, required: false, }) @ApiQuery({ @@ -472,7 +504,7 @@ export class ApiController { name: "blockno", type: "integer", description: "The integer block number to estimate time remaining to be mined", - example: 12697906, + example: 20697906, required: true, }) @ApiOkResponse({ @@ -490,7 +522,7 @@ export class ApiController { name: "blockno", type: "integer", description: "The integer block number to check block rewards", - example: 12697906, + example: 1500, required: true, }) @ApiOkResponse({ @@ -507,21 +539,21 @@ export class ApiController { @ApiQuery({ name: "address", description: "The address to filter logs by", - example: "0xFb7E0856e44Eff812A44A9f47733d7d55c39Aa28", + example: constants.contractAddressWithLogs, required: true, }) @ApiQuery({ name: "fromBlock", type: "integer", description: "The integer block number to start searching for logs", - example: 12878196, + example: 0, required: false, }) @ApiQuery({ name: "toBlock", type: "integer", description: "The integer block number to stop searching for logs ", - example: 12879196, + example: 99999999, required: false, }) @ApiExtraModels(LogApiDto) diff --git a/packages/api/src/api/dtos/contract/verifyContractRequest.dto.ts b/packages/api/src/api/dtos/contract/verifyContractRequest.dto.ts index 22ec017f81..ab25d00703 100644 --- a/packages/api/src/api/dtos/contract/verifyContractRequest.dto.ts +++ b/packages/api/src/api/dtos/contract/verifyContractRequest.dto.ts @@ -41,7 +41,8 @@ export class VerifyContractRequestDto { }, sources: { "contracts/HelloWorld.sol": { - content: "// SPDX-License-Identifier: UNLICENSED ..", + content: + "// SPDX-License-Identifier: UNLICENSED\n// Specifies the version of Solidity, using semantic versioning.\n// Learn more: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma\npragma solidity >=0.7.3;\n\n// Defines a contract named `HelloWorld`.\n// A contract is a collection of functions and data (its state). Once deployed, a contract resides at a specific address on the Ethereum blockchain. Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html\ncontract HelloWorld {\n\n //Emitted when update function is called\n //Smart contract events are a way for your contract to communicate that something happened on the blockchain to your app front-end, which can be 'listening' for certain events and take action when they happen.\n event UpdatedMessages(string oldStr, string newStr);\n\n // Declares a state variable `message` of type `string`.\n // State variables are variables whose values are permanently stored in contract storage. The keyword `public` makes variables accessible from outside a contract and creates a function that other contracts or clients can call to access the value.\n string public message;\n\n // Similar to many class-based object-oriented languages, a constructor is a special function that is only executed upon contract creation.\n // Constructors are used to initialize the contract's data. Learn more:https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors\n constructor(string memory initMessage) {\n\n // Accepts a string argument `initMessage` and sets the value into the contract's `message` storage variable).\n message = initMessage;\n }\n\n // A public function that accepts a string argument and updates the `message` storage variable.\n function update(string memory newMessage) public {\n string memory oldMsg = message;\n message = newMessage;\n emit UpdatedMessages(oldMsg, newMessage);\n }\n}", }, }, }, @@ -125,7 +126,8 @@ export class VerifyContractRequestDto { @ApiProperty({ name: "constructorArguements", description: "Contract constructor arguments", - example: "0x94869207468657265210000000000000000000000000000000000000000000000", + example: + "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000094869207468657265210000000000000000000000000000000000000000000000", required: false, }) @IsOptional() diff --git a/packages/api/src/config/docs/constants.mainnet.json b/packages/api/src/config/docs/constants.mainnet.json new file mode 100644 index 0000000000..ce002c0988 --- /dev/null +++ b/packages/api/src/config/docs/constants.mainnet.json @@ -0,0 +1,10 @@ +{ + "verifiedContractAddress": "0x2da10A1e27bF85cEdD8FFb1AbBe97e53391C0295", + "verifiedContractAddress2": "0x1fa66e2B38d0cC496ec51F81c3e05E6A6708986F", + "contractAddressWithLogs": "0x1fa66e2B38d0cC496ec51F81c3e05E6A6708986F", + "txHash": "0xbca76582802d8172fe5bf6ba9c42d675a2ceab858dff555929f2c4021bf77386", + "address": "0xd3D526A8CCA22Fe072cD1852faA4F0a6F2C21765", + "addressWithInternalTx": "0x8B021d2BeD73675F7715422C4BFcBfE759890308", + "addressTxWithInternalTransfers": "0x4683986b4d6b07aa5a8ba6c4a6aa7b1a420a0dfd944ac7128f3e4ab7bd74567c", + "tokenAddress": "0x000000000000000000000000000000000000800A" +} \ No newline at end of file diff --git a/packages/api/src/config/docs/constants.testnet-goerli.json b/packages/api/src/config/docs/constants.testnet-goerli.json new file mode 100644 index 0000000000..1f3dd3fc30 --- /dev/null +++ b/packages/api/src/config/docs/constants.testnet-goerli.json @@ -0,0 +1,10 @@ +{ + "verifiedContractAddress": "0x53E185A2FA7c9caF14A887E8E9a4862D4bd094ea", + "verifiedContractAddress2": "0xbf2A1ACE3B12b81bab4985f05E850AcFCCb416E0", + "contractAddressWithLogs": "0xbf2A1ACE3B12b81bab4985f05E850AcFCCb416E0", + "txHash": "0x3d36c6e6a3625d698ef41d20c9457a6628254c8307df54b7c887e30f7dda00c8", + "address": "0xE4ce1da467a7Ca37727eb7e19857e5167DE25966", + "addressWithInternalTx": "0xbf2A1ACE3B12b81bab4985f05E850AcFCCb416E0", + "addressTxWithInternalTransfers": "0x8a453b8dd3e095b3034dc3692663d5bf0c9883cbe6e9f9a0425a3ebf9b9360ab", + "tokenAddress": "0x000000000000000000000000000000000000800A" +} \ No newline at end of file diff --git a/packages/api/src/config/docs/index.spec.ts b/packages/api/src/config/docs/index.spec.ts new file mode 100644 index 0000000000..fc32352367 --- /dev/null +++ b/packages/api/src/config/docs/index.spec.ts @@ -0,0 +1,68 @@ +const existsSyncMock = jest.fn().mockReturnValue(false); +const readFileSyncMock = jest.fn().mockReturnValue(null); + +jest.mock("node:fs", () => ({ + existsSync: existsSyncMock, + readFileSync: readFileSyncMock, +})); + +jest.mock("../../logger", () => ({ + getLogger: () => ({ error: jest.fn() }), +})); + +describe("constants", () => { + const env = process.env; + const defaultConstants = { + address: "0xFb7E0856e44Eff812A44A9f47733d7d55c39Aa28", + addressTxWithInternalTransfers: "0x04a4757cd59681b037c1e7bd2402cc45a23c66ed7497614879376719d34e020a", + addressWithInternalTx: "0x0E03197d697B592E5AE49EC14E952cddc9b28e14", + contractAddressWithLogs: "0x8A63F953e19aA4Ce3ED90621EeF61E17A95c6594", + tokenAddress: "0x0faF6df7054946141266420b43783387A78d82A9", + txHash: "0x04a4757cd59681b037c1e7bd2402cc45a23c66ed7497614879376719d34e020a", + verifiedContractAddress: "0x8A63F953e19aA4Ce3ED90621EeF61E17A95c6594", + verifiedContractAddress2: "0x0E03197d697B592E5AE49EC14E952cddc9b28e14", + }; + + beforeEach(() => { + jest.resetModules(); + }); + + afterAll(() => { + process.env = env; + }); + + it("should return default constants", async () => { + const { constants } = await import("./"); + expect(constants).toEqual(defaultConstants); + }); + + it("should return constants for the specified environment", async () => { + process.env.NETWORK_NAME = "testnet"; + const testnetConfig = { + verifiedContractAddress: "0x53E185A2FA7c9caF14A887E8E9a4862D4bd094ea", + verifiedContractAddress2: "0xbf2A1ACE3B12b81bab4985f05E850AcFCCb416E0", + contractAddressWithLogs: "0xbf2A1ACE3B12b81bab4985f05E850AcFCCb416E0", + txHash: "0x3d36c6e6a3625d698ef41d20c9457a6628254c8307df54b7c887e30f7dda00c8", + address: "0xE4ce1da467a7Ca37727eb7e19857e5167DE25966", + addressWithInternalTx: "0xbf2A1ACE3B12b81bab4985f05E850AcFCCb416E0", + addressTxWithInternalTransfers: "0x8a453b8dd3e095b3034dc3692663d5bf0c9883cbe6e9f9a0425a3ebf9b9360ab", + tokenAddress: "0x000000000000000000000000000000000000800A", + }; + + existsSyncMock.mockReturnValue(true); + readFileSyncMock.mockReturnValue(JSON.stringify(testnetConfig)); + const { constants } = await import("./"); + + expect(constants).toEqual(testnetConfig); + }); + + it("should return default constants if environment constants cannot be read", async () => { + process.env.NETWORK_NAME = "testnet"; + + existsSyncMock.mockReturnValue(true); + readFileSyncMock.mockReturnValue(new Error("Cannot read the file")); + const { constants } = await import("./"); + + expect(constants).toEqual(defaultConstants); + }); +}); diff --git a/packages/api/src/config/docs/index.ts b/packages/api/src/config/docs/index.ts new file mode 100644 index 0000000000..df2b7171da --- /dev/null +++ b/packages/api/src/config/docs/index.ts @@ -0,0 +1,37 @@ +import { config } from "dotenv"; +import * as path from "path"; +import * as fs from "node:fs"; +import { getLogger } from "../../logger"; + +config(); + +const { NETWORK_NAME } = process.env; + +const defaultDocsConstants = { + verifiedContractAddress: "0x8A63F953e19aA4Ce3ED90621EeF61E17A95c6594", + verifiedContractAddress2: "0x0E03197d697B592E5AE49EC14E952cddc9b28e14", + contractAddressWithLogs: "0x8A63F953e19aA4Ce3ED90621EeF61E17A95c6594", + txHash: "0x04a4757cd59681b037c1e7bd2402cc45a23c66ed7497614879376719d34e020a", + address: "0xFb7E0856e44Eff812A44A9f47733d7d55c39Aa28", + addressWithInternalTx: "0x0E03197d697B592E5AE49EC14E952cddc9b28e14", + addressTxWithInternalTransfers: "0x04a4757cd59681b037c1e7bd2402cc45a23c66ed7497614879376719d34e020a", + tokenAddress: "0x0faF6df7054946141266420b43783387A78d82A9", +}; +let networkDocsConstants = {}; + +if (NETWORK_NAME) { + try { + const networkConstantsFilePath = path.resolve(__dirname, `./constants.${NETWORK_NAME.toLowerCase()}.json`); + if (fs.existsSync(networkConstantsFilePath)) { + networkDocsConstants = JSON.parse(fs.readFileSync(networkConstantsFilePath, { encoding: "utf8" })); + } + } catch (error) { + const logger = getLogger(process.env.NODE_ENV, process.env.LOG_LEVEL); + logger.error(error.message, error.stack, "ConfigDocs"); + } +} + +export const constants = { + ...defaultDocsConstants, + ...networkDocsConstants, +}; diff --git a/packages/api/src/token/token.controller.ts b/packages/api/src/token/token.controller.ts index 6a17e72657..501db99a84 100644 --- a/packages/api/src/token/token.controller.ts +++ b/packages/api/src/token/token.controller.ts @@ -16,6 +16,7 @@ import { TokenDto } from "./token.dto"; import { TransferDto } from "../transfer/transfer.dto"; import { ParseAddressPipe, ADDRESS_REGEX_PATTERN } from "../common/pipes/parseAddress.pipe"; import { swagger } from "../config/featureFlags"; +import { constants } from "../config/docs"; const entityName = "tokens"; @@ -39,7 +40,7 @@ export class TokenController { @ApiParam({ name: "address", schema: { pattern: ADDRESS_REGEX_PATTERN }, - example: "0xd754ff5e8a6f257e162f72578a4bb0493c0681d8", + example: constants.tokenAddress, description: "Valid hex address", }) @ApiOkResponse({ description: "Token was returned successfully", type: TokenDto }) @@ -57,7 +58,7 @@ export class TokenController { @ApiParam({ name: "address", schema: { pattern: ADDRESS_REGEX_PATTERN }, - example: "0xd754ff5e8a6f257e162f72578a4bb0493c0681d8", + example: constants.tokenAddress, description: "Valid hex address", }) @ApiListPageOkResponse(TransferDto, { description: "Successfully returned token transfers list" }) diff --git a/packages/api/src/transaction/transaction.controller.ts b/packages/api/src/transaction/transaction.controller.ts index de59347454..087e73eecd 100644 --- a/packages/api/src/transaction/transaction.controller.ts +++ b/packages/api/src/transaction/transaction.controller.ts @@ -20,6 +20,7 @@ import { LogService } from "../log/log.service"; import { TransactionService } from "./transaction.service"; import { ParseTransactionHashPipe, TX_HASH_REGEX_PATTERN } from "../common/pipes/parseTransactionHash.pipe"; import { swagger } from "../config/featureFlags"; +import { constants } from "../config/docs"; const entityName = "transactions"; @@ -63,7 +64,7 @@ export class TransactionController { @ApiParam({ name: "transactionHash", schema: { pattern: TX_HASH_REGEX_PATTERN }, - example: "0xd99bd0a1ed5de1c258637e40f3e4e1f461375f5ca4712339031a8dade8079e88", + example: constants.txHash, description: "Valid transaction hash", }) @ApiOkResponse({ description: "Transaction was returned successfully", type: TransactionDto }) @@ -83,7 +84,7 @@ export class TransactionController { @ApiParam({ name: "transactionHash", schema: { pattern: TX_HASH_REGEX_PATTERN }, - example: "0xd99bd0a1ed5de1c258637e40f3e4e1f461375f5ca4712339031a8dade8079e88", + example: constants.txHash, description: "Valid transaction hash", }) @ApiListPageOkResponse(TransferDto, { description: "Successfully returned transaction transfers list" }) @@ -112,7 +113,7 @@ export class TransactionController { @ApiParam({ name: "transactionHash", schema: { pattern: TX_HASH_REGEX_PATTERN }, - example: "0xd99bd0a1ed5de1c258637e40f3e4e1f461375f5ca4712339031a8dade8079e88", + example: constants.txHash, description: "Valid transaction hash", }) @ApiListPageOkResponse(LogDto, { description: "Successfully returned transaction logs list" })