diff --git a/config-overrides b/config-overrides new file mode 100644 index 0000000..253164f --- /dev/null +++ b/config-overrides @@ -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; +}; diff --git a/package-lock.json b/package-lock.json index 83024ae..9fa0aa5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 44e3855..8283534 100644 --- a/package.json +++ b/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" } } diff --git a/src/App.js b/src/App.js index 522a65b..1346835 100644 --- a/src/App.js +++ b/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 + + + + ); + }; + + 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 (
-
+
+ + Website + + icon by + + Icons8 +
@@ -182,39 +311,7 @@ function App() { ? renderLoadingRipple() : renderCardContent()} -
- - - opensea logo - - - - - ens logo - - -
+
{renderIcons()}
diff --git a/src/asset/image/github-icon.svg b/src/asset/image/github-icon.svg new file mode 100644 index 0000000..fd37ebf --- /dev/null +++ b/src/asset/image/github-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/asset/image/twitter-icon.svg b/src/asset/image/twitter-icon.svg new file mode 100644 index 0000000..7c5d896 --- /dev/null +++ b/src/asset/image/twitter-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/asset/image/website-icon.png b/src/asset/image/website-icon.png new file mode 100644 index 0000000..7110972 Binary files /dev/null and b/src/asset/image/website-icon.png differ diff --git a/src/utility.js b/src/utility.js index e0a5f67..dcd5de7 100644 --- a/src/utility.js +++ b/src/utility.js @@ -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}`; + } + } +};