feat: query blockchain for ens text records
This commit is contained in:
21
config-overrides
Normal file
21
config-overrides
Normal file
@@ -0,0 +1,21 @@
|
||||
const webpack = require("webpack");
|
||||
module.exports = function override(config) {
|
||||
const fallback = config.resolve.fallback || {};
|
||||
Object.assign(fallback, {
|
||||
crypto: require.resolve("crypto-browserify"),
|
||||
stream: require.resolve("stream-browserify"),
|
||||
assert: require.resolve("assert"),
|
||||
http: require.resolve("stream-http"),
|
||||
https: require.resolve("https-browserify"),
|
||||
os: require.resolve("os-browserify"),
|
||||
url: require.resolve("url"),
|
||||
});
|
||||
config.resolve.fallback = fallback;
|
||||
config.plugins = (config.plugins || []).concat([
|
||||
new webpack.ProvidePlugin({
|
||||
process: "process/browser",
|
||||
Buffer: ["buffer", "Buffer"],
|
||||
}),
|
||||
]);
|
||||
return config;
|
||||
};
|
||||
53
package-lock.json
generated
53
package-lock.json
generated
@@ -28,6 +28,18 @@
|
||||
"sha3": "^2.1.4",
|
||||
"web-vitals": "^2.1.4",
|
||||
"web3": "^1.7.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"assert": "^1.5.0",
|
||||
"buffer": "^6.0.3",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
"process": "^0.11.10",
|
||||
"react-app-rewired": "^2.2.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"stream-http": "^3.2.0",
|
||||
"url": "^0.11.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ampproject/remapping": {
|
||||
@@ -18531,6 +18543,30 @@
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/react-app-rewired": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-app-rewired/-/react-app-rewired-2.2.1.tgz",
|
||||
"integrity": "sha512-uFQWTErXeLDrMzOJHKp0h8P1z0LV9HzPGsJ6adOtGlA/B9WfT6Shh4j2tLTTGlXOfiVx6w6iWpp7SOC5pvk+gA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"semver": "^5.6.0"
|
||||
},
|
||||
"bin": {
|
||||
"react-app-rewired": "bin/index.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-scripts": ">=2.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/react-app-rewired/node_modules/semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dev-utils": {
|
||||
"version": "12.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
||||
@@ -36007,6 +36043,23 @@
|
||||
"whatwg-fetch": "^3.6.2"
|
||||
}
|
||||
},
|
||||
"react-app-rewired": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-app-rewired/-/react-app-rewired-2.2.1.tgz",
|
||||
"integrity": "sha512-uFQWTErXeLDrMzOJHKp0h8P1z0LV9HzPGsJ6adOtGlA/B9WfT6Shh4j2tLTTGlXOfiVx6w6iWpp7SOC5pvk+gA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"semver": "^5.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-dev-utils": {
|
||||
"version": "12.0.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz",
|
||||
|
||||
24
package.json
24
package.json
@@ -25,12 +25,12 @@
|
||||
"web-vitals": "^2.1.4",
|
||||
"web3": "^1.7.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-app-rewired start",
|
||||
"build": "react-app-rewired build",
|
||||
"test": "react-app-rewired test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
@@ -48,5 +48,17 @@
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"assert": "^1.5.0",
|
||||
"buffer": "^6.0.3",
|
||||
"crypto-browserify": "^3.12.0",
|
||||
"https-browserify": "^1.0.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
"process": "^0.11.10",
|
||||
"react-app-rewired": "^2.2.1",
|
||||
"stream-browserify": "^3.0.0",
|
||||
"stream-http": "^3.2.0",
|
||||
"url": "^0.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
185
src/App.js
185
src/App.js
@@ -2,20 +2,24 @@ import "./App.css";
|
||||
import "animate.css";
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
//import namehash from "eth-ens-namehash"; // DO NOT REMOVE COMMENT
|
||||
import namehash from "eth-ens-namehash";
|
||||
import { Keccak } from "sha3";
|
||||
//import axiosClient from "axios"; // DO NOT REMOVE COMMENT
|
||||
|
||||
import Card from "@mui/material/Card";
|
||||
import CardActions from "@mui/material/CardActions";
|
||||
import ButtonBase from "@mui/material/ButtonBase";
|
||||
import Tooltip from "@mui/material/Tooltip";
|
||||
|
||||
import OpenSeaIcon from "./asset/image/opensea-icon.svg";
|
||||
import EnsIcon from "./asset/image/ens-icon.jpeg";
|
||||
import GithubIcon from "./asset/image/github-icon.svg";
|
||||
import TwitterIcon from "./asset/image/twitter-icon.svg";
|
||||
import WebsiteIcon from "./asset/image/website-icon.png";
|
||||
|
||||
import { hexToDec } from "./utility";
|
||||
import { EnsContract, hexToDec, textRecordToUrl } from "./utility";
|
||||
|
||||
//var ensContract = new EnsContract();
|
||||
var ensContract = new EnsContract();
|
||||
|
||||
/* vanilla js to adapt height to actual viewport vs therotical */
|
||||
let vh = window.innerHeight * 0.01;
|
||||
@@ -30,6 +34,12 @@ window.addEventListener("resize", () => {
|
||||
function App() {
|
||||
const [seconds, setSeconds] = useState("00");
|
||||
const [currentEnsImage, setCurrentEnsImage] = useState("");
|
||||
const [currentEnsMetadata, setCurrentEnsMetadata] = useState({
|
||||
url: "",
|
||||
twitter: "",
|
||||
github: "",
|
||||
});
|
||||
|
||||
const ensImages = {};
|
||||
const donationLink =
|
||||
"https://heliowallet.com/ethereum-donation?address=0x2652CBE035CF346A4E085D4C2Fb30F2B5Abf3d3d&amount=0.01&accessWallet=1&qrCode=1ðBalance=1";
|
||||
@@ -37,11 +47,14 @@ function App() {
|
||||
useEffect(() => {
|
||||
setSeconds(getSeconds());
|
||||
setInterval(() => {
|
||||
if (getSeconds() === "59") {
|
||||
getEnsMetadata(getEnsName(1));
|
||||
}
|
||||
setSeconds(getSeconds());
|
||||
setCurrentEnsImage(ensImages[getEnsName()]);
|
||||
}, 1000);
|
||||
|
||||
preloadImages();
|
||||
getEnsMetadata(getEnsName());
|
||||
// eslint-disable-next-line
|
||||
}, []);
|
||||
|
||||
@@ -82,6 +95,31 @@ function App() {
|
||||
await new Promise((r) => setTimeout(r, timeDelay));
|
||||
};
|
||||
|
||||
const getEnsMetadata = async (ensName) => {
|
||||
const urlPromise = ensContract.getTextRecord("url", getNameHash(ensName));
|
||||
const twitterPromise = ensContract.getTextRecord(
|
||||
"com.twitter",
|
||||
getNameHash(ensName)
|
||||
);
|
||||
const githubPromise = ensContract.getTextRecord(
|
||||
"com.github",
|
||||
getNameHash(ensName)
|
||||
);
|
||||
|
||||
const results = await Promise.all([
|
||||
urlPromise,
|
||||
twitterPromise,
|
||||
githubPromise,
|
||||
]);
|
||||
|
||||
const ensMetadata = {
|
||||
url: results[0] ? textRecordToUrl(results[0], "uri") : "",
|
||||
twitter: results[1] ? textRecordToUrl(results[1], "twitter") : "",
|
||||
github: results[2] ? textRecordToUrl(results[2], "github") : "",
|
||||
};
|
||||
setCurrentEnsMetadata(ensMetadata);
|
||||
};
|
||||
|
||||
const getEnsImageUri = (ensName) => {
|
||||
return `https://metadata.ens.domains/mainnet/0x57f1887a8BF19b14fC0dF6Fd9B2acc9Af147eA85/${getLabelHash(
|
||||
ensName
|
||||
@@ -93,21 +131,21 @@ function App() {
|
||||
};
|
||||
|
||||
const getOpenSeaUri = (ensName) => {
|
||||
const tokenId = hexToDec(getLabelHash(ensName).replace("0x", ""));
|
||||
const tokenId = hexToDec(getLabelHash(ensName, true));
|
||||
return `https://opensea.io/assets/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85/${tokenId}`;
|
||||
};
|
||||
|
||||
// const getNameHash = (ensName) => { // DO NOT REMOVE COMMENT
|
||||
// return namehash.hash(ensName);
|
||||
// };
|
||||
const getNameHash = (ensName) => {
|
||||
return namehash.hash(ensName);
|
||||
};
|
||||
|
||||
const getLabelHash = (ensName) => {
|
||||
const getLabelHash = (ensName, omitPrefix) => {
|
||||
const label = ensName.replace(".eth", "");
|
||||
|
||||
const hasher = new Keccak(256);
|
||||
hasher.update(label);
|
||||
|
||||
return "0x" + hasher.digest("hex");
|
||||
return (!omitPrefix ? "0x" : "") + hasher.digest("hex");
|
||||
};
|
||||
|
||||
const getEnsName = (minutesToAdd) => {
|
||||
@@ -167,11 +205,102 @@ function App() {
|
||||
);
|
||||
};
|
||||
|
||||
const renderIcon = (icon, uri, tooltip, style) => {
|
||||
return (
|
||||
<Tooltip title={tooltip} placement="bottom">
|
||||
<ButtonBase
|
||||
sx={{
|
||||
marginRight: 1,
|
||||
marginLeft: 1,
|
||||
}}
|
||||
>
|
||||
<a href={uri} target="_blank" rel="noreferrer">
|
||||
<img
|
||||
src={icon}
|
||||
className="icon-size"
|
||||
alt={tooltip + " icon"}
|
||||
style={!style ? {} : style}
|
||||
/>
|
||||
</a>
|
||||
</ButtonBase>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
const websiteIconStyle = {
|
||||
backgroundColor: "white",
|
||||
borderRadius: "50%",
|
||||
};
|
||||
const ensIconStyle = {
|
||||
borderRadius: "50%",
|
||||
};
|
||||
|
||||
const hasEnsMetadata = () => {
|
||||
return (
|
||||
currentEnsMetadata.url ||
|
||||
currentEnsMetadata.twitter ||
|
||||
currentEnsMetadata.github
|
||||
);
|
||||
};
|
||||
|
||||
const renderIcons = () => {
|
||||
return (
|
||||
<>
|
||||
{currentEnsMetadata.url ? (
|
||||
renderIcon(
|
||||
WebsiteIcon,
|
||||
currentEnsMetadata.url,
|
||||
"Website",
|
||||
websiteIconStyle
|
||||
)
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{currentEnsMetadata.twitter ? (
|
||||
renderIcon(TwitterIcon, currentEnsMetadata.twitter, "Twitter")
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{currentEnsMetadata.github ? (
|
||||
renderIcon(GithubIcon, currentEnsMetadata.github, "Github")
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{!hasEnsMetadata() ? (
|
||||
renderIcon(OpenSeaIcon, getOpenSeaUri(getEnsName()), "OpenSea")
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
{!hasEnsMetadata() ? (
|
||||
renderIcon(
|
||||
EnsIcon,
|
||||
getEnsDetailsUri(getEnsName()),
|
||||
"ENS",
|
||||
ensIconStyle
|
||||
)
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="App">
|
||||
<div className="App-header container">
|
||||
<div className="col">
|
||||
<div className="col" style={{ display: "none" }}>
|
||||
<div className="row"></div>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://icons8.com/icon/63807/website"
|
||||
>
|
||||
Website
|
||||
</a>
|
||||
icon by
|
||||
<a target="_blank" rel="noreferrer" href="https://icons8.com">
|
||||
Icons8
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="App-body container align-self-center">
|
||||
@@ -182,39 +311,7 @@ function App() {
|
||||
? renderLoadingRipple()
|
||||
: renderCardContent()}
|
||||
<CardActions sx={{ marginBottom: 1, marginTop: 1 }}>
|
||||
<div style={{ margin: "auto" }}>
|
||||
<ButtonBase
|
||||
sx={{
|
||||
marginRight: 2,
|
||||
}}
|
||||
>
|
||||
<a
|
||||
href={getOpenSeaUri(getEnsName())}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src={OpenSeaIcon}
|
||||
className="icon-size"
|
||||
alt="opensea logo"
|
||||
/>
|
||||
</a>
|
||||
</ButtonBase>
|
||||
<ButtonBase>
|
||||
<a
|
||||
href={getEnsDetailsUri(getEnsName())}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
<img
|
||||
src={EnsIcon}
|
||||
className="icon-size"
|
||||
style={{ borderRadius: "50%" }}
|
||||
alt="ens logo"
|
||||
/>
|
||||
</a>
|
||||
</ButtonBase>
|
||||
</div>
|
||||
<div style={{ margin: "auto" }}>{renderIcons()}</div>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
1
src/asset/image/github-icon.svg
Normal file
1
src/asset/image/github-icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="4 4 40 40" width="144px" height="144px"><path fill="#2100c4" d="M24,4C12.954,4,4,12.954,4,24c0,8.887,5.801,16.411,13.82,19.016h12.36 C38.199,40.411,44,32.887,44,24C44,12.954,35.046,4,24,4z"/><path fill="#ddbaff" d="M37,23.5c0-2.897-0.875-4.966-2.355-6.424C35.591,15.394,34.339,12,34.339,12 c-2.5,0.5-4.367,1.5-5.609,2.376C27.262,14.115,25.671,14,24,14c-1.71,0-3.339,0.118-4.834,0.393 c-1.242-0.879-3.115-1.889-5.632-2.393c0,0-1.284,3.492-0.255,5.146C11.843,18.6,11,20.651,11,23.5 c0,6.122,3.879,8.578,9.209,9.274C19.466,33.647,19,34.764,19,36l0,0.305c-0.163,0.045-0.332,0.084-0.514,0.108 c-1.107,0.143-2.271,0-2.833-0.333c-0.562-0.333-1.229-1.083-1.729-1.813c-0.422-0.616-1.263-2.032-3.416-1.979 c-0.376-0.01-0.548,0.343-0.5,0.563c0.043,0.194,0.213,0.5,0.896,0.75c0.685,0.251,1.063,0.854,1.438,1.458 c0.418,0.674,0.417,2.468,2.562,3.416c1.53,0.677,2.988,0.594,4.097,0.327l0.001,3.199c0,0.639-0.585,1.125-1.191,1.013 C19.755,43.668,21.833,44,24,44c2.166,0,4.243-0.332,6.19-0.984C29.584,43.127,29,42.641,29,42.002L29,36 c0-1.236-0.466-2.353-1.209-3.226C33.121,32.078,37,29.622,37,23.5z"/><path fill="#ddbaff" d="M15,18l3.838-1.279c1.01-0.337,1.231-1.684,0.365-2.302l-0.037-0.026 c-2.399,0.44-4.445,1.291-5.888,2.753C13.596,17.658,14.129,18,15,18z"/><path fill="#ddbaff" d="M28.693,14.402c-0.878,0.623-0.655,1.987,0.366,2.327L32.872,18c0.913,0,1.461-0.37,1.773-0.924 c-1.46-1.438-3.513-2.274-5.915-2.701C28.717,14.384,28.705,14.393,28.693,14.402z"/><path fill="#ddbaff" d="M24,31c-1.525,0-2.874,0.697-3.791,1.774C21.409,32.931,22.681,33,24,33s2.591-0.069,3.791-0.226 C26.874,31.697,25.525,31,24,31z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
1
src/asset/image/twitter-icon.svg
Normal file
1
src/asset/image/twitter-icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="4 4 40 40" width="148px" height="148px"><path fill="#03a9f4" d="M24,4C13,4,4,13,4,24s9,20,20,20s20-9,20-20S35,4,24,4z"/><path fill="#fff" d="M36,17.1c-0.9,0.4-2,0.8-3,0.9c1-0.6,2.6-1.9,3-3c-1,0.6-2.7,1.2-3.8,1.4C31.3,15.4,30,15,28.6,15 c-2.7,0-4.6,2.3-4.6,5v2c-4,0-7.9-3-10.3-6c-0.4,0.7-0.7,1.6-0.7,2.5c0,1.8,1.7,3.7,3,4.5c-0.8,0-2.3-0.6-3-1c0,0,0,0,0,0.1 c0,2.4,1.7,4,3.9,4.4C16.5,26.6,16,27,14.1,27c0.6,1.9,3.8,3,5.9,3c-1.7,1.3-4.7,2-7,2c-0.4,0-0.6,0-1,0c2.2,1.4,5.2,2,8,2 c9.1,0,14-6.9,14-13.4c0-0.2,0-0.9,0-1.1C35,18.8,35.3,18.1,36,17.1"/></svg>
|
||||
|
After Width: | Height: | Size: 598 B |
BIN
src/asset/image/website-icon.png
Normal file
BIN
src/asset/image/website-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -1,24 +1,25 @@
|
||||
// import Web3 from "web3";
|
||||
// import ensPublicResolverAbi from "./abi/EnsPublicResolver.json";
|
||||
import Web3 from "web3";
|
||||
import ensPublicResolverAbi from "./abi/EnsPublicResolver.json";
|
||||
|
||||
// const moralisEthNode =
|
||||
// const ensPublicResolverAddress = "0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41";
|
||||
const moralisEthNode =
|
||||
"https://speedy-nodes-nyc.moralis.io/bb898aa02165ac02e978282e/eth/mainnet"; // TODO: add to a config file
|
||||
const ensPublicResolverAddress = "0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41";
|
||||
|
||||
// export class EnsContract {
|
||||
// constructor() {
|
||||
// this.web3 = new Web3(moralisEthNode);
|
||||
// this.publicResolverContract = new this.web3.eth.Contract(
|
||||
// ensPublicResolverAbi,
|
||||
// ensPublicResolverAddress
|
||||
// );
|
||||
// }
|
||||
export class EnsContract {
|
||||
constructor() {
|
||||
this.web3 = new Web3(moralisEthNode);
|
||||
this.publicResolverContract = new this.web3.eth.Contract(
|
||||
ensPublicResolverAbi,
|
||||
ensPublicResolverAddress
|
||||
);
|
||||
}
|
||||
|
||||
// async getTextRecord(textRecord, labelHash) {
|
||||
// return this.publicResolverContract.methods
|
||||
// .text(labelHash, textRecord)
|
||||
// .call();
|
||||
// }
|
||||
// }
|
||||
async getTextRecord(textRecord, labelHash) {
|
||||
return this.publicResolverContract.methods
|
||||
.text(labelHash, textRecord)
|
||||
.call();
|
||||
}
|
||||
}
|
||||
|
||||
export const hexToDec = (hexString) => {
|
||||
let s = hexString;
|
||||
@@ -40,3 +41,37 @@ export const hexToDec = (hexString) => {
|
||||
}
|
||||
return digits.reverse().join("");
|
||||
};
|
||||
|
||||
export const textRecordToUrl = (textRecord, type) => {
|
||||
if (type === "uri") {
|
||||
if (textRecord.startsWith("http")) {
|
||||
return textRecord;
|
||||
} else {
|
||||
return `http://${textRecord}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === "twitter") {
|
||||
if (textRecord.includes("twitter.com")) {
|
||||
return textRecord;
|
||||
}
|
||||
|
||||
if (textRecord.includes("@")) {
|
||||
return `https://twitter.com/${textRecord.replace("@", "")}`;
|
||||
} else {
|
||||
return `https://twitter.com/${textRecord}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (type === "github") {
|
||||
if (textRecord.includes("github.com")) {
|
||||
return textRecord;
|
||||
}
|
||||
|
||||
if (textRecord.includes("@")) {
|
||||
return `https://github.com/${textRecord.replace("@", "")}`;
|
||||
} else {
|
||||
return `https://github.com/${textRecord}`;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user