diff --git a/docker-compose-cli.yaml b/docker-compose-cli.yaml index d45e7307cb..1343eb8fa2 100644 --- a/docker-compose-cli.yaml +++ b/docker-compose-cli.yaml @@ -5,8 +5,6 @@ services: build: context: . dockerfile: ./packages/app/Dockerfile - environment: - - VITE_APP_ENVIRONMENT=local ports: - '3010:3010' depends_on: @@ -27,10 +25,6 @@ services: - DATABASE_NAME=block-explorer - BLOCKCHAIN_RPC_URL=http://host.docker.internal:3050 - BATCHES_PROCESSING_POLLING_INTERVAL=1000 - ports: - - '3001:3001' - - '9229:9229' - - '9230:9230' restart: unless-stopped api: @@ -45,9 +39,6 @@ services: - DATABASE_URL=postgres://postgres:postgres@postgres:5432/block-explorer ports: - '3020:3020' - - '3005:3005' - - '9231:9229' - - '9232:9230' depends_on: - worker restart: unless-stopped @@ -58,8 +49,6 @@ services: driver: none volumes: - postgres:/var/lib/postgresql/data - ports: - - "5432:5432" healthcheck: test: [ "CMD-SHELL", "pg_isready -U postgres" ] interval: 5s diff --git a/packages/api/src/balance/balance.entity.ts b/packages/api/src/balance/balance.entity.ts index a06ff27866..1d0dc898e5 100644 --- a/packages/api/src/balance/balance.entity.ts +++ b/packages/api/src/balance/balance.entity.ts @@ -1,6 +1,6 @@ -import { Entity, Column, PrimaryColumn, Index, ManyToOne, JoinColumn } from "typeorm"; +import { Entity, Column, PrimaryColumn, Index, ManyToOne, JoinColumn, AfterLoad } from "typeorm"; import { BaseEntity } from "../common/entities/base.entity"; -import { Token } from "../token/token.entity"; +import { Token, ETH_TOKEN } from "../token/token.entity"; import { normalizeAddressTransformer } from "../common/transformers/normalizeAddress.transformer"; import { bigIntNumberTransformer } from "../common/transformers/bigIntNumber.transformer"; @@ -9,9 +9,9 @@ export class Balance extends BaseEntity { @PrimaryColumn({ type: "bytea", transformer: normalizeAddressTransformer }) public readonly address: string; - @ManyToOne(() => Token) + @ManyToOne(() => Token, { createForeignKeyConstraints: false }) @JoinColumn({ name: "tokenAddress" }) - public readonly token?: Token; + public token?: Token; @PrimaryColumn({ type: "bytea", transformer: normalizeAddressTransformer }) public readonly tokenAddress: string; @@ -22,4 +22,11 @@ export class Balance extends BaseEntity { @Column({ type: "varchar", length: 128 }) public readonly balance: string; + + @AfterLoad() + populateEthToken() { + if (this.tokenAddress === ETH_TOKEN.l2Address && !this.token) { + this.token = ETH_TOKEN; + } + } } diff --git a/packages/api/src/token/token.entity.ts b/packages/api/src/token/token.entity.ts index 5c4b55f6e5..d2280eb047 100644 --- a/packages/api/src/token/token.entity.ts +++ b/packages/api/src/token/token.entity.ts @@ -8,6 +8,14 @@ export enum TokenType { ERC721 = "ERC721", } +export const ETH_TOKEN: Token = { + l2Address: "0x000000000000000000000000000000000000800A", + l1Address: null, + symbol: "ETH", + name: "Ether", + decimals: 18, +} as Token; + @Entity({ name: "tokens" }) @Index(["blockNumber", "logIndex"]) export class Token extends BaseEntity { diff --git a/packages/api/src/token/token.service.spec.ts b/packages/api/src/token/token.service.spec.ts index 5cdccb82dd..d756d85f8d 100644 --- a/packages/api/src/token/token.service.spec.ts +++ b/packages/api/src/token/token.service.spec.ts @@ -54,6 +54,28 @@ describe("TokenService", () => { const result = await service.findOne(tokenAddress); expect(result).toBe(token); }); + + describe("when requested token does not exist", () => { + beforeEach(() => { + (repositoryMock.findOneBy as jest.Mock).mockResolvedValue(null); + }); + + it("returns ETH token for ETH address", async () => { + const result = await service.findOne("0x000000000000000000000000000000000000800a"); + expect(result).toEqual({ + decimals: 18, + l1Address: null, + l2Address: "0x000000000000000000000000000000000000800A", + name: "Ether", + symbol: "ETH", + }); + }); + + it("returns null for non ETH address", async () => { + const result = await service.findOne("0x000000000000000000000000000000000000800b"); + expect(result).toBeNull(); + }); + }); }); describe("exists", () => { @@ -82,10 +104,20 @@ describe("TokenService", () => { expect(result).toBe(true); }); - it("returns false if there is no token with the specified address", async () => { - (repositoryMock.findOne as jest.Mock).mockResolvedValue(null); - const result = await service.exists(tokenAddress); - expect(result).toBe(false); + describe("when requested token does not exist", () => { + beforeEach(() => { + (repositoryMock.findOne as jest.Mock).mockResolvedValue(null); + }); + + it("returns true for ETH address", async () => { + const result = await service.exists("0x000000000000000000000000000000000000800a"); + expect(result).toBe(true); + }); + + it("returns false for non ETH address", async () => { + const result = await service.exists(tokenAddress); + expect(result).toBe(false); + }); }); }); diff --git a/packages/api/src/token/token.service.ts b/packages/api/src/token/token.service.ts index 09695b66e3..b4d2d89288 100644 --- a/packages/api/src/token/token.service.ts +++ b/packages/api/src/token/token.service.ts @@ -3,7 +3,7 @@ import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; import { Pagination, IPaginationOptions } from "nestjs-typeorm-paginate"; import { paginate } from "../common/utils"; -import { Token } from "./token.entity"; +import { Token, ETH_TOKEN } from "./token.entity"; @Injectable() export class TokenService { @@ -13,11 +13,20 @@ export class TokenService { ) {} public async findOne(address: string): Promise { - return await this.tokenRepository.findOneBy({ l2Address: address }); + const token = await this.tokenRepository.findOneBy({ l2Address: address }); + if (!token && address === ETH_TOKEN.l2Address.toLowerCase()) { + return ETH_TOKEN; + } + return token; } public async exists(address: string): Promise { - return (await this.tokenRepository.findOne({ where: { l2Address: address }, select: { l2Address: true } })) != null; + const tokenExists = + (await this.tokenRepository.findOne({ where: { l2Address: address }, select: { l2Address: true } })) != null; + if (!tokenExists && address === ETH_TOKEN.l2Address.toLowerCase()) { + return true; + } + return tokenExists; } public async findAll(paginationOptions: IPaginationOptions): Promise> { diff --git a/packages/api/src/transfer/transfer.entity.ts b/packages/api/src/transfer/transfer.entity.ts index 213fdff959..c4ec6c6727 100644 --- a/packages/api/src/transfer/transfer.entity.ts +++ b/packages/api/src/transfer/transfer.entity.ts @@ -1,6 +1,6 @@ -import { Entity, Column, Index, ManyToOne, JoinColumn, PrimaryColumn } from "typeorm"; +import { Entity, Column, Index, ManyToOne, JoinColumn, PrimaryColumn, AfterLoad } from "typeorm"; import { BaseEntity } from "../common/entities/base.entity"; -import { Token, TokenType } from "../token/token.entity"; +import { Token, TokenType, ETH_TOKEN } from "../token/token.entity"; import { normalizeAddressTransformer } from "../common/transformers/normalizeAddress.transformer"; import { bigIntNumberTransformer } from "../common/transformers/bigIntNumber.transformer"; import { hexTransformer } from "../common/transformers/hex.transformer"; @@ -53,9 +53,9 @@ export class Transfer extends BaseEntity { @Column({ type: "varchar", length: 128, nullable: true }) public readonly amount?: string; - @ManyToOne(() => Token) + @ManyToOne(() => Token, { createForeignKeyConstraints: false }) @JoinColumn({ name: "tokenAddress" }) - public readonly token?: Token; + public token?: Token; @Index() @Column({ type: "bytea", transformer: normalizeAddressTransformer }) @@ -84,4 +84,11 @@ export class Transfer extends BaseEntity { const { number, ...restFields } = this; return restFields; } + + @AfterLoad() + populateEthToken() { + if (!this.token && this.tokenAddress === ETH_TOKEN.l2Address) { + this.token = ETH_TOKEN; + } + } } diff --git a/packages/api/test/account-api.e2e-spec.ts b/packages/api/test/account-api.e2e-spec.ts index a395a00023..16f260e8d4 100644 --- a/packages/api/test/account-api.e2e-spec.ts +++ b/packages/api/test/account-api.e2e-spec.ts @@ -8,8 +8,10 @@ import { BlockDetail } from "../src/block/blockDetail.entity"; import { AddressTransaction } from "../src/transaction/entities/addressTransaction.entity"; import { Transaction } from "../src/transaction/entities/transaction.entity"; import { TransactionReceipt } from "../src/transaction/entities/transactionReceipt.entity"; -import { Token } from "../src/token/token.entity"; +import { Token, TokenType } from "../src/token/token.entity"; import { Balance } from "../src/balance/balance.entity"; +import { AddressTransfer } from "../src/transfer/addressTransfer.entity"; +import { Transfer, TransferType } from "../src/transfer/transfer.entity"; import { L2_ETH_TOKEN_ADDRESS } from "../src/common/constants"; import { AppModule } from "../src/app.module"; import { configureApp } from "../src/configureApp"; @@ -18,6 +20,8 @@ describe("Account API (e2e)", () => { let app: INestApplication; let addressTransactionRepository: Repository; let transactionRepository: Repository; + let addressTransferRepository: Repository; + let transferRepository: Repository; let transactionReceiptRepository: Repository; let blockRepository: Repository; let batchRepository: Repository; @@ -35,6 +39,8 @@ describe("Account API (e2e)", () => { addressTransactionRepository = app.get>(getRepositoryToken(AddressTransaction)); transactionRepository = app.get>(getRepositoryToken(Transaction)); + addressTransferRepository = app.get>(getRepositoryToken(AddressTransfer)); + transferRepository = app.get>(getRepositoryToken(Transfer)); transactionReceiptRepository = app.get>(getRepositoryToken(TransactionReceipt)); blockRepository = app.get>(getRepositoryToken(BlockDetail)); batchRepository = app.get>(getRepositoryToken(BatchDetails)); @@ -53,19 +59,21 @@ describe("Account API (e2e)", () => { executeTxHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e23", }); - await blockRepository.insert({ - number: 1, - hash: "0x4f86d6647711915ac90e5ef69c29845946f0a55b3feaa0488aece4a359f79cb1", - timestamp: new Date("2022-11-10T14:44:08.000Z"), - gasLimit: "0", - gasUsed: "0", - baseFeePerGas: "100000000", - extraData: "0x", - l1TxCount: 1, - l2TxCount: 1, - l1BatchNumber: 0, - miner: "0x0000000000000000000000000000000000000000", - }); + for (let i = 1; i <= 2; i++) { + await blockRepository.insert({ + number: i, + hash: `0x4f86d6647711915ac90e5ef69c29845946f0a55b3feaa0488aece4a359f79cb${i}`, + timestamp: new Date("2022-11-10T14:44:08.000Z"), + gasLimit: "0", + gasUsed: "0", + baseFeePerGas: "100000000", + extraData: "0x", + l1TxCount: 1, + l2TxCount: 1, + l1BatchNumber: 0, + miner: "0x0000000000000000000000000000000000000000", + }); + } await transactionRepository.insert({ to: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", @@ -104,16 +112,6 @@ describe("Account API (e2e)", () => { transactionIndex: 1, }); - await tokenRepository.insert({ - l1Address: L2_ETH_TOKEN_ADDRESS, - l2Address: L2_ETH_TOKEN_ADDRESS, - symbol: "ETH", - name: "ETH", - decimals: 18, - blockNumber: 1, - logIndex: 1, - }); - await tokenRepository.insert({ l1Address: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe111", l2Address: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe112", @@ -124,6 +122,51 @@ describe("Account API (e2e)", () => { logIndex: 1, }); + const tokens = [ + { + tokenType: TokenType.ETH, + tokenAddress: "0x000000000000000000000000000000000000800a", + }, + { + tokenType: TokenType.ERC20, + tokenAddress: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe112", + }, + ]; + + for (let i = 0; i < 6; i++) { + const transferSpec = { + from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", + to: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35E", + blockNumber: i < 3 ? 1 : 2, + transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e20", + transactionIndex: i, + timestamp: new Date("2022-11-21T18:16:51.000Z"), + type: TransferType.Deposit, + tokenType: tokens[i % 2].tokenType, + tokenAddress: tokens[i % 2].tokenAddress, + logIndex: i, + isFeeOrRefund: false, + isInternal: false, + amount: (100 + i).toString(), + }; + + const insertResult = await transferRepository.insert(transferSpec); + + for (const address of new Set([transferSpec.from, transferSpec.to])) { + await addressTransferRepository.insert({ + transferNumber: Number(insertResult.identifiers[0].number), + address, + tokenAddress: transferSpec.tokenAddress, + blockNumber: transferSpec.blockNumber, + timestamp: transferSpec.timestamp, + tokenType: transferSpec.tokenType, + isFeeOrRefund: transferSpec.isFeeOrRefund, + logIndex: transferSpec.logIndex, + isInternal: transferSpec.isInternal, + }); + } + } + await balanceRepository.insert({ address: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", tokenAddress: L2_ETH_TOKEN_ADDRESS, @@ -147,9 +190,12 @@ describe("Account API (e2e)", () => { }); afterAll(async () => { + await addressTransferRepository.delete({}); + await transferRepository.delete({}); await addressTransactionRepository.delete({}); await transactionReceiptRepository.delete({}); await transactionRepository.delete({}); + await transactionRepository.delete({}); await balanceRepository.delete({}); await tokenRepository.delete({}); await blockRepository.delete({}); @@ -184,7 +230,7 @@ describe("Account API (e2e)", () => { blockHash: "0x4f86d6647711915ac90e5ef69c29845946f0a55b3feaa0488aece4a359f79cb1", blockNumber: "1", commitTxHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e21", - confirmations: "0", + confirmations: "1", contractAddress: "0xc7E0220D02D549C4846a6eC31d89c3b670ebE35e", cumulativeGasUsed: "1100000", executeTxHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e23", @@ -214,6 +260,286 @@ describe("Account API (e2e)", () => { }); }); + describe("/api?module=account&action=tokentx GET", () => { + it("returns HTTP 200 and no transactions found response when no account transfers found", () => { + return request(app.getHttpServer()) + .get(`/api?module=account&action=tokentx&address=0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35b`) + .expect(200) + .expect((res) => + expect(res.body).toStrictEqual({ + status: "0", + message: "No transactions found", + result: [], + }) + ); + }); + + it("returns HTTP 200 and token transfers for the specified address", () => { + return request(app.getHttpServer()) + .get(`/api?module=account&action=tokentx&address=0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C`) + .expect(200) + .expect((res) => + expect(res.body).toStrictEqual({ + message: "OK", + result: [ + { + blockHash: "0x4f86d6647711915ac90e5ef69c29845946f0a55b3feaa0488aece4a359f79cb1", + blockNumber: "2", + confirmations: "0", + contractAddress: "0xC7e0220D02d549c4846A6ec31d89c3B670ebe112", + cumulativeGasUsed: "1100000", + fee: "10000000000000000", + from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", + gas: "1000000", + gasPrice: "100", + gasUsed: "900000", + hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e20", + input: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + l1BatchNumber: "0", + nonce: "42", + timeStamp: "1669054611", + to: "0xc7E0220D02D549C4846a6eC31d89c3b670ebE35e", + tokenDecimal: "18", + tokenName: "TKN", + tokenSymbol: "TKN", + transactionIndex: "1", + value: "105", + }, + { + blockHash: "0x4f86d6647711915ac90e5ef69c29845946f0a55b3feaa0488aece4a359f79cb1", + blockNumber: "2", + confirmations: "0", + contractAddress: "0xC7e0220D02d549c4846A6ec31d89c3B670ebe112", + cumulativeGasUsed: "1100000", + fee: "10000000000000000", + from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", + gas: "1000000", + gasPrice: "100", + gasUsed: "900000", + hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e20", + input: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + l1BatchNumber: "0", + nonce: "42", + timeStamp: "1669054611", + to: "0xc7E0220D02D549C4846a6eC31d89c3b670ebE35e", + tokenDecimal: "18", + tokenName: "TKN", + tokenSymbol: "TKN", + transactionIndex: "1", + value: "103", + }, + { + blockHash: "0x4f86d6647711915ac90e5ef69c29845946f0a55b3feaa0488aece4a359f79cb1", + blockNumber: "1", + confirmations: "1", + contractAddress: "0xC7e0220D02d549c4846A6ec31d89c3B670ebe112", + cumulativeGasUsed: "1100000", + fee: "10000000000000000", + from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", + gas: "1000000", + gasPrice: "100", + gasUsed: "900000", + hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e20", + input: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + l1BatchNumber: "0", + nonce: "42", + timeStamp: "1669054611", + to: "0xc7E0220D02D549C4846a6eC31d89c3b670ebE35e", + tokenDecimal: "18", + tokenName: "TKN", + tokenSymbol: "TKN", + transactionIndex: "1", + value: "101", + }, + ], + status: "1", + }) + ); + }); + + it("returns HTTP 200 and ETH transfers for the specified address and contract address", () => { + return request(app.getHttpServer()) + .get( + `/api?module=account&action=tokentx&address=0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C&contractaddress=0x000000000000000000000000000000000000800a` + ) + .expect(200) + .expect((res) => + expect(res.body).toStrictEqual({ + message: "OK", + result: [ + { + blockHash: "0x4f86d6647711915ac90e5ef69c29845946f0a55b3feaa0488aece4a359f79cb1", + blockNumber: "2", + confirmations: "0", + contractAddress: "0x000000000000000000000000000000000000800A", + cumulativeGasUsed: "1100000", + fee: "10000000000000000", + from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", + gas: "1000000", + gasPrice: "100", + gasUsed: "900000", + hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e20", + input: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + l1BatchNumber: "0", + nonce: "42", + timeStamp: "1669054611", + to: "0xc7E0220D02D549C4846a6eC31d89c3b670ebE35e", + tokenDecimal: "18", + tokenName: "Ether", + tokenSymbol: "ETH", + transactionIndex: "1", + value: "104", + }, + { + blockHash: "0x4f86d6647711915ac90e5ef69c29845946f0a55b3feaa0488aece4a359f79cb1", + blockNumber: "1", + confirmations: "1", + contractAddress: "0x000000000000000000000000000000000000800A", + cumulativeGasUsed: "1100000", + fee: "10000000000000000", + from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", + gas: "1000000", + gasPrice: "100", + gasUsed: "900000", + hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e20", + input: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + l1BatchNumber: "0", + nonce: "42", + timeStamp: "1669054611", + to: "0xc7E0220D02D549C4846a6eC31d89c3b670ebE35e", + tokenDecimal: "18", + tokenName: "Ether", + tokenSymbol: "ETH", + transactionIndex: "1", + value: "102", + }, + { + blockHash: "0x4f86d6647711915ac90e5ef69c29845946f0a55b3feaa0488aece4a359f79cb1", + blockNumber: "1", + confirmations: "1", + contractAddress: "0x000000000000000000000000000000000000800A", + cumulativeGasUsed: "1100000", + fee: "10000000000000000", + from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", + gas: "1000000", + gasPrice: "100", + gasUsed: "900000", + hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e20", + input: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + l1BatchNumber: "0", + nonce: "42", + timeStamp: "1669054611", + to: "0xc7E0220D02D549C4846a6eC31d89c3b670ebE35e", + tokenDecimal: "18", + tokenName: "Ether", + tokenSymbol: "ETH", + transactionIndex: "1", + value: "100", + }, + ], + status: "1", + }) + ); + }); + + it("returns HTTP 200 and token transfers for the specified address and startblock param", () => { + return request(app.getHttpServer()) + .get(`/api?module=account&action=tokentx&address=0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C&startblock=2`) + .expect(200) + .expect((res) => + expect(res.body).toStrictEqual({ + message: "OK", + result: [ + { + blockHash: "0x4f86d6647711915ac90e5ef69c29845946f0a55b3feaa0488aece4a359f79cb1", + blockNumber: "2", + confirmations: "0", + contractAddress: "0xC7e0220D02d549c4846A6ec31d89c3B670ebe112", + cumulativeGasUsed: "1100000", + fee: "10000000000000000", + from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", + gas: "1000000", + gasPrice: "100", + gasUsed: "900000", + hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e20", + input: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + l1BatchNumber: "0", + nonce: "42", + timeStamp: "1669054611", + to: "0xc7E0220D02D549C4846a6eC31d89c3b670ebE35e", + tokenDecimal: "18", + tokenName: "TKN", + tokenSymbol: "TKN", + transactionIndex: "1", + value: "105", + }, + { + blockHash: "0x4f86d6647711915ac90e5ef69c29845946f0a55b3feaa0488aece4a359f79cb1", + blockNumber: "2", + confirmations: "0", + contractAddress: "0xC7e0220D02d549c4846A6ec31d89c3B670ebe112", + cumulativeGasUsed: "1100000", + fee: "10000000000000000", + from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", + gas: "1000000", + gasPrice: "100", + gasUsed: "900000", + hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e20", + input: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + l1BatchNumber: "0", + nonce: "42", + timeStamp: "1669054611", + to: "0xc7E0220D02D549C4846a6eC31d89c3b670ebE35e", + tokenDecimal: "18", + tokenName: "TKN", + tokenSymbol: "TKN", + transactionIndex: "1", + value: "103", + }, + ], + status: "1", + }) + ); + }); + + it("returns HTTP 200 and token transfers for the specified address and endblock param", () => { + return request(app.getHttpServer()) + .get(`/api?module=account&action=tokentx&address=0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C&endblock=1`) + .expect(200) + .expect((res) => + expect(res.body).toStrictEqual({ + message: "OK", + result: [ + { + blockHash: "0x4f86d6647711915ac90e5ef69c29845946f0a55b3feaa0488aece4a359f79cb1", + blockNumber: "1", + confirmations: "1", + contractAddress: "0xC7e0220D02d549c4846A6ec31d89c3B670ebe112", + cumulativeGasUsed: "1100000", + fee: "10000000000000000", + from: "0xc7e0220d02d549c4846A6EC31D89C3B670Ebe35C", + gas: "1000000", + gasPrice: "100", + gasUsed: "900000", + hash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e20", + input: "0x000000000000000000000000000000000000000000000000016345785d8a0000", + l1BatchNumber: "0", + nonce: "42", + timeStamp: "1669054611", + to: "0xc7E0220D02D549C4846a6eC31d89c3b670ebE35e", + tokenDecimal: "18", + tokenName: "TKN", + tokenSymbol: "TKN", + transactionIndex: "1", + value: "101", + }, + ], + status: "1", + }) + ); + }); + }); + describe("/api?module=account&action=balance GET", () => { it("returns HTTP 200 and 0 balance when account has no Ether balance", () => { return request(app.getHttpServer()) @@ -315,9 +641,14 @@ describe("Account API (e2e)", () => { message: "OK", result: [ { - blockNumber: "1", + blockNumber: "2", + blockReward: "0", timeStamp: "1668091448", + }, + { + blockNumber: "1", blockReward: "0", + timeStamp: "1668091448", }, ], }) diff --git a/packages/api/test/address.e2e-spec.ts b/packages/api/test/address.e2e-spec.ts index 02337af4ab..01c15fd0c8 100644 --- a/packages/api/test/address.e2e-spec.ts +++ b/packages/api/test/address.e2e-spec.ts @@ -184,6 +184,13 @@ describe("AddressController (e2e)", () => { blockNumber: i + 3, balance: (345 * i).toString(), }); + + await balanceRepository.insert({ + address: "0x91d0a23f34e535e44df8ba84c53a0945cf0eeb67", + tokenAddress: "0x000000000000000000000000000000000000800A", + blockNumber: i + 3, + balance: (345 * i).toString(), + }); } // balances without address record @@ -208,6 +215,13 @@ describe("AddressController (e2e)", () => { blockNumber: i + 3, balance: (345 * i).toString(), }); + + await balanceRepository.insert({ + address: "0x91d0a23f34e535e44df8ba84c53a0945cf0eeb71", + tokenAddress: "0x000000000000000000000000000000000000800A", + blockNumber: i + 3, + balance: (345 * i).toString(), + }); } // contract address @@ -278,16 +292,6 @@ describe("AddressController (e2e)", () => { logIndex: 0, }); - await tokenRepository.insert({ - l2Address: "0x000000000000000000000000000000000000800a", - l1Address: "0x0000000000000000000000000000000000000000", - symbol: "ETH", - name: "Ether", - decimals: 18, - blockNumber: 1, - logIndex: 0, - }); - for (let i = 0; i < 30; i++) { let type = TransferType.Transfer; if (i % 6 === 1) { @@ -378,6 +382,16 @@ describe("AddressController (e2e)", () => { expect(res.body).toStrictEqual({ address: "0x91D0a23f34E535E44dF8ba84c53A0945CF0EEb67", balances: { + "0x000000000000000000000000000000000000800A": { + balance: "34500", + token: { + decimals: 18, + l1Address: null, + l2Address: "0x000000000000000000000000000000000000800A", + name: "Ether", + symbol: "ETH", + }, + }, "0x9488FC54FcCc6f319D4863Ddc2c2899Ed35d8956": { balance: "34500", token: { @@ -425,6 +439,16 @@ describe("AddressController (e2e)", () => { expect(res.body).toStrictEqual({ address: "0x91D0a23f34E535E44dF8ba84c53A0945CF0EEb67", balances: { + "0x000000000000000000000000000000000000800A": { + balance: "34500", + token: { + decimals: 18, + l1Address: null, + l2Address: "0x000000000000000000000000000000000000800A", + name: "Ether", + symbol: "ETH", + }, + }, "0x9488FC54FcCc6f319D4863Ddc2c2899Ed35d8956": { balance: "34500", token: { @@ -472,6 +496,16 @@ describe("AddressController (e2e)", () => { expect(res.body).toStrictEqual({ address: "0x91D0a23f34E535E44dF8ba84c53A0945CF0EEb67", balances: { + "0x000000000000000000000000000000000000800A": { + balance: "34500", + token: { + decimals: 18, + l1Address: null, + l2Address: "0x000000000000000000000000000000000000800A", + name: "Ether", + symbol: "ETH", + }, + }, "0x9488FC54FcCc6f319D4863Ddc2c2899Ed35d8956": { balance: "34500", token: { @@ -519,6 +553,16 @@ describe("AddressController (e2e)", () => { expect(res.body).toStrictEqual({ address: "0x91D0a23f34E535E44dF8ba84c53A0945CF0EEb67", balances: { + "0x000000000000000000000000000000000000800A": { + balance: "34500", + token: { + decimals: 18, + l1Address: null, + l2Address: "0x000000000000000000000000000000000000800A", + name: "Ether", + symbol: "ETH", + }, + }, "0x9488FC54FcCc6f319D4863Ddc2c2899Ed35d8956": { balance: "34500", token: { @@ -568,6 +612,16 @@ describe("AddressController (e2e)", () => { expect(res.body).toStrictEqual({ address: "0x91d0A23F34e535E44dF8ba84c53a0945cf0EEB71", balances: { + "0x000000000000000000000000000000000000800A": { + balance: "34500", + token: { + decimals: 18, + l1Address: null, + l2Address: "0x000000000000000000000000000000000000800A", + name: "Ether", + symbol: "ETH", + }, + }, "0x9488FC54FcCc6f319D4863Ddc2c2899Ed35d8956": { balance: "34500", token: { @@ -1043,7 +1097,7 @@ describe("AddressController (e2e)", () => { to: "0x91d0a23f34e535e44Df8Ba84c53a0945cf0eEB60", token: { l2Address: "0x000000000000000000000000000000000000800A", - l1Address: "0x0000000000000000000000000000000000000000", + l1Address: null, symbol: "ETH", name: "Ether", decimals: 18, diff --git a/packages/api/test/token.e2e-spec.ts b/packages/api/test/token.e2e-spec.ts index 4db8d41e10..9c4c885ef8 100644 --- a/packages/api/test/token.e2e-spec.ts +++ b/packages/api/test/token.e2e-spec.ts @@ -212,6 +212,22 @@ describe("TokenController (e2e)", () => { isFeeOrRefund: true, isInternal: false, }); + + await transferRepository.insert({ + from: "0x0000000000000000000000000000000000008001", + to: "0x52312ad6f01657413b2eae9287f6b9adad93d5fe", + blockNumber: 1, + transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10", + tokenAddress: "0x000000000000000000000000000000000000800A", + amount: "1000", + type: TransferType.Refund, + tokenType: TokenType.ETH, + logIndex: transferIndex++, + transactionIndex: 0, + timestamp: "2022-11-21T18:16:51.000Z", + isFeeOrRefund: false, + isInternal: false, + }); } }); @@ -271,6 +287,21 @@ describe("TokenController (e2e)", () => { ); }); + it("returns HTTP 200 and ETH token even if it does not exist in DB", () => { + return request(app.getHttpServer()) + .get("/tokens/0x000000000000000000000000000000000000800a") + .expect(200) + .expect((res) => + expect(res.body).toStrictEqual({ + l2Address: "0x000000000000000000000000000000000000800A", + l1Address: null, + symbol: "ETH", + name: "Ether", + decimals: 18, + }) + ); + }); + it("returns HTTP 400 for not valid token address", () => { return request(app.getHttpServer()).get("/tokens/invalidAddressParam").expect(400); }); @@ -557,6 +588,91 @@ describe("TokenController (e2e)", () => { ); }); + it("returns HTTP 200 and transfers for ETH token even if it is not in DB", () => { + return request(app.getHttpServer()) + .get("/tokens/0x000000000000000000000000000000000000800a/transfers?page=1&limit=7") + .expect(200) + .expect((res) => + expect(res.body).toStrictEqual({ + items: [ + { + amount: "1000", + blockNumber: 1, + fields: null, + from: "0x0000000000000000000000000000000000008001", + isInternal: false, + timestamp: "2022-11-21T18:16:51.000Z", + to: "0x52312AD6f01657413b2eaE9287f6B9ADaD93D5FE", + token: { + decimals: 18, + l1Address: null, + l2Address: "0x000000000000000000000000000000000000800A", + name: "Ether", + symbol: "ETH", + }, + tokenAddress: "0x000000000000000000000000000000000000800A", + tokenType: "ETH", + transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10", + type: "refund", + }, + { + amount: "1000", + blockNumber: 1, + fields: null, + from: "0x0000000000000000000000000000000000008001", + isInternal: false, + timestamp: "2022-11-21T18:16:51.000Z", + to: "0x52312AD6f01657413b2eaE9287f6B9ADaD93D5FE", + token: { + decimals: 18, + l1Address: null, + l2Address: "0x000000000000000000000000000000000000800A", + name: "Ether", + symbol: "ETH", + }, + tokenAddress: "0x000000000000000000000000000000000000800A", + tokenType: "ETH", + transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10", + type: "refund", + }, + { + amount: "1000", + blockNumber: 1, + fields: null, + from: "0x0000000000000000000000000000000000008001", + isInternal: false, + timestamp: "2022-11-21T18:16:51.000Z", + to: "0x52312AD6f01657413b2eaE9287f6B9ADaD93D5FE", + token: { + decimals: 18, + l1Address: null, + l2Address: "0x000000000000000000000000000000000000800A", + name: "Ether", + symbol: "ETH", + }, + tokenAddress: "0x000000000000000000000000000000000000800A", + tokenType: "ETH", + transactionHash: "0x8a008b8dbbc18035e56370abb820e736b705d68d6ac12b203603db8d9ea87e10", + type: "refund", + }, + ], + links: { + first: "tokens/0x000000000000000000000000000000000000800a/transfers?limit=7", + last: "tokens/0x000000000000000000000000000000000000800a/transfers?page=1&limit=7", + next: "", + previous: "", + }, + meta: { + currentPage: 1, + itemCount: 3, + itemsPerPage: 7, + totalItems: 3, + totalPages: 1, + }, + }) + ); + }); + it("returns HTTP 400 for not valid token address", () => { return request(app.getHttpServer()).get("/tokens/invalidAddressParam/transfers").expect(400); }); diff --git a/packages/api/test/transaction.e2e-spec.ts b/packages/api/test/transaction.e2e-spec.ts index 61d66f96ac..d770c9dec1 100644 --- a/packages/api/test/transaction.e2e-spec.ts +++ b/packages/api/test/transaction.e2e-spec.ts @@ -165,16 +165,6 @@ describe("TransactionController (e2e)", () => { logIndex: 0, }); - await tokenRepository.insert({ - l2Address: "0x000000000000000000000000000000000000800a", - l1Address: "0x0000000000000000000000000000000000000000", - symbol: "ETH", - name: "Ether", - decimals: 18, - blockNumber: 1, - logIndex: 0, - }); - let enumIndex = 0; const transferTypeValues = Object.values(TransferType); for (let i = 0; i < 20; i++) { @@ -998,7 +988,7 @@ describe("TransactionController (e2e)", () => { to: "0x52312Ad6f01657413b2eae9287F6b9Adad93d5fd", token: { l2Address: "0x000000000000000000000000000000000000800A", - l1Address: "0x0000000000000000000000000000000000000000", + l1Address: null, symbol: "ETH", name: "Ether", decimals: 18, @@ -1038,7 +1028,7 @@ describe("TransactionController (e2e)", () => { to: "0x52312Ad6f01657413b2eae9287F6b9Adad93d5fd", token: { l2Address: "0x000000000000000000000000000000000000800A", - l1Address: "0x0000000000000000000000000000000000000000", + l1Address: null, symbol: "ETH", name: "Ether", decimals: 18, @@ -1078,7 +1068,7 @@ describe("TransactionController (e2e)", () => { to: "0x52312Ad6f01657413b2eae9287F6b9Adad93d5fd", token: { l2Address: "0x000000000000000000000000000000000000800A", - l1Address: "0x0000000000000000000000000000000000000000", + l1Address: null, symbol: "ETH", name: "Ether", decimals: 18, @@ -1118,7 +1108,7 @@ describe("TransactionController (e2e)", () => { to: "0x52312Ad6f01657413b2eae9287F6b9Adad93d5fd", token: { l2Address: "0x000000000000000000000000000000000000800A", - l1Address: "0x0000000000000000000000000000000000000000", + l1Address: null, symbol: "ETH", name: "Ether", decimals: 18, @@ -1158,7 +1148,7 @@ describe("TransactionController (e2e)", () => { to: "0x52312Ad6f01657413b2eae9287F6b9Adad93d5fd", token: { l2Address: "0x000000000000000000000000000000000000800A", - l1Address: "0x0000000000000000000000000000000000000000", + l1Address: null, symbol: "ETH", name: "Ether", decimals: 18, @@ -1223,7 +1213,7 @@ describe("TransactionController (e2e)", () => { to: "0x52312Ad6f01657413b2eae9287F6b9Adad93d5fd", token: { decimals: 18, - l1Address: "0x0000000000000000000000000000000000000000", + l1Address: null, l2Address: "0x000000000000000000000000000000000000800A", name: "Ether", symbol: "ETH",