diff --git a/configs/app/features/index.ts b/configs/app/features/index.ts index 3b25e5570c..08dbd2fe57 100644 --- a/configs/app/features/index.ts +++ b/configs/app/features/index.ts @@ -25,6 +25,7 @@ export { default as publicTagsSubmission } from './publicTagsSubmission'; export { default as restApiDocs } from './restApiDocs'; export { default as rollup } from './rollup'; export { default as safe } from './safe'; +export { default as saveOnGas } from './saveOnGas'; export { default as sentry } from './sentry'; export { default as sol2uml } from './sol2uml'; export { default as stats } from './stats'; diff --git a/configs/app/features/saveOnGas.ts b/configs/app/features/saveOnGas.ts new file mode 100644 index 0000000000..8cabb51f05 --- /dev/null +++ b/configs/app/features/saveOnGas.ts @@ -0,0 +1,27 @@ +import type { Feature } from './types'; + +import { getEnvValue } from '../utils'; +import marketplace from './marketplace'; + +const title = 'Save on gas with GasHawk'; + +const config: Feature<{ + apiUrlTemplate: string; + dappId: string; +}> = (() => { + if (getEnvValue('NEXT_PUBLIC_SAVE_ON_GAS_ENABLED') === 'true' && marketplace.isEnabled) { + return Object.freeze({ + title, + isEnabled: true, + dappId: 'gas-hawk', + apiUrlTemplate: 'https://core.gashawk.io/apiv2/stats/address/
/savingsPotential/0x1', + }); + } + + return Object.freeze({ + title, + isEnabled: false, + }); +})(); + +export default config; diff --git a/configs/envs/.env.eth b/configs/envs/.env.eth index 42745d3f92..0ea799c9a3 100644 --- a/configs/envs/.env.eth +++ b/configs/envs/.env.eth @@ -62,3 +62,4 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-eth-main.k8s-prod-1.blockscout.com NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout NEXT_PUBLIC_VIEWS_CONTRACT_SOLIDITYSCAN_ENABLED=true NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com +NEXT_PUBLIC_SAVE_ON_GAS_ENABLED=true diff --git a/deploy/tools/envs-validator/schema.ts b/deploy/tools/envs-validator/schema.ts index 7f31fb219d..95c07d0237 100644 --- a/deploy/tools/envs-validator/schema.ts +++ b/deploy/tools/envs-validator/schema.ts @@ -801,6 +801,7 @@ const schema = yup value => value === undefined, ), }), + NEXT_PUBLIC_SAVE_ON_GAS_ENABLED: yup.boolean(), // 6. External services envs NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: yup.string(), diff --git a/deploy/tools/envs-validator/test/.env.base b/deploy/tools/envs-validator/test/.env.base index fc628aeefd..9d0e91dacb 100644 --- a/deploy/tools/envs-validator/test/.env.base +++ b/deploy/tools/envs-validator/test/.env.base @@ -84,3 +84,4 @@ NEXT_PUBLIC_VALIDATORS_CHAIN_TYPE=stability NEXT_PUBLIC_DEFI_DROPDOWN_ITEMS=[{'text':'Swap','icon':'swap','dappId':'uniswap'},{'text':'Payment link','icon':'payment_link','url':'https://example.com'}] NEXT_PUBLIC_MULTICHAIN_BALANCE_PROVIDER_CONFIG={'name': 'zerion', 'url_template': 'https://app.zerion.io/{address}/overview', 'logo': 'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-logos/zerion.svg'} NEXT_PUBLIC_GAS_REFUEL_PROVIDER_CONFIG={'name': 'Need gas?', 'dapp_id': 'smol-refuel', 'url_template': 'https://smolrefuel.com/?outboundChain={chainId}&partner=blockscout&utm_source=blockscout&utm_medium=address&disableBridges=true', 'logo': 'https://blockscout-content.s3.amazonaws.com/smolrefuel-logo-action-button.png'} +NEXT_PUBLIC_SAVE_ON_GAS_ENABLED=true diff --git a/docs/ENVS.md b/docs/ENVS.md index fa32d893e6..cdc4bc8708 100644 --- a/docs/ENVS.md +++ b/docs/ENVS.md @@ -59,9 +59,10 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will - [Validators list](ENVS.md#validators-list) - [Sentry error monitoring](ENVS.md#sentry-error-monitoring) - [OpenTelemetry](ENVS.md#opentelemetry) - - [Swap button](ENVS.md#defi-dropdown) + - [DeFi dropdown](ENVS.md#defi-dropdown) - [Multichain balance button](ENVS.md#multichain-balance-button) - [Get gas button](ENVS.md#get-gas-button) + - [Save on gas with GasHawk](ENVS.md#save-on-gas-with-gashawk) - [3rd party services configuration](ENVS.md#external-services-configuration) @@ -755,6 +756,16 @@ If the feature is enabled, a Get gas button will be displayed in the top bar, wh +### Save on gas with GasHawk + +The feature enables a "Save with GasHawk" button next to the "Gas used" value on the address page. + +| Variable | Type| Description | Compulsoriness | Default value | Example value | Version | +| --- | --- | --- | --- | --- | --- | --- | +| NEXT_PUBLIC_SAVE_ON_GAS_ENABLED | `boolean` | Set to "true" to enable the feature | - | - | `true` | v1.35.0+ | + + + ## External services configuration ### Google ReCaptcha diff --git a/nextjs/csp/generateCspPolicy.ts b/nextjs/csp/generateCspPolicy.ts index 758612f3f6..044f267083 100644 --- a/nextjs/csp/generateCspPolicy.ts +++ b/nextjs/csp/generateCspPolicy.ts @@ -6,6 +6,7 @@ function generateCspPolicy() { descriptors.app(), descriptors.ad(), descriptors.cloudFlare(), + descriptors.gasHawk(), descriptors.googleAnalytics(), descriptors.googleFonts(), descriptors.googleReCaptcha(), diff --git a/nextjs/csp/policies/gasHawk.ts b/nextjs/csp/policies/gasHawk.ts new file mode 100644 index 0000000000..6a1a8e624a --- /dev/null +++ b/nextjs/csp/policies/gasHawk.ts @@ -0,0 +1,30 @@ +import type CspDev from 'csp-dev'; + +import config from 'configs/app'; + +const feature = config.features.saveOnGas; + +export function gasHawk(): CspDev.DirectiveDescriptor { + if (!feature.isEnabled) { + return {}; + } + + const apiOrigin = (() => { + try { + const url = new URL(feature.apiUrlTemplate); + return url.origin; + } catch (error) { + return ''; + } + })(); + + if (!apiOrigin) { + return {}; + } + + return { + 'connect-src': [ + apiOrigin, + ], + }; +} diff --git a/nextjs/csp/policies/index.ts b/nextjs/csp/policies/index.ts index 1cbe44f1bc..7fc5a5a68e 100644 --- a/nextjs/csp/policies/index.ts +++ b/nextjs/csp/policies/index.ts @@ -1,6 +1,7 @@ export { ad } from './ad'; export { app } from './app'; export { cloudFlare } from './cloudFlare'; +export { gasHawk } from './gasHawk'; export { googleAnalytics } from './googleAnalytics'; export { googleFonts } from './googleFonts'; export { googleReCaptcha } from './googleReCaptcha'; diff --git a/public/static/gas_hawk_logo.svg b/public/static/gas_hawk_logo.svg new file mode 100644 index 0000000000..ffa6acdde5 --- /dev/null +++ b/public/static/gas_hawk_logo.svg @@ -0,0 +1,24 @@ + diff --git a/ui/address/AddressDetails.tsx b/ui/address/AddressDetails.tsx index b370ec7be1..e781f5b519 100644 --- a/ui/address/AddressDetails.tsx +++ b/ui/address/AddressDetails.tsx @@ -21,6 +21,7 @@ import AddressBalance from './details/AddressBalance'; import AddressImplementations from './details/AddressImplementations'; import AddressNameInfo from './details/AddressNameInfo'; import AddressNetWorth from './details/AddressNetWorth'; +import AddressSaveOnGas from './details/AddressSaveOnGas'; import TokenSelect from './tokenSelect/TokenSelect'; import useAddressCountersQuery from './utils/useAddressCountersQuery'; import type { AddressQuery } from './utils/useAddressQuery'; @@ -211,6 +212,12 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { /> ) : 0 } + { !countersQuery.isPlaceholderData && countersQuery.data?.gas_usage_count && ( +