diff --git a/package-lock.json b/package-lock.json index b9150a4f..d0d593a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "my-dao", + "name": "mydao", "version": "2.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "my-dao", + "name": "mydao", "version": "2.0.0", "dependencies": { "@thirdweb-dev/react": "^2.0.3", diff --git a/public/index.html b/public/index.html index e3453fed..2ba473e8 100644 --- a/public/index.html +++ b/public/index.html @@ -30,7 +30,7 @@ /> - My DAO + My CokoDAO { + try { + const address = await sdk.getSigner().getAddress(); + console.log("SDK initialized by address:", address) + } catch (err) { + console.error("Failed to get apps from the sdk", err); + process.exit(1); + } +})(); + +// We are exporting the initialized thirdweb SDK so that we can use it in our other scripts +export default sdk; \ No newline at end of file diff --git a/scripts/10-create-vote-proposals.js b/scripts/10-create-vote-proposals.js index e69de29b..876b704e 100644 --- a/scripts/10-create-vote-proposals.js +++ b/scripts/10-create-vote-proposals.js @@ -0,0 +1,73 @@ +import sdk from "./1-initialize-sdk.js"; +import { ethers } from "ethers"; + +// This is our governance contract. +const vote = sdk.getVote("0x74FB3B7259CE850fA3A2EED3Bf9F610fC9756164"); + +// This is our ERC-20 contract. +const token = sdk.getToken("0x6B8f666c5F843056DEfA2ef8124A562E5496d141"); + +(async () => { + try { + // Create proposal to mint 420,000 new token to the treasury. + const amount = 420_000; + const description = "Should the DAO mint an additional " + amount + " tokens into the treasury?"; + const executions = [ + { + // Our token contract that actually executes the mint. + toAddress: token.getAddress(), + // Our nativeToken is ETH. nativeTokenValue is the amount of ETH we want + // to send in this proposal. In this case, we're sending 0 ETH. + // We're just minting new tokens to the treasury. So, set to 0. + nativeTokenValue: 0, + // We're doing a mint! And, we're minting to the vote, which is + // acting as our treasury. + // in this case, we need to use ethers.js to convert the amount + // to the correct format. This is because the amount it requires is in wei. + transactionData: token.encoder.encode( + "mintTo", [ + vote.getAddress(), + ethers.utils.parseUnits(amount.toString(), 18), + ] + ), + } + ]; + + await vote.propose(description, executions); + + console.log("✅ Successfully created proposal to mint tokens"); + } catch (error) { + console.error("failed to create first proposal", error); + process.exit(1); + } + + try { + // Create proposal to transfer ourselves 6,900 tokens for being awesome. + const amount = 6_900; + const description = "Should the DAO transfer " + amount + " tokens from the treasury to " + + process.env.WALLET_ADDRESS + " for being awesome?"; + const executions = [ + { + // Again, we're sending ourselves 0 ETH. Just sending our own token. + nativeTokenValue: 0, + transactionData: token.encoder.encode( + // We're doing a transfer from the treasury to our wallet. + "transfer", + [ + process.env.WALLET_ADDRESS, + ethers.utils.parseUnits(amount.toString(), 18), + ] + ), + toAddress: token.getAddress(), + }, + ]; + + await vote.propose(description, executions); + + console.log( + "✅ Successfully created proposal to reward ourselves from the treasury, let's hope people vote for it!" + ); + } catch (error) { + console.error("failed to create second proposal", error); + } +})(); \ No newline at end of file diff --git a/scripts/11-revoke-roles.js b/scripts/11-revoke-roles.js index e69de29b..87286988 100644 --- a/scripts/11-revoke-roles.js +++ b/scripts/11-revoke-roles.js @@ -0,0 +1,23 @@ +import sdk from "./1-initialize-sdk.js"; + +const token = sdk.getToken("0x6B8f666c5F843056DEfA2ef8124A562E5496d141"); + +(async () => { + try { + // Log the current roles. + const allRoles = await token.roles.getAll(); + + console.log("👀 Roles that exist right now:", allRoles); + + // Revoke all the superpowers your wallet had over the ERC-20 contract. + await token.roles.setAll({ admin: [], minter: [] }); + console.log( + "🎉 Roles after revoking ourselves", + await token.roles.getAll() + ); + console.log("✅ Successfully revoked our superpowers from the ERC-20 contract"); + + } catch (error) { + console.error("Failed to revoke ourselves from the DAO trasury", error); + } +})(); \ No newline at end of file diff --git a/scripts/2-deploy-drop.js b/scripts/2-deploy-drop.js index e69de29b..60f37839 100644 --- a/scripts/2-deploy-drop.js +++ b/scripts/2-deploy-drop.js @@ -0,0 +1,37 @@ +import { AddressZero } from "@ethersproject/constants"; +import sdk from "./1-initialize-sdk.js"; +import { readFileSync } from "fs"; + +(async () => { + try { + const editionDropAddress = await sdk.deployer.deployEditionDrop({ + // The collection's name, ex. CryptoPunks + name: "CokoDAO Membership", + // A description for the collection. + description: "A DAO for fans of Coko.", + // The image that will be held on our NFT! The fun part :). + image: readFileSync("scripts/assets/lotsofdogs.jpg"), + // We need to pass in the address of the person who will be receiving the proceeds from sales of nfts in the contract. + // We're planning on not charging people for the drop, so we'll pass in the 0x0 address + // you can set this to your own wallet address if you want to charge for the drop. + primary_sale_recipient: AddressZero, + }); + + // this initialization returns the address of our contract + // we use this to initialize the contract on the thirdweb sdk + const editionDrop = sdk.getEditionDrop(editionDropAddress); + + // with this, we can get the metadata of our contract + const metadata = await editionDrop.metadata.get(); + + console.log( + "✅ Successfully deployed editionDrop contract, address:", + editionDropAddress, + ); + console.log("✅ editionDrop metadata:", metadata); + } catch (error) { + console.log("failed to deploy editionDrop contract", error); + } +})(); + +// edition drop = 0x6288757CC1d20E19d48Fc44C99Eb222C3A2cEAD5 \ No newline at end of file diff --git a/scripts/3-config-nft.js b/scripts/3-config-nft.js index e69de29b..8b78ebc5 100644 --- a/scripts/3-config-nft.js +++ b/scripts/3-config-nft.js @@ -0,0 +1,19 @@ +import sdk from "./1-initialize-sdk.js"; +import { readFileSync } from "fs"; + +const editionDrop = sdk.getEditionDrop("0x6288757CC1d20E19d48Fc44C99Eb222C3A2cEAD5"); + +(async () => { + try { + await editionDrop.createBatch([ + { + name: "Cutest Flower Coko", + description: "This NFT will give you access to CokoDAO!", + image: readFileSync("scripts/assets/coko_flower.jpg"), + }, + ]); + console.log("✅ Successfully created a new NFT in the drop!"); + } catch (error) { + console.error("failed to create the new NFT", error); + } +})(); \ No newline at end of file diff --git a/scripts/4-set-claim-condition.js b/scripts/4-set-claim-condition.js index e69de29b..aad13955 100644 --- a/scripts/4-set-claim-condition.js +++ b/scripts/4-set-claim-condition.js @@ -0,0 +1,29 @@ +import sdk from "./1-initialize-sdk.js"; +import { MaxUint256 } from "@ethersproject/constants"; + +const editionDrop = sdk.getEditionDrop("0x6288757CC1d20E19d48Fc44C99Eb222C3A2cEAD5"); + +(async () => { + try { + // We define our claim conditions, this is an array of objects because + // we can have multiple phases starting at different times if we want to + const claimConditions = [{ + // When people are gonna be able to start claiming the NFTs (now) + startTime: new Date(), + // The maximum number of NFTs that can be claimed. + maxQuantity: 50_000, + // The price of our NFT (free) + price: 0, + // The amount of NFTs people can claim in one transaction. + quantityLimitPerTransaction: 1, + // We set the wait between transactions to MaxUint256, which means + // people are only allowed to claim once. + waitInSeconds: MaxUint256, + }] + + await editionDrop.claimConditions.set("0", claimConditions); + console.log("✅ Sucessfully set claim condition!"); + } catch (error) { + console.error("Failed to set claim condition", error); + } +})(); \ No newline at end of file diff --git a/scripts/5-deploy-token.js b/scripts/5-deploy-token.js index e69de29b..0130b40b 100644 --- a/scripts/5-deploy-token.js +++ b/scripts/5-deploy-token.js @@ -0,0 +1,25 @@ +import { AddressZero } from "@ethersproject/constants"; +import sdk from "./1-initialize-sdk.js"; + +(async () => { + try { + // Deploy a standard ERC-20 contract. + const tokenAddress = await sdk.deployer.deployToken({ + // What's your token's name? Ex. "Ethereum" + name: "CokoDAO Governance Token", + // What's your token's symbol? Ex. "ETH" + symbol: "COKO", + // This will be in case we want to sell our token, + // because we don't, we set it to AddressZero again. + primary_sale_recipient: AddressZero, + }); + console.log( + "✅ Successfully deployed token module, address:", + tokenAddress, + ); + } catch (error) { + console.error("failed to deploy token module", error); + } +})(); + +//TOKEN ADDRESS 0x6B8f666c5F843056DEfA2ef8124A562E5496d141 \ No newline at end of file diff --git a/scripts/6-print-money.js b/scripts/6-print-money.js index e69de29b..e96785c5 100644 --- a/scripts/6-print-money.js +++ b/scripts/6-print-money.js @@ -0,0 +1,19 @@ +import sdk from "./1-initialize-sdk.js"; + +// This is the address of our ERC-20 contract printed out in the step before. +const token = sdk.getToken("0x6B8f666c5F843056DEfA2ef8124A562E5496d141"); + +(async () => { + try { + // What's the max supply you want to set? 1,000,000 is a nice number! + const amount = 1000000; + // Interact with your deployed ERC-20 contract and mint the tokens! + await token.mint(amount); + const totalSupply = await token.totalSupply(); + + // Print out how many of our token's are out there now! + console.log("✅ There now is", totalSupply.displayValue, "$COKO in circulation"); + } catch (error) { + console.error("Failed to print money", error); + } +})(); \ No newline at end of file diff --git a/scripts/7-airdrop-token.js b/scripts/7-airdrop-token.js index e69de29b..3150b3a7 100644 --- a/scripts/7-airdrop-token.js +++ b/scripts/7-airdrop-token.js @@ -0,0 +1,44 @@ +import sdk from "./1-initialize-sdk.js"; + +// This is the address to our ERC-1155 membership NFT contract. +const editionDrop = sdk.getEditionDrop("0x6288757CC1d20E19d48Fc44C99Eb222C3A2cEAD5"); + +// This is the address to our ERC-20 token contract. +const token = sdk.getToken("0x6B8f666c5F843056DEfA2ef8124A562E5496d141"); + +(async () => { + try { + // Grab all the addresses of people who own our membership NFT, + // which has a tokenId of 0. + const walletAddresses = await editionDrop.history.getAllClaimerAddresses(0); + + if (walletAddresses.length === 0) { + console.log( + "No NFTs have been claimed yet, maybe get some friends to claim your free NFTs!", + ); + process.exit(0); + } + + // Loop through the array of addresses. + const airdropTargets = walletAddresses.map((address) => { + // Pick a random # between 1000 and 10000. + const randomAmount = Math.floor(Math.random() * (10000 - 1000 + 1) + 1000); + console.log("✅ Going to airdrop", randomAmount, "tokens to", address); + + // Set up the target. + const airdropTarget = { + toAddress: address, + amount: randomAmount, + }; + + return airdropTarget; + }); + + // Call transferBatch on all our airdrop targets. + console.log("🌈 Starting airdrop..."); + await token.transferBatch(airdropTargets); + console.log("✅ Successfully airdropped tokens to all the holders of the NFT!"); + } catch (err) { + console.error("Failed to airdrop tokens", err); + } +})(); \ No newline at end of file diff --git a/scripts/8-deploy-vote.js b/scripts/8-deploy-vote.js index e69de29b..671adcb9 100644 --- a/scripts/8-deploy-vote.js +++ b/scripts/8-deploy-vote.js @@ -0,0 +1,42 @@ +import sdk from "./1-initialize-sdk.js"; + +(async () => { + try { + const voteContractAddress = await sdk.deployer.deployVote({ + // Give your governance contract a name. + name: "My amazing DAO", + + // This is the location of our governance token, our ERC-20 contract! + voting_token_address: "0x6B8f666c5F843056DEfA2ef8124A562E5496d141", + + // These parameters are specified in number of blocks. + // Assuming block time of around 13.14 seconds (for Ethereum) + + // After a proposal is created, when can members start voting? + // For now, we set this to immediately. + voting_delay_in_blocks: 0, + + // How long do members have to vote on a proposal when it's created? + // we will set it to 1 day = 6570 blocks + voting_period_in_blocks: 6570, + + // The minimum % of the total supply that need to vote for + // the proposal to be valid after the time for the proposal has ended. + voting_quorum_fraction: 0, + + // What's the minimum # of tokens a user needs to be allowed to create a proposal? + // I set it to 0. Meaning no tokens are required for a user to be allowed to + // create a proposal. + proposal_token_threshold: 0, + }); + + console.log( + "✅ Successfully deployed vote contract, address:", + voteContractAddress, + ); + } catch (err) { + console.error("Failed to deploy vote contract", err); + } +})(); + +// voting address 0x74FB3B7259CE850fA3A2EED3Bf9F610fC9756164 \ No newline at end of file diff --git a/scripts/9-setup-vote.js b/scripts/9-setup-vote.js index e69de29b..d538a751 100644 --- a/scripts/9-setup-vote.js +++ b/scripts/9-setup-vote.js @@ -0,0 +1,45 @@ +import sdk from "./1-initialize-sdk.js"; + +// This is our governance contract. +const vote = sdk.getVote("0x74FB3B7259CE850fA3A2EED3Bf9F610fC9756164"); + +// This is our ERC-20 contract. +const token = sdk.getToken("0x6B8f666c5F843056DEfA2ef8124A562E5496d141"); + +(async () => { + try { + // Give our treasury the power to mint additional token if needed. + await token.roles.grant("minter", vote.getAddress()); + + console.log( + "Successfully gave vote contract permissions to act on token contract" + ); + } catch (error) { + console.error( + "failed to grant vote contract permissions on token contract", + error + ); + process.exit(1); + } + + try { + // Grab our wallet's token balance, remember -- we hold basically the entire supply right now! + const ownedTokenBalance = await token.balanceOf( + process.env.WALLET_ADDRESS + ); + + // Grab 90% of the supply that we hold. + const ownedAmount = ownedTokenBalance.displayValue; + const percent90 = Number(ownedAmount) / 100 * 90; + + // Transfer 90% of the supply to our voting contract. + await token.transfer( + vote.getAddress(), + percent90 + ); + + console.log("✅ Successfully transferred " + percent90 + " tokens to vote contract"); + } catch (err) { + console.error("failed to transfer tokens to vote contract", err); + } +})(); \ No newline at end of file diff --git a/scripts/assets/coko_flower.jpg b/scripts/assets/coko_flower.jpg new file mode 100644 index 00000000..8b75f112 Binary files /dev/null and b/scripts/assets/coko_flower.jpg differ diff --git a/scripts/assets/lotsofdogs.jpg b/scripts/assets/lotsofdogs.jpg new file mode 100644 index 00000000..0615128c Binary files /dev/null and b/scripts/assets/lotsofdogs.jpg differ diff --git a/src/App.jsx b/src/App.jsx index d6b00225..e3ea056d 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,9 +1,395 @@ +import { useAddress, useMetamask, useEditionDrop, useToken, useNetwork, useVote } from '@thirdweb-dev/react'; +import { ChainId } from '@thirdweb-dev/sdk' +import { useState, useEffect, useMemo } from 'react'; +import { AddressZero } from "@ethersproject/constants"; + const App = () => { + // Use the hooks thirdweb give us. + const address = useAddress(); + const network = useNetwork(); + const connectWithMetamask = useMetamask(); + console.log("👋 Address:", address); + + + console.log("Network:", network) + + // Initialize our editionDrop contract + const editionDrop = useEditionDrop("0x6288757CC1d20E19d48Fc44C99Eb222C3A2cEAD5"); + + // Initialize our token contract + const token = useToken("0x6B8f666c5F843056DEfA2ef8124A562E5496d141"); + + const vote = useVote("0x74FB3B7259CE850fA3A2EED3Bf9F610fC9756164"); + + // State variable for us to know if user has our NFT. + const [hasClaimedNFT, setHasClaimedNFT] = useState(false); + // isClaiming lets us easily keep a loading state while the NFT is minting. + const [isClaiming, setIsClaiming] = useState(false); + + // Holds the amount of token each member has in state. + const [memberTokenAmounts, setMemberTokenAmounts] = useState([]); + // The array holding all of our members addresses. + const [memberAddresses, setMemberAddresses] = useState([]); + + // A fancy function to shorten someones wallet address, no need to show the whole thing. + const shortenAddress = (str) => { + return str.substring(0, 6) + "..." + str.substring(str.length - 4); + }; + + const [proposals, setProposals] = useState([]); + const [isVoting, setIsVoting] = useState(false); + const [hasVoted, setHasVoted] = useState(false); + + // Retrieve all our existing proposals from the contract. + useEffect(() => { + if (!hasClaimedNFT) { + return; + } + + // A simple call to vote.getAll() to grab the proposals. + const getAllProposals = async () => { + try { + const proposals = await vote.getAll(); + setProposals(proposals); + } catch (error) { + console.log("failed to get proposals", error); + } + }; + getAllProposals(); + }, [hasClaimedNFT, vote]); + + // We also need to check if the user already voted. + useEffect(() => { + if (!hasClaimedNFT) { + return; + } + + // If we haven't finished retrieving the proposals from the useEffect above + // then we can't check if the user voted yet! + if (!proposals.length) { + return; + } + + const checkIfUserHasVoted = async () => { + try { + const hasVoted = await vote.hasVoted(proposals[0].proposalId, address); + setHasVoted(hasVoted); + if (hasVoted) { + console.log("🥵 User has already voted"); + } else { + console.log("🙂 User has not voted yet"); + } + } catch (error) { + console.error("Failed to check if wallet has voted", error); + } + }; + checkIfUserHasVoted(); + + }, [hasClaimedNFT, proposals, address, vote]); + + + // This useEffect grabs all the addresses of our members holding our NFT. + useEffect(() => { + if (!hasClaimedNFT) { + return; + } + + // Just like we did in the 7-airdrop-token.js file! Grab the users who hold our NFT + // with tokenId 0. + const getAllAddresses = async () => { + try { + const memberAddresses = await editionDrop.history.getAllClaimerAddresses(0); + setMemberAddresses(memberAddresses); + console.log("🚀 Members addresses", memberAddresses); + } catch (error) { + console.error("failed to get member list", error); + } + + }; + getAllAddresses(); + }, [hasClaimedNFT, editionDrop.history]); + + // This useEffect grabs the # of token each member holds. + useEffect(() => { + if (!hasClaimedNFT) { + return; + } + + const getAllBalances = async () => { + try { + const amounts = await token.history.getAllHolderBalances(); + setMemberTokenAmounts(amounts); + console.log("👜 Amounts", amounts); + } catch (error) { + console.error("failed to get member balances", error); + } + }; + getAllBalances(); + }, [hasClaimedNFT, token.history]); + + // Now, we combine the memberAddresses and memberTokenAmounts into a single array + const memberList = useMemo(() => { + return memberAddresses.map((address) => { + // We're checking if we are finding the address in the memberTokenAmounts array. + // If we are, we'll return the amount of token the user has. + // Otherwise, return 0. + const member = memberTokenAmounts?.find(({ holder }) => holder === address); + + return { + address, + tokenAmount: member?.balance.displayValue || "0", + } + }); + }, [memberAddresses, memberTokenAmounts]); + + useEffect(() => { + // If they don't have an connected wallet, exit! + if (!address) { + return; + } + + const checkBalance = async () => { + try { + const balance = await editionDrop.balanceOf(address, 0); + if (balance.gt(0)) { + setHasClaimedNFT(true); + console.log("🌟 this user has a membership NFT!"); + } else { + setHasClaimedNFT(false); + console.log("😭 this user doesn't have a membership NFT."); + } + } catch (error) { + setHasClaimedNFT(false); + console.error("Failed to get balance", error); + } + }; + checkBalance(); + }, [address, editionDrop]); + + const mintNft = async () => { + try { + setIsClaiming(true); + await editionDrop.claim("0", 1); + console.log(`🌊 Successfully Minted! Check it out on OpenSea: https://testnets.opensea.io/assets/${editionDrop.getAddress()}/0`); + setHasClaimedNFT(true); + } catch (error) { + setHasClaimedNFT(false); + console.error("Failed to mint NFT", error); + } finally { + setIsClaiming(false); + } + }; + // console.log(network?.[0]) + // if (network?.[0].data.chain.id !== ChainId.Rinkeby) { + // return ( + //
+ //

