Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/deploy-review-l2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ on:
- arbitrum_nova
- arbitrum_sepolia
- base
- celo
- celo_alfajores
- garnet
- gnosis
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/deploy-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ on:
- arbitrum_nova
- arbitrum_sepolia
- base
- celo
- celo_alfajores
- garnet
- gnosis
Expand Down
1 change: 1 addition & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,7 @@
"arbitrum_sepolia",
"base",
"blackfort_testnet",
"celo",
"celo_alfajores",
"garnet",
"gnosis",
Expand Down
3 changes: 2 additions & 1 deletion configs/app/features/celo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { getEnvValue } from '../utils';

const title = 'Celo chain';

const config: Feature<{ }> = (() => {
const config: Feature<{ nativeTokenAddress?: string }> = (() => {

if (getEnvValue('NEXT_PUBLIC_CELO_ENABLED') === 'true') {
return Object.freeze({
title,
isEnabled: true,
nativeTokenAddress: getEnvValue('NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS'),
});
}

Expand Down
64 changes: 64 additions & 0 deletions configs/envs/.env.celo
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Set of ENVs for Celo Mainnet network explorer
# https://celo.blockscout.com
# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=celo"

# Local ENVs
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_ENV=development
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws

NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS=0x471EcE3750Da237f93B8E339c536989b8978a438

# Instance ENVs
NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS=['talentprotocol', 'efp', 'webacy', 'deepdao', 'humanpassport', 'bankless', 'blockscoutbadges']
NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/widgets/config.json
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/
NEXT_PUBLIC_API_HOST=celo.blockscout.com
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
NEXT_PUBLIC_CELO_ENABLED=true
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_DEFI_DROPDOWN_ITEMS=[{'text':'Swapscout','icon':'swap','dappId':'swapscout'},{'text':'Revokescout','icon':'integration/partial','dappId':'revokescout'}]
NEXT_PUBLIC_DEX_POOLS_ENABLED=true
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/featured-networks/celo.json
NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge
NEXT_PUBLIC_GAS_TRACKER_ENABLED=false
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=0xa214ff978b8535434d5806d8e9d0d5f8c946905fbba1def121014e7af36c1e8f
NEXT_PUBLIC_HAS_USER_OPS=true
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
NEXT_PUBLIC_HOMEPAGE_HERO_BANNER_CONFIG={'background':['rgba(252, 255, 82, 1)'],'text_color':['rgba(0, 0, 0, 1)']}
NEXT_PUBLIC_HOMEPAGE_STATS=['total_blocks','average_block_time','total_txs','wallet_addresses','current_epoch']
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/0xdeval/b27a4aecaad513fa033e37430a4f9a47/raw/3a2fa70068ea27c3e6d58dc4cdbeb732968d62f3/revokescout-banner.html
NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://revoke.blockscout.com?utm_source=blockscout&utm_medium=celo
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
NEXT_PUBLIC_NAVIGATION_HIGHLIGHTED_ROUTES=['/apps']
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=CELO
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=CELO
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/celo-icon-light.svg
NEXT_PUBLIC_NETWORK_ICON_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-icons/celo-icon-dark.svg
NEXT_PUBLIC_NETWORK_ID=42220
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/celo-logo-light.svg
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/celo-logo-dark.svg
NEXT_PUBLIC_NETWORK_MULTIPLE_GAS_CURRENCIES=true
NEXT_PUBLIC_NETWORK_NAME=Celo Mainnet
NEXT_PUBLIC_NETWORK_RPC_URL=https://forno.celo.org
NEXT_PUBLIC_NETWORK_SHORT_NAME=Mainnet
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/celo.png
NEXT_PUBLIC_PUZZLE_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/capyPuzzleBadge
NEXT_PUBLIC_STATS_API_BASE_PATH=/stats-service
NEXT_PUBLIC_STATS_API_HOST=https://celo.blockscout.com
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
NEXT_PUBLIC_VIEWS_BLOCK_HIDDEN_FIELDS=['burnt_fees']
NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true
NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED=true
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
10 changes: 10 additions & 0 deletions deploy/tools/envs-validator/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,6 +364,16 @@ const celoSchema = yup
.object()
.shape({
NEXT_PUBLIC_CELO_ENABLED: yup.boolean(),
NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS: yup
.string()
.min(42)
.max(42)
.matches(regexp.HEX_REGEXP_WITH_0X)
.when('NEXT_PUBLIC_CELO_ENABLED', {
is: (value: boolean) => value,
then: (schema) => schema,
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS can only be used if NEXT_PUBLIC_CELO_ENABLED is set to \'true\''),
}),
});

const apiDocsScheme = yup
Expand Down
3 changes: 2 additions & 1 deletion deploy/tools/envs-validator/test/.env.celo
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
NEXT_PUBLIC_CELO_ENABLED=true
NEXT_PUBLIC_CELO_ENABLED=true
NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS=0x471EcE3750Da237f93B8E339c536989b8978a438
1 change: 1 addition & 0 deletions docs/ENVS.md
Original file line number Diff line number Diff line change
Expand Up @@ -792,6 +792,7 @@ For blockchains that use the Celo platform. _Note_, that once the Celo mainnet b
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
| --- | --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_CELO_ENABLED | `boolean` | Indicates that it is a Celo-based chain. | - | - | `true` | v1.37.0+ |
| NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS | `string` | The address of the CELO ERC-20 token. Used to exclude its balance from the net worth value of user tokens. | - | - | `0x471EcE3750Da237f93B8E339c536989b8978a438` | v2.3.0+ |

&nbsp;

Expand Down
1 change: 1 addition & 0 deletions lib/hooks/useFetch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export default function useFetch() {
statusText: response.statusText,
rateLimits: {
bypassOptions: response.headers.get('bypass-429-option'),
reset: response.headers.get('x-ratelimit-reset'),
},
};

Expand Down
42 changes: 22 additions & 20 deletions nextjs/getServerSideProps/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import config from 'configs/app';
import * as cookies from 'lib/cookies';
import type * as metadata from 'lib/metadata';

import detectBotRequest from '../utils/detectBotRequest';
import { isLikelyHumanBrowser, isKnownBotRequest } from '../utils/checkRealBrowser';

const adBannerFeature = config.features.adsBanner;

Expand Down Expand Up @@ -45,28 +45,30 @@ Promise<GetServerSidePropsResult<Props<Pathname>>> => {
}

const isTrackingDisabled = process.env.DISABLE_TRACKING === 'true';
const isBot = Boolean(detectBotRequest(req));

if (!isTrackingDisabled && !isBot) {
if (!isTrackingDisabled) {
const isRealUser = isLikelyHumanBrowser(req) && !isKnownBotRequest(req);
if (isRealUser) {
// log pageview
const hostname = req.headers.host;
const timestamp = new Date().toISOString();
const chainId = process.env.NEXT_PUBLIC_NETWORK_ID;
const chainName = process.env.NEXT_PUBLIC_NETWORK_NAME;
const publicRPC = process.env.NEXT_PUBLIC_NETWORK_RPC_URL;
const hostname = req.headers.host;
const timestamp = new Date().toISOString();
const chainId = process.env.NEXT_PUBLIC_NETWORK_ID;
const chainName = process.env.NEXT_PUBLIC_NETWORK_NAME;
const publicRPC = process.env.NEXT_PUBLIC_NETWORK_RPC_URL;

fetch('https://monitor.blockscout.com/count', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
hostname,
timestamp,
chainId,
chainName,
publicRPC,
uuid,
}),
});
fetch('https://monitor.blockscout.com/count', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
hostname,
timestamp,
chainId,
chainName,
publicRPC,
uuid,
}),
});
}
}

