Este artigo técnico explorará o conceito de whitelisting de NFT e como implementar isto em uma coleção NFT baseada em Ethereum.
Foto de Shubham Dhage no Unsplash
Primeiro, vamos definir o que é whitelisting de NFT: é o processo de obter um endereço de carteira cripto pré-aprovado para a cunhagem.
É uma abordagem comum para evitar as chamadas "guerras de gas", onde as pessoas aumentam o preço do gas que estão dispostas a pagar para cunhar um NFT para que suas transações sejam coletadas primeiro, e pode ser uma ferramenta de marketing útil onde as pessoas são adicionadas à whitelist após realizar determinadas ações (por exemplo: em troca da assinatura de um boletim informativo por e-mail).
Este artigo irá guiá-lo através das etapas envolvidas na implementação deste tipo de sistema usando contratos inteligentes na blockchain Ethereum.
Embora existam múltiplas abordagens válidas, estaremos usando um sistema de cupons onde os endereços das carteiras são assinados off-chain, de forma que o contrato inteligente possa verificar se ele vem de uma fonte confiável.
Ao final deste artigo, você será capaz de:
- Adicionar a funcionalidade de "whitelisting" ao seu contrato inteligente, que permitirá que as carteiras pré-aprovadas cunhem um único NFT.
- Arquitetar uma solução web que se integra com a whitelist de seu contrato inteligente.
Como vai funcionar?
Geração
Cada cupom será um simples objeto javascript contendo um endereço de carteira que é assinado off-chain usando uma chave privada que só é conhecida por nós.
Obtenção
Nossos cupons ficarão em um simples arquivo JSON e serão expostos através de uma simples API.
Consumo
A assinatura do cupom resultante pode ser usada quando chamamos nosso contrato inteligente para provar que os dados que estão sendo recebidos foram gerados por nós.
Vamos construí-lo
Geração
Comecemos por gerar nossos cupons. Usaremos criptografia de chave pública para criptografar um endereço de carteira dentro de nosso "cupom".
O script abaixo expõe uma função createCoupon
que aceita um address
e uma privateKey
e devolverá um cupom.
const { ethers } = require('ethers');
async function createCoupon(address, privateKey) {
// Aproveitaremos a biblioteca de ethers para criar uma nova carteira digital
// que usaremos para assinar o cupom com a chave privada.
const signer = new ethers.Wallet(privateKey);
// Precisamos codificar o endereço da carteira de uma maneira que
// pode ser assinado e posteriormente recuperado do contrato inteligente.
// Hashing o endereço usando o algoritmo de hashing SHA-256
// é uma boa maneira de fazer isso.
const message = ethers.utils.solidityKeccak256(
['address'],
[address]
);
// Agora podemos assinar a mensagem usando a chave privada.
const signature = await signer.signMessage(ethers.utils.arrayify(message));
// A assinatura pode ser expandida em seus componentes subjacentes
// que podemos passar diretamente para o contrato inteligente.
// Se não dividíssemos a assinatura aqui - teríamos que fazer isso
// no contrato inteligente, o que é um pouco incômodo.
let { r, s, v } = ethers.utils.splitSignature(signature);
return {r,s,v}
}
module.exports = {
createCoupon,
}
Vamos precisar de um par de chaves para trabalhar - as mesmas utilizadas para implantar seu contrato irão funcionar, mas se você ainda precisar gerar uma, você pode rapidamente criar uma nova carteira com a Metamask e exportar a chave privada de lá.
Aqui está um pequeno script node.js que irá gerar nossos cupons:
const { createCoupon } = require("./coupons");
const fs = require('fs');
require("dotenv").config();
// Insira a chave privada correspondente a _couponSigner
const privateKey = process.env.PRIVATE_KEY;
// Povoar com endereços para a whitelist
let addresses = [
// ..
];
const main = async () => {
let output = [];
console.log('Generating...');
for (let i = 0; i < addresses.length; i++) {
let signature = await createCoupon(addresses[i], 0, privateKey);
output.push({
wallet: mint[i],
r: signature.r,
s: signature.s,
v: signature.v
})
}
// Salvar os cupons gerados em um arquivo coupons.json
let data = JSON.stringify(output);
fs.writeFileSync('coupons.json', data);
console.log('Done.');
console.log('Check the coupons.json file.');
};
const runMain = async () => {
try {
await main();
process.exit(0);
} catch (error) {
console.log(error);
process.exit(1);
}
};
runMain();
Primeiro certifique-se de preencher seu arquivo .env
com a chave privada da carteira que será usada para assinar. Em seguida, preencha a matriz addresses
no script com uma lista de endereços da carteira.
Execute node generateCoupons.js
para gerar e salvar seus cupons em um arquivo coupons.json.
Pronto!
Recuperaração
Como cada cupom só é válido para um único endereço de carteira, não há risco se os cupons forem expostos. Entretanto, para manter a whitelist privada, ainda é uma boa ideia escondê-la atrás de um endpoint API que responda a um endereço de carteira e devolva o cupom correspondente se for encontrado.
Enquanto escrever uma API para servir estes cupons está além do escopo deste artigo, posso mostrar como seria fácil usar o código abaixo para encontrar e devolver o cupom correto:
// Recuperar a carteira da carteira de consulta
const wallet = req.query.wallet
// Encontre um cupom para o endereço da carteira aprovada
const c = coupons.filter(coupon => coupon.wallet.toLowerCase() === wallet.toLowerCase())
if (0 == c.length) {
return res.status(200).json({
coupon: null,
message: 'Coupon not found'
})
}
return res.status(200).json({coupon: c[0]})
A estrutura Next.js é uma excelente escolha para construir esta API e o restante do site de cunhagem front-end.
Consumo
Em nosso contrato inteligente, vamos começar definindo uma struct
para representar nosso cupom.
struct Coupon {
bytes32 r;
bytes32 s;
uint8 v;
}
Você pode notar que isto já se parece com o cupom que geramos com Javascript.
Em nosso contrato inteligente, precisamos fazer um par de coisas para verificar se o cupom é válido.
- Crie o mesmo dígito de mensagem (contendo o endereço da carteira) que nós criamos em nosso código Javascript.
- Use esse código para recuperar o signatário de nosso cupom.
- Certifique-se de que o signatário recuperado seja, de fato, nós.
Em Solidity, podemos conseguir isso escrevendo duas funções internas:
// Recuperar o signatário original usando a resumo da mensagem e
// o aprovado em cupom, para então confirmar que o original
// signatário é, de fato, o _couponSigner estabelecido neste contrato.
function _isVerifiedCoupon(bytes32 digest, Coupon memory coupon)
internal
view
returns (bool)
{
address signer = ecrecover(digest, coupon.v, coupon.r, coupon.s);
require(signer != address(0), "ECDSA: invalid signature");
return signer == _couponSigner;
}
// Crie o mesmo resumo de mensagem que sabemos que o cupom foi criado
// em nosso código JavaScript.
function _createMessageDigest(address _address)
internal
pure
returns (bytes32)
{
return keccak256(
abi.encodePacked(
"\x19Ethereum Signed Message:\n32",
keccak256(abi.encodePacked(_address))
)
);
}
Então podemos atualizar nossa função de cunhagem para usar nosso novo sistema de cupons:
function mint(Coupon memory coupon)
external
payable
{
require(
_isVerifiedCoupon(_createMessageDigest(msg.sender), coupon),
"Coupon is not valid."
);
// exige que cada carteira só possa cunhar um token
require(
!_mintedAddresses[msg.sender],
"Wallet has already minted."
);
// Mantenha-se atento ao fato de que esta carteira cunhou um token
_mintedAddresses[msg.sender] = true;
// ...
}
E aí está! É importante manter um registro das carteiras que foram cunhadas a fim de evitar que os cupons sejam reutilizados.
No site da cunhagem, precisamos passar nosso cupom ao chamar a função de cunhagem:
async function fetchCoupon(wallet) {
const res = await fetch(`/api/coupons?wallet=${wallet}`)
return await res.json()
}
async function mint(wallet) {
const coupon = await fetchCoupon(wallet)
let tx = await contract.mint(coupon)
// ...
}
Conclusão
Você aprendeu um método simples, seguro e eficaz para implementar uma whitelist NFT.
Este artigo é um trecho especialmente reescrito do meu próximo livro: "Um guia do desenvolvedor para o lançamento de uma coleção NFT".
Se você gostaria de começar a aprender a codificar uma Coleção NFT do zero, confira meu livro: "Um guia do desenvolvedor para lançamento de uma Coleção NFT" - use este link para 10% de desconto!
Siga-me no twitter para mais dicas e truques relacionados à blockchain, e para acompanhar enquanto eu uso minhas habilidades de desenvolvimento de software para construir $10.000/mês em renda passiva.
Artigo escrito por Michael Stivala, seu original é encontrado aqui. Traduzido e adaptado por Marcelo Panegali.
Latest comments (1)
É o primeiro artigo que vejo que não utiliza MerkleProof, massa 👏