Skip to content

Commit df9e291

Browse files
authored
pools pages (blockscout#2468)
* pools pages * fixes and changes * revert demo config
1 parent 8538b69 commit df9e291

File tree

63 files changed

+1183
-201
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+1183
-201
lines changed

configs/app/features/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export { default as mixpanel } from './mixpanel';
2323
export { default as mudFramework } from './mudFramework';
2424
export { default as multichainButton } from './multichainButton';
2525
export { default as nameService } from './nameService';
26+
export { default as pools } from './pools';
2627
export { default as publicTagsSubmission } from './publicTagsSubmission';
2728
export { default as restApiDocs } from './restApiDocs';
2829
export { default as rewards } from './rewards';

configs/app/features/pools.ts

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type { Feature } from './types';
2+
3+
import { getEnvValue } from '../utils';
4+
5+
const contractInfoApiHost = getEnvValue('NEXT_PUBLIC_CONTRACT_INFO_API_HOST');
6+
const dexPoolsEnabled = getEnvValue('NEXT_PUBLIC_DEX_POOLS_ENABLED') === 'true';
7+
8+
const title = 'DEX Pools';
9+
10+
const config: Feature<{ api: { endpoint: string; basePath: string } }> = (() => {
11+
if (contractInfoApiHost && dexPoolsEnabled) {
12+
return Object.freeze({
13+
title,
14+
isEnabled: true,
15+
api: {
16+
endpoint: contractInfoApiHost,
17+
basePath: '',
18+
},
19+
});
20+
}
21+
22+
return Object.freeze({
23+
title,
24+
isEnabled: false,
25+
});
26+
})();
27+
28+
export default config;

configs/envs/.env.eth_sepolia

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ NEXT_PUBLIC_API_BASE_PATH=/
1717
NEXT_PUBLIC_API_HOST=eth-sepolia.k8s-dev.blockscout.com
1818
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
1919
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'}]
20-
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
20+
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info-test.k8s-dev.blockscout.com
2121
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED=true
2222
NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'cow-swap'},{'text':'Payment link','icon':'payment_link','dappId':'peanut-protocol'}]
2323
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/eth-sepolia.json
@@ -69,3 +69,4 @@ NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=noves
6969
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
7070
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
7171
NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address
72+
NEXT_PUBLIC_DEX_POOLS_ENABLED=true

deploy/tools/envs-validator/schema.ts

+10
Original file line numberDiff line numberDiff line change
@@ -870,6 +870,16 @@ const schema = yup
870870
value => value === undefined,
871871
),
872872
}),
873+
NEXT_PUBLIC_DEX_POOLS_ENABLED: yup.boolean()
874+
.when('NEXT_PUBLIC_CONTRACT_INFO_API_HOST', {
875+
is: (value: string) => Boolean(value),
876+
then: (schema) => schema,
877+
otherwise: (schema) => schema.test(
878+
'not-exist',
879+
'NEXT_PUBLIC_DEX_POOLS_ENABLED can only be used with NEXT_PUBLIC_CONTRACT_INFO_API_HOST',
880+
value => value === undefined,
881+
),
882+
}),
873883
NEXT_PUBLIC_SAVE_ON_GAS_ENABLED: yup.boolean(),
874884
NEXT_PUBLIC_ADDRESS_USERNAME_TAG: yup
875885
.mixed()

docs/ENVS.md

