Skip to content

Commit 8b61127

Browse files
committed
set contenthash to ens
1 parent febc4ad commit 8b61127

File tree

3 files changed

+257
-17
lines changed

3 files changed

+257
-17
lines changed

apps/quick-dapp/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@
77
"@dnd-kit/core": "^6.1.0",
88
"@dnd-kit/sortable": "^8.0.0",
99
"@drafish/surge-client": "^1.1.5",
10+
"cid": "multiformats/cid",
11+
"content-hash": "^2.5.2",
1012
"esbuild-wasm": "^0.25.12",
13+
"ethers": "^6.15.0",
1114
"ipfs-http-client": "^47.0.1"
1215
}
1316
}

apps/quick-dapp/src/components/DeployPanel/index.tsx

Lines changed: 125 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 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';
34
import { FormattedMessage, useIntl } from 'react-intl';
45
import {
56
emptyInstance,
@@ -12,6 +13,12 @@ import { AppContext } from '../../contexts';
1213
import IpfsHttpClient from 'ipfs-http-client';
1314
import { readDappFiles } from '../EditHtmlTemplate';
1415
import { 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

1623
function 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(/^ipfs:\/\//, '');
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

Comments
 (0)