Neste tutorial, vamos dar uma amostra de algumas das funcionalidades da StarkNet ao criar o esqueleto inicial de um mercado NFT, começando com a construção de um aplicativo básico que usa o SDK StarkNet.js para interagir com um contrato ERC721. Postagens futuras vão se aprofundar no desenvolvimento personalizado de contratos inteligentes com Cairo e na construção de um dapp usando o MetaMask Flask (com o starknet-snap instalado).
Antes de começarmos, vamos falar um pouco sobre a tecnologia StarkNet. A StarkNet é um rollup de Validação descentralizado permissionado (também conhecido como "ZK-Rollup"). Ele opera como uma rede de camada 2 sobre a Ethereum, permitindo que qualquer dapp alcance escalabilidade ilimitada para seus cálculos sem comprometer a composabilidade e a segurança da Ethereum, graças à confiança da StarkNet em um dos sistemas de prova criptográfica mais seguros e escaláveis - STARK.
A ConsenSys e a StarkNet se uniram no início deste ano para trazer essa tecnologia segura e de alto desempenho para a comunidade Web3, integrando-a ao Infura e tornando-a compatível com a MetaMask.
O Infura fornece um padrão de pontos de extremidade JSON-RPC para se comunicar de forma transparente e direta com a rede StarkNet por meio do cliente Pathfinder construído pela Equilibrium.
A API da StarkNet mapeia um subconjunto dos métodos JSON-RPC da Ethereum (com algumas diferenças menores), de modo que usuários familiarizados com o ETH possam substituir facilmente as chamadas prefixadas com eth_
por starknet_
e interagir imediatamente com um nó ou contrato StarkNet.
Embora os contratos StarkNet sejam escritos em Cairo, uma linguagem e framework Turing-completa de alto nível diferente do Solidity, o usuário não precisa ter esse conhecimento para concluir com êxito o tutorial abaixo.
Felizmente para nós, a comunidade Web3 (particularmente a equipe da OpenZeppelin) criou alguns modelos prontos para uso dos padrões ERC mais comuns. Neste tutorial, vamos usar os contratos Cairo da OpenZeppelin, especificamente:
- Account (🧐vamos explorar isso no próximo capítulo)
- ERC721EnumerableMintableBurnable
A versão dos contratos Cairo da OpenZeppelin usada neste tutorial é a 0.3.1.
Vamos disponibilizar os arquivos de contrato já compilados contendo a definição de todos os métodos e estruturas (ABI), prontos para serem implantados na rede StarkNet.
Aplicativo básico com SDK StarkNet.js
Pré-requisitos
Antes de começar, certifique-se de ter todos os ingredientes necessários para esta deliciosa refeição:
- Uma conta Infura
- Node e npm instalados (neste guia → v16.xx.x)
- Um entendimento básico do padrão ERC721
- Seu IDE favorito.
Começando
Nesta seção, vamos lavar, enxaguar e cortar todos os ingredientes para o nosso delicioso ensopado.
Começaremos criando uma nova chave de acesso Web3 Infura (anteriormente, ID do projeto) com pontos de extremidade da StarkNet ativados e uma chave de acesso IPFS para armazenar nossas obras de arte NFT. Essas duas chaves serão usadas pelo nosso aplicativo para executar transações na rede StarkNet e obter informações úteis sobre saldos de contas e coleções de NFTs.
Criando uma nova chave de acesso Web3 com pontos de extremidade da StarkNet
Para acessar a rede StarkNet, precisamos ter um ponto de extremidade através do qual todas as nossas solicitações de/para um nó StarkNet serão executadas. Vamos ver como fazer isso com o Infura (ou siga este guia de início rápido).
- Faça login na página principal do Infura
- Clique no botão do lado direito - Create new key (Criar nova chave)
- Na janela modal que aparece:
- Selecione Network (Rede) → API Web3
- Digite o que você quiser no campo “Name” (Nome) 🙂
- Clique em "Create" (Criar)
- Role a página para baixo até encontrar StarkNet
- No menu suspenso de rede, selecione o ponto de extremidade da rede de testes Goerli
- Clique no ícone à direita para copiar o conteúdo
A saída deve ser:
https://starknet-goerli.infura.io/v3/<CHAVE_DE_API>
Anote isso, pois essa informação será útil mais adiante neste tutorial.
Criando uma nova chave de acesso IPFS e gateway
O armazenamento na blockchain é caro, e é por isso que é uma prática comum armazenar a mídia de NFTs fora da cadeia. O IPFS é geralmente a escolha preferida de muitos desenvolvedores, por três motivos principais: é gratuito, é descentralizado e pode garantir acesso a um recurso por um tempo muito longo.
Precisaremos criar uma nova chave de acesso IPFS no Infura e configurar um gateway dedicado que usaremos mais tarde.
- Faça login na página principal do Infura
- Clique no botão do lado direito - Create new key
- Na janela modal que aparece:
- Selecione Network → API Web3
- Digite o que você quiser no campo “Name”
- Clique em "Create"
- Na seção "Dedicated Gateways" (Gateways dedicados), alterne o botão para habilitar
- Insira um subdomínio exclusivo de sua escolha
- Copie e salve o URL do gateway
- Copie e salve tanto o ID do projeto quanto a chave secreta da API
Agora, temos todos os ingredientes prontos para começar nosso curso de MasterChef 🧑🍳.
Construindo
Vamos começar verificando se todas as nossas dependências foram instaladas corretamente.
O código deste tutorial foi testado com as seguintes versões de ferramentas:
- Node → 16.17.0
- Npm → 8.18.0
Outras versões podem funcionar, mas é recomendável que você use as principais versões para garantir total compatibilidade. (Dica: quer alternar rapidamente entre diferentes versões do Node? Experimente o nvm!)
node --version
v16.17.0
npm --version
8.18.0
Tudo em ordem, perfeito! Agora vamos criar um novo projeto do Node.
mkdir my-starknft-world
cd my-starknft-world
npm init -y
O último comando executará o utilitário de CLI do npm e criará um novo package.json
.
StarkNet.js
Starknet.js é a biblioteca oficial em JavaScript (SDK) para interagir com a StarkNet e é mantida por uma comunidade de contribuidores independentes.
A versão do StarkNet.js usada neste tutorial é a 4.6.0.
Starknet.js pode ser instalado como um módulo node padrão executando o seguinte comando npm
:
npm install --save starknet@next
Também precisaremos adicionar "type": "module"
ao nosso package.json
para habilitar o ES-module e poder usar o import
.
Após a conclusão da instalação, agora você deve ter um arquivo package.json
que se parece com isso:
{
"name": "my-starknft-world",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"starknet": "^4.6.0"
},
"type": "module"
}
Maravilhoso! E agora é hora de começarmos com o código. 🧑💻
Implantando uma nova conta na StarkNet
Na StarkNet, o modelo de conta é diferente daquele encontrado em blockchains baseadas em EVM. É um modelo mais flexível e pelo qual a comunidade Ethereum tem esperado há muito tempo, como este excelente artigo explora.
Aqui, o conceito de uma conta de usuário se torna um contrato (contract_address
); os usuários da StarkNet compartilham publicamente esse endereço para que alguém possa enviar tokens para ele, em vez de um endereço público convencional derivado diretamente do par de chaves público/privado do usuário. Esse contrato pode conter qualquer código, e o usuário ainda interage com ele assinando transações com sua chave privada.
Em outras palavras, a StarkNet diferencia o conceito de carteira de usuário e conta de usuário.
Além disso, pode-se dizer que uma conta de usuário é meramente um tipo especial de contrato Cairo, desde que contenha a lógica necessária para encaminhar uma transação que foi assinada com uma chave privada válida.
Abra o projeto em seu IDE favorito e crie um novo arquivo index.js
.
Inicializando um novo provedor
A API do provedor permite que você interaja com a rede StarkNet sem assinar transações ou mensagens.
import {
Provider,
} from "starknet";
const infuraEndpoint = "https://starknet-goerli.infura.io/v3/<API_KEY>";
const provider = new Provider({
rpc: {
nodeUrl: infuraEndpoint,
},
});
Baixando a ABI do contrato de conta
Conforme mencionado acima, os contratos StarkNet são escritos em Cairo. No entanto, para manter este tutorial o mais simples possível, estamos fornecendo a versão compilada equivalente do contrato contendo a ABI em formato JSON.
Esses contratos compilados podem ser encontrados no seguinte repositório.
Acesse essa página e baixe o arquivo OZAccount.json
em um novo diretório contracts
localizado na raiz do seu projeto. Sua árvore de diretórios deve parecer com isso:
.
├── contracts
│ └── OZAccount.json
├── index.js
├── node_modules
├── package-lock.json
└── package.json
Lendo o contrato compilado da conta
Observe que o módulo json
usado neste tutorial para fazer a análise sintática do contrato compilado faz parte do pacote starknet
, o qual fornece uma implementação personalizada do módulo-padrão json
do Node.
import fs from "fs";
import {
Provider,
json,
} from "starknet";
console.log("Lendo o contrato da conta OpenZeppelin...");
const compiledOZAccount = json.parse(fs.readFileSync("./contracts/OZAccount.json").toString("ascii"));
Gerando um par de chaves privada e pública
Usando o módulo stark
, geramos o par de chaves privada e pública que serão usados para assinar e executar transações.
Como mencionado anteriormente, na StarkNet, um endereço de conta é calculado como um contrato inteligente sem relação direta com a(s) chave(s) que controlam a conta, e é por isso que não podemos derivar um endereço de conta a partir dessas chaves e, em vez disso, precisamos realizar etapas adicionais para criar um.
import {
defaultProvider,
ec,
json,
stark,
} from "starknet";
const privateKey = stark.randomAddress();
const starkKeyPair = ec.getKeyPair(privateKey);
const starkKeyPub = ec.getStarkKey(starkKeyPair);
console.log(`🚨NÃO COMPARTILHE ISSO !!! 🚨 Chave privada: ${privateKey}`); // <-- MANTENHA ISSO EM SEGREDO! 🔐
console.log(`Chave pública: ${starkKeyPub}`);
Anote ambas as chaves, pois podemos reutilizá-las posteriormente para outras operações. Tenha cuidado! Armazene suas chaves privadas em um local seguro e nunca as compartilhe com ninguém.
Implantando uma nova conta como um contrato
Podemos implantar o contrato de conta pré-compilado na StarkNet usando o método do provedor deployContract
e passando como entrada a chave pública gerada anteriormente.
console.log("Transação de Implantação - Contrato de Conta para StarkNet...");
const accountResponse = await provider.deployContract({
contract: compiledOZAccount,
constructorCalldata: [starkKeyPub],
addressSalt: starkKeyPub,
});
const accountAddress = accountResponse.contract_address;
console.log(`Endereço da conta: ${accountAddress}`);
console.log(
"Aguardando a transação ser Aceita na StarkNet - Implantação de Conta OpenZeppelin..."
);
console.log(
`Siga o status da transação em: https://goerli.voyager.online/tx/${accountResponse.transaction_hash}`
);
await provider.waitForTransaction(accountResponse.transaction_hash);
Essa operação pode levar alguns minutos (~10 minutos) para ser concluída - um bom momento para preparar um ☕ ️ 🙂.
Você pode sempre acompanhar o status da transação por meio do explorador de blocos StarkNet (Voyager) em:
https://goerli.voyager.online/tx/<HASH_DA_TRANSAÇÃO>
E por fim, vamos criar um novo objeto Account
que usaremos mais adiante neste guia.
Um objeto Account estende a classe Provider
e herda todos os seus métodos. Ele também introduz novos métodos que permitem que Accounts criem e verifiquem assinaturas com um Signer
(signatário) personalizado. Esta API é a forma primária de interagir com um contrato de Conta na StarkNet.
import {
Account,
ec,
json,
stark,
Provider,
} from "starknet";
const account = new Account(provider, accountAddress, starkKeyPair);
Juntando tudo, nosso index.js deve ficar assim:
import fs from "fs";
import {
Account,
ec,
json,
stark,
Provider,
} from "starknet";
/*
====================================
🦊 1. Criação da Conta
====================================
*/
// Inicialize o provedor
const infuraEndpoint = "https://starknet-goerli.infura.io/v3/...";
const provider = new Provider({
rpc: {
nodeUrl: infuraEndpoint,
},
});
console.log("Lendo o Contrato da Conta OpenZeppelin...");
const compiledOZAccount = json.parse(
fs.readFileSync("./contracts/OZAccount.json").toString("ascii")
);
// Gere par de chaves públicas e privadas.
const privateKey = stark.randomAddress();
const starkKeyPair = ec.getKeyPair(privateKey);
const starkKeyPub = ec.getStarkKey(starkKeyPair);
console.log(`🚨NÃO COMPARTILHE ISSO !!! 🚨 Chave privada: ${privateKey}`); // <-- MANTENHA ISSO EM SEGREDO! 🔐
console.log(`Chave pública: ${starkKeyPub}`);
// Implante o contrato da conta e aguarde a verificação na StarkNet
console.log("Transação de Implantação - Contrato de Conta para StarkNet...");
const accountResponse = await provider.deployContract({
contract: compiledOZAccount,
constructorCalldata: [starkKeyPub],
addressSalt: starkKeyPub,
});
const accountAddress = accountResponse.contract_address;
console.log(`Endereço da conta: ${accountAddress}`);
// Aguarde até que a transação de implantação seja aceita na StarkNet
console.log(
"Aguardando a transação ser Aceita na StarkNet - Implantação de Conta OpenZeppelin..."
);
console.log(
`Siga o status da transação em: https://goerli.voyager.online/tx/${accountResponse.transaction_hash}`
);
await provider.waitForTransaction(accountResponse.transaction_hash);
// Use seu novo endereço de conta
const account = new Account(provider, accountAddress, starkKeyPair);
DICA: Este trecho de código pode ser executado diretamente. No entanto, para economizar algum tempo ao executar as seções de código subsequentes, você pode substituir a geração aleatória da chave privada pelo resultado da sua primeira execução. Isso reutilizará o mesmo par de chaves sem a necessidade de uma nova implantação de conta.
const privateKey = <CHAVE_PRIVADA>;
Contrato NFT ERC721
Como mencionado anteriormente, neste tutorial vamos utilizar um contrato ERC721EnumerableMintableBurnable
baseado na implementação do padrão ERC721 do OpenZeppelin.
Baixando o arquivo da ABI do contrato ERC721
Baixe o arquivo ERC721EnumerableMintableBurnable.json
do seguinte repositório na pasta contracts
localizada na raiz do seu projeto. Agora a estrutura de diretórios do seu projeto deve estar assim:
.
├── contracts
│ ├── ERC721EnumerableMintableBurnable.json
│ └── OZAccount.json
├── index.js
├── node_modules
├── package-lock.json
└── package.json
Lendo o contrato ERC721 compilado
Observe que o módulo json
usado neste tutorial para analisar o contrato compilado faz parte do pacote starknet
, que fornece uma implementação personalizada do módulo-padrão json
do node.
// Leia ABI do contrato NFT
console.log(
"Lendo o contrato ERC721EnumerableMintableBurnable do OpenZeppelin...."
);
const compiledErc721 = json.parse(
fs
.readFileSync("./contracts/ERC721EnumerableMintableBurnable.json")
.toString("ascii")
);
Adicionando fundos à conta
Para executar transações na rede StarkNet (assim como na Ethereum), precisamos encher nosso tanque com um pouco de combustível. Adicionar fundos à uma conta pode ser feito manualmente usando a torneira (faucet) Goerli oficial da StarkNet. No entanto, uma vez iniciado, nosso código não irá parar e a transação precisará de algum tempo para ser confirmada, precisamos adicionar um pequeno trecho de código para pausar a execução e reiniciá-la assim que todas essas operações forem concluídas.
Portanto, vamos adicionar a seguinte função ao nosso index.js
:
import readline from "readline";
function prompt(query) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
return new Promise((resolve) =>
rl.question(query, (ans) => {
rl.close();
resolve(ans);
})
);
}
E vamos chamar esse método logo antes da implantação.
await prompt(
`
IMPORTANTE: você precisa adicionar fundos à sua nova conta antes de usá-la.
Você pode fazer isso usando uma torneira: https://faucet.goerli.starknet.io/
Insira o seguinte endereço da conta: ${accountAddress}
Aguarde a confirmação antes de continuar.
[Pressione ENTER para continuar]`
);
Implantando contrato ERC721
Depois de completar a operação de adição de fundos e a conta de destino ser atualizada com o novo saldo, estamos prontos para implantar o contrato ERC721 NFT.
import {
Account,
ec,
json,
number,
shortString,
stark,
Provider,
} from "starknet";
console.log("Transação de Implantação - Contrato ERC721 para a StarkNet...");
const erc721Response = await provider.deployContract({
contract: compiledErc721,
constructorCalldata: [
number.hexToDecimalString(shortString.encodeShortString("MyStarkNFT")),
number.hexToDecimalString(shortString.encodeShortString("MSN")),
accountAddress,
],
addressSalt: starkKeyPub,
});
// Aguarde até que a transação de implantação seja aceita na StarkNet
console.log(
"Aguardando Tx ser Aceita na StarkNet - Implantação ERC721...");
console.log(
`Siga o status da transação em: https://goerli.voyager.online/tx/${erc721Response.transaction_hash}`
);
await provider.waitForTransaction(erc721Response.transaction_hash);
E uma vez confirmada a transação, podemos imprimir o endereço do contrato e visualizá-lo no explorador de blocos.
const erc721Address = erc721Response.contract_address;
console.log("Endereço ERC721: ", erc721Address);
console.log(`Link do Explorador: https://goerli.voyager.online/contract/${erc721Address}`);
Conectando conta ao contrato NFT
Os contratos podem fazer transformações de dados em JavaScript com base em uma ABI. Eles também podem chamar e invocar a StarkNet por meio de um Signatário fornecido.
Ao conectar uma Conta a um Contrato, definimos implicitamente quem estará interagindo com a rede e assinando transações com sua chave privada.
import {
Account,
Contract,
ec,
json,
number,
shortString,
stark,
Provider,
} from "starknet";
// Crie um novo objeto de contrato erc721
const erc721 = new Contract(compiledErc721.abi, erc721Address, provider);
// Conecte a conta corrente para executar transações
erc721.connect(account);
Cunhando um NFT
Finalmente chegamos à parte mais empolgante deste tutorial: criar nossa primeira arte digital Stark-web3. 👨🎨
A transação mint
, como por definição padrão, terá o endereço da conta do destinatário e o ID do token, como entrada para cunhar o NFT.
A API do Contrato nos permite usar diretamente a interface <Contract>.mint
, já que este método é definido na ABI do erc721
.
import {
Account,
Contract,
ec,
json,
number,
shortString,
stark,
Provider,
uint256,
} from "starknet";
const tokenId = 1;
const value = uint256.bnToUint256(tokenId + "000000000000000000");
console.log(
`Chame a Transação - Cunhando um NFT com tokenId. ${tokenId} para ${accountAddress} ...`
);
const { transaction_hash: mintTxHash } = await erc721.mint(
accountAddress,
[value.low, value.high],
{
maxFee: "999999995330000",
addressSalt: starkKeyPub,
}
);
// Aguarde até que a transação de chamada seja aceita na StarkNet
console.log(`Esperando Tx ser aceita na Starknet - Cunhando...`);
console.log(`Siga o status da transação em: https://goerli.voyager.online/tx/${mintTxHash}`);
await provider.waitForTransaction(mintTxHash);
Costurando todos os fios:
/*
====================================
📜 2. Contrato NFT ERC721
====================================
*/
// Leia ABI do contrato NFT
console.log(
"Lendo o contrato ERC721EnumerableMintableBurnable do OpenZeppelin...."
);
const compiledErc721 = json.parse(
fs
.readFileSync("./contracts/ERC721EnumerableMintableBurnable.json")
.toString("ascii")
);
// Adicione fundos à conta
await prompt(
`
IMPORTANTE: você precisa adicionar fundos à sua nova conta antes de usá-la.
Você pode fazer isso usando uma torneira: https://faucet.goerli.starknet.io/
Insira o seguinte endereço da conta: ${accountAddress}
Aguarde a confirmação antes de continuar.
[Pressione ENTER para continuar]`
);
// Implante um contrato ERC721 e aguarde a verificação na StarkNet.
console.log("Transação de Implantação - Contrato ERC721 para a StarkNet...");
const erc721Response = await provider.deployContract({
contract: compiledErc721,
constructorCalldata: [
number.hexToDecimalString(shortString.encodeShortString("MyStarkNFT")),
number.hexToDecimalString(shortString.encodeShortString("MSN")),
accountAddress,
],
addressSalt: starkKeyPub,
});
// Aguarde até que a transação de implantação seja aceita na StarkNet
console.log("Aguardando Tx ser Aceita na StarkNet - Implantação ERC721...");
console.log(
`Siga o status da transação em: https://goerli.voyager.online/tx/${erc721Response.transaction_hash}`
);
await provider.waitForTransaction(erc721Response.transaction_hash);
// Obtenha o endereço do contrato
const erc721Address = erc721Response.contract_address;
console.log("Endereço ERC721: ", erc721Address);
console.log(`Link do Explorador: https://goerli.voyager.online/contract/${erc721Address}`);
// Crie um novo objeto de contrato erc721
const erc721 = new Contract(compiledErc721.abi, erc721Address, provider);
// Conecte a conta corrente para executar transações
erc721.connect(account);
// Cunhe 1 NFT com tokenId para accountAddress
const tokenId = 1;
const value = uint256.bnToUint256(tokenId + "000000000000000000");
console.log(
`Chame a Transação - Cunhando um NFT com tokenId. ${tokenId} para ${accountAddress} ...`
);
const { transaction_hash: mintTxHash } = await erc721.mint(
accountAddress,
[value.low, value.high],
{
maxFee: "999999995330000",
addressSalt: starkKeyPub,
}
);
// Aguarde até que a transação de chamada seja aceita na StarkNet
console.log(`Esperando Tx ser aceita na Starknet - Cunhando...`);
console.log(
`Siga o status da transação em: https://goerli.voyager.online/tx/${mintTxHash}`
);
await provider.waitForTransaction(mintTxHash);
O usuário pode ter notado algumas conversões “interessantes” de tipo aqui e ali neste tutorial. A razão pela qual não podemos simplesmente passar um uint256
como faríamos no Solidity é que, no Cairo, existe apenas um tipo de dado chamado felt
, que significa Field Element (elemento de campo finito). Em termos simples, é um inteiro sem sinal com até 76 casas decimais, mas também pode ser usado para armazenar endereços. Não se preocupe, isso não faz parte deste tutorial, mas vale a pena mencionar. 🙂
Carregando dados do NFT para o IPFS
Nesta seção, vamos usar o gateway IPFS que configuramos anteriormente no Infura para carregar nossa mídia NFT e metadados.
Para começar, precisaremos instalar o pacote do cliente IPFS.
npm install --save ipfs-http-client
Inicializando o cliente IPFS
Agora é hora de lembrar as informações que anotamos antes, ou seja:
- INFURA_IPFS_PROJECT_ID
- INFURA_IPFS_SECRET
- INFURA_IPFS_GATEWAY
E então você colocaria essas informações em nosso código, da seguinte forma:
const infuraIpfsIdAndSecret = "<INFURA_IPFS_PROJECT_ID>:<INFURA_IPFS_SECRET>";
const infuraIpfsGateway = "https://<CUSTOM_GATEWAY>.infura-ipfs.io/ipfs/";
// Sinta-se à vontade para substituir este URL por qualquer imagem que desejar na web - por padrão, definimos como nosso logotipo ❤️
const imageUrl = "https://pbs.twimg.com/profile_images/1357501845145485316/yo6M6Y9u_400x400.jpg";
const { create, urlSource } = await import("ipfs-http-client");
const ipfs = await create({
host: "ipfs.infura.io",
port: 5001,
protocol: "https",
headers: {
Authorization: `Basic ${Buffer.from(infuraIpfsIdAndSecret).toString(
"base64"
)}`,
},
});
Este trecho de código criará uma instância de um gateway IPFS usando nossas informações pessoais do Infura.
Observe que você sempre pode substituir o imageUrl
por qualquer coisa que desejar!
Carregando mídia para o IPFS
Primeiro, vamos carregar a mídia NFT obtendo-a diretamente da web.
let fileUrl;
try {
const added = await ipfs.add(urlSource(imageUrl));
console.log("Imagem", added);
fileUrl = infuraIpfsGateway + added.cid;
} catch (error) {
console.log("Erro ao carregar o arquivo: ", error);
}
console.log(`URL do arquivo IPFS: ${fileUrl}`);
Carregando os metadados do NFT para o IPFS
Depois de concluir o upload de nossa mídia, podemos anexar ao nosso NFT algumas informações básicas, como nome, descrição e o URI da imagem - geralmente chamados de metadados.
const metadata = JSON.stringify({
name: "StarkNFT",
description: "Meu primeiro NFT na StarkNet com o Infura! 🥳",
image: fileUrl,
});
let metadataUrl;
try {
const added = await ipfs.add(metadata);
console.log("Metadados", added);
metadataUrl = infuraIpfsGateway + added.cid;
} catch (error) {
console.log("Erro ao carregar o arquivo: ", error);
}
console.log(`URL do arquivo IPFS: ${metadataUrl}`);
Definindo tokenURI do NFT
Vamos chamar o método do padrão ERC721, setTokenURI
, para atualizar as informações associadas ao nosso NFT. No entanto, antes de fazer isso, precisamos realizar um pequeno truque mágico 🪄.
Conforme mencionado acima, o Cairo usa apenas um tipo de dados e, ao converter uma string
em felt
, precisamos ter certeza de que esse valor não terá mais que 31 caracteres. Como os URIs do IPFS provavelmente não atenderão a esse critério, precisamos de uma solução alternativa para "encurtar" esse texto. A maneira mais rápida e simples de fazer isso é usar um encurtador de URL gratuito disponível online. Para fins deste tutorial, vamos usar o tinyurl.com.
Vamos chamar uma API do tinyurl.com para criar um novo URL curto a partir do nosso URI do Infura IPFS. Vamos adicionar este trecho ao nosso código.
O pacote http
já faz parte do node, portanto, não precisamos instalar nenhuma nova dependência.
import https from "http";
function shortenUrl(url) {
return new Promise((resolve, reject) => {
const options = `https://tinyurl.com/api-create.php?url=${encodeURIComponent(
url
)}`;
https
.get(options, (response) => {
if (response.statusCode >= 400) {
reject(new Error(`${response.statusCode} ${response.statusMessage}`));
}
response.on("data", (data) => {
resolve(data.toString().replace(/https?:\/\//i, ""));
});
})
.on("error", (error) => {
reject(error);
});
});
}
E então, vamos chamar esse método logo após o upload dos metadados.
metadataUrl = await shortenUrl(metadataUrl);
console.log(`O URL encurtado dos metadados é: ${metadataUrl}`);
Truque de mágica concluído!✨🧙🏼♀️ Agora, tudo está pronto para executarmos a transação.
// Atualize o URI de metadados do token
console.log(`Chame a transação - Definir URI do tokenId ${tokenId} para ${metadataUrl} ...`);
const { transaction_hash: tokenUriTxHash } = await erc721.setTokenURI(
[value.low, value.high],
number.hexToDecimalString(shortString.encodeShortString(metadataUrl)),
{
maxFee: "999999995330000",
addressSalt: starkKeyPub,
}
);
// Aguarde até que a transação de chamada seja aceita na StarkNet
console.log(`Aguardando que a Transação seja aceita na Starknet - Definindo o URI do token...`);
console.log(
`Siga o status da transação em: https://goerli.voyager.online/tx/${tokenUriTxHash}`
);
await provider.waitForTransaction(tokenUriTxHash);
Recuperando informações de metadados do NFT
Por fim, vamos recuperar os metadados do NFT chamando o método do padrão ERC721, tokenURI
.
console.log(`Recuperando metadados do tokenId ${tokenId} ...`);
const tokenURI = await erc721.tokenURI([value.low, value.high]);
const resultDecoded = shortString.decodeShortString(number.toHex(number.toBN(tokenURI[0])));
console.log(
`URI de token para o ${tokenId} is`,
resultDecoded
);
console.log(`Link direto --> https://${resultDecoded}`);
Juntando tudo:
/*
====================================
🖼 3. Carregar arte NFT e metadados
====================================
*/
// Inicialize o cliente IPFS
const infuraIpfsIdAndSecret = "<INFURA_IPFS_PROJECT_ID>:<INFURA_IPFS_SECRET>";
const infuraIpfsGateway = "https://<CUSTOM_GATEWAY>.infura-ipfs.io/ipfs/";
// Sinta-se à vontade para substituir este URL por qualquer imagem que desejar na web - por padrão, definimos como nosso logotipo ❤️
const imageUrl = "https://pbs.twimg.com/profile_images/1357501845145485316/yo6M6Y9u_400x400.jpg";
const { create, urlSource } = await import("ipfs-http-client");
const ipfs = await create({
host: "ipfs.infura.io",
port: 5001,
protocol: "https",
headers: {
Authorization: `Basic ${Buffer.from(infuraIpfsIdAndSecret).toString(
"base64"
)}`,
},
});
// Carregue imagem para o IPFS
let fileUrl;
try {
const added = await ipfs.add(urlSource(imageUrl));
console.log("Imagem", added);
fileUrl = infuraIpfsGateway + added.cid;
} catch (error) {
console.log("Erro ao carregar o arquivo: ", error);
}
console.log(`URL do arquivo IPFS: ${fileUrl}`);
// Carregue metadados do NFT para o IPFS
const metadata = JSON.stringify({
name: "StarkNFT",
description: "Meu primeiro NFT na StarkNet com o Infura! 🥳",
image: fileUrl,
});
let metadataUrl;
try {
const added = await ipfs.add(metadata);
console.log("Metadados", added);
metadataUrl = infuraIpfsGateway + added.cid;
} catch (error) {
console.log("Erro ao carregar o arquivo: ", error);
}
console.log(`URL do arquivo IPFS: ${metadataUrl}`);
// Encurte o URI para um formato shortString compatível
metadataUrl = await shortenUrl(metadataUrl);
console.log(`O URL encurtado dos metadados é: ${metadataUrl}`);
// Atualize o URI de metadados do token
console.log(`Chame a transação - Definir URI do tokenId ${tokenId} para ${metadataUrl} ...`);
const { transaction_hash: tokenUriTxHash } = await erc721.setTokenURI(
[value.low, value.high],
number.hexToDecimalString(shortString.encodeShortString(metadataUrl)),
{
maxFee: "999999995330000",
addressSalt: starkKeyPub,
}
);
// Aguarde até que a transação de chamada seja aceita na StarkNet
console.log(`Aguardando que a Transação seja aceita na Starknet - Definindo o URI do token...`);
console.log(`Siga o status da transação em: https://goerli.voyager.online/tx/${tokenUriTxHash}`
);
await provider.waitForTransaction(tokenUriTxHash);
// Recupere informações de metadados do NFT
console.log(`Recuperando metadados do tokenId ${tokenId} ...`);
const tokenURI = await erc721.tokenURI([value.low, value.high]);
const resultDecoded = shortString.decodeShortString(number.toHex(number.toBN(tokenURI[0])));
console.log(
`URI de token para o ${tokenId} is`,
resultDecoded
);
console.log(`Link direto --> https://${resultDecoded}`);
console.log("\nParabéns! Você cunhou seu primeiro NFT na StarkNet com o Infura 🥳");
Parabéns, você conseguiu! 👏 Você cunhou seu primeiro NFT na StarkNet com o Infura. 🥳
Você pode encontrar o código completo deste tutorial aqui: https://github.com/czar0/my-starknft-world.
Recursos
E por último, mas não menos importante, se você quiser se aprofundar no incrível mundo da StarkNet, aqui estão alguns recursos:
- Documentação da StarkNet
- Documentação do StarkNet.js
- Documentação do contrato Cairo de Conta do OpenZeppelin
- Documentação do contrato Cairo ERC721 do OpenZeppelin
Artigo original publicado por Infura. Traduzido por Paulinho Giovannini.
Oldest comments (0)