Skip to content

Commit

Permalink
Merge pull request #780 from blockscout/beacon
Browse files Browse the repository at this point in the history
Beacon
  • Loading branch information
isstuev committed Apr 25, 2023
2 parents b4b90cc + dbe5c80 commit aa1411a
Show file tree
Hide file tree
Showing 25 changed files with 623 additions and 13 deletions.
3 changes: 3 additions & 0 deletions .env.template
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,6 @@ NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_GOOGLE_AN
NEXT_PUBLIC_IS_L2_NETWORK=__PLACEHOLDER_FOR_NEXT_PUBLIC_IS_L2_NETWORKL__
NEXT_PUBLIC_L1_BASE_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_L1_BASE_URL__
NEXT_PUBLIC_L2_WITHDRAWAL_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_L2_WITHDRAWAL_URL__

# beacon chain config
NEXT_PUBLIC_HAS_BEACON_CHAIN=__PLACEHOLDER_FOR_NEXT_PUBLIC_HAS_BEACON_CHAIN__
3 changes: 3 additions & 0 deletions configs/app/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ const config = Object.freeze({
L1BaseUrl: getEnvValue(process.env.NEXT_PUBLIC_L1_BASE_URL),
withdrawalUrl: getEnvValue(process.env.NEXT_PUBLIC_L2_WITHDRAWAL_URL) || '',
},
beaconChain: {
hasBeaconChain: getEnvValue(process.env.NEXT_PUBLIC_HAS_BEACON_CHAIN) === 'true',
},
statsApi: {
endpoint: getEnvValue(process.env.NEXT_PUBLIC_STATS_API_HOST),
basePath: '',
Expand Down
9 changes: 9 additions & 0 deletions docs/ENVS.md
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,15 @@ For each application, you need to specify the `MarketplaceCategoryId` to which i
| NEXT_PUBLIC_L1_BASE_URL | `string` | Base Blockscout URL for L1 network | yes | - | `'http://eth-goerli.blockscout.com'` |
| NEXT_PUBLIC_L2_WITHDRAWAL_URL | `string` | URL for L2 -> L1 withdrawals | yes | - | `https://app.optimism.io/bridge/withdraw` |

## Beacon chain configuration

| Variable | Type| Description | Is required | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_HAS_BEACON_CHAIN | `boolean` | Set to true for networks with the beacon chain | - | - | `true` |




# How to add new environment variable

If the variable should be exposed to the browser don't forget to add prefix `NEXT_PUBLIC_` to its name.
Expand Down
27 changes: 25 additions & 2 deletions lib/api/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import type {
AddressTokenTransferFilters,
AddressTokensFilter,
AddressTokensResponse,
AddressWithdrawalsResponse,
} from 'types/api/address';
import type { AddressesResponse } from 'types/api/addresses';
import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters } from 'types/api/block';
import type { BlocksResponse, BlockTransactionsResponse, Block, BlockFilters, BlockWithdrawalsResponse } from 'types/api/block';
import type { ChartMarketResponse, ChartTransactionResponse } from 'types/api/charts';
import type { SmartContract, SmartContractReadMethod, SmartContractWriteMethod, SmartContractVerificationConfig } from 'types/api/contract';
import type { VerifiedContractsResponse, VerifiedContractsFilters, VerifiedContractsCounters } from 'types/api/contracts';
Expand Down Expand Up @@ -42,6 +43,7 @@ import type { TransactionsResponseValidated, TransactionsResponsePending, Transa
import type { TTxsFilters } from 'types/api/txsFilters';
import type { TxStateChanges } from 'types/api/txStateChanges';
import type { VisualizedContract } from 'types/api/visualization';
import type { WithdrawalsResponse } from 'types/api/withdrawals';
import type { ArrayElement } from 'types/utils';

import appConfig from 'configs/app/config';
Expand Down Expand Up @@ -127,6 +129,12 @@ export const RESOURCES = {
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const ],
filterFields: [],
},
block_withdrawals: {
path: '/api/v2/blocks/:height/withdrawals',
pathParams: [ 'height' as const ],
paginationFields: [ 'items_count' as const, 'index' as const ],
filterFields: [],
},
txs_validated: {
path: '/api/v2/transactions',
paginationFields: [ 'block_number' as const, 'items_count' as const, 'filter' as const, 'index' as const ],
Expand Down Expand Up @@ -167,6 +175,11 @@ export const RESOURCES = {
path: '/api/v2/transactions/:hash/state-changes',
pathParams: [ 'hash' as const ],
},
withdrawals: {
path: '/api/v2/withdrawals',
paginationFields: [ 'index' as const, 'items_count' as const ],
filterFields: [],
},

// ADDRESSES
addresses: {
Expand Down Expand Up @@ -234,6 +247,12 @@ export const RESOURCES = {
paginationFields: [ 'items_count' as const, 'token_name' as const, 'token_type' as const, 'value' as const ],
filterFields: [ 'type' as const ],
},
address_withdrawals: {
path: '/api/v2/addresses/:hash/withdrawals',
pathParams: [ 'hash' as const ],
paginationFields: [ 'items_count' as const, 'index' as const ],
filterFields: [],
},

// CONTRACT
contract: {
Expand Down Expand Up @@ -474,7 +493,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' |
'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens' |
'token_instance_transfers' |
'verified_contracts' |
'l2_output_roots' | 'l2_withdrawals' | 'l2_txn_batches' | 'l2_deposits';
'l2_output_roots' | 'l2_withdrawals' | 'l2_txn_batches' | 'l2_deposits' |
'withdrawals' | 'address_withdrawals' | 'block_withdrawals';

export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;

Expand All @@ -500,6 +520,7 @@ Q extends 'stats_line' ? StatsChart :
Q extends 'blocks' ? BlocksResponse :
Q extends 'block' ? Block :
Q extends 'block_txs' ? BlockTransactionsResponse :
Q extends 'block_withdrawals' ? BlockWithdrawalsResponse :
Q extends 'txs_validated' ? TransactionsResponseValidated :
Q extends 'txs_pending' ? TransactionsResponsePending :
Q extends 'tx' ? Transaction :
Expand All @@ -519,6 +540,7 @@ Q extends 'address_coin_balance' ? AddressCoinBalanceHistoryResponse :
Q extends 'address_coin_balance_chart' ? AddressCoinBalanceHistoryChart :
Q extends 'address_logs' ? LogsResponseAddress :
Q extends 'address_tokens' ? AddressTokensResponse :
Q extends 'address_withdrawals' ? AddressWithdrawalsResponse :
Q extends 'token' ? TokenInfo :
Q extends 'token_counters' ? TokenCounters :
Q extends 'token_transfers' ? TokenTransferResponse :
Expand All @@ -539,6 +561,7 @@ Q extends 'verified_contracts' ? VerifiedContractsResponse :
Q extends 'verified_contracts_counters' ? VerifiedContractsCounters :
Q extends 'visualize_sol2uml' ? VisualizedContract :
Q extends 'contract_verification_config' ? SmartContractVerificationConfig :
Q extends 'withdrawals' ? WithdrawalsResponse :
Q extends 'l2_output_roots' ? L2OutputRootsResponse :
Q extends 'l2_withdrawals' ? L2WithdrawalsResponse :
Q extends 'l2_deposits' ? L2DepositsResponse :
Expand Down
9 changes: 8 additions & 1 deletion lib/hooks/useNavItems.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,14 @@ export default function useNavItems(): ReturnType {
blocks,
topAccounts,
verifiedContracts,
];
appConfig.beaconChain.hasBeaconChain && {
text: 'Withdrawals',
nextRoute: { pathname: '/withdrawals' as const },
icon: withdrawalsIcon,
isActive: pathname === '/withdrawals',
isNewUi: true,
},
].filter(Boolean);
}

const otherNavItems: Array<NavItem> = [
Expand Down
15 changes: 15 additions & 0 deletions lib/next/getServerSidePropsBeacon.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { GetServerSideProps } from 'next';

import appConfig from 'configs/app/config';
import type { Props } from 'lib/next/getServerSideProps';
import { getServerSideProps as getServerSidePropsBase } from 'lib/next/getServerSideProps';

export const getServerSideProps: GetServerSideProps<Props> = async(args) => {
if (!appConfig.beaconChain.hasBeaconChain) {
return {
notFound: true,
};
}

return getServerSidePropsBase(args);
};
50 changes: 50 additions & 0 deletions mocks/withdrawals/withdrawals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
export const data = {
items: [
{
amount: '192175',
block_number: 43242,
index: 11688,
receiver: {
hash: '0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134',
implementation_name: null,
is_contract: false,
is_verified: null,
name: null,
},
timestamp: '2022-06-07T18:12:24.000000Z',
validator_index: 49622,
},
{
amount: '192175',
block_number: 43242,
index: 11687,
receiver: {
hash: '0xf97e987c050e5Ab072211Ad2C213Eb5AEE4DF134',
implementation_name: null,
is_contract: false,
is_verified: null,
name: null,
},
timestamp: '2022-05-07T18:12:24.000000Z',
validator_index: 49621,
},
{
amount: '182773',
block_number: 43242,
index: 11686,
receiver: {
hash: '0xf97e123c050e5Ab072211Ad2C213Eb5AEE4DF134',
implementation_name: null,
is_contract: false,
is_verified: null,
name: null,
},
timestamp: '2022-04-07T18:12:24.000000Z',
validator_index: 49620,
},
],
next_page_params: {
index: 11639,
items_count: 50,
},
};
22 changes: 22 additions & 0 deletions pages/withdrawals.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';

import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Withdrawals from 'ui/pages/Withdrawals';

const WithdrawalsPage: NextPage = () => {
const title = getNetworkTitle();
return (
<>
<Head>
<title>{ title }</title>
</Head>
<Withdrawals/>
</>
);
};

export default WithdrawalsPage;

export { getServerSideProps } from 'lib/next/getServerSidePropsBeacon';
17 changes: 17 additions & 0 deletions types/api/address.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface Address {
creator_address_hash: string | null;
creation_tx_hash: string | null;
exchange_rate: string | null;
has_beacon_chain_withdrawals?: boolean;
has_custom_methods_read: boolean;
has_custom_methods_write: boolean;
has_decompiled_code: boolean;
Expand Down Expand Up @@ -128,3 +129,19 @@ export interface AddressInternalTxsResponse {
transaction_index: number;
} | null;
}

export type AddressWithdrawalsResponse = {
items: Array<AddressWithdrawalsItem>;
next_page_params: {
index: number;
items_count: number;
};
}

export type AddressWithdrawalsItem = {
amount: string;
block_number: number;
index: number;
timestamp: string;
validator_index: number;
}
16 changes: 16 additions & 0 deletions types/api/block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export interface Block {
tx_count: number;
miner: AddressParam;
size: number;
has_beacon_chain_withdrawals?: boolean;
hash: string;
parent_hash: string;
difficulty: string;
Expand Down Expand Up @@ -56,3 +57,18 @@ export interface NewBlockSocketResponse {
export interface BlockFilters {
type?: BlockType;
}

export type BlockWithdrawalsResponse = {
items: Array<BlockWithdrawalsItem>;
next_page_params: {
index: number;
items_count: number;
};
}

export type BlockWithdrawalsItem = {
amount: string;
index: number;
receiver: AddressParam;
validator_index: number;
}
18 changes: 18 additions & 0 deletions types/api/withdrawals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { AddressParam } from './addressParams';

export type WithdrawalsResponse = {
items: Array<WithdrawalsItem>;
next_page_params: {
index: number;
items_count: number;
};
}

export type WithdrawalsItem = {
amount: string;
block_number: number;
index: number;
receiver: AddressParam;
timestamp: string;
validator_index: number;
}
3 changes: 2 additions & 1 deletion types/nextjs-routes.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ declare module "nextjs-routes" {
| DynamicRoute<"/tx/[hash]", { "hash": string }>
| StaticRoute<"/txs">
| StaticRoute<"/verified-contracts">
| StaticRoute<"/visualize/sol2uml">;
| StaticRoute<"/visualize/sol2uml">
| StaticRoute<"/withdrawals">;

interface StaticRoute<Pathname> {
pathname: Pathname;
Expand Down
53 changes: 53 additions & 0 deletions ui/address/AddressWithdrawals.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Show, Hide } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';

import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import getQueryParamString from 'lib/router/getQueryParamString';
import ActionBar from 'ui/shared/ActionBar';
import DataListDisplay from 'ui/shared/DataListDisplay';
import Pagination from 'ui/shared/Pagination';
import WithdrawalsListItem from 'ui/withdrawals/WithdrawalsListItem';
import WithdrawalsTable from 'ui/withdrawals/WithdrawalsTable';

const AddressWithdrawals = ({ scrollRef }: {scrollRef?: React.RefObject<HTMLDivElement>}) => {
const router = useRouter();

const hash = getQueryParamString(router.query.hash);

const { data, isLoading, isError, pagination, isPaginationVisible } = useQueryWithPages({
resourceName: 'address_withdrawals',
pathParams: { hash },
scrollRef,
});
const content = data?.items ? (
<>
<Show below="lg" ssr={ false }>
{ data.items.map((item) => <WithdrawalsListItem item={ item } key={ item.index } view="address"/>) }
</Show>
<Hide below="lg" ssr={ false }>
<WithdrawalsTable items={ data.items } view="address" top={ isPaginationVisible ? 80 : 0 }/>
</Hide>
</>
) : null ;

const actionBar = isPaginationVisible ? (
<ActionBar mt={ -6 } showShadow={ isLoading }>
<Pagination ml="auto" { ...pagination }/>
</ActionBar>
) : null;

return (
<DataListDisplay
isError={ isError }
isLoading={ isLoading }
items={ data?.items }
skeletonProps={{ isLongSkeleton: true, skeletonDesktopColumns: Array(5).fill(`${ 100 / 5 }%`), skeletonDesktopMinW: '950px' }}
emptyText="There are no withdrawals for this address."
content={ content }
actionBar={ actionBar }
/>
);
};

export default AddressWithdrawals;
Loading

0 comments on commit aa1411a

Please sign in to comment.