Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion configs/envs/.env.eth
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/ma
NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://eth.blockscout.com/apps/revokescout?chainId=1
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_CONFIG={'swap': {'chains': ['1', '10', '30', '100', '122', '130', '137', '324', '480', '1135', '1514', '1868', '8453', '13371', '42161', '42220', '57073', '534352', '11155111', '1313161554', '1923', '42793', '59144'], 'fee': '0.004', 'integrator': 'blockscout'}, 'multisend': {'chains': ['1', '10', '30', '100', '122', '130', '137', '324', '480', '1135', '1514', '1868', '8453', '13371', '42161', '42220', '57073', '534352', '11155111', '1313161554', '59144', '7000'], 'posthogKey': 'phc_7O4WGsecqqDO1PeaKayHAxUWN1PjheOmQCiDxEMcmkx', 'posthogHost': 'https://us.i.posthog.com'}}
NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_CONFIG={'swap': {'url': 'https://swapscout-git-test-custom-connector-blockscout.vercel.app', 'chains': ['1', '10', '30', '100', '122', '130', '137', '324', '480', '1135', '1514', '1868', '8453', '13371', '42161', '42220', '57073', '534352', '11155111', '1313161554', '1923', '42793', '59144'], 'fee': '0.004', 'integrator': 'blockscout'}, 'multisend': {'chains': ['1', '10', '30', '100', '122', '130', '137', '324', '480', '1135', '1514', '1868', '8453', '13371', '42161', '42220', '57073', '534352', '11155111', '1313161554', '59144', '7000'], 'posthogKey': 'phc_7O4WGsecqqDO1PeaKayHAxUWN1PjheOmQCiDxEMcmkx', 'posthogHost': 'https://us.i.posthog.com'}}
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
Expand Down
4 changes: 2 additions & 2 deletions deploy/tools/envs-validator/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,11 @@ const schema = yup
NEXT_PUBLIC_APP_PROTOCOL: yup.string().oneOf(protocols),
NEXT_PUBLIC_APP_PORT: yup.number().positive().integer(),
NEXT_PUBLIC_APP_ENV: yup.string(),
NEXT_PUBLIC_APP_INSTANCE: yup.string(),
NEXT_PUBLIC_APP_INSTANCE: yup.string(),


// Features configuration
// NOTE: As a rule of thumb, only include features that require a single ENV variable here.
// NOTE: As a rule of thumb, only include features that require a single ENV variable here.
// Otherwise, consider placing them in the corresponding schema file in the "./schemas/features" directory.
NEXT_PUBLIC_WEB3_WALLETS: yup
.mixed()
Expand Down
3 changes: 2 additions & 1 deletion deploy/tools/envs-validator/schemas/features/marketplace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export const marketplaceSchema = yup
const valueSchema = yup.object<EssentialDappsConfig>().transform(replaceQuotes).json().shape({
swap: yup.lazy(value => value ?
yup.object<EssentialDappsConfig['swap']>().shape({
url: yup.string().test(urlTest).required(),
chains: chainsSchema,
fee: yup.string().required(),
integrator: yup.string().required(),
Expand Down Expand Up @@ -170,4 +171,4 @@ export const marketplaceSchema = yup
value => value === undefined,
),
}),
});
});
2 changes: 1 addition & 1 deletion deploy/tools/envs-validator/test/.env.marketplace
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://example.com
NEXT_PUBLIC_MARKETPLACE_FEATURED_APP=aave
NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maxaleks/36f779fd7d74877b57ec7a25a9a3a6c9/raw/746a8a59454c0537235ee44616c4690ce3bbf3c8/banner.html
NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://www.basename.app
NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_CONFIG={'swap': {'chains': ['1', '10', '100', '11155111'], 'fee': '0.004', 'integrator': 'blockscout'}, 'revoke': {'chains': ['1', '10', '100', '11155111']}, 'multisend': {'chains': ['1', '10', '100', '11155111'], 'posthogKey': '123', 'posthogHost': 'https://example.com'}}
NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_CONFIG={'swap': {'url': 'https://example.com', 'chains': ['1', '10', '100', '11155111'], 'fee': '0.004', 'integrator': 'blockscout'}, 'revoke': {'chains': ['1', '10', '100', '11155111']}, 'multisend': {'chains': ['1', '10', '100', '11155111'], 'posthogKey': '123', 'posthogHost': 'https://example.com'}}
NEXT_PUBLIC_MARKETPLACE_TITLES={'menu_item': 'Dapps', 'title': 'Dappscout'}
NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_AD_ENABLED=true
2 changes: 1 addition & 1 deletion docs/ENVS.md
Original file line number Diff line number Diff line change
Expand Up @@ -622,7 +622,7 @@ Essential dapps are built-in dapps that are displayed on the Marketplace page in

