feat: query blockchain for ens text records

This commit is contained in:
2022-05-11 01:35:14 -04:00
parent f1a448ba08
commit ad63e1dd17
8 changed files with 288 additions and 68 deletions

21
config-overrides Normal file
View 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
View File

@@ -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",

View File

@@ -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"
}
}

View File

@@ -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&ethBalance=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>

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -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}`;
}
}
};