11import React , { useContext , useState , useEffect } from 'react' ;
2- import { Form , Button , Alert , InputGroup } from 'react-bootstrap' ;
2+ import { Form , Button , Alert , Card } from 'react-bootstrap' ;
3+ import { ethers , namehash } from 'ethers' ;
34import { FormattedMessage , useIntl } from 'react-intl' ;
45import {
56 emptyInstance ,
@@ -12,6 +13,12 @@ import { AppContext } from '../../contexts';
1213import IpfsHttpClient from 'ipfs-http-client' ;
1314import { readDappFiles } from '../EditHtmlTemplate' ;
1415import { InBrowserVite } from '../../InBrowserVite' ;
16+ import * as contentHash from 'content-hash' ;
17+ import { CID } from 'multiformats/cid' ;
18+
19+ const ENS_RESOLVER_ABI = [
20+ "function setContenthash(bytes32 node, bytes calldata hash) external"
21+ ] ;
1522
1623function DeployPanel ( ) : JSX . Element {
1724 const intl = useIntl ( )
@@ -31,6 +38,10 @@ function DeployPanel(): JSX.Element {
3138 const [ isDeploying , setIsDeploying ] = useState ( false ) ;
3239 const [ deployResult , setDeployResult ] = useState ( { cid : '' , error : '' } ) ;
3340
41+ const [ ensName , setEnsName ] = useState ( '' ) ;
42+ const [ isEnsLoading , setIsEnsLoading ] = useState ( false ) ;
43+ const [ ensResult , setEnsResult ] = useState ( { success : '' , error : '' } ) ;
44+
3445 const getRemixIpfsSettings = ( ) => {
3546 let result = null ;
3647
@@ -52,7 +63,7 @@ function DeployPanel(): JSX.Element {
5263 break ;
5364 }
5465 } catch ( err ) {
55- console . warn ( `⚠️ ${ key } JSON parse error:` , err ) ;
66+ console . warn ( `${ key } JSON parse error:` , err ) ;
5667 }
5768 }
5869 }
@@ -188,18 +199,11 @@ function DeployPanel(): JSX.Element {
188199 }
189200 ] ;
190201
191- const addOptions = {
192- wrapWithDirectory : true ,
193- cidVersion : 1 ,
194- } ;
195-
196- let rootCid = '' ;
197- for await ( const result of ipfsClient . addAll ( filesToUpload , addOptions ) ) {
198- if ( result . path === '' ) {
199- rootCid = result . cid . toString ( ) ;
200- }
202+ let last : any ;
203+ for await ( const r of ipfsClient . addAll ( filesToUpload , { wrapWithDirectory : true } ) ) {
204+ last = r ;
201205 }
202-
206+ const rootCid = last ?. cid ?. toString ( ) || '' ;
203207 setDeployResult ( { cid : rootCid , error : '' } ) ;
204208 setIsDeploying ( false ) ;
205209
@@ -212,6 +216,72 @@ function DeployPanel(): JSX.Element {
212216 }
213217 } ;
214218
219+ const handleEnsLink = async ( ) => {
220+ setIsEnsLoading ( true ) ;
221+ setEnsResult ( { success : '' , error : '' } ) ;
222+
223+ if ( ! ensName || ! deployResult . cid ) {
224+ setEnsResult ( { success : '' , error : 'ENS name or IPFS CID is missing.' } ) ;
225+ setIsEnsLoading ( false ) ;
226+ return ;
227+ }
228+ if ( typeof window . ethereum === 'undefined' ) {
229+ setEnsResult ( { success : '' , error : 'MetaMask (or a compatible wallet) is not installed.' } ) ;
230+ setIsEnsLoading ( false ) ;
231+ return ;
232+ }
233+
234+ try {
235+ const provider = new ethers . BrowserProvider ( window . ethereum as any ) ;
236+ await provider . send ( 'eth_requestAccounts' , [ ] ) ;
237+ const signer = await provider . getSigner ( ) ;
238+
239+ const network = await provider . getNetwork ( ) ;
240+ if ( network . chainId !== 1n ) {
241+ throw new Error ( 'Updating ENS records is supported only on Ethereum Mainnet (Chain ID: 1). Please switch your wallet network.' ) ;
242+ }
243+
244+ const userAddress = await signer . getAddress ( ) ;
245+
246+ const ownerAddress = await provider . resolveName ( ensName ) ;
247+
248+ if ( ! ownerAddress ) {
249+ throw new Error ( `'${ ensName } ' is not registered.` ) ;
250+ }
251+ if ( ownerAddress . toLowerCase ( ) !== userAddress . toLowerCase ( ) ) {
252+ throw new Error ( `The current wallet (${ userAddress . slice ( 0 , 6 ) } ...) does not match the address record of '${ ensName } '.` ) ;
253+ }
254+
255+ const resolver = await provider . getResolver ( ensName ) ;
256+ if ( ! resolver ) {
257+ throw new Error ( `Resolver for '${ ensName } ' was not found.` ) ;
258+ }
259+
260+ const writeableResolver = new ethers . Contract ( resolver . address , ENS_RESOLVER_ABI , signer ) ;
261+
262+ const pureCid = deployResult . cid . replace ( / ^ i p f s : \/ \/ / , '' ) ;
263+ const cidForEns = CID . parse ( pureCid ) . version === 0 ? pureCid : CID . parse ( pureCid ) . toV0 ( ) . toString ( ) ;
264+
265+ const chHex = '0x' + contentHash . encode ( 'ipfs-ns' , cidForEns ) ;
266+ const node = namehash ( ensName ) ;
267+
268+ const tx = await writeableResolver . setContenthash ( node , chHex ) ;
269+ setEnsResult ( { success : 'Transaction sent. Waiting for confirmation...' , error : '' } ) ;
270+ await tx . wait ( ) ;
271+
272+ setEnsResult ( { success : `'${ ensName } ' has been updated to the new DApp CID successfully!` , error : '' } ) ;
273+ } catch ( e : any ) {
274+ console . error ( e ) ;
275+ let message = e . message ;
276+ if ( e . code === 'UNSUPPORTED_OPERATION' && e . message ?. includes ( 'setContenthash' ) ) {
277+ message = "The current resolver doesn't support 'setContenthash'. You may need to switch to the Public Resolver in the ENS Manager." ;
278+ }
279+ setEnsResult ( { success : '' , error : `Failed to update ENS: ${ message } ` } ) ;
280+ } finally {
281+ setIsEnsLoading ( false ) ;
282+ }
283+ } ;
284+
215285 return (
216286 < div className = "d-inline-block" >
217287 < h3 className = "mb-3" data-id = "quick-dapp-admin" > QuickDapp < FormattedMessage id = "quickDapp.admin" /> </ h3 >
@@ -373,9 +443,14 @@ function DeployPanel(): JSX.Element {
373443 < >
374444 < hr />
375445 < h5 className = "mb-2" > < FormattedMessage id = "quickDapp.ipfsSettings" defaultMessage = "IPFS Settings" /> </ h5 >
376- < Alert variant = "info" className = "mb-2 small" >
377- < FormattedMessage id = "quickDapp.ipfsSettings.info" defaultMessage = "No global IPFS settings found. Please provide credentials below, or configure them in the 'Settings' plugin." />
378- </ Alert >
446+ { showIpfsSettings && ( ! ipfsHost || ! ipfsPort || ! ipfsProtocol ) && (
447+ < Alert variant = "info" className = "mb-2 small" >
448+ < FormattedMessage
449+ id = "quickDapp.ipfsSettings.info"
450+ defaultMessage = "No global IPFS settings found. Please provide credentials below, or configure them in the 'Settings' plugin."
451+ />
452+ </ Alert >
453+ ) }
379454 < Form . Group className = "mb-2" controlId = "formIpfsHost" >
380455 < Form . Label className = "text-uppercase mb-0" > IPFS Host</ Form . Label >
381456 < Form . Control type = "text" placeholder = "e.g., ipfs.infura.io" value = { ipfsHost } onChange = { ( e ) => setIpfsHost ( e . target . value ) } />
@@ -431,6 +506,40 @@ function DeployPanel(): JSX.Element {
431506 </ Alert >
432507 ) }
433508
509+ { deployResult . cid && (
510+ < Card className = "mt-3" >
511+ < Card . Body >
512+ < h5 className = "mb-3" > Link to ENS</ h5 >
513+ < Form . Group className = "mb-2" controlId = "formEnsName" >
514+ < Form . Label className = "text-uppercase mb-0" > ENS Name</ Form . Label >
515+ < Form . Control
516+ type = "text"
517+ placeholder = "e.g., my-dapp.eth"
518+ value = { ensName }
519+ onChange = { ( e ) => setEnsName ( e . target . value ) }
520+ />
521+ </ Form . Group >
522+ < Button
523+ variant = "secondary"
524+ className = "w-100"
525+ onClick = { handleEnsLink }
526+ disabled = { isEnsLoading || ! ensName }
527+ >
528+ { isEnsLoading ? (
529+ < > < i className = "fas fa-spinner fa-spin me-1" > </ i > Linking to ENS...</ >
530+ ) : (
531+ 'Link ENS Name (Mainnet Only)'
532+ ) }
533+ </ Button >
534+ { ensResult . success && (
535+ < Alert variant = "success" className = "mt-3 small" > { ensResult . success } </ Alert >
536+ ) }
537+ { ensResult . error && (
538+ < Alert variant = "danger" className = "mt-3 small" > { ensResult . error } </ Alert >
539+ ) }
540+ </ Card . Body >
541+ </ Card >
542+ ) }
434543 </ Form >
435544 </ div >
436545 ) ;
0 commit comments