Please connect to Rinkeby

+ //

+ // This dapp only works on the Rinkeby network, please switch networks + // in your connected wallet. + //

+ //
+ // ); + // } + // This is the case where the user hasn't connected their wallet + // to your web app. Let them call connectWallet. + if (!address) { + return ( +
+

Welcome to CokoDAO

+ +
+ ); + } + + // Add this little piece! + // if (hasClaimedNFT) { + // return ( + //
+ //

🍪DAO Member Page

+ //

Congratulations on being a member

+ //
+ // ); + // }; + // If the user has already claimed their NFT we want to display the interal DAO page to them + // only DAO members will see this. Render all the members + token amounts. + if (hasClaimedNFT) { + return ( +
+

🍪DAO Member Page

+

Congratulations on being a member

+
+
+

Member List

+ + + + + + + + + {memberList.map((member) => { + return ( + + + + + ); + })} + +
AddressToken Amount
{shortenAddress(member.address)}{member.tokenAmount}
+
+
+

Active Proposals

+
{ + e.preventDefault(); + e.stopPropagation(); + + //before we do async things, we want to disable the button to prevent double clicks + setIsVoting(true); + + // lets get the votes from the form for the values + const votes = proposals.map((proposal) => { + const voteResult = { + proposalId: proposal.proposalId, + //abstain by default + vote: 2, + }; + proposal.votes.forEach((vote) => { + const elem = document.getElementById( + proposal.proposalId + "-" + vote.type + ); + + if (elem.checked) { + voteResult.vote = vote.type; + return; + } + }); + return voteResult; + }); + + // first we need to make sure the user delegates their token to vote + try { + //we'll check if the wallet still needs to delegate their tokens before they can vote + const delegation = await token.getDelegationOf(address); + // if the delegation is the 0x0 address that means they have not delegated their governance tokens yet + if (delegation === AddressZero) { + //if they haven't delegated their tokens yet, we'll have them delegate them before voting + await token.delegateTo(address); + } + // then we need to vote on the proposals + try { + await Promise.all( + votes.map(async ({ proposalId, vote: _vote }) => { + // before voting we first need to check whether the proposal is open for voting + // we first need to get the latest state of the proposal + const proposal = await vote.get(proposalId); + // then we check if the proposal is open for voting (state === 1 means it is open) + if (proposal.state === 1) { + // if it is open for voting, we'll vote on it + return vote.vote(proposalId, _vote); + } + // if the proposal is not open for voting we just return nothing, letting us continue + return; + }) + ); + try { + // if any of the propsals are ready to be executed we'll need to execute them + // a proposal is ready to be executed if it is in state 4 + await Promise.all( + votes.map(async ({ proposalId }) => { + // we'll first get the latest state of the proposal again, since we may have just voted before + const proposal = await vote.get(proposalId); + + //if the state is in state 4 (meaning that it is ready to be executed), we'll execute the proposal + if (proposal.state === 4) { + return vote.execute(proposalId); + } + }) + ); + // if we get here that means we successfully voted, so let's set the "hasVoted" state to true + setHasVoted(true); + // and log out a success message + console.log("successfully voted"); + } catch (err) { + console.error("failed to execute votes", err); + } + } catch (err) { + console.error("failed to vote", err); + } + } catch (err) { + console.error("failed to delegate tokens"); + } finally { + // in *either* case we need to set the isVoting state to false to enable the button again + setIsVoting(false); + } + }} + > + {proposals.map((proposal) => ( +
+
{proposal.description}
+
+ {proposal.votes.map(({ type, label }) => ( +
+ + +
+ ))} +
+
+ ))} + + {!hasVoted && ( + + This will trigger multiple transactions that you will need to + sign. + + )} +
+
+
+
+ ); + }; + + // Render mint nft screen. return ( -
-

Welcome to My DAO

+
+

Mint your free 🍪DAO Membership NFT

+
); -}; -export default App; + // This is the case where we have the user's address + // which means they've connected their wallet to our site! + // return ( + //
+ //

👀 wallet connected, now what!

+ //
); +} + +export default App; \ No newline at end of file diff --git a/src/index.css b/src/index.css index 98eee89d..b0985af8 100644 --- a/src/index.css +++ b/src/index.css @@ -4,7 +4,7 @@ body { font-family: Inter, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - background-color: #7700ee; + background-color: #ee0077; color: #fff; overflow-x: hidden; } diff --git a/src/index.js b/src/index.js index f5fb98f2..02dd7c9e 100644 --- a/src/index.js +++ b/src/index.js @@ -17,4 +17,5 @@ ReactDOM.render( , document.getElementById('root'), -); \ No newline at end of file +); +