Skip to content

Commit

Permalink
Celo: Block views customizations (#2185)
Browse files Browse the repository at this point in the history
* show flag next to epoch block

* block view customizations

* add hint with link

* display block epoch transfers

* show epoch election reward types

* show reward details

* show base fee token from API

* display current epoch on main page

* mobile view

* tests

* add infinite scroll loading to reward details resource

* update useApiQuery options

* update screenshots

* update layout of reward details on desktop

* change hints of block epoch reward distribution
  • Loading branch information
tom2drum authored Aug 28, 2024
1 parent 4221b04 commit 54a512b
Show file tree
Hide file tree
Showing 52 changed files with 1,187 additions and 36 deletions.
5 changes: 5 additions & 0 deletions icons/checkered_flag.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 23 additions & 2 deletions lib/api/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,16 @@ import type {
ArbitrumL2TxnBatchesItem,
} from 'types/api/arbitrumL2';
import type { TxBlobs, Blob } from 'types/api/blobs';
import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters, BlockWithdrawalsResponse, BlockCountdownResponse } from 'types/api/block';
import type {
BlocksResponse,
BlockTransactionsResponse,
Block,
BlockFilters,
BlockWithdrawalsResponse,
BlockCountdownResponse,
BlockEpoch,
BlockEpochElectionRewardDetailsResponse,
} from 'types/api/block';
import type { ChartMarketResponse, ChartSecondaryCoinPriceResponse, ChartTransactionResponse } from 'types/api/charts';
import type { BackendVersionConfig } from 'types/api/configs';
import type {
Expand Down Expand Up @@ -327,6 +336,16 @@ export const RESOURCES = {
pathParams: [ 'height_or_hash' as const ],
filterFields: [],
},
block_epoch: {
path: '/api/v2/blocks/:height_or_hash/epoch',
pathParams: [ 'height_or_hash' as const ],
filterFields: [],
},
block_election_rewards: {
path: '/api/v2/blocks/:height_or_hash/election-rewards/:reward_type',
pathParams: [ 'height_or_hash' as const, 'reward_type' as const ],
filterFields: [],
},
txs_stats: {
path: '/api/v2/transactions/stats',
},
Expand Down Expand Up @@ -938,7 +957,7 @@ export interface ResourceError<T = unknown> {

export type ResourceErrorAccount<T> = ResourceError<{ errors: T }>

export type PaginatedResources = 'blocks' | 'block_txs' |
export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_rewards' |
'txs_validated' | 'txs_pending' | 'txs_with_blobs' | 'txs_watchlist' | 'txs_execution_node' |
'tx_internal_txs' | 'tx_logs' | 'tx_token_transfers' | 'tx_state_changes' | 'tx_blobs' |
'addresses' |
Expand Down Expand Up @@ -998,6 +1017,8 @@ Q extends 'block' ? Block :
Q extends 'block_countdown' ? BlockCountdownResponse :
Q extends 'block_txs' ? BlockTransactionsResponse :
Q extends 'block_withdrawals' ? BlockWithdrawalsResponse :
Q extends 'block_epoch' ? BlockEpoch :
Q extends 'block_election_rewards' ? BlockEpochElectionRewardDetailsResponse :
Q extends 'txs_stats' ? TransactionsStats :
Q extends 'txs_validated' ? TransactionsResponseValidated :
Q extends 'txs_pending' ? TransactionsResponsePending :
Expand Down
2 changes: 1 addition & 1 deletion lib/api/useApiFetch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import type { ApiResource, ResourceName, ResourcePathParams } from './resources'

export interface Params<R extends ResourceName> {
pathParams?: ResourcePathParams<R>;
queryParams?: Record<string, string | Array<string> | number | boolean | undefined>;
queryParams?: Record<string, string | Array<string> | number | boolean | undefined | null>;
fetchParams?: Pick<FetchParams, 'body' | 'method' | 'signal' | 'headers'>;
}

Expand Down
43 changes: 43 additions & 0 deletions lib/api/useApiInfiniteQuery.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { InfiniteData, QueryKey, UseInfiniteQueryResult } from '@tanstack/react-query';
import { useInfiniteQuery, type UseInfiniteQueryOptions } from '@tanstack/react-query';

import type { PaginatedResources, ResourceError, ResourcePayload } from 'lib/api/resources';
import useApiFetch from 'lib/api/useApiFetch';
import type { Params as ApiFetchParams } from 'lib/api/useApiFetch';

import { getResourceKey } from './useApiQuery';

type TQueryData<R extends PaginatedResources> = ResourcePayload<R>;
type TError = ResourceError<unknown>;
type TPageParam<R extends PaginatedResources> = ApiFetchParams<R>['queryParams'] | null;

export interface Params<R extends PaginatedResources> {
resourceName: R;
// eslint-disable-next-line max-len
queryOptions?: Omit<UseInfiniteQueryOptions<TQueryData<R>, TError, InfiniteData<TQueryData<R>>, TQueryData<R>, QueryKey, TPageParam<R>>, 'queryKey' | 'queryFn' | 'getNextPageParam' | 'initialPageParam'>;
pathParams?: ApiFetchParams<R>['pathParams'];
}

type ReturnType<Resource extends PaginatedResources> = UseInfiniteQueryResult<InfiniteData<ResourcePayload<Resource>>, ResourceError<unknown>>;

export default function useApiInfiniteQuery<R extends PaginatedResources>({
resourceName,
queryOptions,
pathParams,
}: Params<R>): ReturnType<R> {
const apiFetch = useApiFetch();

return useInfiniteQuery<TQueryData<R>, TError, InfiniteData<TQueryData<R>>, QueryKey, TPageParam<R>>({
// eslint-disable-next-line @tanstack/query/exhaustive-deps
queryKey: getResourceKey(resourceName, { pathParams }),
queryFn: (context) => {
const queryParams = 'pageParam' in context ? (context.pageParam || undefined) : undefined;
return apiFetch(resourceName, { pathParams, queryParams }) as Promise<TQueryData<R>>;
},
initialPageParam: null,
getNextPageParam: (lastPage) => {
return lastPage.next_page_params as TPageParam<R>;
},
...queryOptions,
});
}
9 changes: 4 additions & 5 deletions lib/api/useApiQuery.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { QueryKey, UseQueryOptions } from '@tanstack/react-query';
import type { UseQueryOptions } from '@tanstack/react-query';
import { useQuery } from '@tanstack/react-query';

import type { Params as FetchParams } from 'lib/hooks/useFetch';
Expand All @@ -10,8 +10,7 @@ export interface Params<R extends ResourceName, E = unknown, D = ResourcePayload
pathParams?: ResourcePathParams<R>;
queryParams?: Record<string, string | Array<string> | number | boolean | undefined>;
fetchParams?: Pick<FetchParams, 'body' | 'method' | 'headers'>;
queryOptions?: Omit<UseQueryOptions<ResourcePayload<R>, ResourceError<E>, D>, 'queryKey' | 'queryFn'>;
queryKey?: QueryKey;
queryOptions?: Partial<Omit<UseQueryOptions<ResourcePayload<R>, ResourceError<E>, D>, 'queryFn'>>;
}

export function getResourceKey<R extends ResourceName>(resource: R, { pathParams, queryParams }: Params<R> = {}) {
Expand All @@ -24,13 +23,13 @@ export function getResourceKey<R extends ResourceName>(resource: R, { pathParams

export default function useApiQuery<R extends ResourceName, E = unknown, D = ResourcePayload<R>>(
resource: R,
{ queryOptions, pathParams, queryParams, queryKey, fetchParams }: Params<R, E, D> = {},
{ queryOptions, pathParams, queryParams, fetchParams }: Params<R, E, D> = {},
) {
const apiFetch = useApiFetch();

return useQuery<ResourcePayload<R>, ResourceError<E>, D>({
// eslint-disable-next-line @tanstack/query/exhaustive-deps
queryKey: queryKey || getResourceKey(resource, { pathParams, queryParams }),
queryKey: queryOptions?.queryKey || getResourceKey(resource, { pathParams, queryParams }),
queryFn: async({ signal }) => {
// all errors and error typing is handled by react-query
// so error response will never go to the data
Expand Down
33 changes: 33 additions & 0 deletions mocks/blocks/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ import type { RpcBlock } from 'viem';

import type { Block, BlocksResponse } from 'types/api/block';

import { ZERO_ADDRESS } from 'lib/consts';

import * as addressMock from '../address/address';
import * as tokenMock from '../tokens/tokenInfo';

export const base: Block = {
base_fee_per_gas: '10000000000',
burnt_fees: '5449200000000000',
Expand Down Expand Up @@ -137,6 +142,34 @@ export const rootstock: Block = {
minimum_gas_price: '59240000',
};

export const celo: Block = {
...base,
celo: {
base_fee: {
token: tokenMock.tokenInfoERC20a,
amount: '445690000000000',
breakdown: [
{
address: addressMock.withName,
amount: '356552000000000.0000000000000',
percentage: 80,
},
{
address: {
...addressMock.withoutName,
hash: ZERO_ADDRESS,
},
amount: '89138000000000.0000000000000',
percentage: 20,
},
],
recipient: addressMock.contract,
},
epoch_number: 1486,
is_epoch_block: true,
},
};

export const withBlobTxs: Block = {
...base,
blob_gas_price: '21518435987',
Expand Down
57 changes: 57 additions & 0 deletions mocks/blocks/epoch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import _padStart from 'lodash/padStart';

import type { BlockEpoch, BlockEpochElectionRewardDetails, BlockEpochElectionRewardDetailsResponse } from 'types/api/block';

import * as addressMock from '../address/address';
import * as tokenMock from '../tokens/tokenInfo';
import * as tokenTransferMock from '../tokens/tokenTransfer';

export const blockEpoch1: BlockEpoch = {
number: 1486,
distribution: {
carbon_offsetting_transfer: tokenTransferMock.erc20,
community_transfer: tokenTransferMock.erc20,
reserve_bolster_transfer: null,
},
aggregated_election_rewards: {
delegated_payment: {
count: 0,
total: '71210001063118670575',
token: tokenMock.tokenInfoERC20d,
},
group: {
count: 10,
total: '157705500305820107521',
token: tokenMock.tokenInfoERC20b,
},
validator: {
count: 10,
total: '1348139501689262297152',
token: tokenMock.tokenInfoERC20c,
},
voter: {
count: 38,
total: '2244419545166303388',
token: tokenMock.tokenInfoERC20a,
},
},
};

function getRewardDetailsItem(index: number): BlockEpochElectionRewardDetails {
return {
amount: `${ 100 - index }210001063118670575`,
account: {
...addressMock.withoutName,
hash: `0x30D060F129817c4DE5fBc1366d53e19f43c8c6${ _padStart(String(index), 2, '0') }`,
},
associated_account: {
...addressMock.withoutName,
hash: `0x456f41406B32c45D59E539e4BBA3D7898c3584${ _padStart(String(index), 2, '0') }`,
},
};
}

export const electionRewardDetails1: BlockEpochElectionRewardDetailsResponse = {
items: Array(15).fill('').map((item, index) => getRewardDetailsItem(index)),
next_page_params: null,
};
1 change: 1 addition & 0 deletions public/icons/name.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
| "burger"
| "certified"
| "check"
| "checkered_flag"
| "clock-light"
| "clock"
| "coins/bitcoin"
Expand Down
24 changes: 23 additions & 1 deletion stubs/block.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Block } from 'types/api/block';
import type { Block, BlockEpochElectionReward, BlockEpoch } from 'types/api/block';

import { ADDRESS_PARAMS } from './addressParams';
import { TOKEN_INFO_ERC_20, TOKEN_TRANSFER_ERC_20 } from './token';

export const BLOCK_HASH = '0x8fa7b9e5e5e79deeb62d608db22ba9a5cb45388c7ebb9223ae77331c6080dc70';

Expand Down Expand Up @@ -35,3 +36,24 @@ export const BLOCK: Block = {
type: 'block',
uncles_hashes: [],
};

const BLOCK_EPOCH_REWARD: BlockEpochElectionReward = {
count: 10,
total: '157705500305820107521',
token: TOKEN_INFO_ERC_20,
};

export const BLOCK_EPOCH: BlockEpoch = {
number: 1486,
aggregated_election_rewards: {
group: BLOCK_EPOCH_REWARD,
validator: BLOCK_EPOCH_REWARD,
voter: BLOCK_EPOCH_REWARD,
delegated_payment: BLOCK_EPOCH_REWARD,
},
distribution: {
carbon_offsetting_transfer: TOKEN_TRANSFER_ERC_20,
community_transfer: TOKEN_TRANSFER_ERC_20,
reserve_bolster_transfer: TOKEN_TRANSFER_ERC_20,
},
};
3 changes: 2 additions & 1 deletion stubs/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ import type { TokenInstanceTransferPagination, TokenInstanceTransferResponse } f
import type { TokenTransfer, TokenTransferPagination, TokenTransferResponse } from 'types/api/tokenTransfer';

import { ADDRESS_PARAMS, ADDRESS_HASH } from './addressParams';
import { BLOCK_HASH } from './block';
import { TX_HASH } from './tx';
import { generateListStub } from './utils';

export const BLOCK_HASH = '0x8fa7b9e5e5e79deeb62d608db22ba9a5cb45388c7ebb9223ae77331c6080dc70';

export const TOKEN_INFO_ERC_20: TokenInfo<'ERC-20'> = {
address: ADDRESS_HASH,
circulating_market_cap: '117629601.61913824',
Expand Down
1 change: 1 addition & 0 deletions theme/foundations/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const colors = {
facebook: '#4460A0',
medium: '#231F20',
reddit: '#FF4500',
celo: '#FCFF52',
};

export default colors;
47 changes: 47 additions & 0 deletions types/api/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,19 @@ import type { Reward } from 'types/api/reward';
import type { Transaction } from 'types/api/transaction';

import type { ArbitrumBatchStatus, ArbitrumL2TxData } from './arbitrumL2';
import type { TokenInfo } from './token';
import type { TokenTransfer } from './tokenTransfer';
import type { ZkSyncBatchesItem } from './zkSyncL2';

export type BlockType = 'block' | 'reorg' | 'uncle';

export interface BlockBaseFeeCelo {
amount: string;
breakdown: Array<{ amount: string; percentage: number; address: AddressParam }>;
recipient: AddressParam;
token: TokenInfo;
}

export interface Block {
height: number;
timestamp: string;
Expand Down Expand Up @@ -50,6 +59,12 @@ export interface Block {
'batch_number': number | null;
};
arbitrum?: ArbitrumBlockData;
// CELO FIELDS
celo?: {
epoch_number: number;
is_epoch_block: boolean;
base_fee?: BlockBaseFeeCelo;
};
}

type ArbitrumBlockData = {
Expand Down Expand Up @@ -112,3 +127,35 @@ export interface BlockCountdownResponse {
RemainingBlock: string;
} | null;
}

export interface BlockEpochElectionReward {
count: number;
token: TokenInfo<'ERC-20'>;
total: string;
}

export interface BlockEpoch {
number: number;
distribution: {
carbon_offsetting_transfer: TokenTransfer | null;
community_transfer: TokenTransfer | null;
reserve_bolster_transfer: TokenTransfer | null;
};
aggregated_election_rewards: {
delegated_payment: BlockEpochElectionReward | null;
group: BlockEpochElectionReward | null;
validator: BlockEpochElectionReward | null;
voter: BlockEpochElectionReward | null;
};
}

export interface BlockEpochElectionRewardDetails {
account: AddressParam;
amount: string;
associated_account: AddressParam;
}

export interface BlockEpochElectionRewardDetailsResponse {
items: Array<BlockEpochElectionRewardDetails>;
next_page_params: null;
}
3 changes: 3 additions & 0 deletions types/api/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export type HomeStats = {
rootstock_locked_btc?: string | null;
last_output_root_size?: string | null;
secondary_coin_price?: string | null;
celo?: {
epoch_number: number;
};
}

export type GasPrices = {
Expand Down
Loading

0 comments on commit 54a512b

Please sign in to comment.