From aa2d134edbca2862bdb979b9d69ccec11de244fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Tue, 24 Jun 2025 10:08:39 +0200 Subject: [PATCH 1/7] Added token symbol and decimals in escrow details --- .../src/modules/details/details.service.ts | 36 +++++++++++++++---- .../src/modules/details/dto/escrow.dto.ts | 12 ++++++- 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/packages/apps/dashboard/server/src/modules/details/details.service.ts b/packages/apps/dashboard/server/src/modules/details/details.service.ts index 361e674c10..bf7aac2ddd 100644 --- a/packages/apps/dashboard/server/src/modules/details/details.service.ts +++ b/packages/apps/dashboard/server/src/modules/details/details.service.ts @@ -48,18 +48,39 @@ export class DetailsService { chainId: ChainId, address: string, ): Promise { + const network = this.networkConfig.networks.find( + (network) => network.chainId === chainId, + ); + if (!network) throw new BadRequestException('Invalid chainId provided'); + const provider = new ethers.JsonRpcProvider(network.rpcUrl); + const escrowData = await EscrowUtils.getEscrow(chainId, address); if (escrowData) { const escrowDto: EscrowDto = plainToInstance(EscrowDto, escrowData, { excludeExtraneousValues: true, }); + + const erc20Contract = HMToken__factory.connect( + escrowData.token, + provider, + ); + const [decimals, symbol] = await Promise.all([ + erc20Contract.decimals(), + erc20Contract.symbol(), + ]); + escrowDto.balance = ethers.formatUnits(escrowData.balance, decimals); + escrowDto.totalFundedAmount = ethers.formatUnits( + escrowData.totalFundedAmount, + decimals, + ); + escrowDto.amountPaid = ethers.formatUnits( + escrowData.amountPaid, + decimals, + ); + escrowDto.tokenSymbol = symbol; + escrowDto.tokenDecimals = Number(decimals); return escrowDto; } - const network = this.networkConfig.networks.find( - (network) => network.chainId === chainId, - ); - if (!network) throw new BadRequestException('Invalid chainId provided'); - const provider = new ethers.JsonRpcProvider(network.rpcUrl); const stakingClient = await StakingClient.build(provider); const stakingData = await stakingClient.getStakerInfo(address); @@ -111,7 +132,10 @@ export class DetailsService { return walletDto; } - private async getHmtBalance(chainId: ChainId, hmtAddress: string) { + private async getHmtBalance( + chainId: ChainId, + hmtAddress: string, + ): Promise { const network = this.networkConfig.networks.find( (network) => network.chainId === chainId, ); diff --git a/packages/apps/dashboard/server/src/modules/details/dto/escrow.dto.ts b/packages/apps/dashboard/server/src/modules/details/dto/escrow.dto.ts index 4f48910aa5..69e8e7bc00 100644 --- a/packages/apps/dashboard/server/src/modules/details/dto/escrow.dto.ts +++ b/packages/apps/dashboard/server/src/modules/details/dto/escrow.dto.ts @@ -1,5 +1,5 @@ import { Expose } from 'class-transformer'; -import { IsEnum, IsString, IsUrl } from 'class-validator'; +import { IsEnum, IsNumber, IsString, IsUrl } from 'class-validator'; import { ApiProperty } from '@nestjs/swagger'; import { ChainId } from '@human-protocol/sdk'; @@ -73,6 +73,16 @@ export class EscrowDto { @IsUrl() @Expose() public finalResultsUrl?: string; + + @ApiProperty({ example: 'HMT' }) + @IsString() + @Expose() + public tokenSymbol: string; + + @ApiProperty({ example: 18 }) + @IsNumber() + @Expose() + public tokenDecimals: number; } export class EscrowPaginationDto { From afecc9708fd3dcf9e2b21abc340636e996ab40ec Mon Sep 17 00:00:00 2001 From: kirill Date: Tue, 24 Jun 2025 15:09:26 +0300 Subject: [PATCH 2/7] feat: display token symbol and rework amounts --- .../model/addressDetailsSchema.ts | 22 ++++++-- .../searchResults/ui/EscrowAddress.tsx | 54 ++++++++++--------- 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/packages/apps/dashboard/client/src/features/searchResults/model/addressDetailsSchema.ts b/packages/apps/dashboard/client/src/features/searchResults/model/addressDetailsSchema.ts index 42d7f3881d..40cdb66ce5 100644 --- a/packages/apps/dashboard/client/src/features/searchResults/model/addressDetailsSchema.ts +++ b/packages/apps/dashboard/client/src/features/searchResults/model/addressDetailsSchema.ts @@ -35,6 +35,20 @@ const walletSchema = z.object({ export type AddressDetailsWallet = z.infer; +const transformEscrowAddressTokenAmount = ( + value: string | undefined | null +) => { + if (value === undefined || value === null) return value; + + const _value = Number(value); + + if (_value >= 1000) { + return Math.round(_value); + } else { + return parseFloat(_value.toFixed(3)); + } +}; + const escrowSchema = z.object({ chainId: z.number().optional().nullable(), address: z.string().optional().nullable(), @@ -42,19 +56,19 @@ const escrowSchema = z.object({ .string() .optional() .nullable() - .transform(transformOptionalTokenAmount), + .transform(transformEscrowAddressTokenAmount), token: z.string().optional().nullable(), factoryAddress: z.string().optional().nullable(), totalFundedAmount: z .string() .optional() .nullable() - .transform(transformOptionalTokenAmount), + .transform(transformEscrowAddressTokenAmount), amountPaid: z .string() .optional() .nullable() - .transform(transformOptionalTokenAmount), + .transform(transformEscrowAddressTokenAmount), status: z.string().optional().nullable(), manifest: z.string().optional().nullable(), launcher: z.string().optional().nullable(), @@ -62,6 +76,8 @@ const escrowSchema = z.object({ recordingOracle: z.string().optional().nullable(), reputationOracle: z.string().optional().nullable(), finalResultsUrl: z.string().nullable(), + tokenSymbol: z.string().optional().nullable(), + tokenDecimals: z.number().optional().nullable(), }); export type AddressDetailsEscrow = z.infer; diff --git a/packages/apps/dashboard/client/src/features/searchResults/ui/EscrowAddress.tsx b/packages/apps/dashboard/client/src/features/searchResults/ui/EscrowAddress.tsx index 8801d5d77a..7267cbdce4 100644 --- a/packages/apps/dashboard/client/src/features/searchResults/ui/EscrowAddress.tsx +++ b/packages/apps/dashboard/client/src/features/searchResults/ui/EscrowAddress.tsx @@ -15,9 +15,27 @@ type Props = { data: AddressDetailsEscrow; }; +const renderTokenAmount = ( + amount: number | null | undefined, + tokenSymbol: string | null | undefined +) => { + return ( + + {amount} + + {tokenSymbol} + + + ); +}; + const EscrowAddress: FC = ({ data }) => { const { - token, balance, factoryAddress, totalFundedAmount, @@ -27,16 +45,22 @@ const EscrowAddress: FC = ({ data }) => { exchangeOracle, recordingOracle, reputationOracle, + tokenSymbol, } = data; + const isHmt = tokenSymbol === 'HMT'; return ( - {token} + {tokenSymbol} {balance !== undefined && balance !== null ? ( - + {isHmt ? ( + + ) : ( + renderTokenAmount(balance, tokenSymbol) + )} ) : null} = ({ data }) => { {factoryAddress} - - {totalFundedAmount} - - HMT - - + {renderTokenAmount(totalFundedAmount, tokenSymbol)} - - {amountPaid} - - HMT - - + {renderTokenAmount(amountPaid, tokenSymbol)} From 7dc82e527e52a79caa6b8a3c2c8a3b4bdcdf89df Mon Sep 17 00:00:00 2001 From: kirill Date: Tue, 24 Jun 2025 15:21:24 +0300 Subject: [PATCH 3/7] refactor: remove transform --- .../model/addressDetailsSchema.ts | 32 ++----------------- .../searchResults/ui/EscrowAddress.tsx | 4 +-- .../features/searchResults/ui/HmtBalance.tsx | 2 +- 3 files changed, 6 insertions(+), 32 deletions(-) diff --git a/packages/apps/dashboard/client/src/features/searchResults/model/addressDetailsSchema.ts b/packages/apps/dashboard/client/src/features/searchResults/model/addressDetailsSchema.ts index 40cdb66ce5..10b6b53ddd 100644 --- a/packages/apps/dashboard/client/src/features/searchResults/model/addressDetailsSchema.ts +++ b/packages/apps/dashboard/client/src/features/searchResults/model/addressDetailsSchema.ts @@ -35,40 +35,14 @@ const walletSchema = z.object({ export type AddressDetailsWallet = z.infer; -const transformEscrowAddressTokenAmount = ( - value: string | undefined | null -) => { - if (value === undefined || value === null) return value; - - const _value = Number(value); - - if (_value >= 1000) { - return Math.round(_value); - } else { - return parseFloat(_value.toFixed(3)); - } -}; - const escrowSchema = z.object({ chainId: z.number().optional().nullable(), address: z.string().optional().nullable(), - balance: z - .string() - .optional() - .nullable() - .transform(transformEscrowAddressTokenAmount), + balance: z.string().optional().nullable(), token: z.string().optional().nullable(), factoryAddress: z.string().optional().nullable(), - totalFundedAmount: z - .string() - .optional() - .nullable() - .transform(transformEscrowAddressTokenAmount), - amountPaid: z - .string() - .optional() - .nullable() - .transform(transformEscrowAddressTokenAmount), + totalFundedAmount: z.string().optional().nullable(), + amountPaid: z.string().optional().nullable(), status: z.string().optional().nullable(), manifest: z.string().optional().nullable(), launcher: z.string().optional().nullable(), diff --git a/packages/apps/dashboard/client/src/features/searchResults/ui/EscrowAddress.tsx b/packages/apps/dashboard/client/src/features/searchResults/ui/EscrowAddress.tsx index 7267cbdce4..68f0c0c644 100644 --- a/packages/apps/dashboard/client/src/features/searchResults/ui/EscrowAddress.tsx +++ b/packages/apps/dashboard/client/src/features/searchResults/ui/EscrowAddress.tsx @@ -16,12 +16,12 @@ type Props = { }; const renderTokenAmount = ( - amount: number | null | undefined, + amount: string | null | undefined, tokenSymbol: string | null | undefined ) => { return ( - {amount} + {Number(amount)} = ({ balance }) => { From 27b2783295285b83df73ccc6cd641d35c1df04c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Tue, 24 Jun 2025 15:39:28 +0200 Subject: [PATCH 4/7] implement caching for token data retrieval in DetailsService --- .../src/modules/details/details.service.ts | 42 +++++++++++++++---- .../src/modules/details/details.spec.ts | 8 ++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/packages/apps/dashboard/server/src/modules/details/details.service.ts b/packages/apps/dashboard/server/src/modules/details/details.service.ts index bf7aac2ddd..cb143d88b7 100644 --- a/packages/apps/dashboard/server/src/modules/details/details.service.ts +++ b/packages/apps/dashboard/server/src/modules/details/details.service.ts @@ -1,5 +1,11 @@ import { plainToInstance } from 'class-transformer'; -import { BadRequestException, Injectable, Logger } from '@nestjs/common'; +import { + BadRequestException, + Injectable, + Logger, + Inject, +} from '@nestjs/common'; +import { Cache, CACHE_MANAGER } from '@nestjs/cache-manager'; import { ChainId, EscrowUtils, @@ -39,6 +45,7 @@ import { KVStoreDataDto } from './dto/details-response.dto'; export class DetailsService { private readonly logger = new Logger(DetailsService.name); constructor( + @Inject(CACHE_MANAGER) private readonly cacheManager: Cache, private readonly configService: EnvironmentConfigService, private readonly httpService: HttpService, private readonly networkConfig: NetworkConfigService, @@ -60,14 +67,12 @@ export class DetailsService { excludeExtraneousValues: true, }); - const erc20Contract = HMToken__factory.connect( + const { decimals, symbol } = await this.getTokenData( + chainId, escrowData.token, provider, ); - const [decimals, symbol] = await Promise.all([ - erc20Contract.decimals(), - erc20Contract.symbol(), - ]); + escrowDto.balance = ethers.formatUnits(escrowData.balance, decimals); escrowDto.totalFundedAmount = ethers.formatUnits( escrowData.totalFundedAmount, @@ -78,7 +83,7 @@ export class DetailsService { decimals, ); escrowDto.tokenSymbol = symbol; - escrowDto.tokenDecimals = Number(decimals); + escrowDto.tokenDecimals = decimals; return escrowDto; } const stakingClient = await StakingClient.build(provider); @@ -427,4 +432,27 @@ export class DetailsService { return data; } + + private async getTokenData( + chainId: ChainId, + tokenAddress: string, + provider: ethers.JsonRpcProvider, + ): Promise<{ decimals: number; symbol: string }> { + const tokenCacheKey = `token:${chainId}:${tokenAddress.toLowerCase()}`; + let data = await this.cacheManager.get<{ + decimals: number; + symbol: string; + }>(tokenCacheKey); + console.log(data); + if (!data) { + const erc20Contract = HMToken__factory.connect(tokenAddress, provider); + const [decimals, symbol] = await Promise.all([ + erc20Contract.decimals(), + erc20Contract.symbol(), + ]); + data = { decimals: Number(decimals), symbol }; + await this.cacheManager.set(tokenCacheKey, data); + } + return data; + } } diff --git a/packages/apps/dashboard/server/src/modules/details/details.spec.ts b/packages/apps/dashboard/server/src/modules/details/details.spec.ts index ead21997de..8fdfdc2b5f 100644 --- a/packages/apps/dashboard/server/src/modules/details/details.spec.ts +++ b/packages/apps/dashboard/server/src/modules/details/details.spec.ts @@ -13,6 +13,7 @@ import { OrderDirection, } from '@human-protocol/sdk'; import { OperatorsOrderBy } from '../../common/enums/operator'; +import { CACHE_MANAGER } from '@nestjs/cache-manager'; jest.mock('@human-protocol/sdk', () => ({ ...jest.requireActual('@human-protocol/sdk'), @@ -57,6 +58,13 @@ describe('DetailsService', () => { .mockResolvedValue([ChainId.MAINNET]), }, }, + { + provide: CACHE_MANAGER, + useValue: { + get: jest.fn(), + set: jest.fn(), + }, + }, Logger, ], }).compile(); From f163d6fbb659a60f9b89d5b5e7ccd2965e93e75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20L=C3=B3pez?= Date: Tue, 24 Jun 2025 16:02:19 +0200 Subject: [PATCH 5/7] remove console log from getTokenData method --- .../apps/dashboard/server/src/modules/details/details.service.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/apps/dashboard/server/src/modules/details/details.service.ts b/packages/apps/dashboard/server/src/modules/details/details.service.ts index cb143d88b7..1be4efe63b 100644 --- a/packages/apps/dashboard/server/src/modules/details/details.service.ts +++ b/packages/apps/dashboard/server/src/modules/details/details.service.ts @@ -443,7 +443,6 @@ export class DetailsService { decimals: number; symbol: string; }>(tokenCacheKey); - console.log(data); if (!data) { const erc20Contract = HMToken__factory.connect(tokenAddress, provider); const [decimals, symbol] = await Promise.all([ From 3f24f2eabe393aca652efea914bf220dbb87f153 Mon Sep 17 00:00:00 2001 From: kirill Date: Tue, 24 Jun 2025 17:42:28 +0300 Subject: [PATCH 6/7] refactor: add component, that displays token amount --- .../searchResults/ui/EscrowAddress.tsx | 38 +++++++---------- .../features/searchResults/ui/StakeInfo.tsx | 32 +++----------- .../features/searchResults/ui/TokenAmount.tsx | 42 +++++++++++++++++++ .../searchResults/ui/WalletAddress.tsx | 19 +-------- 4 files changed, 65 insertions(+), 66 deletions(-) create mode 100644 packages/apps/dashboard/client/src/features/searchResults/ui/TokenAmount.tsx diff --git a/packages/apps/dashboard/client/src/features/searchResults/ui/EscrowAddress.tsx b/packages/apps/dashboard/client/src/features/searchResults/ui/EscrowAddress.tsx index 68f0c0c644..5270129696 100644 --- a/packages/apps/dashboard/client/src/features/searchResults/ui/EscrowAddress.tsx +++ b/packages/apps/dashboard/client/src/features/searchResults/ui/EscrowAddress.tsx @@ -10,30 +10,12 @@ import { AddressDetailsEscrow } from '../model/addressDetailsSchema'; import HmtBalance from './HmtBalance'; import TitleSectionWrapper from './TitleSectionWrapper'; +import TokenAmount from './TokenAmount'; type Props = { data: AddressDetailsEscrow; }; -const renderTokenAmount = ( - amount: string | null | undefined, - tokenSymbol: string | null | undefined -) => { - return ( - - {Number(amount)} - - {tokenSymbol} - - - ); -}; - const EscrowAddress: FC = ({ data }) => { const { balance, @@ -59,7 +41,11 @@ const EscrowAddress: FC = ({ data }) => { {isHmt ? ( ) : ( - renderTokenAmount(balance, tokenSymbol) + )} ) : null} @@ -70,10 +56,18 @@ const EscrowAddress: FC = ({ data }) => { {factoryAddress} - {renderTokenAmount(totalFundedAmount, tokenSymbol)} + - {renderTokenAmount(amountPaid, tokenSymbol)} + diff --git a/packages/apps/dashboard/client/src/features/searchResults/ui/StakeInfo.tsx b/packages/apps/dashboard/client/src/features/searchResults/ui/StakeInfo.tsx index 449d26c9ea..8731f44cfc 100644 --- a/packages/apps/dashboard/client/src/features/searchResults/ui/StakeInfo.tsx +++ b/packages/apps/dashboard/client/src/features/searchResults/ui/StakeInfo.tsx @@ -3,43 +3,21 @@ import { FC } from 'react'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; -import { useIsMobile } from '@/shared/hooks/useBreakpoints'; -import FormattedNumber from '@/shared/ui/FormattedNumber'; import SectionWrapper from '@/shared/ui/SectionWrapper'; +import TokenAmount from './TokenAmount'; + type Props = { amountStaked?: number | null; amountLocked?: number | null; amountWithdrawable?: number | null; }; -const renderAmount = (amount: number | null | undefined, isMobile: boolean) => { - return ( - - - - - - HMT - - - ); -}; - const StakeInfo: FC = ({ amountStaked, amountLocked, amountWithdrawable, }) => { - const isMobile = useIsMobile(); if (!amountStaked && !amountLocked && !amountWithdrawable) return null; return ( @@ -53,7 +31,7 @@ const StakeInfo: FC = ({ Staked Tokens - {renderAmount(amountStaked, isMobile)} + )} {Number.isFinite(amountLocked) && ( @@ -61,7 +39,7 @@ const StakeInfo: FC = ({ Locked Tokens - {renderAmount(amountLocked, isMobile)} + )} {Number.isFinite(amountWithdrawable) && ( @@ -69,7 +47,7 @@ const StakeInfo: FC = ({ Withdrawable Tokens - {renderAmount(amountWithdrawable, isMobile)} + )} diff --git a/packages/apps/dashboard/client/src/features/searchResults/ui/TokenAmount.tsx b/packages/apps/dashboard/client/src/features/searchResults/ui/TokenAmount.tsx new file mode 100644 index 0000000000..8d57079fb1 --- /dev/null +++ b/packages/apps/dashboard/client/src/features/searchResults/ui/TokenAmount.tsx @@ -0,0 +1,42 @@ +import { FC } from 'react'; + +import Stack from '@mui/material/Stack'; +import Typography from '@mui/material/Typography'; + +import { useIsMobile } from '@/shared/hooks/useBreakpoints'; +import FormattedNumber from '@/shared/ui/FormattedNumber'; + +type Props = { + amount: number | string | null | undefined; + tokenSymbol?: string | null | undefined; + alreadyParsed?: boolean; +}; + +const TokenAmount: FC = ({ + amount, + tokenSymbol = 'HMT', + alreadyParsed = false, +}) => { + const isMobile = useIsMobile(); + + return ( + + + + + + {tokenSymbol} + + + ); +}; + +export default TokenAmount; diff --git a/packages/apps/dashboard/client/src/features/searchResults/ui/WalletAddress.tsx b/packages/apps/dashboard/client/src/features/searchResults/ui/WalletAddress.tsx index 5275c53c20..bc74edf214 100644 --- a/packages/apps/dashboard/client/src/features/searchResults/ui/WalletAddress.tsx +++ b/packages/apps/dashboard/client/src/features/searchResults/ui/WalletAddress.tsx @@ -3,8 +3,6 @@ import { FC } from 'react'; import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; -import { useIsMobile } from '@/shared/hooks/useBreakpoints'; -import FormattedNumber from '@/shared/ui/FormattedNumber'; import SectionWrapper from '@/shared/ui/SectionWrapper'; import { @@ -18,6 +16,7 @@ import KVStore from './KvStore'; import ReputationScore from './ReputationScore'; import StakeInfo from './StakeInfo'; import TitleSectionWrapper from './TitleSectionWrapper'; +import TokenAmount from './TokenAmount'; type Props = { data: AddressDetailsWallet | AddressDetailsOperator; @@ -31,7 +30,6 @@ const WalletAddress: FC = ({ data }) => { reputation, amountWithdrawable, } = data; - const isMobile = useIsMobile(); const isWallet = 'totalHMTAmountReceived' in data; return ( @@ -56,20 +54,7 @@ const WalletAddress: FC = ({ data }) => { title="Earned Payouts" tooltip="Total amount earned by participating in jobs" > - - - - - HMT - + )} From 593a230df96a4cec4cb4e347a00b08bd52bbbbc9 Mon Sep 17 00:00:00 2001 From: kirill Date: Tue, 24 Jun 2025 17:50:08 +0300 Subject: [PATCH 7/7] refactor HmtBalance --- .../features/searchResults/ui/HmtBalance.tsx | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/packages/apps/dashboard/client/src/features/searchResults/ui/HmtBalance.tsx b/packages/apps/dashboard/client/src/features/searchResults/ui/HmtBalance.tsx index 0512c79f14..0d9d00c134 100644 --- a/packages/apps/dashboard/client/src/features/searchResults/ui/HmtBalance.tsx +++ b/packages/apps/dashboard/client/src/features/searchResults/ui/HmtBalance.tsx @@ -4,8 +4,8 @@ import Stack from '@mui/material/Stack'; import Typography from '@mui/material/Typography'; import useHmtPrice from '@/shared/api/useHmtPrice'; -import { useIsMobile } from '@/shared/hooks/useBreakpoints'; -import FormattedNumber from '@/shared/ui/FormattedNumber'; + +import TokenAmount from './TokenAmount'; type Props = { balance?: number | string | null; @@ -13,7 +13,6 @@ type Props = { const HmtBalance: FC = ({ balance }) => { const { data, isError, isPending } = useHmtPrice(); - const isMobile = useIsMobile(); if (isError) { return N/A; @@ -29,16 +28,9 @@ const HmtBalance: FC = ({ balance }) => { return ( - - - - - {`HMT($${balanceInDollars})`} + + + {`($${balanceInDollars})`} );