Skip to content

Commit 1e62595

Browse files
authored
Move Swap essential dapp to iframe (#3167)
1 parent a48bcd6 commit 1e62595

File tree

12 files changed

+280
-1410
lines changed

12 files changed

+280
-1410
lines changed

configs/envs/.env.eth

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/ma
4242
NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://eth.blockscout.com/apps/revokescout?chainId=1
4343
NEXT_PUBLIC_MARKETPLACE_CATEGORIES_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/marketplace-categories/default.json
4444
NEXT_PUBLIC_MARKETPLACE_ENABLED=true
45-
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'}}
45+
NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_CONFIG={'swap': {'url': 'https://swapscout.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'}}
4646
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/appiy5yijZpMMSKjT/shr6uMGPKjj1DK7NL
4747
NEXT_PUBLIC_MARKETPLACE_SUGGEST_IDEAS_FORM=https://airtable.com/appiy5yijZpMMSKjT/pag3t82DUCyhGRZZO/form
4848
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com

deploy/tools/envs-validator/schema.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@ const schema = yup
5656
NEXT_PUBLIC_APP_PROTOCOL: yup.string().oneOf(protocols),
5757
NEXT_PUBLIC_APP_PORT: yup.number().positive().integer(),
5858
NEXT_PUBLIC_APP_ENV: yup.string(),
59-
NEXT_PUBLIC_APP_INSTANCE: yup.string(),
59+
NEXT_PUBLIC_APP_INSTANCE: yup.string(),
6060

6161

6262
// Features configuration
63-
// NOTE: As a rule of thumb, only include features that require a single ENV variable here.
63+
// NOTE: As a rule of thumb, only include features that require a single ENV variable here.
6464
// Otherwise, consider placing them in the corresponding schema file in the "./schemas/features" directory.
6565
NEXT_PUBLIC_WEB3_WALLETS: yup
6666
.mixed()

deploy/tools/envs-validator/schemas/features/marketplace.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ export const marketplaceSchema = yup
110110
const valueSchema = yup.object<EssentialDappsConfig>().transform(replaceQuotes).json().shape({
111111
swap: yup.lazy(value => value ?
112112
yup.object<EssentialDappsConfig['swap']>().shape({
113+
url: yup.string().test(urlTest).required(),
113114
chains: chainsSchema,
114115
fee: yup.string().required(),
115116
integrator: yup.string().required(),
@@ -170,4 +171,4 @@ export const marketplaceSchema = yup
170171
value => value === undefined,
171172
),
172173
}),
173-
});
174+
});

deploy/tools/envs-validator/test/.env.marketplace

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://example.com
77
NEXT_PUBLIC_MARKETPLACE_FEATURED_APP=aave
88
NEXT_PUBLIC_MARKETPLACE_BANNER_CONTENT_URL=https://gist.githubusercontent.com/maxaleks/36f779fd7d74877b57ec7a25a9a3a6c9/raw/746a8a59454c0537235ee44616c4690ce3bbf3c8/banner.html
99
NEXT_PUBLIC_MARKETPLACE_BANNER_LINK_URL=https://www.basename.app
10-
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'}}
10+
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'}}
1111
NEXT_PUBLIC_MARKETPLACE_TITLES={'menu_item': 'Dapps', 'title': 'Dappscout'}
1212
NEXT_PUBLIC_MARKETPLACE_ESSENTIAL_DAPPS_AD_ENABLED=true

docs/ENVS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ Essential dapps are built-in dapps that are displayed on the Marketplace page in
641641

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