return {
Expand Down
80 changes: 80 additions & 0 deletions nextjs/utils/checkRealBrowser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type { GetServerSidePropsContext } from 'next';

const GENERIC_BOT_MARKERS = [
'bot',
'spider',
'crawler',
'httpclient',
'headlesschrome',
'phantomjs',
'puppeteer',
'playwright',
'node-fetch',
'axios',
'curl',
'wget',
'python-requests',
'go-http-client',
'libwww-perl',
'okhttp',
'postman',
'insomnia',
];

function toLower(input: string | null | undefined): string {
return (input || '').toLowerCase();
}

function isGenericBotUA(userAgent: string): boolean {
const ua = toLower(userAgent);
return GENERIC_BOT_MARKERS.some((m) => ua.includes(m));
}

function hasBrowserHeuristics(req: GetServerSidePropsContext['req']): boolean {
const acceptLanguage = req.headers['accept-language'];
const secChUa = req.headers['sec-ch-ua'];
const secFetchSite = req.headers['sec-fetch-site'];
const secFetchMode = req.headers['sec-fetch-mode'];
const upgradeInsecure = req.headers['upgrade-insecure-requests'];

const hasLang = Boolean(acceptLanguage);
const hasSecHeaders = Boolean(secChUa || secFetchSite || secFetchMode || upgradeInsecure);

return hasLang && hasSecHeaders;
}

export function isLikelyHumanBrowser(req: GetServerSidePropsContext['req']): boolean {
const userAgent = req.headers['user-agent'];
if (!userAgent) return false;

if (isGenericBotUA(userAgent)) return false;
if (!hasBrowserHeuristics(req)) return false;

// Inclusive check for common engines + brands across desktop and mobile
const hasEngineToken = /applewebkit|gecko/i.test(userAgent);
const hasKnownBrowserBrand = /chrome|safari|firefox|crios|fxios|edg|opr|samsungbrowser|vivaldi|whale|duckduckgo/i.test(userAgent);

return hasEngineToken && hasKnownBrowserBrand;
}

export function isKnownBotRequest(req: GetServerSidePropsContext['req']): boolean {
const ua = toLower(req.headers['user-agent']);
if (!ua) return false;

// Social preview bots
if (ua.includes('twitter')) return true;
if (ua.includes('facebook')) return true;
if (ua.includes('telegram')) return true;
if (ua.includes('slack')) return true;

// Search engine bots
if (ua.includes('googlebot')) return true;
if (ua.includes('bingbot')) return true;
if (ua.includes('yahoo')) return true;
if (ua.includes('duckduck')) return true;

// Generic markers
if (isGenericBotUA(ua)) return true;

return false;
}
1 change: 1 addition & 0 deletions playwright/fixtures/mockEnvs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = {
],
celo: [
[ 'NEXT_PUBLIC_CELO_ENABLED', 'true' ],
[ 'NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS', '0x471EcE3750Da237f93B8E339c536989b8978a438' ],
],
opSuperchain: [
[ 'NEXT_PUBLIC_OP_SUPERCHAIN_ENABLED', 'true' ],
Expand Down
4 changes: 2 additions & 2 deletions toolkit/theme/foundations/semanticTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,8 @@ const semanticTokens: ThemingConfig['semanticTokens'] = {
},
bg: {
info: { value: { _light: '{colors.blackAlpha.50}', _dark: '{colors.whiteAlpha.100}' } },
warning: { value: { _light: '{colors.orange.100}', _dark: '{colors.orange.800/60}' } },
warning_table: { value: { _light: '{colors.orange.50}', _dark: '{colors.orange.800/60}' } },
warning: { value: { _light: '{colors.orange.100}', _dark: '{colors.orange.800/44}' } },
warning_table: { value: { _light: '{colors.orange.50}', _dark: '{colors.orange.800/44}' } },
success: { value: { _light: '{colors.green.100}', _dark: '{colors.green.900}' } },
error: { value: { _light: '{colors.red.100}', _dark: '{colors.red.900}' } },
},
Expand Down
1 change: 1 addition & 0 deletions tools/preset-sync/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const PRESETS = {
arbitrum_sepolia: 'https://arbitrum-sepolia.blockscout.com',
base: 'https://base.blockscout.com',
blackfort_testnet: 'https://blackfort-testnet.blockscout.com',
celo: 'https://celo.blockscout.com',
celo_alfajores: 'https://celo-alfajores.blockscout.com',
eth: 'https://eth.blockscout.com',
eth_goerli: 'https://eth-goerli.blockscout.com',
Expand Down
23 changes: 23 additions & 0 deletions ui/address/AddressTokens.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { AddressTokensResponse } from 'types/api/address';

import * as addressMock from 'mocks/address/address';
import * as tokensMock from 'mocks/address/tokens';
import * as tokenInfo from 'mocks/tokens/tokenInfo';
import * as tokenInstance from 'mocks/tokens/tokenInstance';
import * as socketServer from 'playwright/fixtures/socketServer';
import { test, expect, devices } from 'playwright/lib';
Expand Down Expand Up @@ -268,3 +269,25 @@ test.describe('update balances via socket', () => {
await expect(component).toHaveScreenshot();
});
});

test('native token', async({ render, mockEnvs }) => {
await mockEnvs([
[ 'NEXT_PUBLIC_CELO_ENABLED', 'true' ],
[ 'NEXT_PUBLIC_CELO_NATIVE_TOKEN_ADDRESS', tokenInfo.tokenInfoERC20c.address_hash ],
]);
const hooksConfig = {
router: {
query: { hash: ADDRESS_HASH, tab: 'tokens_erc20' },
isReady: true,
},
};

const component = await render(
<Box pt={{ base: '134px', lg: 6 }}>
<AddressTokens/>
</Box>,
{ hooksConfig },
);

await expect(component).toHaveScreenshot();
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 9 additions & 19 deletions ui/address/address3rdPartyWidgets/Address3rdPartyWidgetCard.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Flex, Text, chakra, Separator, Box } from '@chakra-ui/react';
import { Flex, Text, chakra, Separator } from '@chakra-ui/react';
import { useCallback } from 'react';

import type { Address3rdPartyWidget } from 'types/views/address';
Expand Down Expand Up @@ -125,25 +125,15 @@ const Address3rdPartyWidgetCard = ({ name, config, address, isLoading }: Props)
flexDirection="column"
p={ 3 }
cursor={ isLoading ? 'default' : 'pointer' }
position="relative"
zIndex={ 0 }
borderRadius="md"
border="1px solid"
borderColor={ isLoading ? { _light: 'blackAlpha.50', _dark: 'whiteAlpha.100' } : 'transparent' }
bgColor={ isLoading ? 'transparent' : { _light: 'blackAlpha.50', _dark: 'whiteAlpha.100' } }
transition="border-color 0.2s ease-in-out"
_hover={ isLoading ? {} : {
borderColor: { _light: 'blackAlpha.200', _dark: 'whiteAlpha.200' },
} }
>
<Box
aria-hidden
position="absolute"
inset={ 0 }
borderRadius="md"
border="1px solid"
borderColor={ isLoading ? { _light: 'theme.stats.bg._light', _dark: 'theme.stats.bg._dark' } : 'transparent' }
bgColor={ isLoading ? 'transparent' : { _light: 'theme.stats.bg._light', _dark: 'theme.stats.bg._dark' } }
transform="scale(1)"
transition="transform 0.2s ease-in-out, border-color 0.2s ease-in-out"
zIndex={ -1 }
_groupHover={{
transform: 'scale(1.02)',
borderColor: { _light: 'blackAlpha.50', _dark: 'whiteAlpha.100' },
}}
/>
{ content }
</LinkBox>
);
Expand Down
Loading
Loading