WEB3DEV

Cover image for Cunhe seu primeiro NFT usando o Infura e a StarkNet
Paulo Gio
Paulo Gio

Posted on

Cunhe seu primeiro NFT usando o Infura e a StarkNet

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:

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:

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>
Enter fullscreen mode Exit fullscreen mode

Anote isso, pois essa informação será útil mais adiante neste tutorial.

https://s3.amazonaws.com/infura-blog-content/2022/09/e3ca6bec-9c2f-4ae6-b724-19ee4e65918a.gif

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

https://s3.amazonaws.com/infura-blog-content/2022/09/Kapture-2022-09-15-at-01.11.51-1.gif

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
Enter fullscreen mode Exit fullscreen mode

Tudo em ordem, perfeito! Agora vamos criar um novo projeto do Node.

mkdir my-starknft-world
cd my-starknft-world
npm init -y
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
}
Enter fullscreen mode Exit fullscreen mode

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,
  },
});
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"));
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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>;
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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")
);
Enter fullscreen mode Exit fullscreen mode

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);
    })
  );
}
Enter fullscreen mode Exit fullscreen mode

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]`
);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
    )}`,
  },
});
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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);
      });
  });
}
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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}`);
Enter fullscreen mode Exit fullscreen mode

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 🥳");
Enter fullscreen mode Exit fullscreen mode

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:

Artigo original publicado por Infura. Traduzido por Paulinho Giovannini.

Top comments (0)