| Property | Type | Description | Compulsoriness | Example value |
| --- | --- | --- | --- | --- |
| swap | `{ chains: Array<string>, fee: string, integrator: string }` | Swap config | - | `{'chains': ['1'], 'fee': '0.004', 'integrator': 'blockscout'}` |
| swap | `{ url: string, chains: Array<string>, fee: string, integrator: string }` | Swap config | - | `{'url': 'https://example.com', 'chains': ['1'], 'fee': '0.004', 'integrator': 'blockscout'}` |
| revoke | `{ chains: Array<string> }` | Revoke config | - | `{'chains': ['100']}` |
| multisend | `{ chains: Array<string> }` | Multisend config | - | `{'chains': ['1', '10']}` |

Expand Down
11 changes: 1 addition & 10 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
"postinstall": "chakra typegen ./toolkit/theme/theme.ts"
},
"dependencies": {
"@bigmi/react": "0.5.2",
"@blockscout/bens-types": "1.4.1",
"@blockscout/multichain-aggregator-types": "1.6.3-alpha.3",
"@blockscout/points-types": "1.4.0-alpha.1",
Expand All @@ -54,13 +53,10 @@
"@emotion/react": "11.14.0",
"@growthbook/growthbook-react": "0.21.0",
"@helia/verified-fetch": "2.6.12",
"@lifi/wallet-management": "3.6.1",
"@lifi/widget": "3.25.0",
"@metamask/post-message-stream": "^7.0.0",
"@metamask/providers": "^10.2.1",
"@monaco-editor/react": "^4.7.0",
"@multisender.app/multisender-react-widget": "0.2.3",
"@mysten/dapp-kit": "0.17.6",
"@next/bundle-analyzer": "15.5.2",
"@nouns/assets": "^0.10.0",
"@nouns/sdk": "^1.2.0",
Expand All @@ -79,7 +75,6 @@
"@rollbar/react": "0.12.1",
"@scure/base": "1.1.9",
"@slise/embed-react": "^2.2.0",
"@solana/wallet-adapter-react": "0.15.39",
"@specify-sh/sdk": "0.4.2",
"@tanstack/react-query": "5.55.4",
"@tanstack/react-query-devtools": "5.55.4",
Expand All @@ -92,7 +87,7 @@
"brotli-compress": "1.3.3",
"crypto-js": "^4.2.0",
"d3": "^7.6.1",
"dappscout-iframe": "0.3.2",
"dappscout-iframe": "0.4.0-alpha.7",
"dayjs": "^1.11.5",
"dom-to-image": "^2.6.0",
"es-toolkit": "1.39.10",
Expand Down Expand Up @@ -203,10 +198,6 @@
"resolutions": {
"@types/react": "18.3.12",
"@types/react-dom": "18.3.1",
"@lifi/widget": "3.25.0",
"@lifi/wallet-management": "3.6.1",
"@lifi/wallet-management/**/valibot": "1.2.0",
"@lifi/widget/**/valibot": "1.2.0",
"wagmi/**/ws": "8.17.1",
"@helia/verified-fetch/**/axios": "1.12.0",
"@helia/verified-fetch/**/tar-fs": "2.1.4",
Expand Down
1 change: 1 addition & 0 deletions types/client/marketplace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ export interface EssentialDappsChainConfig extends ExternalChain {

export type EssentialDappsConfig = {
swap?: {
url: string;
chains: Array<string>;
fee: string;
integrator: string;
Expand Down
149 changes: 149 additions & 0 deletions ui/marketplace/MarketplaceAppIframe.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { Center, chakra } from '@chakra-ui/react';
import { DappscoutIframeProvider, useDappscoutIframe } from 'dappscout-iframe';
import React, { useCallback, useEffect, useState, useMemo } from 'react';

import config from 'configs/app';
import essentialDappsChainsConfig from 'configs/essential-dapps-chains';
import { ContentLoader } from 'toolkit/components/loaders/ContentLoader';

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

const IFRAME_SANDBOX_ATTRIBUTE = 'allow-forms allow-orientation-lock ' +
'allow-pointer-lock allow-popups-to-escape-sandbox ' +
'allow-same-origin allow-scripts ' +
'allow-top-navigation-by-user-activation allow-popups';

const IFRAME_ALLOW_ATTRIBUTE = 'clipboard-read; clipboard-write;';

type ContentProps = {
appUrl?: string;
address?: string;
message?: Record<string, unknown>;
isAdaptiveHeight?: boolean;
className?: string;
};

const Content = chakra(({ appUrl, address, message, isAdaptiveHeight, className }: ContentProps) => {
const { iframeRef, isReady } = useDappscoutIframe();

const [ iframeKey, setIframeKey ] = useState(0);
const [ isFrameLoading, setIsFrameLoading ] = useState(true);
const [ iframeHeight, setIframeHeight ] = useState(0);

useEffect(() => {
setIframeKey((key) => key + 1);
}, [ address ]);

const handleIframeLoad = useCallback(() => {
setIsFrameLoading(false);
}, []);

useEffect(() => {
if (!isFrameLoading && message && appUrl) {
iframeRef?.current?.contentWindow?.postMessage(message, appUrl);
}
}, [ isFrameLoading, appUrl, iframeRef, message ]);

useEffect(() => {
const handleMessage = (event: MessageEvent) => {
if (event.origin !== appUrl) {
return;
}
if (event.data?.type === 'window-height' && isAdaptiveHeight) {
setIframeHeight(Number(event.data.height));
}
};

window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [ appUrl, isAdaptiveHeight ]);

return (
<Center
flexGrow={ 1 }
minH={ isAdaptiveHeight ? `${ iframeHeight }px` : undefined }
minW="100%"
className={ className }
>
{ (isFrameLoading) && (
<ContentLoader/>
) }

{ isReady && (
<chakra.iframe
key={ iframeKey }
allow={ IFRAME_ALLOW_ATTRIBUTE }
ref={ iframeRef }
sandbox={ IFRAME_SANDBOX_ATTRIBUTE }
h="100%"
w="100%"
display={ isFrameLoading ? 'none' : 'block' }
src={ appUrl }
title="Marketplace dapp"
onLoad={ handleIframeLoad }
background="transparent"
allowTransparency={ true }
/>
) }
</Center>
);
});

type Props = {
appId: string;
appUrl?: string;
message?: Record<string, unknown>;
isEssentialDapp?: boolean;
className?: string;
};

const MarketplaceAppIframe = ({
appId, appUrl, message, isEssentialDapp, className,
}: Props) => {
const {
address,
chainId: connectedChainId,
sendTransaction,
signMessage,
signTypedData,
switchChain,
} = useMarketplaceWallet(appId, isEssentialDapp);

const [ chainId, rpcUrl ] = useMemo(() => {
let data: [ number?, string? ] = [ Number(config.chain.id), config.chain.rpcUrls[0] ];

if (isEssentialDapp) {
const chainConfig = essentialDappsChainsConfig()?.chains.find(
(chain) => chain.id === String(connectedChainId),
);
if (chainConfig?.app_config?.chain?.rpcUrls[0]) {
data = [ connectedChainId, chainConfig.app_config.chain.rpcUrls[0] ];
}
}

return data;
}, [ isEssentialDapp, connectedChainId ]);

return (
<DappscoutIframeProvider
address={ address }
appUrl={ appUrl }
chainId={ chainId }
rpcUrl={ rpcUrl }
sendTransaction={ sendTransaction }
signMessage={ signMessage }
signTypedData={ signTypedData }
switchChain={ switchChain }
>
<Content
appUrl={ appUrl }
address={ address }
message={ message }
isAdaptiveHeight={ isEssentialDapp }
className={ className }
/>
</DappscoutIframeProvider>
);
};

export default chakra(MarketplaceAppIframe);
Loading
Loading