Skip to content

Commit

Permalink
GasHawk integration
Browse files Browse the repository at this point in the history
Fixes #2205
  • Loading branch information
tom2drum committed Sep 12, 2024
1 parent d1632da commit 8f9b53e
Show file tree
Hide file tree
Showing 13 changed files with 197 additions and 2 deletions.
1 change: 1 addition & 0 deletions configs/app/features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
27 changes: 27 additions & 0 deletions configs/app/features/saveOnGas.ts
Original file line number Diff line number Diff line change
@@ -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/<address>/savingsPotential/0x1',
});
}

return Object.freeze({
title,
isEnabled: false,
});
})();

export default config;
1 change: 1 addition & 0 deletions configs/envs/.env.eth
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions deploy/tools/envs-validator/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
1 change: 1 addition & 0 deletions deploy/tools/envs-validator/test/.env.base
Original file line number Diff line number Diff line change
Expand Up @@ -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
13 changes: 12 additions & 1 deletion docs/ENVS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

&nbsp;
Expand Down Expand Up @@ -755,6 +756,16 @@ If the feature is enabled, a Get gas button will be displayed in the top bar, wh

&nbsp;

### 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+ |

&nbsp;

## External services configuration

### Google ReCaptcha
Expand Down
1 change: 1 addition & 0 deletions nextjs/csp/generateCspPolicy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ function generateCspPolicy() {
descriptors.app(),
descriptors.ad(),
descriptors.cloudFlare(),
descriptors.gasHawk(),
descriptors.googleAnalytics(),
descriptors.googleFonts(),
descriptors.googleReCaptcha(),
Expand Down
30 changes: 30 additions & 0 deletions nextjs/csp/policies/gasHawk.ts
Original file line number Diff line number Diff line change
@@ -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,
],
};
}
1 change: 1 addition & 0 deletions nextjs/csp/policies/index.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down
24 changes: 24 additions & 0 deletions public/static/gas_hawk_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions ui/address/AddressDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -211,6 +212,12 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
/>
) :
0 }
{ !countersQuery.isPlaceholderData && countersQuery.data?.gas_usage_count && (
<AddressSaveOnGas
gasUsed={ countersQuery.data.gas_usage_count }
address={ data.hash }
/>
) }
</DetailsInfoItem.Value>
</>
) }
Expand Down
90 changes: 90 additions & 0 deletions ui/address/details/AddressSaveOnGas.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Image, Skeleton } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import * as v from 'valibot';

import { route } from 'nextjs-routes';

import config from 'configs/app';
import LinkInternal from 'ui/shared/links/LinkInternal';
import TextSeparator from 'ui/shared/TextSeparator';

const feature = config.features.saveOnGas;

const responseSchema = v.object({
percent: v.number(),
});

const ERROR_NAME = 'Invalid response schema';

interface Props {
gasUsed: string;
address: string;
}

const AddressSaveOnGas = ({ gasUsed, address }: Props) => {

const gasUsedNumber = Number(gasUsed);

const query = useQuery({
queryKey: [ 'gas_hawk_saving_potential', { address } ],
queryFn: async() => {
if (!feature.isEnabled) {
return;
}

const response = await fetch(feature.apiUrlTemplate.replace('<address>', address));
const data = await response.json();
return data;
},
select: (response) => {
const parsedResponse = v.safeParse(responseSchema, response);

if (!parsedResponse.success) {
throw Error('Invalid response schema');
}

return parsedResponse.output;
},
placeholderData: { percent: 42 },
enabled: feature.isEnabled && gasUsedNumber > 0,
});

const errorMessage = query.error && 'message' in query.error ? query.error.message : undefined;

React.useEffect(() => {
if (errorMessage === ERROR_NAME) {
fetch('/node-api/monitoring/invalid-api-schema', {
method: 'POST',
body: JSON.stringify({
resource: 'gas_hawk_saving_potential',
url: feature.isEnabled ? feature.apiUrlTemplate.replace('<address>', address) : undefined,
}),
});
}
}, [ address, errorMessage ]);

if (gasUsedNumber <= 0 || !feature.isEnabled || query.isError || !query.data?.percent) {
return null;
}

const percent = Math.round(query.data.percent);

if (percent < 1) {
return null;
}

return (
<>
<TextSeparator color="divider"/>
<Skeleton isLoaded={ !query.isPlaceholderData } display="flex" alignItems="center" columnGap={ 2 }>
<Image src="/static/gas_hawk_logo.svg" w="15px" h="20px" alt="GasHawk logo"/>
<LinkInternal href={ route({ pathname: '/apps/[id]', query: { id: feature.dappId } }) } fontSize="sm">
Save { percent.toLocaleString(undefined, { maximumFractionDigits: 0 }) }% with GasHawk
</LinkInternal>
</Skeleton>
</>
);
};

export default React.memo(AddressSaveOnGas);
2 changes: 1 addition & 1 deletion ui/address/utils/useAddressCountersQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ interface Params {
addressQuery: AddressQuery;
}

export default function useAddressQuery({ hash, addressQuery }: Params): AddressCountersQuery {
export default function useAddressCountersQuery({ hash, addressQuery }: Params): AddressCountersQuery {
const enabled = Boolean(hash) && !addressQuery.isPlaceholderData;

const apiQuery = useApiQuery<'address_counters', { status: number }>('address_counters', {
Expand Down

0 comments on commit 8f9b53e

Please sign in to comment.