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 (
+
);
-};
-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
+);
+