Commit 720fc065 authored by Igor Stuev's avatar Igor Stuev Committed by GitHub

Add support for uniswap_v4 pools (#2748)

parent 5f0c3f5c
......@@ -15,7 +15,7 @@ NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=eth.blockscout.com
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_CONTRACT_CODE_IDES=[{'title':'Remix IDE','url':'https://remix.ethereum.org/?address={hash}&blockscout={domain}','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info-test.k8s-dev.blockscout.com
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true
NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swapscout','icon':'swap','dappId':'swapscout'},{'text':'Revokescout','icon':'integration/partial','dappId':'revokescout'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'}]
NEXT_PUBLIC_DEX_POOLS_ENABLED=true
......
import type { Pool } from 'types/api/pools';
export const base: Pool = {
contract_address: '0x06da0fd433c1a5d7a4faa01111c044910a184553',
pool_id: '0x06da0fd433c1a5d7a4faa01111c044910a184553',
is_contract: true,
chain_id: '1',
base_token_address: '0xdac17f958d2ee523a2206206994597c13d831ec7',
base_token_symbol: 'USDT',
......
export const POOL = {
contract_address: '0x6a1041865b76d1dc33da0257582591227c57832c',
pool_id: '0x6a1041865b76d1dc33da0257582591227c57832c',
is_contract: true,
chain_id: '1',
base_token_address: '0xf63e309818e4ea13782678ce6c31c1234fa61809',
base_token_symbol: 'JANET',
......
......@@ -7,7 +7,8 @@ export type PoolsResponse = {
};
export type Pool = {
contract_address: string;
pool_id: string;
is_contract: boolean;
chain_id: string;
base_token_address: string;
base_token_symbol: string;
......
......@@ -18,7 +18,7 @@ const hooksConfig = {
test('base view +@mobile +@dark-mode', async({ render, mockApiResponse, mockTextAd, mockAssetResponse, page }) => {
await mockTextAd();
await mockApiResponse('contractInfo:pool', poolMock.base, { pathParams: { chainId: config.chain.id, hash: addressHash } });
await mockApiResponse('general:address', addressMock.contract, { pathParams: { hash: poolMock.base.contract_address } });
await mockApiResponse('general:address', addressMock.contract, { pathParams: { hash: poolMock.base.pool_id } });
await mockAssetResponse(poolMock.base.quote_token_icon_url as string, './playwright/mocks/image_s.jpg');
await mockAssetResponse(poolMock.base.base_token_icon_url as string, './playwright/mocks/image_md.jpg');
const component = await render(<Pool/>, { hooksConfig });
......
import { Box, Flex } from '@chakra-ui/react';
import { Flex } from '@chakra-ui/react';
import { useRouter } from 'next/router';
import React from 'react';
......@@ -17,9 +17,11 @@ import { Skeleton } from 'toolkit/chakra/skeleton';
import { Tag } from 'toolkit/chakra/tag';
import PoolInfo from 'ui/pool/PoolInfo';
import isCustomAppError from 'ui/shared/AppError/isCustomAppError';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import DataFetchAlert from 'ui/shared/DataFetchAlert';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import * as PoolEntity from 'ui/shared/entities/pool/PoolEntity';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import InfoButton from 'ui/shared/InfoButton';
import PageTitle from 'ui/shared/Page/PageTitle';
import VerifyWith from 'ui/shared/VerifyWith';
......@@ -38,9 +40,9 @@ const Pool = () => {
});
const addressQuery = useApiQuery('general:address', {
pathParams: { hash: data?.contract_address },
pathParams: { hash: data?.pool_id },
queryOptions: {
enabled: Boolean(data?.contract_address),
enabled: Boolean(data?.is_contract),
placeholderData: addressStubs.ADDRESS_INFO,
},
});
......@@ -81,10 +83,27 @@ const Pool = () => {
});
}, [ externalLinks ]);
const poolIdOrContract = React.useMemo(() => {
if (data?.is_contract && addressQuery.data) {
return <AddressEntity address={ addressQuery.data } isLoading={ addressQuery.isPlaceholderData }/>;
} else if (data?.pool_id) {
return (
<Skeleton loading={ isPlaceholderData } display="flex" alignItems="center" overflow="hidden">
<Flex overflow="hidden">
<HashStringShortenDynamic hash={ data?.pool_id }/>
</Flex>
<CopyToClipboard text={ data?.pool_id }/>
</Skeleton>
);
}
return null;
}, [ data, isPlaceholderData, addressQuery.isPlaceholderData, addressQuery.data ]);
const titleSecondRow = (
<Flex alignItems="center" justifyContent="space-between" w="100%">
{ addressQuery.data ? <AddressEntity address={ addressQuery.data } isLoading={ addressQuery.isPlaceholderData }/> : <Box/> }
<Flex gap={ 2 }>
{ poolIdOrContract }
<Flex gap={ 2 } ml={ 2 }>
<InfoButton>
{ `This Liquidity Provider (LP) token represents ${ data?.base_token_symbol }/${ data?.quote_token_symbol } pairing.` }
</InfoButton>
......
......@@ -42,7 +42,7 @@ const Pools = () => {
<Box hideFrom="lg">
{ poolsQuery.data?.items.map((item, index) => (
<PoolsListItem
key={ item.contract_address + (poolsQuery.isPlaceholderData ? index : '') }
key={ item.pool_id + (poolsQuery.isPlaceholderData ? index : '') }
isLoading={ poolsQuery.isPlaceholderData }
item={ item }
/>
......
......@@ -6,8 +6,10 @@ import getPoolLinks from 'lib/pools/getPoolLinks';
import { Image } from 'toolkit/chakra/image';
import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import PoolEntity from 'ui/shared/entities/pool/PoolEntity';
import HashStringShorten from 'ui/shared/HashStringShorten';
import ListItemMobileGrid from 'ui/shared/ListItemMobile/ListItemMobileGrid';
type Props = {
......@@ -15,7 +17,7 @@ type Props = {
isLoading?: boolean;
};
const UserOpsListItem = ({ item, isLoading }: Props) => {
const PoolsListItem = ({ item, isLoading }: Props) => {
const externalLinks = getPoolLinks(item);
return (
<ListItemMobileGrid.Container gridTemplateColumns="100px auto">
......@@ -25,10 +27,24 @@ const UserOpsListItem = ({ item, isLoading }: Props) => {
<PoolEntity pool={ item } fontWeight={ 700 } isLoading={ isLoading }/>
</ListItemMobileGrid.Value>
<ListItemMobileGrid.Label isLoading={ isLoading }>Contract</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<AddressEntity address={{ hash: item.contract_address }} noIcon linkVariant="secondary" isLoading={ isLoading }/>
</ListItemMobileGrid.Value>
{ item.is_contract && (
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Contract</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<AddressEntity address={{ hash: item.pool_id }} noIcon linkVariant="secondary" isLoading={ isLoading } truncation="constant_long"/>
</ListItemMobileGrid.Value>
</>
) }
{ !item.is_contract && (
<>
<ListItemMobileGrid.Label isLoading={ isLoading }>Pool ID</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
<HashStringShorten hash={ item.pool_id } type="long"/>
<CopyToClipboard text={ item.pool_id }/>
</ListItemMobileGrid.Value>
</>
) }
<ListItemMobileGrid.Label isLoading={ isLoading }>Liquidity</ListItemMobileGrid.Label>
<ListItemMobileGrid.Value>
......@@ -52,4 +68,4 @@ const UserOpsListItem = ({ item, isLoading }: Props) => {
);
};
export default UserOpsListItem;
export default PoolsListItem;
......@@ -27,7 +27,7 @@ const PoolsTable = ({ items, page, isLoading, top }: Props) => {
</TableHeaderSticky>
<TableBody>
{ items.map((item, index) => (
<PoolsTableItem key={ item.contract_address + (isLoading ? index : '') } item={ item } index={ index } page={ page } isLoading={ isLoading }/>
<PoolsTableItem key={ item.pool_id + (isLoading ? index : '') } item={ item } index={ index } page={ page } isLoading={ isLoading }/>
)) }
</TableBody>
</TableRoot>
......
......@@ -10,8 +10,10 @@ import { Link } from 'toolkit/chakra/link';
import { Skeleton } from 'toolkit/chakra/skeleton';
import { TableCell, TableRow } from 'toolkit/chakra/table';
import { Tooltip } from 'toolkit/chakra/tooltip';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import PoolEntity from 'ui/shared/entities/pool/PoolEntity';
import HashStringShorten from 'ui/shared/HashStringShorten';
type Props = {
item: Pool;
......@@ -37,13 +39,20 @@ const PoolsTableItem = ({
</Skeleton>
<Box overflow="hidden">
<PoolEntity pool={ item } fontWeight={ 700 } mb={ 2 } isLoading={ isLoading }/>
<AddressEntity
address={{ hash: item.contract_address }}
noIcon
isLoading={ isLoading }
truncation="constant_long"
linkVariant="secondary"
/>
{ item.is_contract ? (
<AddressEntity
address={{ hash: item.pool_id }}
noIcon
isLoading={ isLoading }
truncation="constant_long"
linkVariant="secondary"
/>
) : (
<Flex color="text.secondary" alignItems="center">
<HashStringShorten hash={ item.pool_id } type="long"/>
<CopyToClipboard text={ item.pool_id }/>
</Flex>
) }
</Box>
</Flex>
</TableCell>
......
......@@ -16,7 +16,7 @@ import * as TokenEntity from '../token/TokenEntity';
type LinkProps = EntityBase.LinkBaseProps & Pick<EntityProps, 'pool'>;
const Link = chakra((props: LinkProps) => {
const defaultHref = route({ pathname: '/pools/[hash]', query: { hash: props.pool.contract_address } });
const defaultHref = route({ pathname: '/pools/[hash]', query: { hash: props.pool.pool_id } });
return (
<EntityBase.Link
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment