1648528260
Apprenez à créer un DAO, à créer des NFT, des jetons de largage aérien et à ajouter une fonctionnalité de vote en utilisant uniquement JavaScript dans ce didacticiel complet.
DAO signifie Organisation Autonome Décentralisée. Comme son nom l'indique, un DAO est une organisation sans chef unique; au lieu de cela, les règles sont encodées dans la blockchain. Pour cette raison, un DAO est complètement transparent et tous ceux qui y participent ont un intérêt. Les décisions importantes sont prises par vote parmi ceux qui possèdent des jetons non fongibles (NFT) du DAO, qui accordent l'adhésion.
Aujourd'hui, nous allons créer notre propre DAO en utilisant Next.js, thirdweb, MetaMask et Alchemy. Il permettra aux utilisateurs de frapper le NFT de votre DAO, de recevoir de la crypto-monnaie via des parachutages et de participer aux sondages du DAO. Ce didacticiel sera écrit uniquement avec JavaScript, vous n'avez donc pas besoin de connaître Solidity.
Pour comprendre et suivre ce didacticiel, vous devez disposer des éléments suivants :
.env
des variablesNous allons commencer par configurer une application Next.js avec la commande suivante :
npx créer-next-app mon-dao
Maintenant, nous allons créer un nouveau projet dans thirdweb . Allez sur thirdweb et connectez votre portefeuille MetaMask. Après avoir connecté votre portefeuille, cliquez sur Créer un projet et choisissez le réseau Rinkeby.
Donnez un nom et une description à votre projet, puis cliquez sur Créer . Si vous n'avez pas assez d'ETH pour payer l'essence, procurez-vous en à ce robinet .
Ensuite, rendez-vous sur Alchemy, connectez-vous, cliquez sur Créer une application et fournissez les détails requis. Assurez-vous d'utiliser la même chaîne que celle que vous avez utilisée dans thirdweb - dans notre cas, il s'agit de la chaîne Ethereum et du réseau Rinkeby.
Une fois l'application créée, copiez la clé d'API HTTP.
Afin de créer des NFT et d'exécuter certains scripts, nous allons avoir besoin de la clé privée du portefeuille.
Pour y accéder, ouvrez l'extension de navigateur MetaMask et cliquez sur Détails du compte . Vous devriez voir votre clé privée ici; exportez-le et copiez-le dans un endroit sûr.
.env
des variablesAjoutons ces variables dans un .env
fichier afin de pouvoir y accéder plus tard :
PRIVATE_KEY=<wallet_private_key>
ALCHEMY_API_URL=<alchemy_http_key>
WALLET_ADDRESS=<public_wallet_address>
Parce que nous ne voulons pas les pousser vers GitHub, assurez-vous de les ajouter dans .gitignore
Dans DApps, MetaMask est le portefeuille le plus utilisé, nous ajouterons donc la connexion MetaMask avec thirdweb.
Nous allons avoir besoin de deux packages à partir de l'installation :
npm i @3rdweb/sdk @3rdweb/hooks # npm
yarn add @3rdweb/sdk @3rdweb/hooks # yarn
Nous devons encapsuler l'ensemble de notre application dans un troisième fournisseur Web afin d'accéder aux informations de connexion et aux autres informations requises pour les composants :
import { thirdwebWeb3Provider } from "@3rdweb/hooks";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return (
<thirdwebWeb3Provider>
<Component {...pageProps} />
</thirdwebWeb3Provider>
);
}
export default MyApp;
À des fins d'authentification, nous devons également spécifier le type d'authentification et les ID de chaîne pris en charge. Nous utilisons MetaMask et la chaîne Rinkeby, alors ajoutez également ce qui suit :
const supportedChainIds = [4];
const connectors = {
injected: {},
};
Enfin, transmettez-les en tant qu'accessoires dans le fournisseur comme suit :
<thirdwebWeb3Provider
connectors={connectors}
supportedChainIds={supportedChainIds}
>
<Component {...pageProps} />
</thirdwebWeb3Provider>
Créez un nouveau dossier appelé components
à la racine du projet et ajoutez-y un Login.js
fichier :
import { useWeb3 } from "@3rdweb/hooks";
const Login = () => {
const { connectWallet } = useWeb3();
return (
<div>
<button onClick={() => connectWallet("injected")}>
Sign in using MetaMask
</button>
</div>
);
};
export default Login;
Heureusement, thirdweb fournit une connectWallet
fonction que nous pouvons utiliser pour ajouter une authentification !
À l' intérieur index.js
de , affichez l'écran de connexion s'il n'y a pas d'adresse (si l'utilisateur n'est pas connecté) :
const { address } = useWeb3();
if (!address) {
return <Login />;
}
Cela permettra à nos utilisateurs de se connecter, mais ensuite, un écran vide s'affichera. Donc, dans l'autre bloc de retour, montrons à l'utilisateur son adresse :
export default function Home() {
const { address } = useWeb3();
if (!address) {
return <Login />;
}
return (
<div className={styles.container}>
<h2>You are signed in as {address}</h2>
</div>
);
}
La connexion fonctionne mais cela ne semble pas bon pour le moment. Créez donc un nouveau fichier Login.module.css
dans le styles
dossier et ajoutez ce qui suit :
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #7449bb;
}
.button {
color: #7449bb;
background-color: white;
border: none;
border-radius: 5px;
padding: 10px;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
font-weight: 500;
}
Ensuite, ajoutez les classes suivantes àLogin.js
:
<div className={styles.container}>
<button className={styles.button} onClick={() => connectWallet("injected")}>
Sign in using MetaMask
</button>
</div>
Et enfin, importez les styles :
import styles from "../styles/Login.module.css";
Cela nous donnera un écran de connexion simple mais beau.
Nous devons maintenant initialiser le SDK thirdweb pour les différents scripts que nous allons exécuter. Commencez par créer un nouveau dossier appelé scripts
avec un initialize-sdk.js
fichier à l'intérieur.
Ajoutez le code suivant au fichier :
import { thirdwebSDK } from "@3rdweb/sdk";
import ethers from "ethers";
import dotenv from "dotenv";
dotenv.config();
if (!process.env.PRIVATE_KEY || process.env.PRIVATE_KEY == "") {
console.log(" Private key not found.");
}
if (!process.env.ALCHEMY_API_URL || process.env.ALCHEMY_API_URL == "") {
console.log(" Alchemy API URL not found.");
}
if (!process.env.WALLET_ADDRESS || process.env.WALLET_ADDRESS == "") {
console.log(" Wallet Address not found.");
}
const sdk = new thirdwebSDK(
new ethers.Wallet(
process.env.PRIVATE_KEY,
ethers.getDefaultProvider(process.env.ALCHEMY_API_URL)
)
);
(async () => {
try {
const apps = await sdk.getApps();
console.log("app address:", apps[0].address);
} catch (err) {
console.error("Failed to get apps from the sdk", err);
process.exit(1);
}
})();
export default sdk;
Cela initialisera le SDK thirdweb, et comme vous pouvez le voir, nous devons installer certains packages :
npm i ethers dotenv # npm
yarn add ethers dotenv # yarn
Nous utilisons ici des importations modulaires, alors créez un nouveau package.json
fichier dans le scripts
dossier et ajoutez simplement ce qui suit :
{
"name": "scripts",
"type": "module"
}
Enfin, exécutez le script :
node scripts/initialize-sdk.js
L'exécution du script peut prendre un certain temps, mais après un certain temps, vous obtiendrez l'adresse de votre application.
Nous en aurons besoin dans les prochaines étapes, alors stockez-le dans un endroit sûr.
Pour cette étape, nous allons avoir besoin d'ETH de test, alors allez à un robinet comme celui -ci et obtenez-en.
Créez un nouveau fichier appelé deploy-drop.js
à l'intérieur du scripts
dossier. Ici, ajoutez le script suivant :
import { ethers } from "ethers";
import sdk from "./initialize-sdk.js";
import { readFileSync } from "fs";
const app = sdk.getAppModule("APP_ADDRESS");
(async () => {
try {
const bundleDropModule = await app.deployBundleDropModule({
name: "LogRocket DAO", // Name of NFT Collection for DAO
description: "A DAO for all the LogRocket readers.", // Description
image: readFileSync("scripts/assets/rocket.png"), // Image for NFT collection
primarySaleRecipientAddress: ethers.constants.AddressZero,
});
console.log(
" Successfully deployed bundleDrop module, address:",
bundleDropModule.address,
);
console.log(
" bundleDrop metadata:",
await bundleDropModule.getMetadata(),
);
} catch (error) {
console.log("failed to deploy bundleDrop module", error);
}
})()
Vous devrez mettre à jour quelques éléments ici :
assets
et en y ajoutant l'image pour votre NFTAprès avoir mis à jour les détails, exécutez le script suivant :
node scripts/deploy-drop.js
Attendez que le script s'exécute et vous devriez obtenir une adresse et les métadonnées.
Vous pouvez même vérifier la transaction sur Rinkeby Etherscan
Configurons notre NFT maintenant ! Créez un nouveau config-nft.js
fichier dans le scripts
dossier et ajoutez ce qui suit :
import sdk from "./initialize-sdk.js";
import { readFileSync } from "fs";
const bundleDrop = sdk.getBundleDropModule(
"BUNDLE_DROP_ADDRESS"
);
(async () => {
try {
await bundleDrop.createBatch([
{
name: "LogRocket DAO", // Name of NFT Collection for DAO
description: "A DAO for all the LogRocket readers.", // Description
image: readFileSync("scripts/assets/rocket.png"), // Image for NFT collection
},
]);
console.log(" Successfully created a new NFT in the drop!");
} catch (error) {
console.error("failed to create the new NFT", error);
}
})();
Vous devez mettre à jour l'adresse de dépôt du bundle et les détails dans l'objet à l'intérieur de createBatch
. Ces détails vont être utilisés pour le NFT !
Une fois que vous les avez tous mis à jour, exécutez le script suivant :
node scripts/config-nft.js
Cela devrait vous donner une sortie comme celle-ci.
Si vous voyez le module dans le tableau de bord thirdweb, vous verrez qu'un NFT a été créé !
Enfin, ajoutons une condition de réclamation à notre NFT
La définition d'une condition de réclamation nous permettra de fixer une limite pour les NFT et d'autoriser une limite maximale spécifique par transaction. Nous définirons une condition de réclamation à partir du tableau de bord lui-même, alors cliquez sur le bouton Paramètres et vous pourrez mettre à jour les données en fonction de vos besoins.
Une fois la mise à jour terminée, cliquez sur Enregistrer et confirmez la petite transaction.
Avant de créer un bouton de menthe qui permet aux utilisateurs de frapper des NFT, vérifions si l'utilisateur a déjà un NFT. Nous ne voulons pas que les utilisateurs frappent plusieurs NFT !
Commencez par ajouter deux nouvelles variables, sdk
et bundleDropModule
, comme ceci avant notre composant fonctionnel :
const sdk = new thirdwebSDK("rinkeby");
const bundleDropModule = sdk.getBundleDropModule(
"BUNDLE_DROP_ADDRESS"
);
Vous devrez également importer thirdwebSDK
:
import { thirdwebSDK } from "@3rdweb/sdk";
Maintenant, créons un état pour hasClaimedNFT
:
const [hasClaimedNFT, setHasClaimedNFT] = useState(false);
Nous devons également créer un useEffect
crochet pour vérifier si l'utilisateur a le NFT :
useEffect(() => {
const handler = async () => {
if (!address) {
return;
}
try {
const balance = await bundleDropModule.balanceOf(address, "0");
if (balance.gt(0)) {
setHasClaimedNFT(true);
console.log(" You have an NFT!");
} else {
setHasClaimedNFT(false);
console.log(" You don't have an NFT.");
}
} catch (error) {
setHasClaimedNFT(false);
console.error("failed to nft balance", error);
}
};
handler();
}, [address]);
Tout d'abord, il vérifiera si l'utilisateur est connecté. Si l'utilisateur n'est pas connecté, il ne renverra rien. Ensuite, cela vérifie si l'utilisateur a le NFT avec l'ID de jeton 0
dans le contrat de dépôt que nous avons importé en haut.
Si vous ouvrez la console sur le site Web, cela devrait indiquer que vous n'avez pas de NFT.
Créons le bouton pour créer des NFT ! Créez une nouvelle fonction appelée mintNft
comme suit :
const mintNft = async () => {
setIsClaiming(true);
try {
await bundleDropModule.claim("0", 1);
setHasClaimedNFT(true);
console.log(" Successfully Minted the NFT!");
} catch (error) {
console.error("failed to claim", error);
} finally {
setIsClaiming(false);
}
};
Nous appellerons cette fonction lorsqu'un bouton est cliqué pour frapper le NFT sur le portefeuille de l'utilisateur. Mais d'abord, ajoutons les deux états requis :
const [hasClaimedNFT, setHasClaimedNFT] = useState(false);
const [isClaiming, setIsClaiming] = useState(false);
Nous devons également appeler la providerSigner
fonction, donc obtenez d'abord le fournisseur de useWeb3
:
const { address, provider } = useWeb3();
Et ensuite appelez la fonction :
const signer = provider ? provider.getSigner() : undefined;
useEffect(() => {
sdk.setProviderOrSigner(signer);
}, [signer]);
Créons le bouton maintenant ! Dans le bloc de retour final, ajoutez ce qui suit :
<div>
<h1>Mint your free LogRocket DAO Membership NFT </h1>
<button disabled={isClaiming} onClick={() => mintNft()}>
{isClaiming ? "Minting..." : "Mint your nft (FREE)"}
</button>
</div>
Maintenant, après nous être connectés, il devrait nous montrer un écran comme celui-ci.
Vous pouvez même visualiser votre NFT sur Opensea Testnets ; allez simplement sur https://testnets.opensea.io/assets//0
Enfin, juste au-dessus du bloc de retour final, ajoutez cette vérification pour voir si l'utilisateur a déjà réclamé le NFT :
if (hasClaimedNFT) {
return (
<div>
<h1>You have the DAO Membership NFT!</h1>
</div>
);
}
Nous avons terminé la construction de la fonctionnalité NFT de frappe, mais elle a l'air moche, alors ajoutons quelques styles de base. À l' intérieur, Home.module.css
ajoutez ce qui suit :
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #7449bb;
}
.container > h1 {
font-size: 3rem;
color: #fff;
font-weight: bold;
}
.button {
color: #7449bb;
background-color: white;
border: none;
border-radius: 5px;
padding: 10px;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
font-weight: 500;
}
Il faut aussi ajouter le classNames
:
if (hasClaimedNFT) {
return (
<div className={styles.container}>
<h1>You have the DAO Membership NFT!</h1>
</div>
);
}
return (
<div className={styles.container}>
<h1>Mint your free LogRocket DAO Membership NFT </h1>
<button
className={styles.button}
disabled={isClaiming}
onClick={() => mintNft()}
>
{isClaiming ? "Minting..." : "Mint your NFT (FREE)"}
</button>
</div>
);
};
Cela nous donne un meilleur écran de menthe.
Créez un nouveau fichier appelé deploy-token.js
dans le scripts
dossier. Ajoutez-y ce qui suit :
import sdk from "./initialize-sdk.js";
const app = sdk.getAppModule("YOUR_APP_ADDRESS");
(async () => {
try {
const tokenModule = await app.deployTokenModule({
name: "LogRocket Token", // name of the token
symbol: "LR", // symbol
});
console.log(
" Successfully deployed token module, address:",
tokenModule.address
);
} catch (error) {
console.error("failed to deploy token module", error);
}
})();
Ce script créera un nouveau module de jeton avec un nom et un symbole. Vous devrez mettre à jour manuellement l'adresse de l'application, le nom du jeton et le symbole vous-même.
Après la mise à jour, exécutez le script.
Vous pouvez vérifier ce jeton par l'adresse sur Rinkeby Etherscan, et également l'ajouter sur votre portefeuille MetaMask en cliquant sur Importer des jetons .
Après l'importation, vous devriez voir le jeton sous vos actifs.
Il est actuellement nul, alors frappons quelques jetons !
Créez un nouveau fichier appelé mint-token.js
dans le scripts
dossier et ajoutez ce qui suit :
import { ethers } from "ethers";
import sdk from "./initialize-sdk.js";
const tokenModule = sdk.getTokenModule(
"TOKEN_MODULE_ADDRESS"
);
(async () => {
try {
const amount = 1_000_000;
const amountWith18Decimals = ethers.utils.parseUnits(amount.toString(), 18);
await tokenModule.mint(amountWith18Decimals);
const totalSupply = await tokenModule.totalSupply();
console.log(
" There now is",
ethers.utils.formatUnits(totalSupply, 18),
"$LR in circulation"
);
} catch (error) {
console.error("Failed to mint tokens", error);
}
})();
Mettez à jour l'adresse du module de jeton avec l'adresse que vous avez obtenue dans le dernier script, et vous pouvez mettre à jour le montant que vous souhaitez frapper.
Une fois que vous êtes prêt à frapper, exécutez le script :
node scripts/mint-token.js
Vous devriez maintenant voir le nombre de jetons que vous avez émis sur votre portefeuille MetaMask !
Nous pourrions souhaiter diffuser les jetons à nos détenteurs de NFT, alors créons un script pour cela. Créez un nouveau airdrop.js
fichier à l'intérieur scripts
et ajoutez ce qui suit :
import { ethers } from "ethers";
import sdk from "./initialize-sdk.js";
const bundleDropModule = sdk.getBundleDropModule(
"BUNDLE_DROP_ADDRESS"
);
const tokenModule = sdk.getTokenModule(
"TOKEN_MODULE_ADDRESS"
);
(async () => {
try {
const walletAddresses = await bundleDropModule.getAllClaimerAddresses("0");
if (walletAddresses.length === 0) {
console.log(
"No NFTs have been claimed yet, ask your friends to mint some free ones :P!"
);
process.exit(0);
}
const airdropTargets = walletAddresses.map((address) => {
const randomAmount = Math.floor(
Math.random() * (10000 - 1000 + 1) + 1000
);
console.log(" Going to airdrop", randomAmount, "tokens to", address);
return {
address,
amount: ethers.utils.parseUnits(randomAmount.toString(), 18),
};
});
console.log(" Starting airdrop...");
await tokenModule.transferBatch(airdropTargets);
console.log(
" Successfully airdropped tokens to all the holders of the NFT!"
);
} catch (err) {
console.error("Failed to airdrop tokens", err);
}
})();
Après avoir exécuté le script, vous devriez obtenir quelque chose comme ça.
Actuellement, vous seul avez créé un NFT, il n'enverra donc pas le jeton à quelqu'un d'autre. Mais cela peut être utilisé pour l'envoyer ultérieurement à d'autres détenteurs de NFT.
Créez un nouveau deploy-vote.js
fichier dans le scripts
dossier et ajoutez ce qui suit :
import sdk from "./initialize-sdk.js";
const appModule = sdk.getAppModule(
"APP_MODULE_ADDRESS"
);
(async () => {
try {
const voteModule = await appModule.deployVoteModule({
name: "LR Dao's Proposals",
votingTokenAddress: "0x6fb07DCBC53Fd8390de38CDBfCc267b5A72761ca",
proposalStartWaitTimeInSeconds: 0,
proposalVotingTimeInSeconds: 24 * 60 * 60,
votingQuorumFraction: 0,
minimumNumberOfTokensNeededToPropose: "0",
});
console.log(
" Successfully deployed vote module, address:",
voteModule.address
);
} catch (err) {
console.error("Failed to deploy vote module", err);
}
})();
Mettez à jour l'adresse de l'application, le nom et l'adresse du jeton de vote, puis exécutez le script :
node scripts/deploy-vote.js
Nous devons également configurer un module de vote, alors créez un nouveau script appelé setup-vote.js
et ajoutez ce qui suit :
import { ethers } from "ethers";
import sdk from "./initialize-sdk.js";
const voteModule = sdk.getVoteModule(
"0xA57A03B9e117723b6873100742116A01140C43f4"
);
const tokenModule = sdk.getTokenModule(
"0x6fb07DCBC53Fd8390de38CDBfCc267b5A72761ca"
);
(async () => {
try {
await tokenModule.grantRole("minter", voteModule.address);
console.log(
"Successfully gave vote module permissions to act on token module"
);
} catch (error) {
console.error(
"failed to grant vote module permissions on token module",
error
);
process.exit(1);
}
try {
const ownedTokenBalance = await tokenModule.balanceOf(
process.env.WALLET_ADDRESS
);
const ownedAmount = ethers.BigNumber.from(ownedTokenBalance.value);
const percent90 = ownedAmount.div(100).mul(90);
await tokenModule.transfer(voteModule.address, percent90);
console.log(" Successfully transferred tokens to vote module");
} catch (err) {
console.error("failed to transfer tokens to vote module", err);
}
})();
Vous devrez exécuter ce script pour le terminer :
node scripts/setup-vote.js
Maintenant que notre module de vote est prêt, créons quelques propositions !
Créez un nouveau fichier appelé vote-proposals.js
dans le scripts
dossier et ajoutez ce qui suit :
import { ethers } from "ethers";
import sdk from "./initialize-sdk.js";
const voteModule = sdk.getVoteModule(
"VOTE_MODULE"
);
const tokenModule = sdk.getTokenModule(
"TOKEN_MODULE"
);
(async () => {
try {
const amount = 10000;
await voteModule.propose(
"Should the DAO mint an additional " +
amount +
" tokens into the treasury?",
[
{
nativeTokenValue: 0,
transactionData: tokenModule.contract.interface.encodeFunctionData(
"mint",
[voteModule.address, ethers.utils.parseUnits(amount.toString(), 18)]
),
toAddress: tokenModule.address,
},
]
);
console.log(" Successfully created proposal to mint tokens");
} catch (error) {
console.error("failed to create first proposal", error);
process.exit(1);
}
})();
Vous devez mettre à jour les adresses des modules, et si vous souhaitez mettre à jour le message de la proposition, vous pouvez également le mettre à jour.
Enfin, exécutez le script. Cela devrait vous donner quelque chose comme ça.
Si vous vérifiez maintenant le tableau de bord thirdweb, la proposition a été créée.
Tout d'abord, importez le module jeton et vote :
const voteModule = sdk.getVoteModule(
"0xf738973379b8B6444e429D2fd6C8B1f223247390"
);
const tokenModule = sdk.getTokenModule(
"0x8C35288de335070dd1C00d68d71383d81437472A"
);
Nous allons avoir besoin de trois useState
s, comme ceci :
const [proposals, setProposals] = useState([]);
const [isVoting, setIsVoting] = useState(false);
const [hasVoted, setHasVoted] = useState(false);
Nous devons récupérer les propositions pour les afficher à l'écran, alors créez ceci useEffect
:
useEffect(() => {
if (!hasClaimedNFT) {
return;
}
voteModule
.getAll()
.then((proposals) => {
setProposals(proposals);
})
.catch((err) => {
console.error("failed to get proposals", err);
});
}, [hasClaimedNFT]);
Ensuite, créez une nouvelle handleFormSubmit
fonction :
const handleFormSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsVoting(true);
const votes = proposals.map((proposal) => {
let voteResult = {
proposalId: proposal.proposalId,
vote: 2,
};
proposal.votes.forEach((vote) => {
const elem = document.getElementById(
proposal.proposalId + "-" + vote.type
);
if (elem.checked) {
voteResult.vote = vote.type;
return;
}
});
return voteResult;
});
try {
const delegation = await tokenModule.getDelegationOf(address);
if (delegation === ethers.constants.AddressZero) {
await tokenModule.delegateTo(address);
}
try {
await Promise.all(
votes.map(async (vote) => {
const proposal = await voteModule.get(vote.proposalId);
if (proposal.state === 1) {
return voteModule.vote(vote.proposalId, vote.vote);
}
return;
})
);
try {
await Promise.all(
votes.map(async (vote) => {
const proposal = await voteModule.get(vote.proposalId);
if (proposal.state === 4) {
return voteModule.execute(vote.proposalId);
}
})
);
setHasVoted(true);
} 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", err);
} finally {
setIsVoting(false);
}
};
Cette fonction va collecter le vote.
Remplacez le if (hasClaimedNFT)
bloc par ceci :
if (hasClaimedNFT) {
return (
<div className={styles.container}>
<h2>Active Proposals</h2>
<form onSubmit={handleFormSubmit}>
{proposals.map((proposal) => (
<Proposal
key={proposal.proposalId}
votes={proposal.votes}
description={proposal.description}
proposalId={proposal.proposalId}
/>
))}
<button
onClick={handleFormSubmit}
type="submit"
className={styles.button}
>
{isVoting
? "Voting..."
"Submit Votes"}
</button>
</form>
</div>
);
}
Nous créons un composant distinct pour la proposition afin de garder les choses propres. Créez donc un nouveau fichier appelé Proposal.js
dans le components
dossier et ajoutez ce qui suit :
import styles from "../styles/Proposal.module.css";
const Proposal = ({ description, votes, proposalId }) => {
return (
<div className={styles.proposal}>
<h5 className={styles.description}>{description}</h5>
<div className={styles.options}>
{votes.map((vote) => (
<div key={vote.type}>
<input
type="radio"
id={proposalId + "-" + vote.type}
name={proposalId}
value={vote.type}
defaultChecked={vote.type === 2}
/>
<label htmlFor={proposalId + "-" + vote.type}>{vote.label}</label>
</div>
))}
</div>
</div>
);
};
export default Proposal;
J'ai également ajouté un style de base, alors créez un nouveau Proposal.module.css
fichier dans le styles
dossier :
.proposal {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fafafa;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
margin: 20px;
}
.options {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
width: 100%;
margin-top: 1rem;
}
Pour centrer les éléments, j'ai également ajouté les styles suivantsHome.module.css
:
.container > form {
display: flex;
flex-direction: column;
align-items: center;
}
Vous arriverez sur cet écran où vous pourrez soumettre vos votes.
Enfin, créons une fonction pour vérifier si la personne a déjà voté.
Tout d'abord, créez un nouveau useEffect
:
useEffect(() => {
if (!hasClaimedNFT) {
return;
}
if (!proposals.length) {
return;
}
voteModule
.hasVoted(proposals[0].proposalId, address)
.then((hasVoted) => {
setHasVoted(hasVoted);
if (hasVoted) {
} else {
}
})
.catch((err) => {
console.error("failed to check if wallet has voted", err);
});
}, [hasClaimedNFT, address, proposals]);
Et remplacez le bouton par ceci :
<button
onClick={handleFormSubmit}
type="submit"
disabled={isVoting || hasVoted}
className={styles.button}
>
{isVoting ? "Voting..." : hasVoted ? "You Already Voted" : "Submit Votes"}
</button>
Après avoir voté, il devrait afficher le message Vous avez déjà voté :
C'était tout pour ce tutoriel, j'espère que vous l'avez aimé et que vous pourrez l'utiliser pour créer votre propre DAO ! Vous pouvez toujours mettre à jour le DAO et ajouter plus de fonctionnalités si vous le souhaitez.
Vous pouvez trouver le référentiel GitHub pour ce projet ici .
Source de l'article original sur https://blog.logrocket.com
1648523737
Learn how to build a DAO, mint NFTs, airdrop tokens, and add voting functionality using only JavaScript in this complete tutorial.
DAO stands for Decentralized Autonomous Organization. As it says in the name, a DAO is an organization without a single leader; instead, rules are encoded in the blockchain. Because of this, a DAO is completely transparent and everyone who participates has a stake. Large decisions are made via voting amongst those who own non-fungible tokens (NFTs) from the DAO, which grant membership.
Today, we are going to build our very own DAO using Next.js, thirdweb, MetaMask, and Alchemy. It will allow users to mint your DAO’s NFT, receive cryptocurrency via airdrops, and participate in the DAO’s polls. This tutorial will be written with just JavaScript, so you don’t need to know any Solidity.
To understand and follow along with this tutorial, you should have the following:
.env
variablesWe will begin by setting up a Next.js app with the following command:
npx create-next-app my-dao
Next, head to Alchemy, sign in, click on Create App, and provide the required details. Make sure to use the same chain as the one you used in thirdweb – in our case, it is the Ethereum chain and the Rinkeby network.
After the app is created, copy the HTTP API key.
In order to mint NFTs and perform certain scripts, we are going to need the wallet’s private key.
To access it, open the MetaMask browser extension and click on Account Details. You should see your private key here; export it and copy it somewhere safe.
.env
variablesLet’s add these variables in a .env
file so we can access them later:
PRIVATE_KEY=<wallet_private_key>
ALCHEMY_API_URL=<alchemy_http_key>
WALLET_ADDRESS=<public_wallet_address>
Because we don’t want to push these to GitHub, be sure to add them in gitignore
In DApps, MetaMask is the most popular wallet used, so we will add MetaMask sign in with thirdweb.
We are going to need two packages from install:
npm i @thirdweb-dev/react @thirdweb-dev/sdk ethers # npm
yarn add @thirdweb-dev/react @thirdweb-dev/sdk ethers # yarn
We need to wrap our whole app in a thirdweb provider in order to access the login details and other information required for the components:
import "../styles/globals.css";
import {ThirdwebProvider } from "@thirdweb-dev/react";
function MyApp({ Component, pageProps }) {
return (
<ThirdwebProvider desiredChainId={activeChainId}>
<Component {...pageProps} />
</ThirdwebProvider>
);
}
export default MyApp;
For authentication purposes, we also have to specify the type of authentication and the supported chain IDs. We are using MetaMask and the Rinkeby chain, so add the following as well:
import { ChainId, ThirdwebProvider } from "@thirdweb-dev/react";
const activeChainId = ChainId.Rinkeby;
Finally, pass these as props in the provider like so:
<ThirdwebProvider desiredChainId={activeChainId}>
<Component {...pageProps} />
</ThirdwebProvider>
Create a new folder called components
in the root of the project and add a Login.js
file to it:
import { useMetamask } from "@thirdweb-dev/react";
const Login = () => {
const connectWithMetamask = useMetamask();
return (
<div>
<button onClick={connectWithMetamask}>Sign in using MetaMask</button>
</div>
);
};
export default Login;
Thankfully, thirdweb provides a connectWallet
function which we can use to add authentication!
Inside index.js
, render the login screen if there is no address (if the user is not signed in):
const address = useAddress();
if (!address) {
return ;
}
This will allow our users to sign in, but afterwards it just shows a blank screen. So, in the other return block, let’s show the user her address:
export default function Home() {
const { address } = useWeb3();
if (!address) {
return <Login />;
}
return (
<div className={styles.container}>
<h2>You are signed in as {address}</h2>
</div>
);
}
The login works but it doesn’t look good right now. So, create a new file Login.module.css
in the styles
folder and add the following:
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #7449bb;
}
.button {
color: #7449bb;
background-color: white;
border: none;
border-radius: 5px;
padding: 10px;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
font-weight: 500;
}
Next, add the following classes to Login.js
:
<div className={styles.container}>
<button className={styles.button} onClick={() => connectWallet("injected")}>
Sign in using MetaMask
</button>
</div>
And finally, import the styles:
import styles from "../styles/Login.module.css";
This will give us a simple, but good-looking login screen.
Now we need to initialize the thirdweb SDK for the various scripts we are going to run. Start by creating a new folder called scripts
with an initialize-sdk.js
file inside of it.
Add the following code to the file:
import { ThirdwebSDK } from "@thirdweb-dev/sdk";
import ethers from "ethers";
import dotenv from "dotenv";
dotenv.config();
if (!process.env.PRIVATE_KEY || process.env.PRIVATE_KEY === "") {
console.log("🛑 Private key not found.");
}
if (!process.env.ALCHEMY_API_URL || process.env.ALCHEMY_API_URL === "") {
console.log("🛑 Alchemy API URL not found.");
}
if (!process.env.WALLET_ADDRESS || process.env.WALLET_ADDRESS === "") {
console.log("🛑 Wallet Address not found.");
}
const sdk = new ThirdwebSDK(
new ethers.Wallet(
process.env.PRIVATE_KEY,
ethers.getDefaultProvider(process.env.ALCHEMY_API_URL)
)
);
(async () => {
try {
const address = await sdk.getSigner().getAddress();
console.log("SDK initialized by address:", address);
} catch (err) {
console.error("Failed to get the address", err);
process.exit(1);
}
})();
export default sdk;
This will initialize the thirdweb SDK, and as you can see, we need to install some packages:
npm i dotenv # npm
yarn add dotenv # yarn
We are using modular imports here, so create a new package.json
file inside the scripts
folder and simply add the following:
{
"name": "scripts",
"type": "module"
}
Finally, run the script:
node scripts/initialize-sdk.js
The script may take some time to run, but after some time you will get your app address.
We are going to need this in the next steps, so store it somewhere safe.
For this step, we are going to need some test ETH, so go to a faucet like this and get some.
Create a new file called deploy-drop.js
inside the scripts
folder. In here, add the following script:
import { ethers } from "ethers";
import sdk from "./initialize-sdk.js";
(async () => {
try {
const editionDropAddress = await sdk.deployer.deployEditionDrop({
name: "LogRocket DAO", // Name of NFT Collection for DAO
description: "A DAO for all the LogRocket readers.", // Description
image: "image_Address", // PFP for NFT collection
primary_sale_recipient: ethers.constants.AddressZero,
});
const editionDrop = sdk.getEditionDrop(editionDropAddress);
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);
}
})();
You will need to update a few things here:
assets
, and adding the image for your NFT thereAfter you have updated the details, run the following script:
node scripts/deploy-drop.js
Wait for the script to run, and you should get an address and the metadata.
This will create a new edition drop contract for us! You can even check out the transaction on Rinkeby Etherscan.
Let’s configure our NFT now! Create a new config-nft.js
file inside the scripts
folder and add the following:
import sdk from "./initialize-sdk.js";
const editionDrop = sdk.getEditionDrop("EDITION_DROP_ADDDRESS");
(async () => {
try {
await editionDrop.createBatch([
{
name: "LogRocket DAO", // Name of NFT Collection for DAO
description: "A DAO for all the LogRocket readers.", // Description
image: "image_address", // Image for NFT
},
]);
console.log("✅ Successfully created a new NFT in the drop!");
} catch (error) {
console.error("failed to create the new NFT", error);
}
})();
You need to update the bundle drop address and the details in the object inside createBatch
. These details are going to be used for the NFT!
Once, you have updated all of them, run the following script:
node scripts/config-nft.js
It should give you an output like this.
If you see the module in the thirdweb dashboard, you will see that an NFT has been created!
Finally, let’s add a claim condition to our NFT.
Setting a claim condition will allow us to set a limit for the NFTs and allow a specific max limit per transaction. We will set a claim condition from the dashboard itself, so click on the Settings button and create a new claim phase.
After you are done updating, click Update claim phase and confirm the small transaction.
Before creating a mint button that allows the users to mint NFTs, let’s check if the user has an NFT already. We don’t want the users to mint multiple NFTs!
Start by adding two new variables, sdk
and bundleDropModule
, like this before our functional component:
const editionDrop = useEditionDrop(
"0x2f66A5A2BCB272FFC9EB873E3482A539BEB6f02a"
);
You will also need to import useEditionDrop
:
import { useAddress, useEditionDrop } from "@thirdweb-dev/react";
Now, let’s create a state for hasClaimedNFT
:
const [hasClaimedNFT, setHasClaimedNFT] = useState(false);
We also need to create a useEffect
Hook to check if the user has the NFT:
useEffect(() => {
if (!address) {
return;
}
const checkBalance = async () => {
try {
const balance = await editionDrop.balanceOf(address, 0);
if (balance.gt(0)) {
setHasClaimedNFT(true);
console.log("🎉 You have an NFT!");
} else {
setHasClaimedNFT(false);
console.log("🤷♂️ You don't have an NFT.");
}
} catch (error) {
setHasClaimedNFT(false);
console.error("Failed to get nft balance", error);
}
};
checkBalance();
}, [address, editionDrop]);
Firstly, it will check if the user is signed in. If the user is not signed in, it will return nothing. Then, this checks if the user has the NFT with the token ID 0
in the drop contract that we imported at the top.
If you, open the console in the website, it should show that you don’t have an NFT.
Let’s create the button to mint NFTs! Create a new function called mintNft
like so:
const mintNft = async () => {
setIsClaiming(true);
try {
await bundleDropModule.claim("0", 1);
setHasClaimedNFT(true);
console.log("🌊 Successfully Minted the NFT!");
} catch (error) {
console.error("failed to claim", error);
} finally {
setIsClaiming(false);
}
};
We will call this function when a button is clicked to mint the NFT to the user’s wallet. But first, let’s add the isClaiming
state:
const [isClaiming, setIsClaiming] = useState(false);
Let’s create the button now! Inside the final return block add the following:
<div>
<h1>Mint your free LogRocket DAO Membership NFT 💳</h1>
<button disabled={isClaiming} onClick={() => mintNft()}>
{isClaiming ? "Minting..." : "Mint your nft (FREE)"}
</button>
</div>
Now, after we sign in, it should show us a screen like this.
If you try the Mint your nft (FREE) button, it should pop up your MetaMask screen to complete the transaction. In the console, should should see the following.
Finally, just above the final return block, add this check to see if the user has claimed the NFT already:
if (hasClaimedNFT) {
return (
<div>
<h1>You have the DAO Membership NFT!</h1>
</div>
);
}
We have completed building the minting NFT functionality, but it looks ugly, so let’s add some basic stylings. Inside Home.module.css
add the following:
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #7449bb;
}
.container > h1 {
font-size: 3rem;
color: #fff;
font-weight: bold;
}
.button {
color: #7449bb;
background-color: white;
border: none;
border-radius: 5px;
padding: 10px;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
font-weight: 500;
}
We also need to add the classNames
:
if (hasClaimedNFT) {
return (
<div className={styles.container}>
<h1>You have the DAO Membership NFT!</h1>
</div>
);
}
return (
<div className={styles.container}>
<h1>Mint your free LogRocket DAO Membership NFT 💳</h1>
<button
className={styles.button}
disabled={isClaiming}
onClick={() => mintNft()}
>
{isClaiming ? "Minting..." : "Mint your NFT (FREE)"}
</button>
</div>
);
};
This gives us a better mint screen.
Create a new file called deploy-token.js
in the scripts
folder. Add the following to it:
import { AddressZero } from "@ethersproject/constants";
import sdk from "./initialize-sdk.js";
(async () => {
try {
const tokenAddress = await sdk.deployer.deployToken({
name: "LogRocket Token", // name of the token
symbol: "LR", // symbol
primary_sale_recipient: AddressZero, // 0x0000000000000000000000000000000000000000
});
console.log(
"✅ Successfully deployed token module, address:",
tokenAddress
);
} catch (error) {
console.error("failed to deploy token module", error);
}
})();
This script will create a new token module with a name and symbol. You will need to manually update the app address, token name, and symbol yourself.
After updating, run the script.
You can check this token by the address on Rinkeby Etherscan, and also add it on your MetaMask wallet by clicking Import tokens.
After importing, you should see the token under your assets.
It is currently zero, so let’s mint some tokens!
Create a new file called mint-token.js
inside the scripts
folder and add the following:
import { ethers } from "ethers";
import sdk from "./initialize-sdk.js";
const tokenModule = sdk.getTokenModule(
"TOKEN_MODULE_ADDRESS"
);
(async () => {
try {
const amount = 1_000_000;
const amountWith18Decimals = ethers.utils.parseUnits(amount.toString(), 18);
await tokenModule.mint(amountWith18Decimals);
const totalSupply = await tokenModule.totalSupply();
console.log(
"✅ There now is",
ethers.utils.formatUnits(totalSupply, 18),
"$LR in circulation"
);
} catch (error) {
console.error("Failed to mint tokens", error);
}
})();
Update the token module address with the address you got in the last script, and you can update the amount you want to mint.
After you are ready to mint, run the script:
node scripts/mint-token.js
You should now see the amount of tokens you minted on your MetaMask wallet!
We might want to airdrop the tokens to our NFT holders, so let’s build a script for that. Create a new airdrop.js
file inside scripts
and add the following:
import sdk from "./initialize-sdk.js";
const editionDrop = sdk.getEditionDrop(
"EDITION_ADDRESS"
);
const token = sdk.getToken("TOKEN_ADDRESS");
(async () => {
try {
const walletAddresses = await editionDrop.history.getAllClaimerAddresses(0);
if (walletAddresses.length === 0) {
console.log(
"No NFTs have been claimed yet, ask yourfriends to claim some free NFTs!"
);
process.exit(0);
}
const airdropTargets = walletAddresses.map((address) => {
const randomAmount = Math.floor(
Math.random() * (10000 - 1000 + 1) + 1000
);
console.log("✅ Going to airdrop", randomAmount, "tokens to", address);
const airdropTarget = {
toAddress: address,
amount: randomAmount,
};
return airdropTarget;
});
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);
}
})();
After you run the script you should get something like this.
Currently, only you have minted an NFT, so it won’t send the token to someone else. But this can be used to send it to other NFT holders later on.
Create a new deploy-vote.js
file in the scripts
folder and add the following:
import sdk from "./initialize-sdk.js";
(async () => {
try {
const voteContractAddress = await sdk.deployer.deployVote({
name: "LR Dao's Proposals",
voting_token_address: "TOKEN_ADDRESS",
voting_delay_in_blocks: 0,
voting_period_in_blocks: 6570,
voting_quorum_fraction: 0,
proposal_token_threshold: 0,
});
console.log(
"✅ Successfully deployed vote contract, address:",
voteContractAddress
);
} catch (err) {
console.error("Failed to deploy vote contract", err);
}
})();
Update the app address, the name, and the voting token address, then run the script:
node scripts/deploy-vote.js
We also need to set up a vote module, so create a new script called setup-vote.js
and add the following:
import sdk from "./initialize-sdk.js";
const vote = sdk.getVote("VOTE_ADDRESS");
const token = sdk.getToken("TOKEN_ADDRESS");
(async () => {
try {
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 {
const ownedTokenBalance = await token.balanceOf(process.env.WALLET_ADDRESS);
const ownedAmount = ownedTokenBalance.displayValue;
const percent90 = (Number(ownedAmount) / 100) * 90;
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);
}
})();
You will need to run this script to finish it up:
node scripts/setup-vote.js
Now that we have our vote module ready, let’s create some proposals!
Create a new file called vote-proposals.js
inside the scripts
folder and add the following:
import sdk from "./initialize-sdk.js";
import { ethers } from "ethers";
const vote = sdk.getVote("0x31c5840b31A1F97745bDCbB1E46954b686828E0F");
const token = sdk.getToken("0x6eefd78C9C73505AA71A13FeE31D9718775c9086");
(async () => {
try {
const amount = 420_000;
const description =
"Should the DAO mint an additional " +
amount +
" tokens into the treasury?";
const executions = [
{
toAddress: token.getAddress(),
nativeTokenValue: 0,
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);
}
})();
You need to update the module addresses, and if you want to update the message of the proposal, you can update that as well.
Finally, run the script. It should give you something like this.
If you now check the thirdweb dashboard, the proposal has been created.
First, import the token and vote module:
const token = useToken("TOKEN_ADDRESS");
const vote = useVote("VOTE_ADDRESS");
We are going to need three useState
s, like so:
const [proposals, setProposals] = useState([]);
const [isVoting, setIsVoting] = useState(false);
const [hasVoted, setHasVoted] = useState(false);
We need to get the proposals to display them on the screen, so create this useEffect
:
useEffect(() => {
if (!hasClaimedNFT) {
return;
}
const getAllProposals = async () => {
try {
const proposals = await vote.getAll();
setProposals(proposals);
console.log("📋 Proposals:", proposals);
} catch (error) {
console.log("failed to get proposals", error);
}
};
getAllProposals();
}, [hasClaimedNFT, vote]);
Then, create a new handleFormSubmit
function:
const handleFormSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsVoting(true);
const votes = proposals.map((proposal) => {
const voteResult = {
proposalId: proposal.proposalId,
vote: 2,
};
proposal.votes.forEach((vote) => {
const elem = document.getElementById(
proposal.proposalId + "-" + vote.type
);
if (elem.checked) {
voteResult.vote = vote.type;
return;
}
});
return voteResult;
});
try {
const delegation = await token.getDelegationOf(address);
if (delegation === AddressZero) {
await token.delegateTo(address);
}
try {
await Promise.all(
votes.map(async ({ proposalId, vote: _vote }) => {
const proposal = await vote.get(proposalId);
if (proposal.state === 1) {
return vote.vote(proposalId, _vote);
}
return;
})
);
try {
await Promise.all(
votes.map(async ({ proposalId }) => {
const proposal = await vote.get(proposalId);
if (proposal.state === 4) {
return vote.execute(proposalId);
}
})
);
setHasVoted(true);
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 {
setIsVoting(false);
}
};
This function is going to collect the vote.
Replace the if (hasClaimedNFT)
block with this:
if (hasClaimedNFT) {
return (
<div className={styles.container}>
<h2>Active Proposals</h2>
<form onSubmit={handleFormSubmit}>
{proposals.map((proposal) => (
<Proposal
key={proposal.proposalId}
votes={proposal.votes}
description={proposal.description}
proposalId={proposal.proposalId}
/>
))}
<button
onClick={handleFormSubmit}
type="submit"
className={styles.button}
>
{isVoting
? "Voting..."
"Submit Votes"}
</button>
</form>
</div>
);
}
We are creating a separate component for the proposal to keep things clean. So, create a new file called Proposal.js
in the components
folder and add the following:
import styles from "../styles/Proposal.module.css";
const Proposal = ({ description, votes, proposalId }) => {
return (
<div className={styles.proposal}>
<h5 className={styles.description}>{description}</h5>
<div className={styles.options}>
{votes.map((vote) => (
<div key={vote.type}>
<input
type="radio"
id={proposalId + "-" + vote.type}
name={proposalId}
value={vote.type}
defaultChecked={vote.type === 2}
/>
<label htmlFor={proposalId + "-" + vote.type}>{vote.label}</label>
</div>
))}
</div>
</div>
);
};
export default Proposal;
I also added basic styling, so create a new Proposal.module.css
file in the styles
folder:
.proposal {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fafafa;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
margin: 20px;
}
.options {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
width: 100%;
margin-top: 1rem;
}
To center the items, I have added the following styles in Home.module.css
as well:
.container > form {
display: flex;
flex-direction: column;
align-items: center;
}
You will get to this screen where you can submit your votes.
Finally, let’s make a function to check if the person has already voted.
First, create a new useEffect
:
useEffect(() => {
if (!hasClaimedNFT) {
return;
}
if (!proposals.length) {
return;
}
const checkIfUserHasVoted = async () => {
try {
const hasVoted = await vote.hasVoted(proposals[0].proposalId, address);
setHasVoted(hasVoted);
} catch (error) {
console.error("Failed to check if wallet has voted", error);
}
};
checkIfUserHasVoted();
}, [hasClaimedNFT, proposals, address, vote]);
And replace the button with this:
<button
onClick={handleFormSubmit}
type="submit"
disabled={isVoting || hasVoted}
className={styles.button}
>
{isVoting ? "Voting..." : hasVoted ? "You Already Voted" : "Submit Votes"}
</button>
After you have voted, it should show the message You Already Voted:
This was it for this tutorial, hope you liked it and can use it to make your own DAO! You can always update the DAO and add more features if you like.
You can find the GitHub repo for this project here.
Original article source at https://blog.logrocket.com
#dao #next #nextjs #javascript #thirdweb #nft #metamask #alchemy #blockchain
1632537859
Not babashka. Node.js babashka!?
Ad-hoc CLJS scripting on Node.js.
Experimental. Please report issues here.
Nbb's main goal is to make it easy to get started with ad hoc CLJS scripting on Node.js.
Additional goals and features are:
Nbb requires Node.js v12 or newer.
CLJS code is evaluated through SCI, the same interpreter that powers babashka. Because SCI works with advanced compilation, the bundle size, especially when combined with other dependencies, is smaller than what you get with self-hosted CLJS. That makes startup faster. The trade-off is that execution is less performant and that only a subset of CLJS is available (e.g. no deftype, yet).
Install nbb
from NPM:
$ npm install nbb -g
Omit -g
for a local install.
Try out an expression:
$ nbb -e '(+ 1 2 3)'
6
And then install some other NPM libraries to use in the script. E.g.:
$ npm install csv-parse shelljs zx
Create a script which uses the NPM libraries:
(ns script
(:require ["csv-parse/lib/sync$default" :as csv-parse]
["fs" :as fs]
["path" :as path]
["shelljs$default" :as sh]
["term-size$default" :as term-size]
["zx$default" :as zx]
["zx$fs" :as zxfs]
[nbb.core :refer [*file*]]))
(prn (path/resolve "."))
(prn (term-size))
(println (count (str (fs/readFileSync *file*))))
(prn (sh/ls "."))
(prn (csv-parse "foo,bar"))
(prn (zxfs/existsSync *file*))
(zx/$ #js ["ls"])
Call the script:
$ nbb script.cljs
"/private/tmp/test-script"
#js {:columns 216, :rows 47}
510
#js ["node_modules" "package-lock.json" "package.json" "script.cljs"]
#js [#js ["foo" "bar"]]
true
$ ls
node_modules
package-lock.json
package.json
script.cljs
Nbb has first class support for macros: you can define them right inside your .cljs
file, like you are used to from JVM Clojure. Consider the plet
macro to make working with promises more palatable:
(defmacro plet
[bindings & body]
(let [binding-pairs (reverse (partition 2 bindings))
body (cons 'do body)]
(reduce (fn [body [sym expr]]
(let [expr (list '.resolve 'js/Promise expr)]
(list '.then expr (list 'clojure.core/fn (vector sym)
body))))
body
binding-pairs)))
Using this macro we can look async code more like sync code. Consider this puppeteer example:
(-> (.launch puppeteer)
(.then (fn [browser]
(-> (.newPage browser)
(.then (fn [page]
(-> (.goto page "https://clojure.org")
(.then #(.screenshot page #js{:path "screenshot.png"}))
(.catch #(js/console.log %))
(.then #(.close browser)))))))))
Using plet
this becomes:
(plet [browser (.launch puppeteer)
page (.newPage browser)
_ (.goto page "https://clojure.org")
_ (-> (.screenshot page #js{:path "screenshot.png"})
(.catch #(js/console.log %)))]
(.close browser))
See the puppeteer example for the full code.
Since v0.0.36, nbb includes promesa which is a library to deal with promises. The above plet
macro is similar to promesa.core/let
.
$ time nbb -e '(+ 1 2 3)'
6
nbb -e '(+ 1 2 3)' 0.17s user 0.02s system 109% cpu 0.168 total
The baseline startup time for a script is about 170ms seconds on my laptop. When invoked via npx
this adds another 300ms or so, so for faster startup, either use a globally installed nbb
or use $(npm bin)/nbb script.cljs
to bypass npx
.
Nbb does not depend on any NPM dependencies. All NPM libraries loaded by a script are resolved relative to that script. When using the Reagent module, React is resolved in the same way as any other NPM library.
To load .cljs
files from local paths or dependencies, you can use the --classpath
argument. The current dir is added to the classpath automatically. So if there is a file foo/bar.cljs
relative to your current dir, then you can load it via (:require [foo.bar :as fb])
. Note that nbb
uses the same naming conventions for namespaces and directories as other Clojure tools: foo-bar
in the namespace name becomes foo_bar
in the directory name.
To load dependencies from the Clojure ecosystem, you can use the Clojure CLI or babashka to download them and produce a classpath:
$ classpath="$(clojure -A:nbb -Spath -Sdeps '{:aliases {:nbb {:replace-deps {com.github.seancorfield/honeysql {:git/tag "v2.0.0-rc5" :git/sha "01c3a55"}}}}}')"
and then feed it to the --classpath
argument:
$ nbb --classpath "$classpath" -e "(require '[honey.sql :as sql]) (sql/format {:select :foo :from :bar :where [:= :baz 2]})"
["SELECT foo FROM bar WHERE baz = ?" 2]
Currently nbb
only reads from directories, not jar files, so you are encouraged to use git libs. Support for .jar
files will be added later.
The name of the file that is currently being executed is available via nbb.core/*file*
or on the metadata of vars:
(ns foo
(:require [nbb.core :refer [*file*]]))
(prn *file*) ;; "/private/tmp/foo.cljs"
(defn f [])
(prn (:file (meta #'f))) ;; "/private/tmp/foo.cljs"
Nbb includes reagent.core
which will be lazily loaded when required. You can use this together with ink to create a TUI application:
$ npm install ink
ink-demo.cljs
:
(ns ink-demo
(:require ["ink" :refer [render Text]]
[reagent.core :as r]))
(defonce state (r/atom 0))
(doseq [n (range 1 11)]
(js/setTimeout #(swap! state inc) (* n 500)))
(defn hello []
[:> Text {:color "green"} "Hello, world! " @state])
(render (r/as-element [hello]))
Working with callbacks and promises can become tedious. Since nbb v0.0.36 the promesa.core
namespace is included with the let
and do!
macros. An example:
(ns prom
(:require [promesa.core :as p]))
(defn sleep [ms]
(js/Promise.
(fn [resolve _]
(js/setTimeout resolve ms))))
(defn do-stuff
[]
(p/do!
(println "Doing stuff which takes a while")
(sleep 1000)
1))
(p/let [a (do-stuff)
b (inc a)
c (do-stuff)
d (+ b c)]
(prn d))
$ nbb prom.cljs
Doing stuff which takes a while
Doing stuff which takes a while
3
Also see API docs.
Since nbb v0.0.75 applied-science/js-interop is available:
(ns example
(:require [applied-science.js-interop :as j]))
(def o (j/lit {:a 1 :b 2 :c {:d 1}}))
(prn (j/select-keys o [:a :b])) ;; #js {:a 1, :b 2}
(prn (j/get-in o [:c :d])) ;; 1
Most of this library is supported in nbb, except the following:
:syms
.-x
notation. In nbb, you must use keywords.See the example of what is currently supported.
See the examples directory for small examples.
Also check out these projects built with nbb:
See API documentation.
See this gist on how to convert an nbb script or project to shadow-cljs.
Prequisites:
To build:
bb release
Run bb tasks
for more project-related tasks.
Download Details:
Author: borkdude
Download Link: Download The Source Code
Official Website: https://github.com/borkdude/nbb
License: EPL-1.0
#node #javascript
1648528260
Apprenez à créer un DAO, à créer des NFT, des jetons de largage aérien et à ajouter une fonctionnalité de vote en utilisant uniquement JavaScript dans ce didacticiel complet.
DAO signifie Organisation Autonome Décentralisée. Comme son nom l'indique, un DAO est une organisation sans chef unique; au lieu de cela, les règles sont encodées dans la blockchain. Pour cette raison, un DAO est complètement transparent et tous ceux qui y participent ont un intérêt. Les décisions importantes sont prises par vote parmi ceux qui possèdent des jetons non fongibles (NFT) du DAO, qui accordent l'adhésion.
Aujourd'hui, nous allons créer notre propre DAO en utilisant Next.js, thirdweb, MetaMask et Alchemy. Il permettra aux utilisateurs de frapper le NFT de votre DAO, de recevoir de la crypto-monnaie via des parachutages et de participer aux sondages du DAO. Ce didacticiel sera écrit uniquement avec JavaScript, vous n'avez donc pas besoin de connaître Solidity.
Pour comprendre et suivre ce didacticiel, vous devez disposer des éléments suivants :
.env
des variablesNous allons commencer par configurer une application Next.js avec la commande suivante :
npx créer-next-app mon-dao
Maintenant, nous allons créer un nouveau projet dans thirdweb . Allez sur thirdweb et connectez votre portefeuille MetaMask. Après avoir connecté votre portefeuille, cliquez sur Créer un projet et choisissez le réseau Rinkeby.
Donnez un nom et une description à votre projet, puis cliquez sur Créer . Si vous n'avez pas assez d'ETH pour payer l'essence, procurez-vous en à ce robinet .
Ensuite, rendez-vous sur Alchemy, connectez-vous, cliquez sur Créer une application et fournissez les détails requis. Assurez-vous d'utiliser la même chaîne que celle que vous avez utilisée dans thirdweb - dans notre cas, il s'agit de la chaîne Ethereum et du réseau Rinkeby.
Une fois l'application créée, copiez la clé d'API HTTP.
Afin de créer des NFT et d'exécuter certains scripts, nous allons avoir besoin de la clé privée du portefeuille.
Pour y accéder, ouvrez l'extension de navigateur MetaMask et cliquez sur Détails du compte . Vous devriez voir votre clé privée ici; exportez-le et copiez-le dans un endroit sûr.
.env
des variablesAjoutons ces variables dans un .env
fichier afin de pouvoir y accéder plus tard :
PRIVATE_KEY=<wallet_private_key>
ALCHEMY_API_URL=<alchemy_http_key>
WALLET_ADDRESS=<public_wallet_address>
Parce que nous ne voulons pas les pousser vers GitHub, assurez-vous de les ajouter dans .gitignore
Dans DApps, MetaMask est le portefeuille le plus utilisé, nous ajouterons donc la connexion MetaMask avec thirdweb.
Nous allons avoir besoin de deux packages à partir de l'installation :
npm i @3rdweb/sdk @3rdweb/hooks # npm
yarn add @3rdweb/sdk @3rdweb/hooks # yarn
Nous devons encapsuler l'ensemble de notre application dans un troisième fournisseur Web afin d'accéder aux informations de connexion et aux autres informations requises pour les composants :
import { thirdwebWeb3Provider } from "@3rdweb/hooks";
import "../styles/globals.css";
function MyApp({ Component, pageProps }) {
return (
<thirdwebWeb3Provider>
<Component {...pageProps} />
</thirdwebWeb3Provider>
);
}
export default MyApp;
À des fins d'authentification, nous devons également spécifier le type d'authentification et les ID de chaîne pris en charge. Nous utilisons MetaMask et la chaîne Rinkeby, alors ajoutez également ce qui suit :
const supportedChainIds = [4];
const connectors = {
injected: {},
};
Enfin, transmettez-les en tant qu'accessoires dans le fournisseur comme suit :
<thirdwebWeb3Provider
connectors={connectors}
supportedChainIds={supportedChainIds}
>
<Component {...pageProps} />
</thirdwebWeb3Provider>
Créez un nouveau dossier appelé components
à la racine du projet et ajoutez-y un Login.js
fichier :
import { useWeb3 } from "@3rdweb/hooks";
const Login = () => {
const { connectWallet } = useWeb3();
return (
<div>
<button onClick={() => connectWallet("injected")}>
Sign in using MetaMask
</button>
</div>
);
};
export default Login;
Heureusement, thirdweb fournit une connectWallet
fonction que nous pouvons utiliser pour ajouter une authentification !
À l' intérieur index.js
de , affichez l'écran de connexion s'il n'y a pas d'adresse (si l'utilisateur n'est pas connecté) :
const { address } = useWeb3();
if (!address) {
return <Login />;
}
Cela permettra à nos utilisateurs de se connecter, mais ensuite, un écran vide s'affichera. Donc, dans l'autre bloc de retour, montrons à l'utilisateur son adresse :
export default function Home() {
const { address } = useWeb3();
if (!address) {
return <Login />;
}
return (
<div className={styles.container}>
<h2>You are signed in as {address}</h2>
</div>
);
}
La connexion fonctionne mais cela ne semble pas bon pour le moment. Créez donc un nouveau fichier Login.module.css
dans le styles
dossier et ajoutez ce qui suit :
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #7449bb;
}
.button {
color: #7449bb;
background-color: white;
border: none;
border-radius: 5px;
padding: 10px;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
font-weight: 500;
}
Ensuite, ajoutez les classes suivantes àLogin.js
:
<div className={styles.container}>
<button className={styles.button} onClick={() => connectWallet("injected")}>
Sign in using MetaMask
</button>
</div>
Et enfin, importez les styles :
import styles from "../styles/Login.module.css";
Cela nous donnera un écran de connexion simple mais beau.
Nous devons maintenant initialiser le SDK thirdweb pour les différents scripts que nous allons exécuter. Commencez par créer un nouveau dossier appelé scripts
avec un initialize-sdk.js
fichier à l'intérieur.
Ajoutez le code suivant au fichier :
import { thirdwebSDK } from "@3rdweb/sdk";
import ethers from "ethers";
import dotenv from "dotenv";
dotenv.config();
if (!process.env.PRIVATE_KEY || process.env.PRIVATE_KEY == "") {
console.log(" Private key not found.");
}
if (!process.env.ALCHEMY_API_URL || process.env.ALCHEMY_API_URL == "") {
console.log(" Alchemy API URL not found.");
}
if (!process.env.WALLET_ADDRESS || process.env.WALLET_ADDRESS == "") {
console.log(" Wallet Address not found.");
}
const sdk = new thirdwebSDK(
new ethers.Wallet(
process.env.PRIVATE_KEY,
ethers.getDefaultProvider(process.env.ALCHEMY_API_URL)
)
);
(async () => {
try {
const apps = await sdk.getApps();
console.log("app address:", apps[0].address);
} catch (err) {
console.error("Failed to get apps from the sdk", err);
process.exit(1);
}
})();
export default sdk;
Cela initialisera le SDK thirdweb, et comme vous pouvez le voir, nous devons installer certains packages :
npm i ethers dotenv # npm
yarn add ethers dotenv # yarn
Nous utilisons ici des importations modulaires, alors créez un nouveau package.json
fichier dans le scripts
dossier et ajoutez simplement ce qui suit :
{
"name": "scripts",
"type": "module"
}
Enfin, exécutez le script :
node scripts/initialize-sdk.js
L'exécution du script peut prendre un certain temps, mais après un certain temps, vous obtiendrez l'adresse de votre application.
Nous en aurons besoin dans les prochaines étapes, alors stockez-le dans un endroit sûr.
Pour cette étape, nous allons avoir besoin d'ETH de test, alors allez à un robinet comme celui -ci et obtenez-en.
Créez un nouveau fichier appelé deploy-drop.js
à l'intérieur du scripts
dossier. Ici, ajoutez le script suivant :
import { ethers } from "ethers";
import sdk from "./initialize-sdk.js";
import { readFileSync } from "fs";
const app = sdk.getAppModule("APP_ADDRESS");
(async () => {
try {
const bundleDropModule = await app.deployBundleDropModule({
name: "LogRocket DAO", // Name of NFT Collection for DAO
description: "A DAO for all the LogRocket readers.", // Description
image: readFileSync("scripts/assets/rocket.png"), // Image for NFT collection
primarySaleRecipientAddress: ethers.constants.AddressZero,
});
console.log(
" Successfully deployed bundleDrop module, address:",
bundleDropModule.address,
);
console.log(
" bundleDrop metadata:",
await bundleDropModule.getMetadata(),
);
} catch (error) {
console.log("failed to deploy bundleDrop module", error);
}
})()
Vous devrez mettre à jour quelques éléments ici :
assets
et en y ajoutant l'image pour votre NFTAprès avoir mis à jour les détails, exécutez le script suivant :
node scripts/deploy-drop.js
Attendez que le script s'exécute et vous devriez obtenir une adresse et les métadonnées.
Vous pouvez même vérifier la transaction sur Rinkeby Etherscan
Configurons notre NFT maintenant ! Créez un nouveau config-nft.js
fichier dans le scripts
dossier et ajoutez ce qui suit :
import sdk from "./initialize-sdk.js";
import { readFileSync } from "fs";
const bundleDrop = sdk.getBundleDropModule(
"BUNDLE_DROP_ADDRESS"
);
(async () => {
try {
await bundleDrop.createBatch([
{
name: "LogRocket DAO", // Name of NFT Collection for DAO
description: "A DAO for all the LogRocket readers.", // Description
image: readFileSync("scripts/assets/rocket.png"), // Image for NFT collection
},
]);
console.log(" Successfully created a new NFT in the drop!");
} catch (error) {
console.error("failed to create the new NFT", error);
}
})();
Vous devez mettre à jour l'adresse de dépôt du bundle et les détails dans l'objet à l'intérieur de createBatch
. Ces détails vont être utilisés pour le NFT !
Une fois que vous les avez tous mis à jour, exécutez le script suivant :
node scripts/config-nft.js
Cela devrait vous donner une sortie comme celle-ci.
Si vous voyez le module dans le tableau de bord thirdweb, vous verrez qu'un NFT a été créé !
Enfin, ajoutons une condition de réclamation à notre NFT
La définition d'une condition de réclamation nous permettra de fixer une limite pour les NFT et d'autoriser une limite maximale spécifique par transaction. Nous définirons une condition de réclamation à partir du tableau de bord lui-même, alors cliquez sur le bouton Paramètres et vous pourrez mettre à jour les données en fonction de vos besoins.
Une fois la mise à jour terminée, cliquez sur Enregistrer et confirmez la petite transaction.
Avant de créer un bouton de menthe qui permet aux utilisateurs de frapper des NFT, vérifions si l'utilisateur a déjà un NFT. Nous ne voulons pas que les utilisateurs frappent plusieurs NFT !
Commencez par ajouter deux nouvelles variables, sdk
et bundleDropModule
, comme ceci avant notre composant fonctionnel :
const sdk = new thirdwebSDK("rinkeby");
const bundleDropModule = sdk.getBundleDropModule(
"BUNDLE_DROP_ADDRESS"
);
Vous devrez également importer thirdwebSDK
:
import { thirdwebSDK } from "@3rdweb/sdk";
Maintenant, créons un état pour hasClaimedNFT
:
const [hasClaimedNFT, setHasClaimedNFT] = useState(false);
Nous devons également créer un useEffect
crochet pour vérifier si l'utilisateur a le NFT :
useEffect(() => {
const handler = async () => {
if (!address) {
return;
}
try {
const balance = await bundleDropModule.balanceOf(address, "0");
if (balance.gt(0)) {
setHasClaimedNFT(true);
console.log(" You have an NFT!");
} else {
setHasClaimedNFT(false);
console.log(" You don't have an NFT.");
}
} catch (error) {
setHasClaimedNFT(false);
console.error("failed to nft balance", error);
}
};
handler();
}, [address]);
Tout d'abord, il vérifiera si l'utilisateur est connecté. Si l'utilisateur n'est pas connecté, il ne renverra rien. Ensuite, cela vérifie si l'utilisateur a le NFT avec l'ID de jeton 0
dans le contrat de dépôt que nous avons importé en haut.
Si vous ouvrez la console sur le site Web, cela devrait indiquer que vous n'avez pas de NFT.
Créons le bouton pour créer des NFT ! Créez une nouvelle fonction appelée mintNft
comme suit :
const mintNft = async () => {
setIsClaiming(true);
try {
await bundleDropModule.claim("0", 1);
setHasClaimedNFT(true);
console.log(" Successfully Minted the NFT!");
} catch (error) {
console.error("failed to claim", error);
} finally {
setIsClaiming(false);
}
};
Nous appellerons cette fonction lorsqu'un bouton est cliqué pour frapper le NFT sur le portefeuille de l'utilisateur. Mais d'abord, ajoutons les deux états requis :
const [hasClaimedNFT, setHasClaimedNFT] = useState(false);
const [isClaiming, setIsClaiming] = useState(false);
Nous devons également appeler la providerSigner
fonction, donc obtenez d'abord le fournisseur de useWeb3
:
const { address, provider } = useWeb3();
Et ensuite appelez la fonction :
const signer = provider ? provider.getSigner() : undefined;
useEffect(() => {
sdk.setProviderOrSigner(signer);
}, [signer]);
Créons le bouton maintenant ! Dans le bloc de retour final, ajoutez ce qui suit :
<div>
<h1>Mint your free LogRocket DAO Membership NFT </h1>
<button disabled={isClaiming} onClick={() => mintNft()}>
{isClaiming ? "Minting..." : "Mint your nft (FREE)"}
</button>
</div>
Maintenant, après nous être connectés, il devrait nous montrer un écran comme celui-ci.
Vous pouvez même visualiser votre NFT sur Opensea Testnets ; allez simplement sur https://testnets.opensea.io/assets//0
Enfin, juste au-dessus du bloc de retour final, ajoutez cette vérification pour voir si l'utilisateur a déjà réclamé le NFT :
if (hasClaimedNFT) {
return (
<div>
<h1>You have the DAO Membership NFT!</h1>
</div>
);
}
Nous avons terminé la construction de la fonctionnalité NFT de frappe, mais elle a l'air moche, alors ajoutons quelques styles de base. À l' intérieur, Home.module.css
ajoutez ce qui suit :
.container {
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #7449bb;
}
.container > h1 {
font-size: 3rem;
color: #fff;
font-weight: bold;
}
.button {
color: #7449bb;
background-color: white;
border: none;
border-radius: 5px;
padding: 10px;
font-size: 16px;
margin: 4px 2px;
cursor: pointer;
font-weight: 500;
}
Il faut aussi ajouter le classNames
:
if (hasClaimedNFT) {
return (
<div className={styles.container}>
<h1>You have the DAO Membership NFT!</h1>
</div>
);
}
return (
<div className={styles.container}>
<h1>Mint your free LogRocket DAO Membership NFT </h1>
<button
className={styles.button}
disabled={isClaiming}
onClick={() => mintNft()}
>
{isClaiming ? "Minting..." : "Mint your NFT (FREE)"}
</button>
</div>
);
};
Cela nous donne un meilleur écran de menthe.
Créez un nouveau fichier appelé deploy-token.js
dans le scripts
dossier. Ajoutez-y ce qui suit :
import sdk from "./initialize-sdk.js";
const app = sdk.getAppModule("YOUR_APP_ADDRESS");
(async () => {
try {
const tokenModule = await app.deployTokenModule({
name: "LogRocket Token", // name of the token
symbol: "LR", // symbol
});
console.log(
" Successfully deployed token module, address:",
tokenModule.address
);
} catch (error) {
console.error("failed to deploy token module", error);
}
})();
Ce script créera un nouveau module de jeton avec un nom et un symbole. Vous devrez mettre à jour manuellement l'adresse de l'application, le nom du jeton et le symbole vous-même.
Après la mise à jour, exécutez le script.
Vous pouvez vérifier ce jeton par l'adresse sur Rinkeby Etherscan, et également l'ajouter sur votre portefeuille MetaMask en cliquant sur Importer des jetons .
Après l'importation, vous devriez voir le jeton sous vos actifs.
Il est actuellement nul, alors frappons quelques jetons !
Créez un nouveau fichier appelé mint-token.js
dans le scripts
dossier et ajoutez ce qui suit :
import { ethers } from "ethers";
import sdk from "./initialize-sdk.js";
const tokenModule = sdk.getTokenModule(
"TOKEN_MODULE_ADDRESS"
);
(async () => {
try {
const amount = 1_000_000;
const amountWith18Decimals = ethers.utils.parseUnits(amount.toString(), 18);
await tokenModule.mint(amountWith18Decimals);
const totalSupply = await tokenModule.totalSupply();
console.log(
" There now is",
ethers.utils.formatUnits(totalSupply, 18),
"$LR in circulation"
);
} catch (error) {
console.error("Failed to mint tokens", error);
}
})();
Mettez à jour l'adresse du module de jeton avec l'adresse que vous avez obtenue dans le dernier script, et vous pouvez mettre à jour le montant que vous souhaitez frapper.
Une fois que vous êtes prêt à frapper, exécutez le script :
node scripts/mint-token.js
Vous devriez maintenant voir le nombre de jetons que vous avez émis sur votre portefeuille MetaMask !
Nous pourrions souhaiter diffuser les jetons à nos détenteurs de NFT, alors créons un script pour cela. Créez un nouveau airdrop.js
fichier à l'intérieur scripts
et ajoutez ce qui suit :
import { ethers } from "ethers";
import sdk from "./initialize-sdk.js";
const bundleDropModule = sdk.getBundleDropModule(
"BUNDLE_DROP_ADDRESS"
);
const tokenModule = sdk.getTokenModule(
"TOKEN_MODULE_ADDRESS"
);
(async () => {
try {
const walletAddresses = await bundleDropModule.getAllClaimerAddresses("0");
if (walletAddresses.length === 0) {
console.log(
"No NFTs have been claimed yet, ask your friends to mint some free ones :P!"
);
process.exit(0);
}
const airdropTargets = walletAddresses.map((address) => {
const randomAmount = Math.floor(
Math.random() * (10000 - 1000 + 1) + 1000
);
console.log(" Going to airdrop", randomAmount, "tokens to", address);
return {
address,
amount: ethers.utils.parseUnits(randomAmount.toString(), 18),
};
});
console.log(" Starting airdrop...");
await tokenModule.transferBatch(airdropTargets);
console.log(
" Successfully airdropped tokens to all the holders of the NFT!"
);
} catch (err) {
console.error("Failed to airdrop tokens", err);
}
})();
Après avoir exécuté le script, vous devriez obtenir quelque chose comme ça.
Actuellement, vous seul avez créé un NFT, il n'enverra donc pas le jeton à quelqu'un d'autre. Mais cela peut être utilisé pour l'envoyer ultérieurement à d'autres détenteurs de NFT.
Créez un nouveau deploy-vote.js
fichier dans le scripts
dossier et ajoutez ce qui suit :
import sdk from "./initialize-sdk.js";
const appModule = sdk.getAppModule(
"APP_MODULE_ADDRESS"
);
(async () => {
try {
const voteModule = await appModule.deployVoteModule({
name: "LR Dao's Proposals",
votingTokenAddress: "0x6fb07DCBC53Fd8390de38CDBfCc267b5A72761ca",
proposalStartWaitTimeInSeconds: 0,
proposalVotingTimeInSeconds: 24 * 60 * 60,
votingQuorumFraction: 0,
minimumNumberOfTokensNeededToPropose: "0",
});
console.log(
" Successfully deployed vote module, address:",
voteModule.address
);
} catch (err) {
console.error("Failed to deploy vote module", err);
}
})();
Mettez à jour l'adresse de l'application, le nom et l'adresse du jeton de vote, puis exécutez le script :
node scripts/deploy-vote.js
Nous devons également configurer un module de vote, alors créez un nouveau script appelé setup-vote.js
et ajoutez ce qui suit :
import { ethers } from "ethers";
import sdk from "./initialize-sdk.js";
const voteModule = sdk.getVoteModule(
"0xA57A03B9e117723b6873100742116A01140C43f4"
);
const tokenModule = sdk.getTokenModule(
"0x6fb07DCBC53Fd8390de38CDBfCc267b5A72761ca"
);
(async () => {
try {
await tokenModule.grantRole("minter", voteModule.address);
console.log(
"Successfully gave vote module permissions to act on token module"
);
} catch (error) {
console.error(
"failed to grant vote module permissions on token module",
error
);
process.exit(1);
}
try {
const ownedTokenBalance = await tokenModule.balanceOf(
process.env.WALLET_ADDRESS
);
const ownedAmount = ethers.BigNumber.from(ownedTokenBalance.value);
const percent90 = ownedAmount.div(100).mul(90);
await tokenModule.transfer(voteModule.address, percent90);
console.log(" Successfully transferred tokens to vote module");
} catch (err) {
console.error("failed to transfer tokens to vote module", err);
}
})();
Vous devrez exécuter ce script pour le terminer :
node scripts/setup-vote.js
Maintenant que notre module de vote est prêt, créons quelques propositions !
Créez un nouveau fichier appelé vote-proposals.js
dans le scripts
dossier et ajoutez ce qui suit :
import { ethers } from "ethers";
import sdk from "./initialize-sdk.js";
const voteModule = sdk.getVoteModule(
"VOTE_MODULE"
);
const tokenModule = sdk.getTokenModule(
"TOKEN_MODULE"
);
(async () => {
try {
const amount = 10000;
await voteModule.propose(
"Should the DAO mint an additional " +
amount +
" tokens into the treasury?",
[
{
nativeTokenValue: 0,
transactionData: tokenModule.contract.interface.encodeFunctionData(
"mint",
[voteModule.address, ethers.utils.parseUnits(amount.toString(), 18)]
),
toAddress: tokenModule.address,
},
]
);
console.log(" Successfully created proposal to mint tokens");
} catch (error) {
console.error("failed to create first proposal", error);
process.exit(1);
}
})();
Vous devez mettre à jour les adresses des modules, et si vous souhaitez mettre à jour le message de la proposition, vous pouvez également le mettre à jour.
Enfin, exécutez le script. Cela devrait vous donner quelque chose comme ça.
Si vous vérifiez maintenant le tableau de bord thirdweb, la proposition a été créée.
Tout d'abord, importez le module jeton et vote :
const voteModule = sdk.getVoteModule(
"0xf738973379b8B6444e429D2fd6C8B1f223247390"
);
const tokenModule = sdk.getTokenModule(
"0x8C35288de335070dd1C00d68d71383d81437472A"
);
Nous allons avoir besoin de trois useState
s, comme ceci :
const [proposals, setProposals] = useState([]);
const [isVoting, setIsVoting] = useState(false);
const [hasVoted, setHasVoted] = useState(false);
Nous devons récupérer les propositions pour les afficher à l'écran, alors créez ceci useEffect
:
useEffect(() => {
if (!hasClaimedNFT) {
return;
}
voteModule
.getAll()
.then((proposals) => {
setProposals(proposals);
})
.catch((err) => {
console.error("failed to get proposals", err);
});
}, [hasClaimedNFT]);
Ensuite, créez une nouvelle handleFormSubmit
fonction :
const handleFormSubmit = async (e) => {
e.preventDefault();
e.stopPropagation();
setIsVoting(true);
const votes = proposals.map((proposal) => {
let voteResult = {
proposalId: proposal.proposalId,
vote: 2,
};
proposal.votes.forEach((vote) => {
const elem = document.getElementById(
proposal.proposalId + "-" + vote.type
);
if (elem.checked) {
voteResult.vote = vote.type;
return;
}
});
return voteResult;
});
try {
const delegation = await tokenModule.getDelegationOf(address);
if (delegation === ethers.constants.AddressZero) {
await tokenModule.delegateTo(address);
}
try {
await Promise.all(
votes.map(async (vote) => {
const proposal = await voteModule.get(vote.proposalId);
if (proposal.state === 1) {
return voteModule.vote(vote.proposalId, vote.vote);
}
return;
})
);
try {
await Promise.all(
votes.map(async (vote) => {
const proposal = await voteModule.get(vote.proposalId);
if (proposal.state === 4) {
return voteModule.execute(vote.proposalId);
}
})
);
setHasVoted(true);
} 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", err);
} finally {
setIsVoting(false);
}
};
Cette fonction va collecter le vote.
Remplacez le if (hasClaimedNFT)
bloc par ceci :
if (hasClaimedNFT) {
return (
<div className={styles.container}>
<h2>Active Proposals</h2>
<form onSubmit={handleFormSubmit}>
{proposals.map((proposal) => (
<Proposal
key={proposal.proposalId}
votes={proposal.votes}
description={proposal.description}
proposalId={proposal.proposalId}
/>
))}
<button
onClick={handleFormSubmit}
type="submit"
className={styles.button}
>
{isVoting
? "Voting..."
"Submit Votes"}
</button>
</form>
</div>
);
}
Nous créons un composant distinct pour la proposition afin de garder les choses propres. Créez donc un nouveau fichier appelé Proposal.js
dans le components
dossier et ajoutez ce qui suit :
import styles from "../styles/Proposal.module.css";
const Proposal = ({ description, votes, proposalId }) => {
return (
<div className={styles.proposal}>
<h5 className={styles.description}>{description}</h5>
<div className={styles.options}>
{votes.map((vote) => (
<div key={vote.type}>
<input
type="radio"
id={proposalId + "-" + vote.type}
name={proposalId}
value={vote.type}
defaultChecked={vote.type === 2}
/>
<label htmlFor={proposalId + "-" + vote.type}>{vote.label}</label>
</div>
))}
</div>
</div>
);
};
export default Proposal;
J'ai également ajouté un style de base, alors créez un nouveau Proposal.module.css
fichier dans le styles
dossier :
.proposal {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fafafa;
border-radius: 10px;
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);
padding: 20px;
margin: 20px;
}
.options {
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: flex-start;
width: 100%;
margin-top: 1rem;
}
Pour centrer les éléments, j'ai également ajouté les styles suivantsHome.module.css
:
.container > form {
display: flex;
flex-direction: column;
align-items: center;
}
Vous arriverez sur cet écran où vous pourrez soumettre vos votes.
Enfin, créons une fonction pour vérifier si la personne a déjà voté.
Tout d'abord, créez un nouveau useEffect
:
useEffect(() => {
if (!hasClaimedNFT) {
return;
}
if (!proposals.length) {
return;
}
voteModule
.hasVoted(proposals[0].proposalId, address)
.then((hasVoted) => {
setHasVoted(hasVoted);
if (hasVoted) {
} else {
}
})
.catch((err) => {
console.error("failed to check if wallet has voted", err);
});
}, [hasClaimedNFT, address, proposals]);
Et remplacez le bouton par ceci :
<button
onClick={handleFormSubmit}
type="submit"
disabled={isVoting || hasVoted}
className={styles.button}
>
{isVoting ? "Voting..." : hasVoted ? "You Already Voted" : "Submit Votes"}
</button>
Après avoir voté, il devrait afficher le message Vous avez déjà voté :
C'était tout pour ce tutoriel, j'espère que vous l'avez aimé et que vous pourrez l'utiliser pour créer votre propre DAO ! Vous pouvez toujours mettre à jour le DAO et ajouter plus de fonctionnalités si vous le souhaitez.
Vous pouvez trouver le référentiel GitHub pour ce projet ici .
Source de l'article original sur https://blog.logrocket.com
1625674200
In this video, we are going to implement Google Analytics to our Next JS application. Tracking page views of an application is very important.
Google analytics will allow us to track analytics information.
Frontend: https://github.com/amitavroy/video-reviews
API: https://github.com/amitavdevzone/video-review-api
App link: https://video-reviews.vercel.app
You can find me on:
Twitter: https://twitter.com/amitavroy7
Discord: https://discord.gg/Em4nuvQk
#next js #js #react js #react #next #google analytics
1625751960
In this video, I wanted to touch upon the functionality of adding Chapters inside a Course. The idea was to not think much and start the development and pick up things as they come.
There are places where I get stuck and trying to find answers to it up doing what every developer does - Google and get help. I hope this will help you understand the flow and also how developers debug while doing development.
App url: https://video-reviews.vercel.app
Github code links below:
Next JS App: https://github.com/amitavroy/video-reviews
Laravel API: https://github.com/amitavdevzone/video-review-api
You can find me on:
Twitter: https://twitter.com/amitavroy7
Discord: https://discord.gg/Em4nuvQk
#next js #api #react next js #next #frontend #development