diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 532435dc83..e0cb8684b8 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -40,7 +40,9 @@ jobs: const tag = process.env.TAG; const REGEXP = /^v[0-9]+.[0-9]+.[0-9]+-[a-z]+((\.|-)\d+)?$/i; const match = tag.match(REGEXP); - return match && !match[1] ? 'true' : 'false'; + const isInitial = match && !match[1] ? true : false; + core.info('is_initial flag value: ', isInitial); + return isInitial; label_issues: name: Add pre-release label to issues diff --git a/configs/app/features/index.ts b/configs/app/features/index.ts index ad45f9a4a1..0879fb17d5 100644 --- a/configs/app/features/index.ts +++ b/configs/app/features/index.ts @@ -17,6 +17,7 @@ export { default as sentry } from './sentry'; export { default as sol2uml } from './sol2uml'; export { default as stats } from './stats'; export { default as suave } from './suave'; +export { default as txInterpretation } from './txInterpretation'; export { default as web3Wallet } from './web3Wallet'; export { default as verifiedTokens } from './verifiedTokens'; export { default as zkEvmRollup } from './zkEvmRollup'; diff --git a/configs/app/features/txInterpretation.ts b/configs/app/features/txInterpretation.ts new file mode 100644 index 0000000000..c22067ee27 --- /dev/null +++ b/configs/app/features/txInterpretation.ts @@ -0,0 +1,34 @@ +import type { Feature } from './types'; +import type { Provider } from 'types/client/txInterpretation'; +import { PROVIDERS } from 'types/client/txInterpretation'; + +import { getEnvValue } from '../utils'; + +const title = 'Transaction interpretation'; + +const provider: Provider = (() => { + const value = getEnvValue('NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER'); + + if (value && PROVIDERS.includes(value as Provider)) { + return value as Provider; + } + + return 'none'; +})(); + +const config: Feature<{ provider: Provider }> = (() => { + if (provider !== 'none') { + return Object.freeze({ + title, + provider, + isEnabled: true, + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/envs/.env.eth b/configs/envs/.env.eth index 71dc3fdd4d..75ef364321 100644 --- a/configs/envs/.env.eth +++ b/configs/envs/.env.eth @@ -42,6 +42,7 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s.blockscout.com NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com +NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout #meta NEXT_PUBLIC_OG_IMAGE_URL=https://github.com/blockscout/frontend-configs/blob/main/configs/og-images/eth.jpg?raw=true diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 85966c74ed..6dee3620b3 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -15,6 +15,7 @@ import type { MarketplaceAppOverview } from '../../../types/client/marketplace'; import { NAVIGATION_LINK_IDS } from '../../../types/client/navigation-items'; import type { NavItemExternal, NavigationLinkId } from '../../../types/client/navigation-items'; import type { BridgedTokenChain, TokenBridge } from '../../../types/client/token'; +import { PROVIDERS as TX_INTERPRETATION_PROVIDERS } from '../../../types/client/txInterpretation'; import type { WalletType } from '../../../types/client/wallets'; import { SUPPORTED_WALLETS } from '../../../types/client/wallets'; import type { CustomLink, CustomLinksGroup } from '../../../types/footerLinks'; @@ -438,6 +439,7 @@ const schema = yup return isNoneSchema.isValidSync(data) || isArrayOfWalletsSchema.isValidSync(data); }), NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET: yup.boolean(), + NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER: yup.string().oneOf(TX_INTERPRETATION_PROVIDERS), NEXT_PUBLIC_AD_TEXT_PROVIDER: yup.string().oneOf(SUPPORTED_AD_TEXT_PROVIDERS), NEXT_PUBLIC_PROMOTE_BLOCKSCOUT_IN_TITLE: yup.boolean(), NEXT_PUBLIC_OG_DESCRIPTION: yup.string(), diff --git a/docs/ENVS.md b/docs/ENVS.md index 58b81b2c11..bee048088a 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -44,6 +44,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will - [Solidity to UML diagrams](ENVS.md#solidity-to-uml-diagrams) - [Blockchain statistics](ENVS.md#blockchain-statistics) - [Web3 wallet integration](ENVS.md#web3-wallet-integration-add-token-or-network-to-the-wallet) (add token or network to the wallet) + - [Transaction interpretation](ENVS.md#transaction-interpretation) - [Verified tokens info](ENVS.md#verified-tokens-info) - [Bridged tokens](ENVS.md#bridged-tokens) - [Safe{Core} address tags](ENVS.md#safecore-address-tags) @@ -473,6 +474,14 @@ This feature is **enabled by default** with the `['metamask']` value. To switch   +### Transaction interpretation + +| Variable | Type| Description | Compulsoriness | Default value | Example value | +| --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER | `blockscout` \| `none` | Transaction interpretation provider that displays human readable transaction description | - | `none` | `blockscout` | + +  + ### Verified tokens info | Variable | Type| Description | Compulsoriness | Default value | Example value | diff --git a/icons/lightning.svg b/icons/lightning.svg new file mode 100644 index 0000000000..91b1ae92ca --- /dev/null +++ b/icons/lightning.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/api/resources.ts b/lib/api/resources.ts index 788a187a22..60d224dad6 100644 --- a/lib/api/resources.ts +++ b/lib/api/resources.ts @@ -65,6 +65,7 @@ import type { TransactionsResponseWatchlist, TransactionsSorting, } from 'types/api/transaction'; +import type { TxInterpretationResponse } from 'types/api/txInterpretation'; import type { TTxsFilters } from 'types/api/txsFilters'; import type { TxStateChanges } from 'types/api/txStateChanges'; import type { VerifiedContractsSorting } from 'types/api/verifiedContracts'; @@ -246,6 +247,10 @@ export const RESOURCES = { pathParams: [ 'hash' as const ], filterFields: [], }, + tx_interpretation: { + path: '/api/v2/transactions/:hash/summary', + pathParams: [ 'hash' as const ], + }, withdrawals: { path: '/api/v2/withdrawals', filterFields: [], @@ -651,6 +656,7 @@ Q extends 'tx_logs' ? LogsResponseTx : Q extends 'tx_token_transfers' ? TokenTransferResponse : Q extends 'tx_raw_trace' ? RawTracesResponse : Q extends 'tx_state_changes' ? TxStateChanges : +Q extends 'tx_interpretation' ? TxInterpretationResponse : Q extends 'addresses' ? AddressesResponse : Q extends 'address' ? Address : Q extends 'address_counters' ? AddressCounters : diff --git a/lib/hooks/useNavItems.tsx b/lib/hooks/useNavItems.tsx index cd2a1adbad..f330a53c07 100644 --- a/lib/hooks/useNavItems.tsx +++ b/lib/hooks/useNavItems.tsx @@ -170,7 +170,6 @@ export default function useNavItems(): ReturnType { { text: 'Verify contract', nextRoute: { pathname: '/contract-verification' as const }, - icon: 'verify-contract', isActive: pathname.startsWith('/contract-verification'), }, ...config.UI.sidebar.otherLinks, diff --git a/lib/mixpanel/utils.ts b/lib/mixpanel/utils.ts index fae1b8e327..b886f32249 100644 --- a/lib/mixpanel/utils.ts +++ b/lib/mixpanel/utils.ts @@ -13,6 +13,7 @@ export enum EventTypes { CONTRACT_VERIFICATION = 'Contract verification', QR_CODE = 'QR code', PAGE_WIDGET = 'Page widget', + TX_INTERPRETATION_INTERACTION = 'Transaction interpratetion interaction' } /* eslint-disable @typescript-eslint/indent */ @@ -78,5 +79,8 @@ Type extends EventTypes.QR_CODE ? { Type extends EventTypes.PAGE_WIDGET ? { 'Type': 'Tokens dropdown' | 'Tokens show all (icon)' | 'Add to watchlist' | 'Address actions (more button)'; } : +Type extends EventTypes.TX_INTERPRETATION_INTERACTION ? { + 'Type': 'Address click' | 'Token click'; +} : undefined; /* eslint-enable @typescript-eslint/indent */ diff --git a/lib/sentry/config.ts b/lib/sentry/config.ts index f02b2b2d1c..d52c20e382 100644 --- a/lib/sentry/config.ts +++ b/lib/sentry/config.ts @@ -56,6 +56,7 @@ export const config: Sentry.BrowserOptions | undefined = (() => { 'Script error.', // Relay and WalletConnect errors + 'The quota has been exceeded', 'Attempt to connect to relay via', 'WebSocket connection failed for URL: wss://relay.walletconnect.com', ], @@ -67,9 +68,11 @@ export const config: Sentry.BrowserOptions | undefined = (() => { // Woopra flakiness /eatdifferent\.com\.woopra-ns\.com/i, /static\.woopra\.com\/js\/woopra\.js/i, - // Chrome extensions + // Chrome and other extensions /extensions\//i, /^chrome:\/\//i, + /^chrome-extension:\/\//i, + /^moz-extension:\/\//i, // Other plugins /127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb /webappstoolbarba\.texthelp\.com\//i, diff --git a/mocks/contract/methods.ts b/mocks/contract/methods.ts index 742efc83a1..745c19770a 100644 --- a/mocks/contract/methods.ts +++ b/mocks/contract/methods.ts @@ -14,7 +14,7 @@ export const read: Array = [ method_id: '70a08231', name: 'FLASHLOAN_PREMIUM_TOTAL', outputs: [ - { internalType: 'uint256', name: '', type: 'uint256' }, + { internalType: 'uint256', name: 'amount', type: 'uint256' }, ], payable: false, stateMutability: 'view', @@ -97,7 +97,7 @@ export const read: Array = [ export const readResultSuccess: SmartContractQueryMethodReadSuccess = { is_error: false, result: { - names: [ 'uint256' ], + names: [ 'amount' ], output: [ { type: 'uint256', value: '42' }, ], diff --git a/mocks/txs/txInterpretation.ts b/mocks/txs/txInterpretation.ts new file mode 100644 index 0000000000..b351363a52 --- /dev/null +++ b/mocks/txs/txInterpretation.ts @@ -0,0 +1,45 @@ +import type { TxInterpretationResponse } from 'types/api/txInterpretation'; + +export const txInterpretation: TxInterpretationResponse = { + data: { + summaries: [ { + summary_template: `{action_type} {amount} {token} to {to_address} on {timestamp}`, + summary_template_variables: { + action_type: { type: 'string', value: 'Transfer' }, + amount: { type: 'currency', value: '100' }, + token: { + type: 'token', + value: { + name: 'Duck', + type: 'ERC-20', + symbol: 'DUCK', + address: '0x486a3c5f34cDc4EF133f248f1C81168D78da52e8', + holders: '1152', + decimals: '18', + icon_url: null, + total_supply: '210000000000000000000000000', + exchange_rate: null, + circulating_market_cap: null, + }, + }, + to_address: { + type: 'address', + value: { + hash: '0x48c04ed5691981C42154C6167398f95e8f38a7fF', + implementation_name: null, + is_contract: false, + is_verified: false, + name: null, + private_tags: [], + public_tags: [], + watchlist_names: [], + }, + }, + timestamp: { + type: 'timestamp', + value: '1687005431', + }, + }, + } ], + }, +}; diff --git a/nextjs/csp/policies/ad.ts b/nextjs/csp/policies/ad.ts index 4b7905fdf2..2808dfb07c 100644 --- a/nextjs/csp/policies/ad.ts +++ b/nextjs/csp/policies/ad.ts @@ -9,11 +9,11 @@ export function ad(): CspDev.DirectiveDescriptor { 'connect-src': [ 'coinzilla.com', '*.coinzilla.com', - 'request-global.czilladx.com', + 'https://request-global.czilladx.com', '*.slise.xyz', ], 'frame-src': [ - 'request-global.czilladx.com', + 'https://request-global.czilladx.com', ], 'script-src': [ 'coinzillatag.com', @@ -27,7 +27,7 @@ export function ad(): CspDev.DirectiveDescriptor { 'cdn.coinzilla.io', ], 'font-src': [ - 'request-global.czilladx.com', + 'https://request-global.czilladx.com', ], }; } diff --git a/nextjs/csp/policies/app.ts b/nextjs/csp/policies/app.ts index ef338a22dc..37822e4766 100644 --- a/nextjs/csp/policies/app.ts +++ b/nextjs/csp/policies/app.ts @@ -9,7 +9,6 @@ import { KEY_WORDS } from '../utils'; const MAIN_DOMAINS = [ `*.${ config.app.host }`, config.app.host, - getFeaturePayload(config.features.sol2uml)?.api.endpoint, ].filter(Boolean); const getCspReportUrl = () => { @@ -113,6 +112,7 @@ export function app(): CspDev.DirectiveDescriptor { 'font-src': [ KEY_WORDS.DATA, + ...MAIN_DOMAINS, ], 'object-src': [ diff --git a/playwright/utils/configs.ts b/playwright/utils/configs.ts index 5871ee3c45..4d9bc8802f 100644 --- a/playwright/utils/configs.ts +++ b/playwright/utils/configs.ts @@ -29,6 +29,9 @@ export const featureEnvs = { value: '[{"type":"omni","title":"OmniBridge","short_title":"OMNI"},{"type":"amb","title":"Arbitrary Message Bridge","short_title":"AMB"}]', }, ], + txInterpretation: [ + { name: 'NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER', value: 'blockscout' }, + ], zkRollup: [ { name: 'NEXT_PUBLIC_IS_ZKEVM_L2_NETWORK', value: 'true' }, { name: 'NEXT_PUBLIC_L1_BASE_URL', value: 'https://localhost:3101' }, diff --git a/public/icons/name.d.ts b/public/icons/name.d.ts index 8a6cb918a7..31e3f50b9d 100644 --- a/public/icons/name.d.ts +++ b/public/icons/name.d.ts @@ -52,6 +52,7 @@ | "graphQL" | "info" | "key" + | "lightning" | "link" | "lock" | "minus" diff --git a/stubs/txInterpretation.ts b/stubs/txInterpretation.ts new file mode 100644 index 0000000000..e54c2cddae --- /dev/null +++ b/stubs/txInterpretation.ts @@ -0,0 +1,34 @@ +import type { TxInterpretationResponse } from 'types/api/txInterpretation'; + +import { TOKEN_INFO_ERC_20 } from './token'; + +export const TX_INTERPRETATION: TxInterpretationResponse = { + data: { + summaries: [ + { + summary_template: '{action_type} {source_amount} Ether into {destination_amount} {destination_token}', + summary_template_variables: { + action_type: { type: 'string', value: 'Wrap' }, + source_amount: { type: 'currency', value: '0.7' }, + destination_amount: { type: 'currency', value: '0.7' }, + destination_token: { + type: 'token', + value: TOKEN_INFO_ERC_20, + }, + }, + }, + { + summary_template: '{action_type} {source_amount} Ether into {destination_amount} {destination_token}', + summary_template_variables: { + action_type: { type: 'string', value: 'Wrap' }, + source_amount: { type: 'currency', value: '0.7' }, + destination_amount: { type: 'currency', value: '0.7' }, + destination_token: { + type: 'token', + value: TOKEN_INFO_ERC_20, + }, + }, + }, + ], + }, +}; diff --git a/types/api/contract.ts b/types/api/contract.ts index f3cf1648eb..dd66d0380f 100644 --- a/types/api/contract.ts +++ b/types/api/contract.ts @@ -99,10 +99,10 @@ export interface SmartContractMethodOutput extends SmartContractMethodInput { export interface SmartContractQueryMethodReadSuccess { is_error: false; result: { - names: Array; + names: Array ]>; output: Array<{ type: string; - value: string; + value: string | Array; }>; }; } diff --git a/types/api/stats.ts b/types/api/stats.ts index d67a53211d..7ce179368f 100644 --- a/types/api/stats.ts +++ b/types/api/stats.ts @@ -16,9 +16,9 @@ export type HomeStats = { } export type GasPrices = { - average: number; - fast: number; - slow: number; + average: number | null; + fast: number | null; + slow: number | null; } export type Counters = { diff --git a/types/api/txInterpretation.ts b/types/api/txInterpretation.ts new file mode 100644 index 0000000000..be9393203f --- /dev/null +++ b/types/api/txInterpretation.ts @@ -0,0 +1,47 @@ +import type { AddressParam } from 'types/api/addressParams'; +import type { TokenInfo } from 'types/api/token'; + +export interface TxInterpretationResponse { + data: { + summaries: Array; + }; +} + +export type TxInterpretationSummary = { + summary_template: string; + summary_template_variables: Record; +} + +export type TxInterpretationVariable = + TxInterpretationVariableString | + TxInterpretationVariableCurrency | + TxInterpretationVariableTimestamp | + TxInterpretationVariableToken | + TxInterpretationVariableAddress; + +export type TxInterpretationVariableType = 'string' | 'currency' | 'timestamp' | 'token' | 'address'; + +export type TxInterpretationVariableString = { + type: 'string'; + value: string; +} + +export type TxInterpretationVariableCurrency = { + type: 'currency'; + value: string; +} + +export type TxInterpretationVariableTimestamp = { + type: 'timestamp'; + value: string; +} + +export type TxInterpretationVariableToken = { + type: 'token'; + value: TokenInfo; +} + +export type TxInterpretationVariableAddress = { + type: 'address'; + value: AddressParam; +} diff --git a/types/client/txInterpretation.ts b/types/client/txInterpretation.ts new file mode 100644 index 0000000000..e264b267bc --- /dev/null +++ b/types/client/txInterpretation.ts @@ -0,0 +1,8 @@ +import type { ArrayElement } from 'types/utils'; + +export const PROVIDERS = [ + 'blockscout', + 'none', +] as const; + +export type Provider = ArrayElement; diff --git a/ui/address/SolidityscanReport.tsx b/ui/address/SolidityscanReport.tsx index 6b6d6200cb..8fe817d4fd 100644 --- a/ui/address/SolidityscanReport.tsx +++ b/ui/address/SolidityscanReport.tsx @@ -84,6 +84,10 @@ const SolidityscanReport = ({ className, hash }: Props) => { const yetAnotherGrayColor = useColorModeValue('gray.400', 'gray.500'); const popoverBgColor = useColorModeValue('white', 'gray.900'); + const greatScoreColor = useColorModeValue('green.600', 'green.400'); + const averageScoreColor = useColorModeValue('purple.600', 'purple.400'); + const lowScoreColor = useColorModeValue('red.600', 'red.400'); + if (isError || !score) { return null; } @@ -91,13 +95,13 @@ const SolidityscanReport = ({ className, hash }: Props) => { let scoreColor; let scoreLevel; if (score >= 80) { - scoreColor = 'green.600'; + scoreColor = greatScoreColor; scoreLevel = 'GREAT'; } else if (score >= 30) { - scoreColor = 'orange.600'; + scoreColor = averageScoreColor; scoreLevel = 'AVERAGE'; } else { - scoreColor = 'red.600'; + scoreColor = lowScoreColor; scoreLevel = 'LOW'; } @@ -112,7 +116,6 @@ const SolidityscanReport = ({ className, hash }: Props) => {