package.json

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
"postinstall": "chakra typegen ./toolkit/theme/theme.ts"
4242
},
4343
"dependencies": {
44-
"@bigmi/react": "0.5.2",
4544
"@blockscout/bens-types": "1.4.1",
4645
"@blockscout/multichain-aggregator-types": "1.6.3-alpha.3",
4746
"@blockscout/points-types": "1.4.0-alpha.1",
@@ -53,13 +52,10 @@
5352
"@emotion/react": "11.14.0",
5453
"@growthbook/growthbook-react": "0.21.0",
5554
"@helia/verified-fetch": "2.6.12",
56-
"@lifi/wallet-management": "3.6.1",
57-
"@lifi/widget": "3.25.0",
5855
"@metamask/post-message-stream": "^7.0.0",
5956
"@metamask/providers": "^10.2.1",
6057
"@monaco-editor/react": "^4.7.0",
6158
"@multisender.app/multisender-react-widget": "0.2.3",
62-
"@mysten/dapp-kit": "0.17.6",
6359
"@next/bundle-analyzer": "15.5.9",
6460
"@nouns/assets": "^0.10.0",
6561
"@nouns/sdk": "^1.2.0",
@@ -78,7 +74,6 @@
7874
"@rollbar/react": "0.12.1",
7975
"@scure/base": "1.1.9",
8076
"@slise/embed-react": "^2.2.0",
81-
"@solana/wallet-adapter-react": "0.15.39",
8277
"@specify-sh/sdk": "0.4.2",
8378
"@tanstack/react-query": "5.55.4",
8479
"@tanstack/react-query-devtools": "5.55.4",
@@ -91,7 +86,7 @@
9186
"brotli-compress": "1.3.3",
9287
"crypto-js": "^4.2.0",
9388
"d3": "^7.6.1",
94-
"dappscout-iframe": "0.3.2",
89+
"dappscout-iframe": "0.4.0",
9590
"dayjs": "^1.11.5",
9691
"dom-to-image": "^2.6.0",
9792
"es-toolkit": "1.39.10",
@@ -200,10 +195,6 @@
200195
"resolutions": {
201196
"@types/react": "18.3.12",
202197
"@types/react-dom": "18.3.1",
203-
"@lifi/widget": "3.25.0",
204-
"@lifi/wallet-management": "3.6.1",
205-
"@lifi/wallet-management/**/valibot": "1.2.0",
206-
"@lifi/widget/**/valibot": "1.2.0",
207198
"wagmi/**/ws": "8.17.1",
208199
"@helia/verified-fetch/**/axios": "1.12.0",
209200
"@helia/verified-fetch/**/tar-fs": "2.1.4",

types/client/marketplace.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export interface EssentialDappsChainConfig extends ExternalChain {
4949

5050
export type EssentialDappsConfig = {
5151
swap?: {
52+
url: string;
5253
chains: Array<string>;
5354
fee: string;
5455
integrator: string;
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import { Center, chakra } from '@chakra-ui/react';
2+
import { DappscoutIframeProvider, useDappscoutIframe } from 'dappscout-iframe';
3+
import React, { useCallback, useEffect, useState, useMemo } from 'react';
4+
5+
import config from 'configs/app';
6+
import essentialDappsChainsConfig from 'configs/essential-dapps-chains';
7+
import useWeb3Wallet from 'lib/web3/useWallet';
8+
import { ContentLoader } from 'toolkit/components/loaders/ContentLoader';
9+
10+
import useMarketplaceWallet from '../marketplace/useMarketplaceWallet';
11+
12+
const IFRAME_SANDBOX_ATTRIBUTE = 'allow-forms allow-orientation-lock ' +
13+
'allow-pointer-lock allow-popups-to-escape-sandbox ' +
14+
'allow-same-origin allow-scripts ' +
15+
'allow-top-navigation-by-user-activation allow-popups';
16+
17+
const IFRAME_ALLOW_ATTRIBUTE = 'clipboard-read; clipboard-write;';
18+
19+
type ContentProps = {
20+
appUrl?: string;
21+
address?: string;
22+
message?: Record<string, unknown>;
23+
isEssentialDapp?: boolean;
24+
className?: string;
25+
};
26+
27+
const Content = chakra(({ appUrl, address, message, isEssentialDapp, className }: ContentProps) => {
28+
const { iframeRef, isReady } = useDappscoutIframe();
29+
const web3Wallet = useWeb3Wallet({ source: 'Essential dapps' });
30+
31+
const [ iframeKey, setIframeKey ] = useState(0);
32+
const [ isFrameLoading, setIsFrameLoading ] = useState(true);
33+
const [ iframeHeight, setIframeHeight ] = useState(0);
34+
35+
useEffect(() => {
36+
setIsFrameLoading(true);
37+
setIframeKey((key) => key + 1);
38+
}, [ address ]);
39+
40+
const handleIframeLoad = useCallback(() => {
41+
setIsFrameLoading(false);
42+
}, []);
43+
44+
useEffect(() => {
45+
if (!isFrameLoading && message && appUrl) {
46+
iframeRef?.current?.contentWindow?.postMessage(message, appUrl);
47+
}
48+
}, [ isFrameLoading, appUrl, iframeRef, message ]);
49+
50+
useEffect(() => {
51+
const handleMessage = (event: MessageEvent) => {
52+
try {
53+
if (event.origin !== new URL(appUrl ?? '').origin || !isEssentialDapp) {
54+
return;
55+
}
56+
switch (event.data?.type) {
57+
case 'window-height':
58+
setIframeHeight(Number(event.data.height));
59+
break;
60+
case 'connect-wallet':
61+
web3Wallet.connect();
62+
break;
63+
}
64+
} catch {}
65+
};
66+
67+
window.addEventListener('message', handleMessage);
68+
return () => window.removeEventListener('message', handleMessage);
69+
}, [ appUrl, isEssentialDapp, web3Wallet ]);
70+
71+
return (
72+
<Center
73+
flexGrow={ 1 }
74+
minH={ isEssentialDapp ? `${ iframeHeight }px` : undefined }
75+
minW="100%"
76+
className={ className }
77+
>
78+
{ (isFrameLoading) && (
79+
<ContentLoader/>
80+
) }
81+
82+
{ isReady && (
83+
<chakra.iframe
84+
key={ iframeKey }
85+
allow={ IFRAME_ALLOW_ATTRIBUTE }
86+
ref={ iframeRef }
87+
sandbox={ IFRAME_SANDBOX_ATTRIBUTE }
88+
h="100%"
89+
w="100%"
90+
display={ isFrameLoading ? 'none' : 'block' }
91+
src={ appUrl }
92+
title="Marketplace dapp"
93+
onLoad={ handleIframeLoad }
94+
background="transparent"
95+
allowTransparency={ true }
96+
/>
97+
) }
98+
</Center>
99+
);
100+
});
101+
102+
type Props = {
103+
appId: string;
104+
appUrl?: string;
105+
message?: Record<string, unknown>;
106+
isEssentialDapp?: boolean;
107+
className?: string;
108+
};
109+
110+
const MarketplaceAppIframe = ({
111+
appId, appUrl, message, isEssentialDapp, className,
112+
}: Props) => {
113+
const {
114+
address,
115+
chainId: connectedChainId,
116+
sendTransaction,
117+
signMessage,
118+
signTypedData,
119+
switchChain,
120+
} = useMarketplaceWallet(appId, isEssentialDapp);
121+
122+
const [ chainId, rpcUrl ] = useMemo(() => {
123+
let data: [ number?, string? ] = [ Number(config.chain.id), config.chain.rpcUrls[0] ];
124+
125+
if (isEssentialDapp) {
126+
const chainConfig = essentialDappsChainsConfig()?.chains.find(
127+
(chain) => chain.id === String(connectedChainId),
128+
);
129+
if (chainConfig?.app_config?.chain?.rpcUrls[0]) {
130+
data = [ connectedChainId, chainConfig.app_config.chain.rpcUrls[0] ];
131+
}
132+
}
133+
134+
return data;
135+
}, [ isEssentialDapp, connectedChainId ]);
136+
137+
return (
138+
<DappscoutIframeProvider
139+
address={ address }
140+
appUrl={ appUrl }
141+
chainId={ chainId }
142+
rpcUrl={ rpcUrl }
143+
sendTransaction={ sendTransaction }
144+
signMessage={ signMessage }
145+
signTypedData={ signTypedData }
146+
switchChain={ switchChain }
147+
>
148+
<Content
149+
appUrl={ appUrl }
150+
address={ address }
151+
message={ message }
152+
isEssentialDapp={ isEssentialDapp }
153+
className={ className }
154+
/>
155+
</DappscoutIframeProvider>
156+
);
157+
};
158+
159+
export default chakra(MarketplaceAppIframe);

0 commit comments

Comments
 (0)