Skip to content

Commit

Permalink
add swap button
Browse files Browse the repository at this point in the history
  • Loading branch information
maxaleks committed Feb 2, 2024
1 parent 037499a commit cd6c555
Show file tree
Hide file tree
Showing 21 changed files with 170 additions and 9 deletions.
1 change: 1 addition & 0 deletions configs/app/features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,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 swapButton } from './swapButton';
export { default as txInterpretation } from './txInterpretation';
export { default as userOps } from './userOps';
export { default as verifiedTokens } from './verifiedTokens';
Expand Down
24 changes: 24 additions & 0 deletions configs/app/features/swapButton.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import type { Feature } from './types';

import { getEnvValue } from '../utils';

const appUrl = getEnvValue('NEXT_PUBLIC_SWAP_BUTTON_URL');

const title = 'Swap button';

const config: Feature<{ appUrl: string }> = (() => {
if (appUrl) {
return Object.freeze({
title,
isEnabled: true,
appUrl,
});
}

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

export default config;
1 change: 1 addition & 0 deletions configs/envs/.env.main
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ NEXT_PUBLIC_STATS_API_HOST=https://stats-goerli.k8s-dev.blockscout.com
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.k8s-dev.blockscout.com
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info-test.k8s-dev.blockscout.com
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs-test.k8s-dev.blockscout.com
NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap
1 change: 1 addition & 0 deletions deploy/tools/envs-validator/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,7 @@ const schema = yup
NEXT_PUBLIC_OG_IMAGE_URL: yup.string().test(urlTest),
NEXT_PUBLIC_IS_SUAVE_CHAIN: yup.boolean(),
NEXT_PUBLIC_HAS_USER_OPS: yup.boolean(),
NEXT_PUBLIC_SWAP_BUTTON_URL: yup.string(),

// 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 @@ -53,3 +53,4 @@ NEXT_PUBLIC_VIEWS_TX_HIDDEN_FIELDS=['value','fee_currency','gas_price','tx_fee',
NEXT_PUBLIC_VISUALIZE_API_HOST=https://example.com
NEXT_PUBLIC_WEB3_DISABLE_ADD_TOKEN_TO_WALLET=false
NEXT_PUBLIC_WEB3_WALLETS=['coinbase','metamask','token_pocket']
NEXT_PUBLIC_SWAP_BUTTON_URL=uniswap
1 change: 1 addition & 0 deletions deploy/values/l2-optimism-goerli/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ frontend:
NEXT_PUBLIC_L1_BASE_URL: https://eth-goerli.blockscout.com/
NEXT_PUBLIC_OPTIMISTIC_L2_WITHDRAWAL_URL: https://app.optimism.io/bridge/withdraw
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62
NEXT_PUBLIC_SWAP_BUTTON_URL: sushiswap
envFromSecret:
NEXT_PUBLIC_AUTH0_CLIENT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_AUTH0_CLIENT_ID
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID
Expand Down
1 change: 1 addition & 0 deletions deploy/values/main/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ frontend:
NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']"
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]"
NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]"
NEXT_PUBLIC_SWAP_BUTTON_URL: uniswap
envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
Expand Down
1 change: 1 addition & 0 deletions deploy/values/review-l2/values.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ frontend:
NEXT_PUBLIC_L1_BASE_URL: https://blockscout-main.k8s-dev.blockscout.com
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0x4a0ed8ddf751a7cb5297f827699117b0f6d21a0b2907594d300dc9fed75c7e62
NEXT_PUBLIC_USE_NEXT_JS_PROXY: true
NEXT_PUBLIC_SWAP_BUTTON_URL: sushiswap
envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review-l2?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
Expand Down
1 change: 1 addition & 0 deletions deploy/values/review/values.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ frontend:
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]"
NEXT_PUBLIC_HAS_USER_OPS: true
NEXT_PUBLIC_CONTRACT_CODE_IDES: "[{'title':'Remix IDE','url':'https://remix.blockscout.com/?address={hash}&blockscout=eth-goerli.blockscout.com','icon_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/ide-icons/remix.png'}]"
NEXT_PUBLIC_SWAP_BUTTON_URL: uniswap
envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/review?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
Expand Down
11 changes: 11 additions & 0 deletions docs/ENVS.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Please be aware that all environment variables prefixed with `NEXT_PUBLIC_` will
- [SUAVE chain](ENVS.md#suave-chain)
- [Sentry error monitoring](ENVS.md#sentry-error-monitoring)
- [OpenTelemetry](ENVS.md#opentelemetry)
- [Swap button](ENVS.md#swap-button)
- [3rd party services configuration](ENVS.md#external-services-configuration)

&nbsp;
Expand Down Expand Up @@ -600,6 +601,16 @@ OpenTelemetry SDK for Node.js app could be enabled by passing `OTEL_SDK_ENABLED=

&nbsp;

### Swap button

If the feature is enabled, a Swap button will be displayed at the top of the explorer page, which will take you to the specified application in the marketplace or to an external site.

| Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_SWAP_BUTTON_URL | `string` | Application ID in the marketplace or website URL | - | - | `uniswap` |

&nbsp;

## External services configuration

### Google ReCaptcha
Expand Down
3 changes: 3 additions & 0 deletions icons/swap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion lib/mixpanel/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Type extends EventTypes.VERIFY_TOKEN ? {
'Action': 'Form opened' | 'Submit';
} :
Type extends EventTypes.WALLET_CONNECT ? {
'Source': 'Header' | 'Smart contracts';
'Source': 'Header' | 'Smart contracts' | 'Swap button';
'Status': 'Started' | 'Connected';
} :
Type extends EventTypes.WALLET_ACTION ? {
Expand Down
7 changes: 7 additions & 0 deletions lib/router/removeQueryParam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { NextRouter } from 'next/router';

export default function removeQueryParam(router: NextRouter, param: string) {
const { pathname, query } = router;
delete router.query[param];
router.replace({ pathname, query }, undefined, { shallow: true });
}
7 changes: 7 additions & 0 deletions lib/router/updateQueryParam.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import type { NextRouter } from 'next/router';

export default function updateQueryParam(router: NextRouter, param: string, newValue: string) {
const { pathname, query } = router;
query[param] = newValue;
router.replace({ pathname, query }, undefined, { shallow: true });
}
1 change: 1 addition & 0 deletions public/icons/name.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@
| "status/success"
| "status/warning"
| "sun"
| "swap"
| "testnet"
| "token-placeholder"
| "token"
Expand Down
39 changes: 39 additions & 0 deletions ui/marketplace/useWalletConnection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { useRouter } from 'next/router';
import { useEffect, useRef } from 'react';

import removeQueryParam from 'lib/router/removeQueryParam';
import updateQueryParam from 'lib/router/updateQueryParam';
import useWallet from 'ui/snippets/walletMenu/useWallet';

export default function useMarketplaceWallet() {
const router = useRouter();
const { isWalletConnected, isModalOpen, connect } = useWallet({ source: 'Swap button' });
const isConnectionStarted = useRef(false);

useEffect(() => {
if (router.query.action !== 'connect') {
return;
}

let timer: ReturnType<typeof setTimeout>;

if (!isWalletConnected && !isModalOpen) {
if (!isConnectionStarted.current) {
timer = setTimeout(() => {
if (!isWalletConnected) {
connect();
isConnectionStarted.current = true;
}
}, 500);
} else {
isConnectionStarted.current = false;
updateQueryParam(router, 'action', 'tooltip');
}
} else if (isWalletConnected) {
isConnectionStarted.current = false;
removeQueryParam(router, 'action');
}

return () => clearTimeout(timer);
}, [ isWalletConnected, isModalOpen, connect, router ]);
}
2 changes: 2 additions & 0 deletions ui/pages/MarketplaceApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import getQueryParamString from 'lib/router/getQueryParamString';
import ContentLoader from 'ui/shared/ContentLoader';

import useMarketplaceWallet from '../marketplace/useMarketplaceWallet';
import useWalletConnection from '../marketplace/useWalletConnection';

const feature = config.features.marketplace;
const configUrl = feature.isEnabled ? feature.configUrl : '';
Expand Down Expand Up @@ -96,6 +97,7 @@ const MarketplaceAppContent = ({ address, data, isPending }: Props) => {

const MarketplaceApp = () => {
const { address, sendTransaction, signMessage, signTypedData } = useMarketplaceWallet();
useWalletConnection();

const apiFetch = useApiFetch();
const router = useRouter();
Expand Down
7 changes: 5 additions & 2 deletions ui/shared/LinkExternal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ interface Props {
children: React.ReactNode;
isLoading?: boolean;
variant?: 'subtle';
hideIcon?: boolean;
}

const LinkExternal = ({ href, children, className, isLoading, variant }: Props) => {
const LinkExternal = ({ href, children, className, isLoading, variant, hideIcon }: Props) => {
const subtleLinkBg = useColorModeValue('gray.100', 'gray.700');

const styleProps: ChakraProps = (() => {
Expand Down Expand Up @@ -59,7 +60,9 @@ const LinkExternal = ({ href, children, className, isLoading, variant }: Props)
return (
<Link className={ className } { ...styleProps } target="_blank" href={ href }>
{ children }
<IconSvg name="arrows/north-east" boxSize={ 4 } verticalAlign="middle" color="gray.400" flexShrink={ 0 }/>
{ !hideIcon && (
<IconSvg name="arrows/north-east" boxSize={ 4 } verticalAlign="middle" color="gray.400" flexShrink={ 0 }/>
) }
</Link>
);
};
Expand Down
35 changes: 35 additions & 0 deletions ui/snippets/topBar/SwapButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Button, Box } from '@chakra-ui/react';
import React from 'react';

import { route } from 'nextjs-routes';

import * as regexp from 'lib/regexp';
import IconSvg from 'ui/shared/IconSvg';
import LinkExternal from 'ui/shared/LinkExternal';
import LinkInternal from 'ui/shared/LinkInternal';

const SwapButton = ({ appUrl }: { appUrl: string }) => {
const button = (
<Button variant="solid" size="xs" borderRadius="sm" height={ 5 } px={ 1.5 }>
<IconSvg name="swap" boxSize={ 3 } mr={{ base: 0, sm: 1 }}/>
<Box display={{ base: 'none', sm: 'inline' }}>
Swap
</Box>
</Button>
);

return regexp.URL_PREFIX.test(appUrl) ? (
<LinkExternal href={ appUrl } hideIcon>
{ button }
</LinkExternal>
) : (
<LinkInternal
href={ route({ pathname: '/apps/[id]', query: { id: appUrl, action: 'connect' } }) }
display="contents"
>
{ button }
</LinkInternal>
);
};

export default React.memo(SwapButton);
21 changes: 18 additions & 3 deletions ui/snippets/topBar/TopBar.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,36 @@
import { Flex, useColorModeValue } from '@chakra-ui/react';
import { Flex, Divider, useColorModeValue } from '@chakra-ui/react';
import React from 'react';

import config from 'configs/app';

import ColorModeSwitch from './ColorModeSwitch';
import SwapButton from './SwapButton';
import TopBarStats from './TopBarStats';

const feature = config.features.swapButton;
const appUrl = feature.isEnabled && feature.appUrl;

const TopBar = () => {
const bgColor = useColorModeValue('gray.50', 'whiteAlpha.100');

return (
<Flex
py={ 2 }
height="34px"
px={ 6 }
bgColor={ bgColor }
justifyContent="space-between"
alignItems="center"
>
<TopBarStats/>
<ColorModeSwitch/>
<Flex alignItems="center">
{ appUrl && (
<>
<SwapButton appUrl={ appUrl }/>
<Divider mr={ 3 } ml={{ base: 2, sm: 3 }} height={ 4 } orientation="vertical"/>
</>
) }
<ColorModeSwitch/>
</Flex>
</Flex>
);
};
Expand Down
12 changes: 9 additions & 3 deletions ui/snippets/walletMenu/WalletTooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useRouter } from 'next/router';
import React from 'react';

import { SECOND } from 'lib/consts';
import removeQueryParam from 'lib/router/removeQueryParam';

type Props = {
children: React.ReactNode;
Expand All @@ -29,14 +30,19 @@ const WalletTooltip = ({ children, isDisabled, isMobile }: Props) => {
React.useEffect(() => {
const wasShown = window.localStorage.getItem(localStorageKey);
const isMarketplacePage = [ '/apps', '/apps/[id]' ].includes(router.pathname);
if (!isDisabled && !wasShown && isMarketplacePage) {
const isTooltipShowAction = router.query.action === 'tooltip';

if (!isDisabled && isMarketplacePage && (!wasShown || isTooltipShowAction)) {
setTimeout(() => {
setIsTooltipShown.on();
window.localStorage.setItem(localStorageKey, 'true');
setTimeout(() => setIsTooltipShown.off(), 5 * SECOND);
}, SECOND);
if (isTooltipShowAction) {
removeQueryParam(router, 'action');
}
}, isTooltipShowAction ? 0 : SECOND);
}
}, [ setIsTooltipShown, localStorageKey, isDisabled, router.pathname ]);
}, [ setIsTooltipShown, localStorageKey, isDisabled, router ]);

return (
<Tooltip
Expand Down

0 comments on commit cd6c555

Please sign in to comment.