Você está procurando construir na interseção de DAOs e NFTs? Você está no lugar certo.
Este post tem como objetivo ajudá-lo a criar um projeto NFT pronto para DAO a partir do primeiro dia. Vamos percorrer as etapas para criar uma DAO on-chain onde 1 NFT = 1 voto. Você precisará de um conhecimento prático do ambiente de construção do Solidity.
Usaremos o Template Solidity do PaulrBeerg, que possui toda a configuração necessária para começar a escrever nossos contratos.
Também iremos misturar algumas bibliotecas do Solidity:
- Open Zeppelin ERC721: um contrato de token que implementa o padrão Token Não Fungível .
- Open Zeppelin Governor: contrato para criação e votação de propostas da DAO.
- Open Zeppelin ERC721Votes: este contrato implementa o suporte para votação e delegação, onde cada NFT individual conta como uma unidade de voto.
- Timelock: este contrato permitirá que as propostas bem-sucedidas entrem em um período de revisão antes da execução.
Vamos mergulhar!
1- Primeiro, vamos criar nosso repositório usando o modelo solidity do Paul. Você pode usar este comando:
git clone (https://github.com/paulrberg/solidity-template.git) my-nft-dao-project
2- Após criar nosso repositório, podemos ir para o diretório criado:
cd my-nft-dao-project
3- No diretório do projeto, vamos executar o comando de instalação yarn
:
yarn install
4- Agora, podemos abrir o projeto em nosso editor favorito, e ele deve ficar assim:
Estrutura do projeto
Não entraremos nos detalhes da estrutura do projeto. Em vez disso, vamos nos concentrar nas seguintes pastas:
Pasta/contracts, onde estarão todos os nossos contratos solidity. Você pode adicionar mais subpastas se desejar uma estrutura mais detalhada. Não faremos isso neste tutorial.
Pasta/tasks, onde adicionaremos os scripts necessários para implantar nossos contratos em nossa rede Ethereum desejada.
5- Precisamos adicionar um arquivo .env seguindo o .env.template definido na pasta do projeto. Essas variáveis são necessárias para implantar nosso contrato posteriormente e são usadas no arquivo hardhat.config.ts em nossa pasta raiz:
`# Qualquer endpoint RPC vai aqui. Não precisa ser Infura
INFURA_API_KEY=zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
# Este é o MNEMONICO de uma conta rinkeby
rinkeby MNEMONIC=aqui é onde seu mnemônico de doze palavras deve ser colocado meu amigo`
6- Agora, vamos adicionar o pacote da biblioteca de contratos do OpenZeppelin ao nosso projeto:
yarn add @openzeppelin/contracts
Vamos começar a codificar
Primeiro, vamos adicionar nosso contrato NFT. Vamos mantê-lo no mínimo para este tutorial:
1- Vamos adicionar um arquivo chamado MyNftToken.sol em nossa pasta de contratos
2- Agora vamos adicionar o seguinte código ao nosso arquivo:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/draft-EIP712.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/draft-ERC721Votes.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
contract MyNftToken is ERC721, Ownable, EIP712, ERC721Votes {
using Counters for Counters.Counter;
Counters.Counter private _tokenIdCounter;
constructor() ERC721("MyNftToken", "MTK") EIP712("MyNftToken", "1") {}
function safeMint(address to) public onlyOwner {
uint256 tokenId = _tokenIdCounter.current();
_tokenIdCounter.increment();
_safeMint(to, tokenId);
}
// The following functions are overrides required by Solidity.
function _afterTokenTransfer(
address from,
address to,
uint256 tokenId
) internal override(ERC721, ERC721Votes) {
super._afterTokenTransfer(from, to, tokenId);
}
}
contratos/MyNftToken.sol
Essa implementação simples de contrato NFT permite cunhar novos tokens usando a função safeMint e mantém o controle da contagem de tokens emitidos usando o _tokenIdCounter .
Além disso, importamos ERC721Votes para tornar nosso contrato de token compatível com o contrato da DAO Governor que implementaremos posteriormente. Isso ajuda a acompanhar quantos votos um detentor de token tem junto com outras informações importantes, como se ele delegou seu poder de voto para outra conta, etc.
Agora, vamos adicionar nosso contrato de Governor. Você pode usar o OpenZeppelin Contract Wizard ou criá-lo passo a passo:
Vamos fazer um fileInalled MyGovernor.sol
em nossa pasta de contratos:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.6;
import "@openzeppelin/contracts/governance/Governor.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorCountingSimple.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol";
import "@openzeppelin/contracts/governance/extensions/GovernorTimelockCompound.sol";
contract MyGovernor is Governor, GovernorSettings, GovernorCountingSimple, GovernorVotes, GovernorTimelockCompound {
constructor(IVotes _token, ICompoundTimelock _timelock)
Governor("MyGovernor")
GovernorSettings(
1, /* 1 block */
63,
2
)
GovernorVotes(_token)
GovernorTimelockCompound(_timelock)
{}
function quorum(uint256 blockNumber) public pure override returns (uint256) {
return 3;
}
// The following functions are overrides required by Solidity.
function votingDelay() public view override(IGovernor, GovernorSettings) returns (uint256) {
return super.votingDelay();
}
function votingPeriod() public view override(IGovernor, GovernorSettings) returns (uint256) {
return super.votingPeriod();
}
function state(uint256 proposalId)
public
view
override(Governor, GovernorTimelockCompound)
returns (ProposalState)
{
return super.state(proposalId);
}
function propose(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
string memory description
) public override(Governor, IGovernor) returns (uint256) {
return super.propose(targets, values, calldatas, description);
}
function proposalThreshold() public view override(Governor, GovernorSettings) returns (uint256) {
return super.proposalThreshold();
}
function _execute(
uint256 proposalId,
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal override(Governor, GovernorTimelockCompound) {
super._execute(proposalId, targets, values, calldatas, descriptionHash);
}
function _cancel(
address[] memory targets,
uint256[] memory values,
bytes[] memory calldatas,
bytes32 descriptionHash
) internal override(Governor, GovernorTimelockCompound) returns (uint256) {
return super._cancel(targets, values, calldatas, descriptionHash);
}
function _executor() internal view override(Governor, GovernorTimelockCompound) returns (address) {
return super._executor();
}
function supportsInterface(bytes4 interfaceId)
public
view
override(Governor, GovernorTimelockCompound)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
contratos/MyGovernor.sol
Agora, vamos revisar este arquivo para entender o que estamos fazendo com ele:
- Importaremos instâncias de contratos do OpenZeppelin Governor, GovernorVotes, GovernorSettings e GovernorCountingSimple para criar a base do contrato MyGovernor.
- Também importamos GovernorTimelockCompound para tornar nosso contrato de governor compatível com o estilo Compound Timelock.
- Em seguida, no construtor do nosso contrato Governor, inicializaremos o contrato Governor com um nome e informaremos os endereços que usaremos para o contrato de token e o contrato de timelock.
- Após chamar o construtor, definiremos a configuração do nosso contrato de governor. Geralmente, essas regras equilibram entre tornar as propostas mais fáceis e mais difíceis de aprovar. Sua DAO pode alterá-los mais tarde, mas apenas com um voto on-chain.
- votingDelay: o número de blocos que uma proposta precisa esperar após a criação para se tornar ativa e permitir a votação. Normalmente definido como zero, um atraso maior dá às pessoas tempo para configurar seus votos antes do início da votação.
- votingPeriod: número de blocos para executar a votação. Um período mais prolongado dá às pessoas mais tempo para votar. Normalmente, as DAOs definem o período para algo entre 3 e 6 dias.
- quorum: o número mínimo de votos 'Sim' necessários para a aprovação da proposta. Normalmente, as DAOs são definidas para 1 a 2% dos tokens pendentes, mas um token com ampla distribuição e poucas baleias pode querer reduzi-lo.
- proposalThreshold: a quantidade mínima de poder de voto que um endereço precisa para criar uma proposta. Um limite baixo permite spam, mas um limite alto torna as propostas quase impossíveis! DAOs são definidas para um número que permitirá que 5 ou 10 dos maiores detentores de token criem propostas.
Por último, adicionaremos substituições para as funções a seguir. Essas interfaces são abstratas, então precisamos implementar essas funções:
getVotes — Obtém o número de votos que um usuário teve antes da proposta ser publicada
state — Obtém o estado atual da proposta especificada
propose — Cria uma proposta se o remetente tiver votos suficientes
_execute — Envia a proposta para execução. A proposta deve ter ultrapassado o atraso de timelock necessário
_cancel — Cancela a proposta especificada se seu criador ficar abaixo do limite exigido
_executor — Obtém o endereço pelo qual o Governor executa uma ação. No nosso caso, o endereço do timelock.
supportInterface — ERC165 que permite validar as interfaces usadas em nosso contrato
Agora, vamos adicionar nosso contrato Timelock:
cd contracts/
curl https://raw.githubusercontent.com/withtally/my-nft-dao-project/main/contracts/Timelock.sol --output Timelock.sol
Agora nossa pasta de contratos deve ficar assim:
pasta de contratos
Se estiver faltando alguma coisa, compare seu projeto com este exemplo do repositório.
Em seguida, garantiremos que os contratos sejam compilados:
yarn compile
Vamos implantar
1- Primeiro, adicionaremos esta função getExpectedContractAddress para que nosso script de implantação possa informar ao Timelock onde encontrar o Governor. Adicionaremos um arquivo utils.ts em nossa pasta tasks/ do projeto e adicionaremos o código abaixo:
import { ethers } from "ethers";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/dist/src/signer-with-address";
export const getExpectedContractAddress = async (deployer: SignerWithAddress): Promise<string> => {
const adminAddressTransactionCount = await deployer.getTransactionCount();
const expectedContractAddress = ethers.utils.getContractAddress({
from: deployer.address,
nonce: adminAddressTransactionCount + 2,
});
return expectedContractAddress;
};
tasks/utils.ts
2- Criaremos uma nova tarefa hardhat. de segurança. Essas tarefas ajudam a automatizar etapas em nosso processo de desenvolvimento de contratos. Vamos adicionar um novo arquivo na pasta tasks/deploy/ em nosso projeto e nomeá-lo dao.ts, e adicionar o seguinte a ele:
import { task } from "hardhat/config";
import { getExpectedContractAddress } from "../utils";
import {
MyNftToken,
MyNftToken__factory,
MyGovernor,
MyGovernor__factory,
Timelock,
Timelock__factory,
} from "../../typechain";
task("deploy:Dao").setAction(async function (_, { ethers, run }) {
const timelockDelay = 2;
const tokenFactory: MyNftToken__factory = await ethers.getContractFactory("MyNftToken");
const signerAddress = await tokenFactory.signer.getAddress();
const signer = await ethers.getSigner(signerAddress);
const governorExpectedAddress = await getExpectedContractAddress(signer);
const token: MyNftToken = <MyNftToken>await tokenFactory.deploy();
await token.deployed();
const timelockFactory: Timelock__factory = await ethers.getContractFactory("Timelock");
const timelock: Timelock = <Timelock>await timelockFactory.deploy(governorExpectedAddress, timelockDelay);
await timelock.deployed();
const governorFactory: MyGovernor__factory = await ethers.getContractFactory("MyGovernor");
const governor: MyGovernor = <MyGovernor>await governorFactory.deploy(token.address, timelock.address);
await governor.deployed();
console.log("Dao deployed to: ", {
governorExpectedAddress,
governor: governor.address,
timelock: timelock.address,
token: token.address,
});
// We'll mint enough NFTs to be able to pass a proposal!
await token.safeMint(signerAddress);
await token.safeMint(signerAddress);
await token.safeMint(signerAddress);
await token.safeMint(signerAddress);
console.log("Minted 4 NFTs to get us started");
// Transfer ownership to the timelock to allow it to perform actions on the NFT contract as part of proposal execution
await token.transferOwnership(timelock.address);
console.log("Granted the timelock ownership of the NFT Token");
await run("verify:verify", {
address: token.address,
});
await run("verify:verify", {
address: timelock.address,
constructorArguments: [governor.address, timelockDelay],
});
await run("verify:verify", {
address: governor.address,
constructorArguments: [token.address, timelock.address],
});
});
tasks/deploy/dao.ts
3- Adicionaremos a tarefa DAO ao arquivo tasks/index.ts :
import "./dao";
4- Vamos atualizar nossa linha de script de implantação package.json para ficar assim:
"scripts": {
...
"deploy": "hardhat deploy:Dao",
}
É sempre essencial verificar nosso código de contratos inteligentes no Etherscan. Dessa forma, nossos usuários podem validar que o contrato implantado faz o que afirma fazer.
4- Agora, vamos adicionar a biblioteca necessária para verificar nossos contratos no Etherscan:
yarn add -D @nomiclabs/hardhat-etherscan
5- Em nossa pasta raiz, vamos encontrar o hardhat.config.ts e atualizar o objeto de configuração para incluir a configuração do Etherscan:
import "@nomiclabs/hardhat-etherscan"; // adiciona a importação do plugin
const config: HardhatUserConfig = {
...
etherscan:{ apiKey: process.env.ETH_SCAN_API_KEY },
}
6- Em seguida, adicionaremos a chave de API como uma variável de ambiente ao nosso arquivo .env . Você pode obter sua chave de API Etherscan aqui:
ETH_SCAN_API_KEY=
7- Agora, podemos implantar nossos contratos no*Rinkeby* usando o comando a seguir. Certifique-se de que a conta que configuramos a mnemônica no .env tem Rinkeby ether na rede de teste para gas!
yarn deploy --network rinkeby
8- Adicione sua DAO ao Tally usando este formulário. Isso tornará mais fácil para sua comunidade criar propostas, votar e delegar.
Tudo bem! Você fez isso!
Agora temos um NFT DAO implantado e verificado no Etherscan. Nossa comunidade NFT em ascensão pode votar na implementação do contrato NFT e como gastar qualquer dinheiro no tesouro.
Por favor, comente com dúvidas, sugestões ou histórias de sucesso!
Este artigo foi publicado por Natacha De la Rosa e traduzido por Arnaldo Campos. Você pode ver o artigo original aqui.
Top comments (0)