import WalletConnectProvider from "@walletconnect/web3-provider"; import { Alert, Button, Card, List, Menu } from "antd"; import "antd/dist/antd.css"; import React, { useCallback, useEffect, useState } from "react"; import { BrowserRouter, Link, Route, Switch } from "react-router-dom"; import Web3Modal from "web3modal"; import "./App.css"; import { useExchangeEthPrice } from "eth-hooks/dapps/dex"; import axios from "axios"; import { DownloadOutlined } from "@ant-design/icons"; import { Account, Address, AddressInput, Contract, Header, ThemeSwitch } from "./components"; import { INFURA_ID, NETWORK, NETWORKS } from "./constants"; import { Transactor } from "./helpers"; import { useBalance, useContractLoader, useContractReader, useEventListener, useGasPrice, useOnBlock, useUserProviderAndSigner, } from "./hooks"; // nprogress import nProgress from "nprogress"; import "nprogress/nprogress.css"; import SingleNFT from "./components/SingleNFT"; // const { Account} = React.lazy(() => import("./components")); const { ethers } = require("ethers"); /* Welcome to π scaffold-eth ! Code: https://github.com/austintgriffith/scaffold-eth Support: https://t.me/joinchat/KByvmRe5wkR-8F_zz6AjpA or DM @austingriffith on twitter or telegram You should get your own Infura.io ID and put it in `constants.js` (this is your connection to the main Ethereum network for ENS etc.) π EXTERNAL CONTRACTS: You can also bring in contract artifacts in `constants.js` (and then use the `useExternalContractLoader()` hook!) */ /// π‘ What chain are your contracts deployed to? const targetNetwork = NETWORKS.localhost; // <------- select your target frontend network (localhost, rinkeby, xdai, mainnet) // π¬ Sorry for all the console logging const DEBUG = true; const NETWORKCHECK = true; // EXAMPLE STARTING JSON: // π° providers if (DEBUG) console.log("π‘ Connecting to Mainnet Ethereum"); // const mainnetProvider = getDefaultProvider("mainnet", { infura: INFURA_ID, etherscan: ETHERSCAN_KEY, quorum: 1 }); // const mainnetProvider = new InfuraProvider("mainnet",INFURA_ID); // // attempt to connect to our own scaffold eth rpc and if that fails fall back to infura... // Using StaticJsonRpcProvider as the chainId won't change see https://github.com/ethers-io/ethers.js/issues/901 const scaffoldEthProvider = navigator.onLine ? new ethers.providers.StaticJsonRpcProvider("https://rpc.scaffoldeth.io:48544") : null; const mainnetInfura = navigator.onLine ? new ethers.providers.StaticJsonRpcProvider("https://mainnet.infura.io/v3/" + INFURA_ID) : null; // ( β οΈ Getting "failed to meet quorum" errors? Check your INFURA_I // IMPORTANT: ENV Example: the variable should be formatted as REACT_APP_* // https://create-react-app.dev/docs/adding-custom-environment-variables/ // const test = process.env.REACT_APP_TEST; // console.log("test:", test); // π Your local provider is usually pointed at your local blockchain const localProviderUrl = targetNetwork.rpcUrl; // as you deploy to other networks you can set REACT_APP_PROVIDER=https://dai.poa.network in packages/react-app/.env const localProviderUrlFromEnv = process.env.REACT_APP_PROVIDER ? process.env.REACT_APP_PROVIDER : localProviderUrl; if (DEBUG) console.log("π Connecting to provider:", localProviderUrlFromEnv); const localProvider = new ethers.providers.StaticJsonRpcProvider(localProviderUrlFromEnv); // IMPORTANT PLACE const backend = process.env.REACT_APP_TAI_SHANG_NFT_PARSER; // "https://taishang.leeduckgo.com/taishang/api/v1/parse?handler_id=1&type=n"; const baseURL = process.env.REACT_APP_BASE_URL; // π block explorer URL const blockExplorer = targetNetwork.blockExplorer; /* Web3 modal helps us "connect" external wallets: */ const web3Modal = new Web3Modal({ // network: "mainnet", // optional cacheProvider: true, // optional providerOptions: { walletconnect: { package: WalletConnectProvider, // required options: { infuraId: INFURA_ID, }, }, }, }); const logoutOfWeb3Modal = async () => { await web3Modal.clearCachedProvider(); setTimeout(() => { window.location.reload(); }, 1); }; function App() { const mainnetProvider = scaffoldEthProvider && scaffoldEthProvider._network ? scaffoldEthProvider : mainnetInfura; console.log("mainnetProvider:", mainnetProvider); const [injectedProvider, setInjectedProvider] = useState(); const [address, setAddress] = useState(); /* π΅ This hook will get the price of ETH from π¦ Uniswap: */ const price = useExchangeEthPrice(targetNetwork, mainnetProvider); /* π₯ This hook will get the price of Gas from β½οΈ EtherGasStation */ const gasPrice = useGasPrice(targetNetwork, "fast"); // Use your injected provider from π¦ Metamask or if you don't have it then instantly generate a π₯ burner wallet. const userProviderAndSigner = useUserProviderAndSigner(injectedProvider, localProvider); const userSigner = userProviderAndSigner.signer; const nftGet = new URLSearchParams(window.location.search) const nftGetQuery = nftGet.get("id"); useEffect(() => { async function getAddress() { if (userSigner) { const newAddress = await userSigner.getAddress(); setAddress(newAddress); } } getAddress(); }, [userSigner]); // You can warn the user if you would like them to be on a specific network const localChainId = localProvider && localProvider._network && localProvider._network.chainId; const selectedChainId = userSigner && userSigner.provider && userSigner.provider._network && userSigner.provider._network.chainId; // For more hooks, check out πeth-hooks at: https://www.npmjs.com/package/eth-hooks // The transactor wraps transactions and provides notificiations const tx = Transactor(userSigner, gasPrice); // Faucet Tx can be used to send funds from the faucet const faucetTx = Transactor(localProvider, gasPrice); // π scaffold-eth is full of handy hooks like this one to get your balance: const yourLocalBalance = useBalance(localProvider, address); // Just plug in different π° providers to get your balance on different chains: const yourMainnetBalance = useBalance(mainnetProvider, address); // Load in your local π contract and read a value from it: const readContracts = useContractLoader(localProvider); // If you want to make π write transactions to your contracts, use the userSigner: const writeContracts = useContractLoader(userSigner, { chainId: localChainId }); // EXTERNAL CONTRACT EXAMPLE: // // If you want to bring in the mainnet DAI contract it would look like: const mainnetContracts = useContractLoader(mainnetProvider); // If you want to call a function on a new block useOnBlock(mainnetProvider, () => { console.log(`β A new mainnet block is here: ${mainnetProvider._lastBlockNumber}`); }); // Then read your DAI balance like: // const myMainnetDAIBalance = useContractReader(mainnetContracts, "DAI", "balanceOf", [ // "0x34aA3F359A9D614239015126635CE7732c18fDF3", // ]); // keep track of a variable from the contract in the local React state: const balance = useContractReader(readContracts, "N", "balanceOf", [address]); console.log("π€ read_contracts:", readContracts); // π Listen for broadcast events const transferEvents = useEventListener(readContracts, "N", "Transfer", localProvider, 1); console.log("π Transfer events:", transferEvents); // // π§ This effect will update Ns by polling when your balance changes // const yourBalance = balance && balance.toNumber && balance.toNumber(); const [Ns, setNs] = useState(); useEffect(() => { const updateNs = async () => { const collectibleUpdate = []; for (let tokenIndex = 0; tokenIndex < balance; tokenIndex++) { try { console.log("Getting token index", tokenIndex); const tokenId = await readContracts.N.tokenOfOwnerByIndex(address, tokenIndex); console.log("tokenId", tokenId); const tokenURI = await readContracts.N.tokenURI(tokenId); console.log("tokenURI", tokenURI); // TODO: Optimize let svg; // const svg = get_svg(tokenURI); // const svg = decodeTokenURI(tokenURI); // const ipfsHash = tokenURI.replace("https://ipfs.io/ipfs/", ""); // console.log("ipfsHash", ipfsHash); axios({ method: "post", url: backend, data: { token_uri: tokenURI, base_url: baseURL, }, headers: { "Content-Type": "application/json", }, }) .then(response => { console.log("svg fetched: ", response.data.result.image); svg = window.atob(response.data.result.image.slice(26)); console.log("svg fetched: ", svg); try { // const jsonManifest = JSON.parse(jsonManifestBuffer.toString()); // console.log("jsonManifest", jsonManifest); collectibleUpdate.push({ id: tokenId, uri: tokenURI, svg: svg, owner: address }); } catch (e) { console.log("error in svg fetched:", e); } }) .catch(error => { console.log("error in svg fetched:", error); }); // const jsonManifestBuffer = await getFromIPFS(ipfsHash); } catch (e) { console.log(e); } } setNs(collectibleUpdate); }; updateNs(); }, [address, yourBalance]); /* const addressFromENS = useResolveName(mainnetProvider, "austingriffith.eth"); console.log("π· Resolved austingriffith.eth as:",addressFromENS) */ // // 𧫠DEBUG π¨π»βπ¬ // useEffect(() => { if ( DEBUG && mainnetProvider && address && selectedChainId && yourLocalBalance && yourMainnetBalance && readContracts && writeContracts && mainnetContracts ) { console.log("_____________________________________ π scaffold-eth _____________________________________"); console.log("π mainnetProvider", mainnetProvider); console.log("π localChainId", localChainId); console.log("π©βπΌ selected address:", address); console.log("π΅π»ββοΈ selectedChainId:", selectedChainId); console.log("π΅ yourLocalBalance", yourLocalBalance ? ethers.utils.formatEther(yourLocalBalance) : "..."); console.log("π΅ yourMainnetBalance", yourMainnetBalance ? ethers.utils.formatEther(yourMainnetBalance) : "..."); console.log("π readContracts", readContracts); console.log("π DAI contract on mainnet:", mainnetContracts); console.log("π writeContracts", writeContracts); } }, [ mainnetProvider, address, selectedChainId, yourLocalBalance, yourMainnetBalance, readContracts, writeContracts, mainnetContracts, ]); let networkDisplay = ""; if (NETWORKCHECK && localChainId && selectedChainId && localChainId !== selectedChainId) { const networkSelected = NETWORK(selectedChainId); const networkLocal = NETWORK(localChainId); if (selectedChainId === 1337 && localChainId === 31337) { networkDisplay = ( <div style={{ zIndex: 2, position: "absolute", right: 0, top: 60, padding: 16 }}> <Alert message="β οΈ Wrong Network ID" description={ <div> You have <b>chain id 1337</b> for localhost and you need to change it to <b>31337</b> to work with HardHat. <div>(MetaMask -> Settings -> Networks -> Chain ID -> 31337)</div> </div> } type="error" closable={false} /> </div> ); } else { networkDisplay = ( <div style={{ zIndex: 2, position: "absolute", right: 0, top: 60, padding: 16 }}> <Alert message="β οΈ Wrong Network" description={ <div> You have <b>{networkSelected && networkSelected.name}</b> selected and you need to be on{" "} <Button onClick={async () => { const ethereum = window.ethereum; const data = [ { chainId: "0x" + targetNetwork.chainId.toString(16), chainName: targetNetwork.name, nativeCurrency: targetNetwork.nativeCurrency, rpcUrls: [targetNetwork.rpcUrl], blockExplorerUrls: [targetNetwork.blockExplorer], }, ]; console.log("data", data); const tx = await ethereum.request({ method: "wallet_addEthereumChain", params: data }).catch(); if (tx) { console.log(tx); } }} > <b>{networkLocal && networkLocal.name}</b> </Button> . </div> } type="error" closable={false} /> </div> ); } } else { networkDisplay = ( <div style={{ zIndex: -1, position: "absolute", right: 154, top: 28, padding: 16, color: targetNetwork.color }}> {targetNetwork.name} </div> ); } const loadWeb3Modal = useCallback(async () => { const provider = await web3Modal.connect(); setInjectedProvider(new ethers.providers.Web3Provider(provider)); provider.on("chainChanged", chainId => { console.log(`chain changed to ${chainId}! updating providers`); setInjectedProvider(new ethers.providers.Web3Provider(provider)); }); provider.on("accountsChanged", () => { console.log(`account changed!`); setInjectedProvider(new ethers.providers.Web3Provider(provider)); }); // Subscribe to session disconnection provider.on("disconnect", (code, reason) => { console.log(code, reason); logoutOfWeb3Modal(); }); }, [setInjectedProvider]); useEffect(() => { if (web3Modal.cachedProvider) { loadWeb3Modal(); } }, [loadWeb3Modal]); const [route, setRoute] = useState(); useEffect(() => { setRoute(window.location.pathname); }, [setRoute]); let faucetHint = ""; const [faucetClicked, setFaucetClicked] = useState(false); if ( !faucetClicked && localProvider && localProvider._network && localProvider._network.chainId == 31337 && yourLocalBalance && ethers.utils.formatEther(yourLocalBalance) <= 0 ) { faucetHint = ( <div style={{ padding: 16 }}> <Button type="primary" onClick={() => { faucetTx({ to: address, value: ethers.utils.parseEther("0.01"), }); setFaucetClicked(true); }} > π° Grab funds from the faucet β½οΈ </Button> </div> ); } const [transferToAddresses, setTransferToAddresses] = useState({}); nProgress.done(); return ( <div className="App"> {/* βοΈ Edit the header and change the title to your project name */} <Header /> {networkDisplay} <BrowserRouter> {nftGetQuery ? ( <SingleNFT nftGetQuery={nftGetQuery} readContracts={readContracts} address={address} /> ) : ( <Menu style={{ textAlign: "center" }} selectedKeys={[route]} mode="horizontal"> <Menu.Item key="/"> <Link // onClick={() => { // setRoute("/"); // }} to="/Tai-Shang-NFT-Wallet" > Ns </Link> </Menu.Item> <Menu.Item key="/contract-interactor"> <Link // onClick={() => { // setRoute("/contract-interactor"); // }} to="/Tai-Shang-NFT-Wallet/contract-interactor" > Contract Interactor </Link> </Menu.Item> <Menu.Item key="/transfers"> <Link // onClick={() => { // setRoute("/transfers"); // }} to="/Tai-Shang-NFT-Wallet/transfers" > Transfers </Link> </Menu.Item> {/* <Menu.Item key="/ipfsup"> <Link onClick={() => { setRoute("/ipfsup"); }} to="/ipfsup" > IPFS Upload </Link> </Menu.Item> <Menu.Item key="/ipfsdown"> <Link onClick={() => { setRoute("/ipfsdown"); }} to="/ipfsdown" > IPFS Download </Link> </Menu.Item> <Menu.Item key="/debugcontracts"> <Link onClick={() => { setRoute("/debugcontracts"); }} to="/debugcontracts" > Debug Contracts </Link> </Menu.Item> */} </Menu> )} <Switch> <Route exact path="/Tai-Shang-NFT-Wallet"> {/* π this scaffolding is full of commonly used components this <Contract/> component will automatically parse your ABI and give you a form to interact with it locally */} <div style={{ width: 640, margin: "auto", marginTop: 32, paddingBottom: 32 }}> <List bordered dataSource={Ns} renderItem={item => { const id = item.id.toNumber(); return ( <List.Item key={id + "_" + item.uri + "_" + item.owner}> <Card title={ <div> <span style={{ fontSize: 16, marginRight: 8 }}>#{id}</span> {item.name} </div> } > <div style={{ width: "300px", height: "300px" }} id={"nft_" + item.id}> <div dangerouslySetInnerHTML={{ __html: item.svg }} /> {/* {item.svg} */} {/* <img src={item.image} style={{ maxWidth: 150 }} /> */} </div> <div>{item.description}</div> <a download={item.id + ".svg"} href={`data:text/plain;charset=utf-8,${encodeURIComponent(item.svg)}`} // href={item.uri} // IMPORTANT: DOWNLOAD BUTTON HERE > <Button type="primary" shape="round" icon={<DownloadOutlined />} style={{ marginTop: "16px" }} > download .svg </Button> </a> {/* <a download={item.id + ".json"} href={item.uri}> <Button type="primary" shape="round" icon={<DownloadOutlined />} style={{ marginTop: "16px" }} > download json </Button> </a> */} </Card> <div> owner:{" "} <Address address={item.owner} ensProvider={mainnetProvider} blockExplorer={blockExplorer} fontSize={16} /> <AddressInput ensProvider={mainnetProvider} placeholder="transfer to address" value={transferToAddresses[id]} onChange={newValue => { const update = {}; update[id] = newValue; setTransferToAddresses({ ...transferToAddresses, ...update }); }} /> <Button onClick={() => { console.log("writeContracts", writeContracts); tx(writeContracts.N.transferFrom(address, transferToAddresses[id], id)); }} > Transfer </Button> </div> </List.Item> ); }} /> </div> </Route> {/* IMPORTANT PLACE */} <Route exact path="/Tai-Shang-NFT-Wallet/contract-interactor"> {/* π this scaffolding is full of commonly used components this <Contract/> component will automatically parse your ABI and give you a form to interact with it locally */} <Contract name="N" signer={userSigner} provider={localProvider} address={address} blockExplorer={blockExplorer} /> </Route> <Route path="/Tai-Shang-NFT-Wallet/transfers"> <div style={{ width: 600, margin: "auto", marginTop: 32, paddingBottom: 32 }}> <List bordered dataSource={transferEvents} renderItem={item => { return ( <List.Item key={item[0] + "_" + item[1] + "_" + item.blockNumber + "_" + item[2].toNumber()}> <span style={{ fontSize: 16, marginRight: 8 }}>#{item[2].toNumber()}</span> <Address address={item[0]} ensProvider={mainnetProvider} fontSize={16} /> => <Address address={item[1]} ensProvider={mainnetProvider} fontSize={16} /> </List.Item> ); }} /> </div> </Route> {/* <Route path="/ipfsup"> <div style={{ paddingTop: 32, width: 740, margin: "auto", textAlign: "left" }}> <ReactJson style={{ padding: 8 }} src={yourJSON} theme="pop" enableClipboard={false} onEdit={(edit, a) => { setYourJSON(edit.updated_src); }} onAdd={(add, a) => { setYourJSON(add.updated_src); }} onDelete={(del, a) => { setYourJSON(del.updated_src); }} /> </div> <Button style={{ margin: 8 }} loading={sending} size="large" shape="round" type="primary" onClick={async () => { console.log("UPLOADING...", yourJSON); setSending(true); setIpfsHash(); const result = await ipfs.add(JSON.stringify(yourJSON)); // addToIPFS(JSON.stringify(yourJSON)) if (result && result.path) { setIpfsHash(result.path); } setSending(false); console.log("RESULT:", result); }} > Upload to IPFS </Button> <div style={{ padding: 16, paddingBottom: 150 }}>{ipfsHash}</div> </Route> <Route path="/ipfsdown"> <div style={{ paddingTop: 32, width: 740, margin: "auto" }}> <Input value={ipfsDownHash} placeHolder="IPFS hash (like QmadqNw8zkdrrwdtPFK1pLi8PPxmkQ4pDJXY8ozHtz6tZq)" onChange={e => { setIpfsDownHash(e.target.value); }} /> </div> <Button style={{ margin: 8 }} loading={sending} size="large" shape="round" type="primary" onClick={async () => { console.log("DOWNLOADING...", ipfsDownHash); setDownloading(true); setIpfsContent(); const result = await getFromIPFS(ipfsDownHash); // addToIPFS(JSON.stringify(yourJSON)) if (result && result.toString) { setIpfsContent(result.toString()); } setDownloading(false); }} > Download from IPFS </Button> <pre style={{ padding: 16, width: 500, margin: "auto", paddingBottom: 150 }}>{ipfsContent}</pre> </Route> <Route path="/debugcontracts"> <Contract name="BeWater N" signer={userSigner} provider={localProvider} address={address} blockExplorer={blockExplorer} /> </Route> */} </Switch> </BrowserRouter> <ThemeSwitch /> {/* π¨βπΌ Your account is in the top right with a wallet at connect options */} <div style={{ position: "fixed", textAlign: "right", right: 0, top: 0, padding: 10 }}> <Account address={address} localProvider={localProvider} userSigner={userSigner} mainnetProvider={mainnetProvider} price={price} web3Modal={web3Modal} loadWeb3Modal={loadWeb3Modal} logoutOfWeb3Modal={logoutOfWeb3Modal} blockExplorer={blockExplorer} /> {faucetHint} </div> </div> ); } export default App;