Commit 1d39d53d authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into feat/token-icon-in-search

parents f4d40cef f3f732e3
...@@ -45,6 +45,9 @@ NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_SHO ...@@ -45,6 +45,9 @@ NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_SHO
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME__ NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME__
NEXT_PUBLIC_AD_DOMAIN_WITH_AD=__PLACEHOLDER_FOR_NEXT_PUBLIC_AD_DOMAIN_WITH_AD__ NEXT_PUBLIC_AD_DOMAIN_WITH_AD=__PLACEHOLDER_FOR_NEXT_PUBLIC_AD_DOMAIN_WITH_AD__
NEXT_PUBLIC_AD_ADBUTLER_ON=__PLACEHOLDER_FOR_NEXT_PUBLIC_AD_ADBUTLER_ON__ NEXT_PUBLIC_AD_ADBUTLER_ON=__PLACEHOLDER_FOR_NEXT_PUBLIC_AD_ADBUTLER_ON__
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP=__PLACEHOLDER_FOR_NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP__
NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE=__PLACEHOLDER_FOR_NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE__
NEXT_PUBLIC_AD_SLISE_ON=__PLACEHOLDER_FOR_NEXT_PUBLIC_AD_SLISE_ON__
NEXT_PUBLIC_GRAPHIQL_TRANSACTION=__PLACEHOLDER_FOR_NEXT_PUBLIC_GRAPHIQL_TRANSACTION__ NEXT_PUBLIC_GRAPHIQL_TRANSACTION=__PLACEHOLDER_FOR_NEXT_PUBLIC_GRAPHIQL_TRANSACTION__
NEXT_PUBLIC_WEB3_DEFAULT_WALLET=__PLACEHOLDER_FOR_NEXT_PUBLIC_WEB3_DEFAULT_WALLET__ NEXT_PUBLIC_WEB3_DEFAULT_WALLET=__PLACEHOLDER_FOR_NEXT_PUBLIC_WEB3_DEFAULT_WALLET__
NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=__PLACEHOLDER_FOR_NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET__ NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=__PLACEHOLDER_FOR_NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET__
......
/* eslint-disable no-restricted-properties */ /* eslint-disable no-restricted-properties */
import type { AdButlerConfig } from 'types/client/adButlerConfig';
import type { NavItemExternal } from 'types/client/navigation-items'; import type { NavItemExternal } from 'types/client/navigation-items';
import type { WalletType } from 'types/client/wallets'; import type { WalletType } from 'types/client/wallets';
import type { NetworkExplorer } from 'types/networks'; import type { NetworkExplorer } from 'types/networks';
...@@ -114,6 +115,9 @@ const config = Object.freeze({ ...@@ -114,6 +115,9 @@ const config = Object.freeze({
ad: { ad: {
domainWithAd: getEnvValue(process.env.NEXT_PUBLIC_AD_DOMAIN_WITH_AD) || 'blockscout.com', domainWithAd: getEnvValue(process.env.NEXT_PUBLIC_AD_DOMAIN_WITH_AD) || 'blockscout.com',
adButlerOn: getEnvValue(process.env.NEXT_PUBLIC_AD_ADBUTLER_ON) === 'true', adButlerOn: getEnvValue(process.env.NEXT_PUBLIC_AD_ADBUTLER_ON) === 'true',
adButlerConfigDesktop: parseEnvJson<AdButlerConfig>(getEnvValue(process.env.NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP)),
adButlerConfigMobile: parseEnvJson<AdButlerConfig>(getEnvValue(process.env.NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE)),
sliseOn: getEnvValue(process.env.NEXT_PUBLIC_AD_SLISE_ON) === 'true',
}, },
web3: { web3: {
defaultWallet: getWeb3DefaultWallet(), defaultWallet: getWeb3DefaultWallet(),
......
...@@ -133,3 +133,5 @@ frontend: ...@@ -133,3 +133,5 @@ frontend:
_default: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY _default: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY
NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID: NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID:
_default: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID _default: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_GOOGLE_ANALYTICS_PROPERTY_ID
NEXT_PUBLIC_AD_SLISE_ON:
_default: 'true'
...@@ -46,6 +46,9 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -46,6 +46,9 @@ The app instance could be customized by passing following variables to NodeJS en
| NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME | `boolean` | Set to false if average block time is useless for the network | - | `true` | `false` | | NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME | `boolean` | Set to false if average block time is useless for the network | - | `true` | `false` |
| NEXT_PUBLIC_AD_DOMAIN_WITH_AD | `string` | The domain on which we display ads | - | - | `blockscout.com` | | NEXT_PUBLIC_AD_DOMAIN_WITH_AD | `string` | The domain on which we display ads | - | - | `blockscout.com` |
| NEXT_PUBLIC_AD_ADBUTLER_ON | `boolean` | Set to true to show Adbutler banner instead of Coinzilla banner | - | `false` | `true` | | NEXT_PUBLIC_AD_ADBUTLER_ON | `boolean` | Set to true to show Adbutler banner instead of Coinzilla banner | - | `false` | `true` |
| NEXT_PUBLIC_AD_ADBUTLER_CONFIG_DESKTOP | `{ id: string; width: string; height: string }` | Placement config for desktop Adbutler banner | - | - | `{'id':'123456','width':'728','height':'90'}` |
| NEXT_PUBLIC_AD_ADBUTLER_CONFIG_MOBILE | `{ id: string; width: number; height: number }` | Placement config for mobile Adbutler banner | - | - | `{'id':'654321','width':'300','height':'100'}` |
| NEXT_PUBLIC_AD_SLISE_ON | `boolean` | Set to true to show Slise banner instead of Coinzilla banner | - | `false` | `true` |
| NEXT_PUBLIC_API_SPEC_URL | `string` | Spec to be displayed on api-docs page | - | - | `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml` | | NEXT_PUBLIC_API_SPEC_URL | `string` | Spec to be displayed on api-docs page | - | - | `https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml` |
| NEXT_PUBLIC_GRAPHIQL_TRANSACTION | `string` | Txn hash for default query at GraphQl playground page | - | - | `0x69e3923eef50eada197c3336d546936d0c994211492c9f947a24c02827568f9f` | | NEXT_PUBLIC_GRAPHIQL_TRANSACTION | `string` | Txn hash for default query at GraphQl playground page | - | - | `0x69e3923eef50eada197c3336d546936d0c994211492c9f947a24c02827568f9f` |
| NEXT_PUBLIC_WEB3_DEFAULT_WALLET | `metamask` \| `coinbase`| Type of Web3 wallet which will be used by default to add tokens or chains to | - | `metamask` | `coinbase` | | NEXT_PUBLIC_WEB3_DEFAULT_WALLET | `metamask` \| `coinbase`| Type of Web3 wallet which will be used by default to add tokens or chains to | - | `metamask` | `coinbase` |
......
...@@ -18,8 +18,8 @@ export default function buildUrl<R extends ResourceName>( ...@@ -18,8 +18,8 @@ export default function buildUrl<R extends ResourceName>(
const url = new URL(compile(path)(pathParams), baseUrl); const url = new URL(compile(path)(pathParams), baseUrl);
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => { queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
// there are some pagination params that can be null for the next page // there are some pagination params that can be null or false for the next page
(value || value === null) && url.searchParams.append(key, String(value)); value !== undefined && value !== '' && url.searchParams.append(key, String(value));
}); });
return url.toString(); return url.toString();
......
import Base64 from 'crypto-js/enc-base64';
import sha256 from 'crypto-js/sha256';
import type CspDev from 'csp-dev'; import type CspDev from 'csp-dev';
import isSelfHosted from 'lib/isSelfHosted'; import isSelfHosted from 'lib/isSelfHosted';
import { connectAdbutler, placeAd } from 'ui/shared/ad/adbutlerScript';
export function ad(): CspDev.DirectiveDescriptor { export function ad(): CspDev.DirectiveDescriptor {
if (!isSelfHosted()) { if (!isSelfHosted()) {
...@@ -12,6 +15,7 @@ export function ad(): CspDev.DirectiveDescriptor { ...@@ -12,6 +15,7 @@ export function ad(): CspDev.DirectiveDescriptor {
'coinzilla.com', 'coinzilla.com',
'*.coinzilla.com', '*.coinzilla.com',
'request-global.czilladx.com', 'request-global.czilladx.com',
'*.slise.xyz',
], ],
'frame-src': [ 'frame-src': [
'request-global.czilladx.com', 'request-global.czilladx.com',
...@@ -19,10 +23,9 @@ export function ad(): CspDev.DirectiveDescriptor { ...@@ -19,10 +23,9 @@ export function ad(): CspDev.DirectiveDescriptor {
'script-src': [ 'script-src': [
'coinzillatag.com', 'coinzillatag.com',
'servedbyadbutler.com', 'servedbyadbutler.com',
// what hash is this? `'sha256-${ Base64.stringify(sha256(connectAdbutler)) }'`,
'\'sha256-wMOeDjJaOTjCfNjluteV+tSqHW547T89sgxd8W6tQJM=\'', `'sha256-${ Base64.stringify(sha256(placeAd)) }'`,
// what hash is this? '*.slise.xyz',
'\'sha256-FcyIn1h7zra8TVnnRhYrwrplxJW7dpD5TV7kP2AG/kI=\'',
], ],
'img-src': [ 'img-src': [
'servedbyadbutler.com', 'servedbyadbutler.com',
......
...@@ -2,6 +2,7 @@ import type { TokenCounters, TokenInfo } from 'types/api/token'; ...@@ -2,6 +2,7 @@ import type { TokenCounters, TokenInfo } from 'types/api/token';
export const tokenInfo: TokenInfo = { export const tokenInfo: TokenInfo = {
address: '0x55d536e4d6c1993d8ef2e2a4ef77f02088419420', address: '0x55d536e4d6c1993d8ef2e2a4ef77f02088419420',
circulating_market_cap: '117629601.61913824',
decimals: '18', decimals: '18',
exchange_rate: '2.0101', exchange_rate: '2.0101',
holders: '46554', holders: '46554',
...@@ -19,6 +20,7 @@ export const tokenCounters: TokenCounters = { ...@@ -19,6 +20,7 @@ export const tokenCounters: TokenCounters = {
export const tokenInfoERC20a: TokenInfo = { export const tokenInfoERC20a: TokenInfo = {
address: '0xb2a90505dc6680a7a695f7975d0d32EeF610f456', address: '0xb2a90505dc6680a7a695f7975d0d32EeF610f456',
circulating_market_cap: '117268489.23970924',
decimals: '18', decimals: '18',
exchange_rate: null, exchange_rate: null,
holders: '23', holders: '23',
...@@ -31,6 +33,7 @@ export const tokenInfoERC20a: TokenInfo = { ...@@ -31,6 +33,7 @@ export const tokenInfoERC20a: TokenInfo = {
export const tokenInfoERC20b: TokenInfo = { export const tokenInfoERC20b: TokenInfo = {
address: '0xc1116c98ba622a6218433fF90a2E40DEa482d7A7', address: '0xc1116c98ba622a6218433fF90a2E40DEa482d7A7',
circulating_market_cap: '115060192.36105014',
decimals: '6', decimals: '6',
exchange_rate: '0.982', exchange_rate: '0.982',
holders: '17', holders: '17',
...@@ -43,6 +46,7 @@ export const tokenInfoERC20b: TokenInfo = { ...@@ -43,6 +46,7 @@ export const tokenInfoERC20b: TokenInfo = {
export const tokenInfoERC20c: TokenInfo = { export const tokenInfoERC20c: TokenInfo = {
address: '0xc1116c98ba622a6218433fF90a2E40DEa482d7A7', address: '0xc1116c98ba622a6218433fF90a2E40DEa482d7A7',
circulating_market_cap: null,
decimals: '18', decimals: '18',
exchange_rate: '1328.89', exchange_rate: '1328.89',
holders: '17', holders: '17',
...@@ -55,6 +59,7 @@ export const tokenInfoERC20c: TokenInfo = { ...@@ -55,6 +59,7 @@ export const tokenInfoERC20c: TokenInfo = {
export const tokenInfoERC20d: TokenInfo = { export const tokenInfoERC20d: TokenInfo = {
address: '0xCc7bb2D219A0FC08033E130629C2B854b7bA9195', address: '0xCc7bb2D219A0FC08033E130629C2B854b7bA9195',
circulating_market_cap: null,
decimals: '18', decimals: '18',
exchange_rate: null, exchange_rate: null,
holders: '102625', holders: '102625',
...@@ -67,6 +72,7 @@ export const tokenInfoERC20d: TokenInfo = { ...@@ -67,6 +72,7 @@ export const tokenInfoERC20d: TokenInfo = {
export const tokenInfoERC20LongSymbol: TokenInfo = { export const tokenInfoERC20LongSymbol: TokenInfo = {
address: '0xCc7bb2D219A0FC08033E130629C2B854b7bA9195', address: '0xCc7bb2D219A0FC08033E130629C2B854b7bA9195',
circulating_market_cap: '112855875.75888918',
decimals: '18', decimals: '18',
exchange_rate: '1328.89', exchange_rate: '1328.89',
holders: '102625', holders: '102625',
...@@ -79,6 +85,7 @@ export const tokenInfoERC20LongSymbol: TokenInfo = { ...@@ -79,6 +85,7 @@ export const tokenInfoERC20LongSymbol: TokenInfo = {
export const tokenInfoERC721a: TokenInfo = { export const tokenInfoERC721a: TokenInfo = {
address: '0xDe7cAc71E072FCBd4453E5FB3558C2684d1F88A0', address: '0xDe7cAc71E072FCBd4453E5FB3558C2684d1F88A0',
circulating_market_cap: null,
decimals: null, decimals: null,
exchange_rate: null, exchange_rate: null,
holders: '7', holders: '7',
...@@ -91,6 +98,7 @@ export const tokenInfoERC721a: TokenInfo = { ...@@ -91,6 +98,7 @@ export const tokenInfoERC721a: TokenInfo = {
export const tokenInfoERC721b: TokenInfo = { export const tokenInfoERC721b: TokenInfo = {
address: '0xA8d5C7beEA8C9bB57f5fBa35fB638BF45550b11F', address: '0xA8d5C7beEA8C9bB57f5fBa35fB638BF45550b11F',
circulating_market_cap: null,
decimals: null, decimals: null,
exchange_rate: null, exchange_rate: null,
holders: '2', holders: '2',
...@@ -103,6 +111,7 @@ export const tokenInfoERC721b: TokenInfo = { ...@@ -103,6 +111,7 @@ export const tokenInfoERC721b: TokenInfo = {
export const tokenInfoERC721c: TokenInfo = { export const tokenInfoERC721c: TokenInfo = {
address: '0x47646F1d7dc4Dd2Db5a41D092e2Cf966e27A4992', address: '0x47646F1d7dc4Dd2Db5a41D092e2Cf966e27A4992',
circulating_market_cap: null,
decimals: null, decimals: null,
exchange_rate: null, exchange_rate: null,
holders: '12', holders: '12',
...@@ -115,6 +124,7 @@ export const tokenInfoERC721c: TokenInfo = { ...@@ -115,6 +124,7 @@ export const tokenInfoERC721c: TokenInfo = {
export const tokenInfoERC721LongSymbol: TokenInfo = { export const tokenInfoERC721LongSymbol: TokenInfo = {
address: '0x47646F1d7dc4Dd2Db5a41D092e2Cf966e27A4992', address: '0x47646F1d7dc4Dd2Db5a41D092e2Cf966e27A4992',
circulating_market_cap: null,
decimals: null, decimals: null,
exchange_rate: null, exchange_rate: null,
holders: '12', holders: '12',
...@@ -127,6 +137,7 @@ export const tokenInfoERC721LongSymbol: TokenInfo = { ...@@ -127,6 +137,7 @@ export const tokenInfoERC721LongSymbol: TokenInfo = {
export const tokenInfoERC1155a: TokenInfo = { export const tokenInfoERC1155a: TokenInfo = {
address: '0x4b333DEd10c7ca855EA2C8D4D90A0a8b73788c8e', address: '0x4b333DEd10c7ca855EA2C8D4D90A0a8b73788c8e',
circulating_market_cap: null,
decimals: null, decimals: null,
exchange_rate: null, exchange_rate: null,
holders: '22', holders: '22',
...@@ -139,6 +150,7 @@ export const tokenInfoERC1155a: TokenInfo = { ...@@ -139,6 +150,7 @@ export const tokenInfoERC1155a: TokenInfo = {
export const tokenInfoERC1155b: TokenInfo = { export const tokenInfoERC1155b: TokenInfo = {
address: '0xf4b71b179132ad457f6bcae2a55efa9e4b26eefc', address: '0xf4b71b179132ad457f6bcae2a55efa9e4b26eefc',
circulating_market_cap: null,
decimals: null, decimals: null,
exchange_rate: null, exchange_rate: null,
holders: '100', holders: '100',
...@@ -151,6 +163,7 @@ export const tokenInfoERC1155b: TokenInfo = { ...@@ -151,6 +163,7 @@ export const tokenInfoERC1155b: TokenInfo = {
export const tokenInfoERC1155WithoutName: TokenInfo = { export const tokenInfoERC1155WithoutName: TokenInfo = {
address: '0x4b333DEd10c7ca855EA2C8D4D90A0a8b73788c8e', address: '0x4b333DEd10c7ca855EA2C8D4D90A0a8b73788c8e',
circulating_market_cap: null,
decimals: null, decimals: null,
exchange_rate: null, exchange_rate: null,
holders: '22', holders: '22',
......
...@@ -23,6 +23,7 @@ export const erc20: TokenTransfer = { ...@@ -23,6 +23,7 @@ export const erc20: TokenTransfer = {
}, },
token: { token: {
address: '0x55d536e4d6c1993d8ef2e2a4ef77f02088419420', address: '0x55d536e4d6c1993d8ef2e2a4ef77f02088419420',
circulating_market_cap: '117629601.61913824',
decimals: '18', decimals: '18',
exchange_rate: null, exchange_rate: null,
holders: '46554', holders: '46554',
...@@ -67,6 +68,7 @@ export const erc721: TokenTransfer = { ...@@ -67,6 +68,7 @@ export const erc721: TokenTransfer = {
}, },
token: { token: {
address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29', address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29',
circulating_market_cap: null,
decimals: null, decimals: null,
exchange_rate: null, exchange_rate: null,
holders: '63090', holders: '63090',
...@@ -110,6 +112,7 @@ export const erc1155A: TokenTransfer = { ...@@ -110,6 +112,7 @@ export const erc1155A: TokenTransfer = {
}, },
token: { token: {
address: '0xF56b7693E4212C584de4a83117f805B8E89224CB', address: '0xF56b7693E4212C584de4a83117f805B8E89224CB',
circulating_market_cap: null,
decimals: null, decimals: null,
exchange_rate: null, exchange_rate: null,
holders: '1', holders: '1',
......
...@@ -24,6 +24,7 @@ export const mintToken: TxStateChange = { ...@@ -24,6 +24,7 @@ export const mintToken: TxStateChange = {
is_miner: false, is_miner: false,
token: { token: {
address: '0x8977EA6C55e878125d1bF3433EBf72138B7a4543', address: '0x8977EA6C55e878125d1bF3433EBf72138B7a4543',
circulating_market_cap: null,
decimals: null, decimals: null,
exchange_rate: null, exchange_rate: null,
holders: '9191', holders: '9191',
...@@ -60,6 +61,7 @@ export const receiveMintedToken: TxStateChange = { ...@@ -60,6 +61,7 @@ export const receiveMintedToken: TxStateChange = {
is_miner: false, is_miner: false,
token: { token: {
address: '0x8977EA6C55e878125d1bF3433EBf72138B7a4543', address: '0x8977EA6C55e878125d1bF3433EBf72138B7a4543',
circulating_market_cap: null,
decimals: null, decimals: null,
exchange_rate: null, exchange_rate: null,
holders: '9191', holders: '9191',
...@@ -89,6 +91,7 @@ export const transfer1155Token: TxStateChange = { ...@@ -89,6 +91,7 @@ export const transfer1155Token: TxStateChange = {
is_miner: false, is_miner: false,
token: { token: {
address: '0x56Cc277717106E528A9FcC2CD342Ff98db758041', address: '0x56Cc277717106E528A9FcC2CD342Ff98db758041',
circulating_market_cap: null,
decimals: null, decimals: null,
exchange_rate: null, exchange_rate: null,
holders: '50413', holders: '50413',
......
...@@ -8,6 +8,7 @@ import { generateListStub } from './utils'; ...@@ -8,6 +8,7 @@ import { generateListStub } from './utils';
export const TOKEN_INFO_ERC_20: TokenInfo<'ERC-20'> = { export const TOKEN_INFO_ERC_20: TokenInfo<'ERC-20'> = {
address: ADDRESS_HASH, address: ADDRESS_HASH,
circulating_market_cap: '117629601.61913824',
decimals: '18', decimals: '18',
exchange_rate: '0.999997', exchange_rate: '0.999997',
holders: '16026', holders: '16026',
...@@ -20,11 +21,13 @@ export const TOKEN_INFO_ERC_20: TokenInfo<'ERC-20'> = { ...@@ -20,11 +21,13 @@ export const TOKEN_INFO_ERC_20: TokenInfo<'ERC-20'> = {
export const TOKEN_INFO_ERC_721: TokenInfo<'ERC-721'> = { export const TOKEN_INFO_ERC_721: TokenInfo<'ERC-721'> = {
...TOKEN_INFO_ERC_20, ...TOKEN_INFO_ERC_20,
circulating_market_cap: null,
type: 'ERC-721', type: 'ERC-721',
}; };
export const TOKEN_INFO_ERC_1155: TokenInfo<'ERC-1155'> = { export const TOKEN_INFO_ERC_1155: TokenInfo<'ERC-1155'> = {
...TOKEN_INFO_ERC_20, ...TOKEN_INFO_ERC_20,
circulating_market_cap: null,
type: 'ERC-1155', type: 'ERC-1155',
}; };
......
...@@ -13,6 +13,7 @@ export interface TokenInfo<T extends TokenType = TokenType> { ...@@ -13,6 +13,7 @@ export interface TokenInfo<T extends TokenType = TokenType> {
exchange_rate: string | null; exchange_rate: string | null;
total_supply: string | null; total_supply: string | null;
icon_url: string | null; icon_url: string | null;
circulating_market_cap: string | null;
} }
export interface TokenCounters { export interface TokenCounters {
......
...@@ -45,7 +45,6 @@ export type TxStateChanges = { ...@@ -45,7 +45,6 @@ export type TxStateChanges = {
items: Array<TxStateChange>; items: Array<TxStateChange>;
next_page_params: { next_page_params: {
items_count: number; items_count: number;
// ???
state_changes: null; state_changes: null;
}; };
}; };
export type AdButlerConfig = {
id: string;
width: string;
height: string;
}
...@@ -2,7 +2,6 @@ import React from 'react'; ...@@ -2,7 +2,6 @@ import React from 'react';
import type { RoutedSubTab } from 'ui/shared/Tabs/types'; import type { RoutedSubTab } from 'ui/shared/Tabs/types';
import { ContractContextProvider } from 'ui/address/contract/context';
import RoutedTabs from 'ui/shared/Tabs/RoutedTabs'; import RoutedTabs from 'ui/shared/Tabs/RoutedTabs';
import Web3ModalProvider from 'ui/shared/Web3ModalProvider'; import Web3ModalProvider from 'ui/shared/Web3ModalProvider';
...@@ -15,17 +14,17 @@ const TAB_LIST_PROPS = { ...@@ -15,17 +14,17 @@ const TAB_LIST_PROPS = {
columnGap: 3, columnGap: 3,
}; };
const AddressContract = ({ addressHash, tabs }: Props) => { const AddressContract = ({ tabs }: Props) => {
const fallback = React.useCallback(() => { const fallback = React.useCallback(() => {
const noProviderTabs = tabs.filter(({ id }) => id === 'contact_code'); const noProviderTabs = tabs.filter(({ id }) => id === 'contact_code');
return <RoutedTabs tabs={ noProviderTabs } variant="outline" colorScheme="gray" size="sm" tabListProps={ TAB_LIST_PROPS }/>; return (
<RoutedTabs tabs={ noProviderTabs } variant="outline" colorScheme="gray" size="sm" tabListProps={ TAB_LIST_PROPS }/>
);
}, [ tabs ]); }, [ tabs ]);
return ( return (
<Web3ModalProvider fallback={ fallback }> <Web3ModalProvider fallback={ fallback }>
<ContractContextProvider addressHash={ addressHash }> <RoutedTabs tabs={ tabs } variant="outline" colorScheme="gray" size="sm" tabListProps={ TAB_LIST_PROPS }/>
<RoutedTabs tabs={ tabs } variant="outline" colorScheme="gray" size="sm" tabListProps={ TAB_LIST_PROPS }/>
</ContractContextProvider>
</Web3ModalProvider> </Web3ModalProvider>
); );
}; };
......
...@@ -212,16 +212,10 @@ const ContractCode = ({ addressHash, noSocket }: Props) => { ...@@ -212,16 +212,10 @@ const ContractCode = ({ addressHash, noSocket }: Props) => {
isLoading={ isPlaceholderData } isLoading={ isPlaceholderData }
/> />
) } ) }
{ data?.source_code && ( { data?.is_verified && (
<ContractSourceCode <ContractSourceCode
data={ data.source_code }
hasSol2Yml={ Boolean(data.can_be_visualized_via_sol2uml) }
address={ addressHash } address={ addressHash }
isViper={ Boolean(data.is_vyper_contract) } implementationAddress={ addressInfo?.implementation_address ?? undefined }
filePath={ data.file_path }
additionalSource={ data.additional_sources }
remappings={ data.compiler_settings?.remappings }
isLoading={ isPlaceholderData }
/> />
) } ) }
{ data?.compiler_settings ? ( { data?.compiler_settings ? (
......
import { Flex, Skeleton, Text, Tooltip } from '@chakra-ui/react'; import { Box, Flex, Select, Skeleton, Text, Tooltip } from '@chakra-ui/react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
import type { SmartContract } from 'types/api/contract'; import type { SmartContract } from 'types/api/contract';
import type { ArrayElement } from 'types/utils';
import useApiQuery from 'lib/api/useApiQuery';
import * as stubs from 'stubs/contract';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
import LinkInternal from 'ui/shared/LinkInternal'; import LinkInternal from 'ui/shared/LinkInternal';
import CodeEditor from 'ui/shared/monaco/CodeEditor'; import CodeEditor from 'ui/shared/monaco/CodeEditor';
import formatFilePath from 'ui/shared/monaco/utils/formatFilePath'; import formatFilePath from 'ui/shared/monaco/utils/formatFilePath';
const SOURCE_CODE_OPTIONS = [
{ id: 'primary', label: 'Proxy' } as const,
{ id: 'secondary', label: 'Implementation' } as const,
];
type SourceCodeType = ArrayElement<typeof SOURCE_CODE_OPTIONS>['id'];
function getEditorData(contractInfo: SmartContract | undefined) {
if (!contractInfo || !contractInfo.source_code) {
return undefined;
}
const defaultName = contractInfo.is_vyper_contract ? '/index.vy' : '/index.sol';
return [
{ file_path: formatFilePath(contractInfo.file_path || defaultName), source_code: contractInfo.source_code },
...(contractInfo.additional_sources || []).map((source) => ({ ...source, file_path: formatFilePath(source.file_path) })),
];
}
interface Props { interface Props {
data: string;
hasSol2Yml: boolean;
address?: string; address?: string;
isViper: boolean; implementationAddress?: string;
filePath?: string;
additionalSource?: SmartContract['additional_sources'];
remappings?: Array<string>;
isLoading?: boolean;
} }
const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, additionalSource, remappings, isLoading }: Props) => { const ContractSourceCode = ({ address, implementationAddress }: Props) => {
const [ sourceType, setSourceType ] = React.useState<SourceCodeType>('primary');
const primaryContractQuery = useApiQuery('contract', {
pathParams: { hash: address },
queryOptions: {
enabled: Boolean(address),
refetchOnMount: false,
placeholderData: stubs.CONTRACT_CODE_VERIFIED,
},
});
const secondaryContractQuery = useApiQuery('contract', {
pathParams: { hash: implementationAddress },
queryOptions: {
enabled: Boolean(implementationAddress),
refetchOnMount: false,
placeholderData: stubs.CONTRACT_CODE_VERIFIED,
},
});
const isLoading = implementationAddress ?
primaryContractQuery.isPlaceholderData || secondaryContractQuery.isPlaceholderData :
primaryContractQuery.isPlaceholderData;
const primaryEditorData = React.useMemo(() => {
return getEditorData(primaryContractQuery.data);
}, [ primaryContractQuery.data ]);
const secondaryEditorData = React.useMemo(() => {
return getEditorData(secondaryContractQuery.data);
}, [ secondaryContractQuery.data ]);
const activeContract = sourceType === 'secondary' ? secondaryContractQuery.data : primaryContractQuery.data;
const activeContractData = sourceType === 'secondary' ? secondaryEditorData : primaryEditorData;
const heading = ( const heading = (
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 }> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>
<span>Contract source code</span> <span>Contract source code</span>
<Text whiteSpace="pre" as="span" variant="secondary"> ({ isViper ? 'Vyper' : 'Solidity' })</Text> <Text whiteSpace="pre" as="span" variant="secondary"> ({ activeContract?.is_vyper_contract ? 'Vyper' : 'Solidity' })</Text>
</Skeleton> </Skeleton>
); );
const diagramLink = hasSol2Yml && address ? ( const diagramLinkAddress = (() => {
if (!activeContract?.can_be_visualized_via_sol2uml) {
return;
}
return sourceType === 'secondary' ? implementationAddress : address;
})();
const diagramLink = diagramLinkAddress ? (
<Tooltip label="Visualize contract code using Sol2Uml JS library"> <Tooltip label="Visualize contract code using Sol2Uml JS library">
<LinkInternal <LinkInternal
href={ route({ pathname: '/visualize/sol2uml', query: { address } }) } href={ route({ pathname: '/visualize/sol2uml', query: { address: diagramLinkAddress } }) }
ml="auto" ml="auto"
> >
<Skeleton isLoaded={ !isLoading }> <Skeleton isLoaded={ !isLoading }>
...@@ -39,27 +96,66 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi ...@@ -39,27 +96,66 @@ const ContractSourceCode = ({ data, hasSol2Yml, address, isViper, filePath, addi
</Skeleton> </Skeleton>
</LinkInternal> </LinkInternal>
</Tooltip> </Tooltip>
) : <Box ml="auto"/>;
const copyToClipboard = activeContractData?.length === 1 ?
<CopyToClipboard text={ activeContractData[0].source_code } isLoading={ isLoading } ml={ 3 }/> :
null;
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setSourceType(event.target.value as SourceCodeType);
}, []);
const editorSourceTypeSelector = !secondaryContractQuery.isPlaceholderData && secondaryContractQuery.data?.source_code ? (
<Select
size="xs"
value={ sourceType }
onChange={ handleSelectChange }
focusBorderColor="none"
w="auto"
ml={ 3 }
borderRadius="base"
>
{ SOURCE_CODE_OPTIONS.map((option) => <option key={ option.id } value={ option.id }>{ option.label }</option>) }
</Select>
) : null; ) : null;
const editorData = React.useMemo(() => { const content = (() => {
const defaultName = isViper ? '/index.vy' : '/index.sol'; if (isLoading) {
return [ return <Skeleton h="557px" w="100%"/>;
{ file_path: formatFilePath(filePath || defaultName), source_code: data }, }
...(additionalSource || []).map((source) => ({ ...source, file_path: formatFilePath(source.file_path) })) ];
}, [ additionalSource, data, filePath, isViper ]);
const copyToClipboard = editorData.length === 1 ? if (!primaryEditorData) {
<CopyToClipboard text={ editorData[0].source_code } isLoading={ isLoading } ml={ 3 }/> : return null;
null; }
return (
<>
<Box display={ sourceType === 'primary' ? 'block' : 'none' }>
<CodeEditor data={ primaryEditorData } remappings={ primaryContractQuery.data?.compiler_settings?.remappings }/>
</Box>
{ secondaryEditorData && (
<Box display={ sourceType === 'secondary' ? 'block' : 'none' }>
<CodeEditor data={ secondaryEditorData } remappings={ secondaryContractQuery.data?.compiler_settings?.remappings }/>
</Box>
) }
</>
);
})();
if (!primaryEditorData) {
return null;
}
return ( return (
<section> <section>
<Flex justifyContent="space-between" alignItems="center" mb={ 3 }> <Flex justifyContent="space-between" alignItems="center" mb={ 3 }>
{ heading } { heading }
{ editorSourceTypeSelector }
{ diagramLink } { diagramLink }
{ copyToClipboard } { copyToClipboard }
</Flex> </Flex>
{ isLoading ? <Skeleton h="557px" w="100%"/> : <CodeEditor data={ editorData } remappings={ remappings }/> } { content }
</section> </section>
); );
}; };
......
...@@ -9,12 +9,12 @@ import ContractMethodsAccordion from 'ui/address/contract/ContractMethodsAccordi ...@@ -9,12 +9,12 @@ import ContractMethodsAccordion from 'ui/address/contract/ContractMethodsAccordi
import ContentLoader from 'ui/shared/ContentLoader'; import ContentLoader from 'ui/shared/ContentLoader';
import DataFetchAlert from 'ui/shared/DataFetchAlert'; import DataFetchAlert from 'ui/shared/DataFetchAlert';
import { useContractContext } from './context';
import ContractConnectWallet from './ContractConnectWallet'; import ContractConnectWallet from './ContractConnectWallet';
import ContractCustomAbiAlert from './ContractCustomAbiAlert'; import ContractCustomAbiAlert from './ContractCustomAbiAlert';
import ContractImplementationAddress from './ContractImplementationAddress'; import ContractImplementationAddress from './ContractImplementationAddress';
import ContractMethodCallable from './ContractMethodCallable'; import ContractMethodCallable from './ContractMethodCallable';
import ContractWriteResult from './ContractWriteResult'; import ContractWriteResult from './ContractWriteResult';
import useContractAbi from './useContractAbi';
import { getNativeCoinValue } from './utils'; import { getNativeCoinValue } from './utils';
interface Props { interface Props {
...@@ -39,18 +39,7 @@ const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => { ...@@ -39,18 +39,7 @@ const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => {
}, },
}); });
const { contractInfo, customInfo, proxyInfo } = useContractContext(); const contractAbi = useContractAbi({ addressHash, isProxy, isCustomAbi });
const abi = (() => {
if (isProxy) {
return proxyInfo?.abi;
}
if (isCustomAbi) {
return customInfo?.abi;
}
return contractInfo?.abi;
})();
const handleMethodFormSubmit = React.useCallback(async(item: SmartContractWriteMethod, args: Array<string | Array<unknown>>) => { const handleMethodFormSubmit = React.useCallback(async(item: SmartContractWriteMethod, args: Array<string | Array<unknown>>) => {
if (!isConnected) { if (!isConnected) {
...@@ -61,7 +50,7 @@ const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => { ...@@ -61,7 +50,7 @@ const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => {
await switchNetworkAsync?.(Number(config.network.id)); await switchNetworkAsync?.(Number(config.network.id));
} }
if (!abi) { if (!contractAbi) {
throw new Error('Something went wrong. Try again later.'); throw new Error('Something went wrong. Try again later.');
} }
...@@ -84,14 +73,14 @@ const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => { ...@@ -84,14 +73,14 @@ const ContractWrite = ({ addressHash, isProxy, isCustomAbi }: Props) => {
const hash = await walletClient?.writeContract({ const hash = await walletClient?.writeContract({
args: _args, args: _args,
abi: abi, abi: contractAbi,
functionName: methodName, functionName: methodName,
address: addressHash as `0x${ string }`, address: addressHash as `0x${ string }`,
value: value as undefined, value: value as undefined,
}); });
return { hash }; return { hash };
}, [ isConnected, chain, abi, walletClient, addressHash, switchNetworkAsync ]); }, [ isConnected, chain, contractAbi, walletClient, addressHash, switchNetworkAsync ]);
const renderContent = React.useCallback((item: SmartContractWriteMethod, index: number, id: number) => { const renderContent = React.useCallback((item: SmartContractWriteMethod, index: number, id: number) => {
return ( return (
......
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import type { Abi } from 'abitype';
import React from 'react'; import React from 'react';
import type { Address } from 'types/api/address'; import type { Address } from 'types/api/address';
import type { SmartContract } from 'types/api/contract';
import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery'; import useApiQuery, { getResourceKey } from 'lib/api/useApiQuery';
type ProviderProps = { interface Params {
addressHash?: string; addressHash?: string;
children: React.ReactNode; isProxy?: boolean;
isCustomAbi?: boolean;
} }
type TContractContext = { export default function useContractAbi({ addressHash, isProxy, isCustomAbi }: Params): Abi | undefined {
contractInfo: SmartContract | undefined;
proxyInfo: SmartContract | undefined;
customInfo: SmartContract | undefined;
};
const ContractContext = React.createContext<TContractContext>({
proxyInfo: undefined,
contractInfo: undefined,
customInfo: undefined,
});
export function ContractContextProvider({ addressHash, children }: ProviderProps) {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { data: contractInfo } = useApiQuery('contract', { const { data: contractInfo } = useApiQuery('contract', {
...@@ -55,23 +44,15 @@ export function ContractContextProvider({ addressHash, children }: ProviderProps ...@@ -55,23 +44,15 @@ export function ContractContextProvider({ addressHash, children }: ProviderProps
}, },
}); });
const value = React.useMemo(() => ({ return React.useMemo(() => {
proxyInfo, if (isProxy) {
contractInfo, return proxyInfo?.abi ?? undefined;
customInfo, }
} as TContractContext), [ proxyInfo, contractInfo, customInfo ]);
return ( if (isCustomAbi) {
<ContractContext.Provider value={ value }> return customInfo;
{ children } }
</ContractContext.Provider>
);
}
export function useContractContext() { return contractInfo?.abi ?? undefined;
const context = React.useContext(ContractContext); }, [ contractInfo?.abi, customInfo, isCustomAbi, isProxy, proxyInfo?.abi ]);
if (context === undefined) {
throw new Error('useContractContext must be used within a ContractContextProvider');
}
return context;
} }
...@@ -94,20 +94,25 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre ...@@ -94,20 +94,25 @@ const AddressVerificationStepSignature = ({ address, signingMessage, contractCre
}, [ clearErrors ]); }, [ clearErrors ]);
const handleOpenWeb3Modal = React.useCallback(() => { const handleOpenWeb3Modal = React.useCallback(() => {
clearErrors('root');
openWeb3Modal(); openWeb3Modal();
}, [ openWeb3Modal ]); }, [ clearErrors, openWeb3Modal ]);
const handleWeb3SignClick = React.useCallback(() => { const handleWeb3SignClick = React.useCallback(() => {
clearErrors('root');
if (!isConnected) { if (!isConnected) {
return setError('root', { type: 'manual', message: 'Please connect to your Web3 wallet first' }); return setError('root', { type: 'manual', message: 'Please connect to your Web3 wallet first' });
} }
const message = getValues('message'); const message = getValues('message');
signMessage({ message }); signMessage({ message });
}, [ getValues, signMessage, isConnected, setError ]); }, [ clearErrors, isConnected, getValues, signMessage, setError ]);
const handleManualSignClick = React.useCallback(() => { const handleManualSignClick = React.useCallback(() => {
clearErrors('root');
onSubmit(); onSubmit();
}, [ onSubmit ]); }, [ clearErrors, onSubmit ]);
const button = (() => { const button = (() => {
if (signMethod === 'manually') { if (signMethod === 'manually') {
......
...@@ -86,11 +86,11 @@ const AddressPageContent = () => { ...@@ -86,11 +86,11 @@ const AddressPageContent = () => {
return 'Contract'; return 'Contract';
}, },
component: <AddressContract tabs={ contractTabs } addressHash={ hash }/>, component: <AddressContract tabs={ contractTabs }/>,
subTabs: contractTabs.map(tab => tab.id), subTabs: contractTabs.map(tab => tab.id),
} : undefined, } : undefined,
].filter(Boolean); ].filter(Boolean);
}, [ addressQuery.data, contractTabs, hash ]); }, [ addressQuery.data, contractTabs ]);
const tags = ( const tags = (
<EntityTags <EntityTags
......
...@@ -178,7 +178,7 @@ const TokenPageContent = () => { ...@@ -178,7 +178,7 @@ const TokenPageContent = () => {
return 'Contract'; return 'Contract';
}, },
component: <AddressContract tabs={ contractTabs } addressHash={ hashString }/>, component: <AddressContract tabs={ contractTabs }/>,
subTabs: contractTabs.map(tab => tab.id), subTabs: contractTabs.map(tab => tab.id),
} : undefined, } : undefined,
].filter(Boolean); ].filter(Boolean);
......
...@@ -16,6 +16,7 @@ const DefaultView = () => { ...@@ -16,6 +16,7 @@ const DefaultView = () => {
const tokenData: TokenInfo = { const tokenData: TokenInfo = {
address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29', address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29',
circulating_market_cap: '117629601.61913824',
type: 'ERC-20', type: 'ERC-20',
symbol: 'SHAAAAAAAAAAAAA', symbol: 'SHAAAAAAAAAAAAA',
name: null, name: null,
......
...@@ -19,6 +19,7 @@ const LongNameAndManyTags = () => { ...@@ -19,6 +19,7 @@ const LongNameAndManyTags = () => {
const tokenData: TokenInfo = { const tokenData: TokenInfo = {
address: '0xa77A39CC9680B10C00af5D4ABFc92e1F07406c64', address: '0xa77A39CC9680B10C00af5D4ABFc92e1F07406c64',
circulating_market_cap: null,
decimals: null, decimals: null,
exchange_rate: null, exchange_rate: null,
holders: '294', holders: '294',
......
...@@ -12,6 +12,7 @@ test.use(devices['iPhone 13 Pro']); ...@@ -12,6 +12,7 @@ test.use(devices['iPhone 13 Pro']);
test('unnamed', async({ mount }) => { test('unnamed', async({ mount }) => {
const data: TokenInfo = { const data: TokenInfo = {
address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29', address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29',
circulating_market_cap: '117629601.61913824',
type: 'ERC-20', type: 'ERC-20',
symbol: 'xDAI', symbol: 'xDAI',
name: null, name: null,
...@@ -33,6 +34,7 @@ test('unnamed', async({ mount }) => { ...@@ -33,6 +34,7 @@ test('unnamed', async({ mount }) => {
test('named', async({ mount }) => { test('named', async({ mount }) => {
const data: TokenInfo = { const data: TokenInfo = {
address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29', address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29',
circulating_market_cap: '117629601.61913824',
type: 'ERC-20', type: 'ERC-20',
symbol: 'SHA', symbol: 'SHA',
name: 'Shavuha token', name: 'Shavuha token',
...@@ -55,6 +57,7 @@ test('with logo and long symbol', async({ mount, page }) => { ...@@ -55,6 +57,7 @@ test('with logo and long symbol', async({ mount, page }) => {
const API_URL = 'https://example.com/logo.png'; const API_URL = 'https://example.com/logo.png';
const data: TokenInfo = { const data: TokenInfo = {
address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29', address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29',
circulating_market_cap: '117629601.61913824',
type: 'ERC-20', type: 'ERC-20',
symbol: 'SHAAAAAAAAAAAAA', symbol: 'SHAAAAAAAAAAAAA',
name: null, name: null,
......
...@@ -8,6 +8,7 @@ import isSelfHosted from 'lib/isSelfHosted'; ...@@ -8,6 +8,7 @@ import isSelfHosted from 'lib/isSelfHosted';
import AdbutlerBanner from './AdbutlerBanner'; import AdbutlerBanner from './AdbutlerBanner';
import CoinzillaBanner from './CoinzillaBanner'; import CoinzillaBanner from './CoinzillaBanner';
import SliseBanner from './SliseBanner';
const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: boolean }) => { const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: boolean }) => {
const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED, useAppContext().cookies); const hasAdblockCookie = cookies.get(cookies.NAMES.ADBLOCK_DETECTED, useAppContext().cookies);
...@@ -16,14 +17,24 @@ const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: bo ...@@ -16,14 +17,24 @@ const AdBanner = ({ className, isLoading }: { className?: string; isLoading?: bo
return null; return null;
} }
const content = appConfig.ad.adButlerOn ? <AdbutlerBanner/> : <CoinzillaBanner/>; const content = (() => {
if (appConfig.ad.adButlerOn) {
return <AdbutlerBanner/>;
}
if (appConfig.ad.sliseOn) {
return <SliseBanner/>;
}
return <CoinzillaBanner/>;
})();
return ( return (
<Skeleton <Skeleton
className={ className } className={ className }
isLoaded={ !isLoading } isLoaded={ !isLoading }
borderRadius="none" borderRadius="none"
maxW={ appConfig.ad.adButlerOn ? '760px' : '728px' } maxW={ appConfig.ad.adButlerOn ? appConfig.ad.adButlerConfigDesktop?.width : '728px' }
w="100%" w="100%"
> >
{ content } { content }
......
...@@ -3,29 +3,14 @@ import { Flex, chakra } from '@chakra-ui/react'; ...@@ -3,29 +3,14 @@ import { Flex, chakra } from '@chakra-ui/react';
import Script from 'next/script'; import Script from 'next/script';
import React from 'react'; import React from 'react';
const scriptText1 = `if (!window.AdButler){(function(){var s = document.createElement("script"); s.async = true; s.type = "text/javascript";s.src = 'https://servedbyadbutler.com/app.js';var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(s, n);}());}`; import { connectAdbutler, placeAd } from 'ui/shared/ad/adbutlerScript';
const scriptText2 = `
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
var abkw = window.abkw || '';
const isMobile = window.matchMedia("only screen and (max-width: 760px)").matches;
if (isMobile) {
var plc539876 = window.plc539876 || 0;
document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_539876_'+plc539876+'"></'+'div>';
document.getElementById("ad-banner").className = "ad-container mb-3";
AdButler.ads.push({handler: function(opt){ AdButler.register(182226, 539876, [320,100], 'placement_539876_'+opt.place, opt); }, opt: { place: plc539876++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
} else {
var plc523705 = window.plc523705 || 0;
document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_523705_'+plc523705+'"></'+'div>';
AdButler.ads.push({handler: function(opt){ AdButler.register(182226, 523705, [728,90], 'placement_523705_'+opt.place, opt); }, opt: { place: plc523705++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
}
`;
const AdbutlerBanner = ({ className }: { className?: string }) => { const AdbutlerBanner = ({ className }: { className?: string }) => {
return ( return (
<Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}> <Flex className={ className } id="adBanner" h={{ base: '100px', lg: '90px' }}>
<div id="ad-banner"></div> <div id="ad-banner"></div>
<Script id="ad-butler-1">{ scriptText1 }</Script> <Script id="ad-butler-1">{ connectAdbutler }</Script>
<Script id="ad-butler-2">{ scriptText2 }</Script> <Script id="ad-butler-2">{ placeAd }</Script>
</Flex> </Flex>
); );
}; };
......
import { Flex, chakra } from '@chakra-ui/react';
import { SliseAd } from '@slise/embed-react';
import React from 'react';
const SliseBanner = ({ className }: { className?: string }) => {
return (
<>
<Flex className={ className } h="90px" display={{ base: 'none', lg: 'flex' }}>
<SliseAd
slotId="leaderboard"
pub="pub-10"
format="728x90"
style={{ width: '728px', height: '90px' }}/>
</Flex>
<Flex className={ className } h="90px" display={{ base: 'flex', lg: 'none' }}>
<SliseAd
slotId="leaderboard"
pub="pub-10"
format="270x90"
style={{ width: '270px', height: '90px' }}/>
</Flex>
</>
);
};
export default chakra(SliseBanner);
/* eslint-disable max-len */
import appConfig from 'configs/app/config';
export const connectAdbutler = `if (!window.AdButler){(function(){var s = document.createElement("script"); s.async = true; s.type = "text/javascript";s.src = 'https://servedbyadbutler.com/app.js';var n = document.getElementsByTagName("script")[0]; n.parentNode.insertBefore(s, n);}());}`;
export const placeAd = `
var AdButler = AdButler || {}; AdButler.ads = AdButler.ads || [];
var abkw = window.abkw || '';
const isMobile = window.matchMedia("only screen and (max-width: 760px)").matches;
if (isMobile) {
var plc${ appConfig.ad.adButlerConfigMobile?.id } = window.plc${ appConfig.ad.adButlerConfigMobile?.id } || 0;
document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_${ appConfig.ad.adButlerConfigMobile?.id }_'+plc${ appConfig.ad.adButlerConfigMobile?.id }+'"></'+'div>';
document.getElementById("ad-banner").className = "ad-container mb-3";
AdButler.ads.push({handler: function(opt){ AdButler.register(182226, ${ appConfig.ad.adButlerConfigMobile?.id }, [${ appConfig.ad.adButlerConfigMobile?.width },${ appConfig.ad.adButlerConfigMobile?.height }], 'placement_${ appConfig.ad.adButlerConfigMobile?.id }_'+opt.place, opt); }, opt: { place: plc${ appConfig.ad.adButlerConfigMobile?.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
} else {
var plc${ appConfig.ad.adButlerConfigDesktop?.id } = window.plc${ appConfig.ad.adButlerConfigDesktop?.id } || 0;
document.getElementById('ad-banner').innerHTML += '<'+'div id="placement_${ appConfig.ad.adButlerConfigDesktop?.id }_'+plc${ appConfig.ad.adButlerConfigDesktop?.id }+'"></'+'div>';
AdButler.ads.push({handler: function(opt){ AdButler.register(182226, ${ appConfig.ad.adButlerConfigDesktop?.id }, [${ appConfig.ad.adButlerConfigDesktop?.width },${ appConfig.ad.adButlerConfigDesktop?.height }], 'placement_${ appConfig.ad.adButlerConfigDesktop?.id }_'+opt.place, opt); }, opt: { place: plc${ appConfig.ad.adButlerConfigDesktop?.id }++, keywords: abkw, domain: 'servedbyadbutler.com', click:'CLICK_MACRO_PLACEHOLDER' }});
}
`;
import { Box, Flex, Grid, Link, Skeleton } from '@chakra-ui/react'; import { Box, Flex, Grid, Link, Skeleton } from '@chakra-ui/react';
import type { UseQueryResult } from '@tanstack/react-query'; import type { UseQueryResult } from '@tanstack/react-query';
import BigNumber from 'bignumber.js';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { scroller } from 'react-scroll'; import { scroller } from 'react-scroll';
...@@ -65,17 +66,16 @@ const TokenDetails = ({ tokenQuery }: Props) => { ...@@ -65,17 +66,16 @@ const TokenDetails = ({ tokenQuery }: Props) => {
const { const {
exchange_rate: exchangeRate, exchange_rate: exchangeRate,
total_supply: totalSupply, total_supply: totalSupply,
circulating_market_cap: marketCap,
decimals, decimals,
symbol, symbol,
type, type,
} = tokenQuery.data || {}; } = tokenQuery.data || {};
let marketcap;
let totalSupplyValue; let totalSupplyValue;
if (type === 'ERC-20') { if (type === 'ERC-20') {
const totalValue = totalSupply ? getCurrencyValue({ value: totalSupply, accuracy: 3, accuracyUsd: 2, exchangeRate, decimals }) : undefined; const totalValue = totalSupply ? getCurrencyValue({ value: totalSupply, accuracy: 3, accuracyUsd: 2, exchangeRate, decimals }) : undefined;
marketcap = totalValue?.usd;
totalSupplyValue = totalValue?.valueStr; totalSupplyValue = totalValue?.valueStr;
} else { } else {
totalSupplyValue = Number(totalSupply).toLocaleString(); totalSupplyValue = Number(totalSupply).toLocaleString();
...@@ -100,7 +100,7 @@ const TokenDetails = ({ tokenQuery }: Props) => { ...@@ -100,7 +100,7 @@ const TokenDetails = ({ tokenQuery }: Props) => {
</Skeleton> </Skeleton>
</DetailsInfoItem> </DetailsInfoItem>
) } ) }
{ marketcap && ( { marketCap && (
<DetailsInfoItem <DetailsInfoItem
title="Fully diluted market cap" title="Fully diluted market cap"
hint="Total supply * Price" hint="Total supply * Price"
...@@ -108,7 +108,7 @@ const TokenDetails = ({ tokenQuery }: Props) => { ...@@ -108,7 +108,7 @@ const TokenDetails = ({ tokenQuery }: Props) => {
isLoading={ tokenQuery.isPlaceholderData } isLoading={ tokenQuery.isPlaceholderData }
> >
<Skeleton isLoaded={ !tokenQuery.isPlaceholderData } display="inline-block"> <Skeleton isLoaded={ !tokenQuery.isPlaceholderData } display="inline-block">
<span>{ `$${ marketcap }` }</span> <span>{ `$${ BigNumber(marketCap).toFormat() }` }</span>
</Skeleton> </Skeleton>
</DetailsInfoItem> </DetailsInfoItem>
) } ) }
......
import { Flex, HStack, Grid, GridItem, Skeleton } from '@chakra-ui/react'; import { Flex, HStack, Grid, GridItem, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { TokenInfo } from 'types/api/token'; import type { TokenInfo } from 'types/api/token';
import getCurrencyValue from 'lib/getCurrencyValue';
import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import Tag from 'ui/shared/chakra/Tag'; import Tag from 'ui/shared/chakra/Tag';
...@@ -29,17 +29,14 @@ const TokensTableItem = ({ ...@@ -29,17 +29,14 @@ const TokensTableItem = ({
const { const {
address, address,
total_supply: totalSupply,
exchange_rate: exchangeRate, exchange_rate: exchangeRate,
type, type,
name, name,
symbol, symbol,
decimals,
holders, holders,
circulating_market_cap: marketCap,
} = token; } = token;
const totalValue = totalSupply !== null ? getCurrencyValue({ value: totalSupply, accuracy: 3, accuracyUsd: 2, exchangeRate, decimals }) : undefined;
const tokenString = [ name, symbol && `(${ symbol })` ].filter(Boolean).join(' '); const tokenString = [ name, symbol && `(${ symbol })` ].filter(Boolean).join(' ');
return ( return (
...@@ -74,10 +71,10 @@ const TokensTableItem = ({ ...@@ -74,10 +71,10 @@ const TokensTableItem = ({
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary"><span>{ exchangeRate }</span></Skeleton> <Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary"><span>{ exchangeRate }</span></Skeleton>
</HStack> </HStack>
) } ) }
{ totalValue?.usd && ( { marketCap && (
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>On-chain market cap</Skeleton> <Skeleton isLoaded={ !isLoading } fontSize="sm" fontWeight={ 500 }>On-chain market cap</Skeleton>
<Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary"><span>{ totalValue.usd }</span></Skeleton> <Skeleton isLoaded={ !isLoading } fontSize="sm" color="text_secondary"><span>{ BigNumber(marketCap).toFormat() }</span></Skeleton>
</HStack> </HStack>
) } ) }
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
......
import { Box, Flex, Td, Tr, Skeleton } from '@chakra-ui/react'; import { Box, Flex, Td, Tr, Skeleton } from '@chakra-ui/react';
import BigNumber from 'bignumber.js';
import React from 'react'; import React from 'react';
import type { TokenInfo } from 'types/api/token'; import type { TokenInfo } from 'types/api/token';
import getCurrencyValue from 'lib/getCurrencyValue';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet'; import AddressAddToWallet from 'ui/shared/address/AddressAddToWallet';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
...@@ -29,17 +29,14 @@ const TokensTableItem = ({ ...@@ -29,17 +29,14 @@ const TokensTableItem = ({
const { const {
address, address,
total_supply: totalSupply,
exchange_rate: exchangeRate, exchange_rate: exchangeRate,
type, type,
name, name,
symbol, symbol,
decimals,
holders, holders,
circulating_market_cap: marketCap,
} = token; } = token;
const totalValue = totalSupply !== null ? getCurrencyValue({ value: totalSupply, accuracy: 3, accuracyUsd: 2, exchangeRate, decimals }) : undefined;
const tokenString = [ name, symbol && `(${ symbol })` ].filter(Boolean).join(' '); const tokenString = [ name, symbol && `(${ symbol })` ].filter(Boolean).join(' ');
return ( return (
...@@ -81,7 +78,7 @@ const TokensTableItem = ({ ...@@ -81,7 +78,7 @@ const TokensTableItem = ({
</Td> </Td>
<Td isNumeric maxWidth="300px" width="300px"> <Td isNumeric maxWidth="300px" width="300px">
<Skeleton isLoaded={ !isLoading } fontSize="sm" lineHeight="24px" fontWeight={ 500 } display="inline-block"> <Skeleton isLoaded={ !isLoading } fontSize="sm" lineHeight="24px" fontWeight={ 500 } display="inline-block">
{ totalValue?.usd && `$${ totalValue.usd }` } { marketCap && `$${ BigNumber(marketCap).toFormat() }` }
</Skeleton> </Skeleton>
</Td> </Td>
<Td isNumeric> <Td isNumeric>
......
...@@ -13,6 +13,7 @@ import Address from 'ui/shared/address/Address'; ...@@ -13,6 +13,7 @@ import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import Icon from 'ui/shared/chakra/Icon'; import Icon from 'ui/shared/chakra/Icon';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import HashStringShorten from 'ui/shared/HashStringShorten'; import HashStringShorten from 'ui/shared/HashStringShorten';
import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile/ListItemMobile';
...@@ -34,6 +35,7 @@ const VerifiedContractsListItem = ({ data, isLoading }: Props) => { ...@@ -34,6 +35,7 @@ const VerifiedContractsListItem = ({ data, isLoading }: Props) => {
<Skeleton isLoaded={ !isLoading } color="text_secondary" ml="auto"> <Skeleton isLoaded={ !isLoading } color="text_secondary" ml="auto">
<HashStringShorten hash={ data.address.hash } isTooltipDisabled/> <HashStringShorten hash={ data.address.hash } isTooltipDisabled/>
</Skeleton> </Skeleton>
<CopyToClipboard text={ data.address.hash } ml={ -1 } isLoading={ isLoading }/>
</Address> </Address>
<Flex columnGap={ 3 }> <Flex columnGap={ 3 }>
<Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>Balance { appConfig.network.currency.symbol }</Skeleton> <Skeleton isLoaded={ !isLoading } fontWeight={ 500 }>Balance { appConfig.network.currency.symbol }</Skeleton>
......
...@@ -12,6 +12,7 @@ import dayjs from 'lib/date/dayjs'; ...@@ -12,6 +12,7 @@ import dayjs from 'lib/date/dayjs';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import Icon from 'ui/shared/chakra/Icon'; import Icon from 'ui/shared/chakra/Icon';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import HashStringShorten from 'ui/shared/HashStringShorten'; import HashStringShorten from 'ui/shared/HashStringShorten';
interface Props { interface Props {
...@@ -31,9 +32,12 @@ const VerifiedContractsTableItem = ({ data, isLoading }: Props) => { ...@@ -31,9 +32,12 @@ const VerifiedContractsTableItem = ({ data, isLoading }: Props) => {
<AddressIcon address={ data.address } isLoading={ isLoading }/> <AddressIcon address={ data.address } isLoading={ isLoading }/>
<Flex columnGap={ 2 } flexWrap="wrap" w="calc(100% - 32px)"> <Flex columnGap={ 2 } flexWrap="wrap" w="calc(100% - 32px)">
<AddressLink hash={ data.address.hash } type="address" alias={ data.address.name } isLoading={ isLoading } my={ 1 }/> <AddressLink hash={ data.address.hash } type="address" alias={ data.address.name } isLoading={ isLoading } my={ 1 }/>
<Skeleton isLoaded={ !isLoading } color="text_secondary" my={ 1 }> <Flex alignItems="center">
<HashStringShorten hash={ data.address.hash } isTooltipDisabled/> <Skeleton isLoaded={ !isLoading } color="text_secondary" my={ 1 }>
</Skeleton> <HashStringShorten hash={ data.address.hash } isTooltipDisabled/>
</Skeleton>
<CopyToClipboard text={ data.address.hash } isLoading={ isLoading }/>
</Flex>
</Flex> </Flex>
</Flex> </Flex>
</Td> </Td>
......
...@@ -3928,6 +3928,13 @@ ...@@ -3928,6 +3928,13 @@
dependencies: dependencies:
"@sinonjs/commons" "^1.7.0" "@sinonjs/commons" "^1.7.0"
"@slise/embed-react@^2.2.0":
version "2.2.0"
resolved "https://registry.yarnpkg.com/@slise/embed-react/-/embed-react-2.2.0.tgz#346bf34d375144a7f5173354c1672d3687fa8b47"
integrity sha512-btboJc24ABEg5ncbVnab+asKarp3kTSTdMHHcndrnkCDlXNQNSw3vL0Lv8tanqgE3Ogt51AF8QGEhWDNOZAcxQ==
dependencies:
react-script-hook "^1.7.2"
"@solana/buffer-layout@^4.0.0": "@solana/buffer-layout@^4.0.0":
version "4.0.1" version "4.0.1"
resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15" resolved "https://registry.yarnpkg.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz#b996235eaec15b1e0b5092a8ed6028df77fa6c15"
...@@ -4671,6 +4678,11 @@ ...@@ -4671,6 +4678,11 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/crypto-js@^4.1.1":
version "4.1.1"
resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-4.1.1.tgz#602859584cecc91894eb23a4892f38cfa927890d"
integrity sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==
"@types/csp-dev@^1.0.0": "@types/csp-dev@^1.0.0":
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/@types/csp-dev/-/csp-dev-1.0.0.tgz#59e2fd69f276988b349765c2f6a39ea0a4a1a161" resolved "https://registry.yarnpkg.com/@types/csp-dev/-/csp-dev-1.0.0.tgz#59e2fd69f276988b349765c2f6a39ea0a4a1a161"
...@@ -6859,6 +6871,11 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: ...@@ -6859,6 +6871,11 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3:
shebang-command "^2.0.0" shebang-command "^2.0.0"
which "^2.0.1" which "^2.0.1"
crypto-js@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.1.1.tgz#9e485bcf03521041bd85844786b83fb7619736cf"
integrity sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==
css-box-model@1.2.1: css-box-model@1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1" resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.2.1.tgz#59951d3b81fd6b2074a62d49444415b0d2b4d7c1"
...@@ -11550,6 +11567,11 @@ react-remove-scroll@^2.4.3, react-remove-scroll@^2.5.5: ...@@ -11550,6 +11567,11 @@ react-remove-scroll@^2.4.3, react-remove-scroll@^2.5.5:
use-callback-ref "^1.3.0" use-callback-ref "^1.3.0"
use-sidecar "^1.1.2" use-sidecar "^1.1.2"
react-script-hook@^1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/react-script-hook/-/react-script-hook-1.7.2.tgz#ec130d67f9a25fcde57fbfd1faa87e5b97521948"
integrity sha512-fhyCEfXb94fag34UPRF0zry1XGwmVY+79iibWwTqAoOiCzYJQOYTiWJ7CnqglA9tMSV8g45cQpHCMcBwr7dwhA==
react-scroll@^1.8.7: react-scroll@^1.8.7:
version "1.8.7" version "1.8.7"
resolved "https://registry.yarnpkg.com/react-scroll/-/react-scroll-1.8.7.tgz#8020035329efad00f03964e18aff6822137de3aa" resolved "https://registry.yarnpkg.com/react-scroll/-/react-scroll-1.8.7.tgz#8020035329efad00f03964e18aff6822137de3aa"
......
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