+10
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
6868
- [Get gas button](ENVS.md#get-gas-button)
6969
- [Save on gas with GasHawk](ENVS.md#save-on-gas-with-gashawk)
7070
- [Rewards service API](ENVS.md#rewards-service-api)
71+
- [DEX pools](ENVS.md#dex-pools)
7172
- [3rd party services configuration](ENVS.md#external-services-configuration)
7273

7374
&nbsp;
@@ -852,6 +853,15 @@ This feature enables Blockscout Merits program. It requires that the [My account
852853
| --- | --- | --- | --- | --- | --- | --- |
853854
| NEXT_PUBLIC_REWARDS_SERVICE_API_HOST | `string` | API URL | - | - | `https://example.com` | v1.36.0+ |
854855

856+
&nbsp;
857+
858+
### DEX pools
859+
860+
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
861+
| --- | --- | --- | --- | --- | --- | --- |
862+
| NEXT_PUBLIC_DEX_POOLS_ENABLED | `boolean` | Set to true to enable the feature | Required | - | `true` | v1.37.0+ |
863+
| NEXT_PUBLIC_CONTRACT_INFO_API_HOST | `string` | Contract Info API endpoint url | Required | - | `https://contracts-info.services.blockscout.com` | v1.0.x+ |
864+
855865
## External services configuration
856866

857867
### Google ReCaptcha

icons/dex-tracker.svg

+4
Loading

lib/api/resources.ts

+21-1
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ import type {
9494
OptimismL2BatchTxs,
9595
OptimismL2BatchBlocks,
9696
} from 'types/api/optimisticL2';
97+
import type { Pool, PoolsResponse } from 'types/api/pools';
9798
import type { RawTracesResponse } from 'types/api/rawTrace';
9899
import type {
99100
RewardsConfigResponse,
@@ -1128,6 +1129,22 @@ export const RESOURCES = {
11281129
path: '/api/v2/advanced-filters/csv',
11291130
},
11301131

1132+
// POOLS
1133+
pools: {
1134+
path: '/api/v1/chains/:chainId/pools',
1135+
pathParams: [ 'chainId' as const ],
1136+
filterFields: [ 'query' as const ],
1137+
endpoint: getFeaturePayload(config.features.pools)?.api.endpoint,
1138+
basePath: getFeaturePayload(config.features.pools)?.api.basePath,
1139+
},
1140+
1141+
pool: {
1142+
path: '/api/v1/chains/:chainId/pools/:hash',
1143+
pathParams: [ 'chainId' as const, 'hash' as const ],
1144+
endpoint: getFeaturePayload(config.features.pools)?.api.endpoint,
1145+
basePath: getFeaturePayload(config.features.pools)?.api.basePath,
1146+
},
1147+
11311148
// CONFIGS
11321149
config_backend_version: {
11331150
path: '/api/v2/config/backend-version',
@@ -1222,7 +1239,7 @@ export type PaginatedResources = 'blocks' | 'block_txs' | 'block_election_reward
12221239
'watchlist' | 'private_tags_address' | 'private_tags_tx' |
12231240
'domains_lookup' | 'addresses_lookup' | 'user_ops' | 'validators_stability' | 'validators_blackfort' | 'noves_address_history' |
12241241
'token_transfers_all' | 'scroll_l2_txn_batches' | 'scroll_l2_txn_batch_txs' | 'scroll_l2_txn_batch_blocks' |
1225-
'scroll_l2_deposits' | 'scroll_l2_withdrawals' | 'advanced_filter';
1242+
'scroll_l2_deposits' | 'scroll_l2_withdrawals' | 'advanced_filter' | 'pools';
12261243

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

@@ -1416,6 +1433,8 @@ Q extends 'scroll_l2_withdrawals' ? ScrollL2MessagesResponse :
14161433
Q extends 'scroll_l2_withdrawals_count' ? number :
14171434
Q extends 'advanced_filter' ? AdvancedFilterResponse :
14181435
Q extends 'advanced_filter_methods' ? AdvancedFilterMethodsResponse :
1436+
Q extends 'pools' ? PoolsResponse :
1437+
Q extends 'pool' ? Pool :
14191438
never;
14201439
/* eslint-enable @stylistic/indent */
14211440

@@ -1452,6 +1471,7 @@ Q extends 'address_mud_tables' ? AddressMudTablesFilter :
14521471
Q extends 'address_mud_records' ? AddressMudRecordsFilter :
14531472
Q extends 'token_transfers_all' ? TokenTransferFilters :
14541473
Q extends 'advanced_filter' ? AdvancedFilterParams :
1474+
Q extends 'pools' ? { query: string } :
14551475
never;
14561476
/* eslint-enable @stylistic/indent */
14571477

lib/getItemIndex.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const DEFAULT_PAGE_SIZE = 50;
2+
3+
export default function getItemIndex(index: number, page: number, pageSize: number = DEFAULT_PAGE_SIZE) {
4+
return (page - 1) * pageSize + index + 1;
5+
};

lib/hooks/useNavItems.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,13 @@ export default function useNavItems(): ReturnType {
197197
icon: 'token-transfers',
198198
isActive: pathname === '/token-transfers',
199199
},
200-
];
200+
config.features.pools.isEnabled && {
201+
text: 'DEX tracker',
202+
nextRoute: { pathname: '/pools' as const },
203+
icon: 'dex-tracker',
204+
isActive: pathname === '/pools' || pathname.startsWith('/pool/'),
205+
},
206+
].filter(Boolean);
201207

202208
const apiNavItems: Array<NavItem> = [
203209
config.features.restApiDocs.isEnabled ? {

lib/metadata/getPageOgType.ts

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
5454
'/mud-worlds': 'Root page',
5555
'/token-transfers': 'Root page',
5656
'/advanced-filter': 'Root page',
57+
'/pools': 'Root page',
58+
'/pools/[hash]': 'Regular page',
5759

5860
// service routes, added only to make typescript happy
5961
'/login': 'Regular page',

lib/metadata/templates/description.ts

+2
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
5757
'/mud-worlds': DEFAULT_TEMPLATE,
5858
'/token-transfers': DEFAULT_TEMPLATE,
5959
'/advanced-filter': DEFAULT_TEMPLATE,
60+
'/pools': DEFAULT_TEMPLATE,
61+
'/pools/[hash]': DEFAULT_TEMPLATE,
6062

6163
// service routes, added only to make typescript happy
6264
'/login': DEFAULT_TEMPLATE,

lib/metadata/templates/title.ts

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
5454
'/mud-worlds': '%network_name% MUD worlds list',
5555
'/token-transfers': '%network_name% token transfers',
5656
'/advanced-filter': '%network_name% advanced filter',
57+
'/pools': '%network_name% DEX pools',
58+
'/pools/[hash]': '%network_name% pool details',
5759

5860
// service routes, added only to make typescript happy
5961
'/login': '%network_name% login',

lib/mixpanel/getPageType.ts

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
5252
'/mud-worlds': 'MUD worlds',
5353
'/token-transfers': 'Token transfers',
5454
'/advanced-filter': 'Advanced filter',
55+
'/pools': 'DEX pools',
56+
'/pools/[hash]': 'Pool details',
5557

5658
// service routes, added only to make typescript happy
5759
'/login': 'Login',

lib/pools/getPoolLinks.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { Pool } from 'types/api/pools';
2+
3+
type PoolLink = {
4+
url: string;
5+
image: string;
6+
title: string;
7+
};
8+
9+
export default function getPoolLinks(pool?: Pool): Array<PoolLink> {
10+
if (!pool) {
11+
return [];
12+
}
13+
14+
return [
15+
{
16+
url: pool.coin_gecko_terminal_url,
17+
image: '/static/gecko_terminal.png',
18+
title: 'GeckoTerminal',
19+
},
20+
].filter(link => Boolean(link.url));
21+
}

lib/pools/getPoolTitle.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import type { Pool } from 'types/api/pools';
2+
3+
export const getPoolTitle = (pool: Pool) => {
4+
return `${ pool.base_token_symbol } / ${ pool.quote_token_symbol } ${ pool.fee ? `(${ pool.fee }%)` : '' }`;
5+
};

mocks/pools/pool.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import type { Pool } from 'types/api/pools';
2+
3+
export const base: Pool = {
4+
contract_address: '0x06da0fd433c1a5d7a4faa01111c044910a184553',
5+
chain_id: '1',
6+
base_token_address: '0xdac17f958d2ee523a2206206994597c13d831ec7',
7+
base_token_symbol: 'USDT',
8+
base_token_icon_url: 'https://localhost:3000/utia.jpg',
9+
quote_token_address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
10+
quote_token_symbol: 'WETH',
11+
quote_token_icon_url: 'https://localhost:3000/secondary_utia.jpg',
12+
fully_diluted_valuation_usd: '75486579078',
13+
market_cap_usd: '139312819076.195',
14+
liquidity: '2099941.2238',
15+
dex: { id: 'sushiswap', name: 'SushiSwap' },
16+
fee: '0.03',
17+
coin_gecko_terminal_url: 'https://www.geckoterminal.com/eth/pools/0x06da0fd433c1a5d7a4faa01111c044910a184553',
18+
};
19+
20+
export const noIcons: Pool = {
21+
...base,
22+
base_token_icon_url: null,
23+
quote_token_icon_url: null,
24+
};

nextjs/getServerSideProps.ts

+10
Original file line numberDiff line numberDiff line change
@@ -315,3 +315,13 @@ export const mud: GetServerSideProps<Props> = async(context) => {
315315

316316
return base(context);
317317
};
318+
319+
export const pools: GetServerSideProps<Props> = async(context) => {
320+
if (!config.features.pools.isEnabled) {
321+
return {
322+
notFound: true,
323+
};
324+
}
325+
326+
return base(context);
327+
};

nextjs/nextjs-routes.d.ts

+2
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ declare module "nextjs-routes" {
5252
| DynamicRoute<"/op/[hash]", { "hash": string }>
5353
| StaticRoute<"/ops">
5454
| StaticRoute<"/output-roots">
55+
| DynamicRoute<"/pools/[hash]", { "hash": string }>
56+
| StaticRoute<"/pools">
5557
| StaticRoute<"/public-tags/submit">
5658
| StaticRoute<"/search-results">
5759
| StaticRoute<"/sprite">

pages/pools/[hash].tsx

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { NextPage } from 'next';
2+
import dynamic from 'next/dynamic';
3+
import React from 'react';
4+
5+
import type { Props } from 'nextjs/getServerSideProps';
6+
import PageNextJs from 'nextjs/PageNextJs';
7+
8+
const Pool = dynamic(() => import('ui/pages/Pool'), { ssr: false });
9+
10+
const Page: NextPage<Props> = (props: Props) => {
11+
return (
12+
<PageNextJs pathname="/pools/[hash]" query={ props.query }>
13+
<Pool/>
14+
</PageNextJs>
15+
);
16+
};
17+
18+
export default Page;
19+
20+
export { pools as getServerSideProps } from 'nextjs/getServerSideProps';

pages/pools/index.tsx

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import type { NextPage } from 'next';
2+
import dynamic from 'next/dynamic';
3+
import React from 'react';
4+
5+
import PageNextJs from 'nextjs/PageNextJs';
6+
7+
const Pools = dynamic(() => import('ui/pages/Pools'), { ssr: false });
8+
9+
const Page: NextPage = () => {
10+
return (
11+
<PageNextJs pathname="/pools">
12+
<Pools/>
13+
</PageNextJs>
14+
);
15+
};
16+
17+
export default Page;
18+
19+
export { pools as getServerSideProps } from 'nextjs/getServerSideProps';

public/icons/name.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
| "copy"
4747
| "cross"
4848
| "delete"
49+
| "dex-tracker"
4950
| "docs"
5051
| "donate"
5152
| "dots"

public/static/gecko_terminal.png

1.8 KB
Loading

stubs/pools.ts

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export const POOL = {
2+
contract_address: '0x6a1041865b76d1dc33da0257582591227c57832c',
3+
chain_id: '1',
4+
base_token_address: '0xf63e309818e4ea13782678ce6c31c1234fa61809',
5+
base_token_symbol: 'JANET',
6+
base_token_icon_url: null,
7+
quote_token_address: '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
8+
quote_token_symbol: 'WETH',
9+
quote_token_icon_url: 'https://coin-images.coingecko.com/coins/images/2518/small/weth.png?1696503332',
10+
fully_diluted_valuation_usd: '15211385',
11+
market_cap_usd: '15211385',
12+
liquidity: '394101.2428',
13+
dex: { id: 'uniswap_v2', name: 'Uniswap V2' },
14+
coin_gecko_terminal_url: 'https://www.geckoterminal.com/eth/pools/0x6a1041865b76d1dc33da0257582591227c57832c',
15+
};

types/api/pools.tsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export type PoolsResponse = {
2+
items: Array<Pool>;
3+
next_page_params: {
4+
page_token: string;
5+
page_size: number;
6+
} | null;
7+
};
8+
9+
export type Pool = {
10+
contract_address: string;
11+
chain_id: string;
12+
base_token_address: string;
13+
base_token_symbol: string;
14+
base_token_icon_url: string | null;
15+
quote_token_address: string;
16+
quote_token_symbol: string;
17+
quote_token_icon_url: string | null;
18+
fully_diluted_valuation_usd: string;
19+
market_cap_usd: string;
20+
liquidity: string;
21+
dex: {
22+
id: string;
23+
name: string;
24+
};
25+
fee?: string;
26+
coin_gecko_terminal_url: string;
27+
};

ui/marketplace/MarketplaceAppInfo.pw.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ test.describe('mobile', () => {
1616

1717
test('base view', async({ render, page }) => {
1818
await render(<MarketplaceAppInfo data={ appsMock[0] }/>);
19-
await page.getByLabel('Show project info').click();
19+
await page.getByLabel('Show info').click();
2020
await expect(page).toHaveScreenshot();
2121
});
2222
});

0 commit comments

Comments
 (0)