WEB3DEV

Cover image for Tutorial sobre contratos inteligentes NEAR – Hello World (Olá Mundo) com JS – Conjunto de tutoriais do protocolo NEAR
Fatima Lima
Fatima Lima

Posted on

Tutorial sobre contratos inteligentes NEAR – Hello World (Olá Mundo) com JS – Conjunto de tutoriais do protocolo NEAR

Neste artigo, examinaremos os elementos de um contrato inteligente NEAR baseado em JavaScript e também veremos como usar a interface de linha de comando (CLI) NEAR, uma ferramenta poderosa que permite que você interaja com a rede NEAR.

Introdução

Saudações, colegas BUIDLers; bem-vindos ao terceiro artigo do conjunto de tutoriais do protocolo NEAR:

Os artigos anteriores lhe deram uma visão geral detalhada do protocolo NEAR, seus recursos e, é claro, uma pequena ferramenta chamada NEAR explorer (explorador NEAR). Portanto, a esta altura, você está equipado com quase todo o conhecimento necessário para começar a criar seus próprios DApps NEAR. Bem, quase!

Uma parte fundamental do quebra-cabeça da criação de DApp que ainda não exploramos é a estruturação de um contrato inteligente NEAR. É claro que temos maneiras de configurar rapidamente a base de código para um DApp simples, mas estou falando de DApps complexos com toneladas de funcionalidades interessantes. Para criar um desses, você precisa entender os componentes de um contato inteligente NEAR e saber como usá-los. Além disso, criar e implantar um contrato inteligente é o primeiro passo para criar seus próprios DApps; depois disso, você precisa encontrar maneiras de interagir com o contrato.

Não se preocupe; este artigo aborda tudo isso e muito mais. Então, vamos começar.

Entendendo um Contrato Inteligente NEAR

Assim como seus correspondentes, os contratos inteligentes da NEAR são essencialmente programas executáveis armazenados na rede NEAR. Na NEAR, depois que você escreve um contrato, ele é compilado no WebAssembly e, a partir daí, é implantado em uma conta na rede NEAR. Quando a rede associa um contrato a uma conta, os usuários ficam livres para interagir com ele.

Uma das principais diferenças entre um contrato inteligente da Ethereum e um da NEAR é que você pode escrever contratos inteligentes da NEAR usando linguagens de programação de uso geral, como JavaScript, TypeScript, ou Rust.

Neste artigo, usaremos JavaScript puro para escrever um contrato inteligente NEAR.

Configurando o ambiente

Muito bem! Antes de começarmos a escrever os contratos, verifique se você tem as seguintes dependências instaladas em seu sistema.

Quando todos os requisitos estiverem instalados, poderemos usar o pacote create-near-app para configurar rapidamente um projeto NEAR. Para usar o pacote:

  1. Crie um novo diretório
  2. Abra um novo terminal no diretório
  3. Digite o seguinte comando:
npx **create**-near-app@latest
Enter fullscreen mode Exit fullscreen mode

O comando solicitará que você selecione um(a):

  • Linguagem preferida (TypeScript)
  • Template preferido (No front-end)

Você também pode selecionar a opção para instalar todos os pacotes NPM necessários.

Depois de tudo o que foi dito e feito, o pacote criará uma base de código devidamente estruturada. Complete com um contrato inteligente de amostra dentro do diretório _contract/src_.

Agora, o pacote, de acordo com nosso comando, gera um contrato inteligente escrito em TypeScript, mas como o artigo promete um contrato puramente baseado em JavaScript, você pode excluir o arquivo TypeScript dentro do diretório contract/src (um com a extensão .ts) e criar um novo arquivo JavaScript em seu lugar (contract.js).


Observe que o TypeScript é como um superconjunto do JavaScript, o que significa que qualquer código JavaScript válido também é um código TypeScript válido. No entanto, o TypeScript tem alguns recursos adicionais que não estão presentes no JavaScript. Portanto, se você for um aficionado por TypeScript, sinta-se à vontade para usar o TypeScript para o contrato.

Além disso, se você abrir o arquivo contract/build.sh, encontrará o comando para criar (compilar) nosso contrato e gerar o arquivo WebAssembly correspondente. Como estamos usando um arquivo JavaScript (.js) para este artigo, certifique-se de editar o comando e substituir<file_name>.ts pelo nome do seu arquivo JavaScript (contract.js, neste caso).


O shell script build.sh vem com o pacote create-near-app e ajuda você a criar seus contratos. Você também pode criar seus contratos executando manualmente o comando mencionado no shell script.

Ok, com isso, temos tudo o que precisamos para desenvolver nosso contrato inteligente. Portanto, vamos ao que interessa.

Escrevendo o Contrato Inteligente

Agora, escreveremos um contrato de votação (um pouco clichê, eu sei!). Nosso contrato permitirá aos usuários:

  • Adicionar novos candidatos
  • Votar
  • Visualizar a lista de candidatos
  • Visualizar o resultado

As importações

Para começar, abra o arquivo contract.js (dentro do diretório contract/src) em seu editor de código e adicione as seguintes linhas:


**import** {

  NearBindgen,

  call,

  view,

  near,

  UnorderedMap,

  initialize,

  **assert**,

} **from** "near-sdk-js";
Enter fullscreen mode Exit fullscreen mode

Essa é uma declaração de importação bastante simples que acrescenta algumas coisas úteis ao nosso contrato (explicaremos cada uma delas em breve). Como você pode ver, todas as entidades estão sendo importadas de um único pacote, o near-sdk-js. Esse pacote é instalado automaticamente quando geramos a base de código usando create-near-app.

Declaração de classe

Após a declaração de importação, podemos declarar a classe do nosso contrato:


@NearBindgen({})//decorador

export **class** **voteContract** {

    //escrever as funções aqui

}
Enter fullscreen mode Exit fullscreen mode

Na NEAR, um contrato é definido por meio de uma classe; dentro dessa classe, podemos adicionar todas as propriedades e funções necessárias. Com base no seu caso de uso, você pode até declarar outras classes "não relacionadas ao contrato" no mesmo arquivo, mas a classe que contém a lógica central do contrato deve ser precedida ou decorada com o NEAR Bindgen.

O decorador @NearBindgen({}) separa a classe do “contract” do resto e ajuda a gerar o código para:

  • Converter a classe em um contrato NEAR válido
  • Configurar a visibilidade das funções públicas
  • Serializar objetos para armazenamento interno e comunicação com atores externos

Configurando valores default (padrão)

Depois de declarar a classe, podemos começar a trabalhar nas funções para definir alguns valores default e inicialização de estado:


@NearBindgen({})

export **class** **voteContract** {

    **constructor**() {

    **this**.vote_map = new UnorderedMap("vote-map");

    **this**.owner_id = "";

  }

  @initialize({})

  **init**(_owner_id) {

    **this**.owner_id = _owner_id;

  }

}
Enter fullscreen mode Exit fullscreen mode

A função constructor(), como de costume, ajuda a definir os valores default para os atributos de classe (aqueles mencionados usando a palavra-chave this) na implantação do contrato. Aqui, estamos usando-a para criar um novo atributo de classe chamado vote_map, que contém um UnorderedMap vazio, e também para criar owner_id, um atributo de classe para armazenar o ID da conta do usuário que implantou o contrato.

O UnorderedMap funciona exatamente como um dicionário ou mapeamento Python no Solidity. Ele ajuda a modelar os dados como pares de valores-chave. Aqui, usaremos isso para mapear o nome dos candidatos para os votos correspondentes que eles recebem.

Na NEAR, estruturas complexas como UnorderedMap devem ser inicializadas usando um prefixo específico (vote-map, em nosso caso). Esse prefixo é usado para identificar as chaves da estrutura no estado serializado.


Além do UnorderedMap, o SDK NEAR fornece um conjunto de outras estruturas de dados complexas que os desenvolvedores podem usar ao escrever contratos inteligentes. Para saber mais sobre essas estruturas de dados, você pode consultar os documentos oficiais da NEAR.

Depois da função constructor(), declaramos uma função de inicialização, init(). O objetivo dessa função é ajudar a definir os valores do estado inicial. A função de inicialização deve ser decorada usando a macro de initialization—@initialize({}).

Aqui, a função init()é usada para definir o ID da conta do usuário que está implantando o contrato no atributo de classe this.owner_id. Passaremos o ID da conta do usuário para a função init() quando implantarmos o contrato.

Por padrão, as funções de inicialização são funções públicas, o que significa que podem ser chamadas por qualquer pessoa. De acordo com as práticas de segurança, é melhor declará-las como uma função privada ou uma função que só pode ser chamada pela conta que hospeda o contrato (a conta do contrato). Para fazer isso, precisamos modificar o decorador @initialize({}):


@NearBindgen({})

export **class** **voteContract** {

    **constructor**() {

    **this**.vote_map = new UnorderedMap("vote-map");

    **this**.owner_id = "";

  }

  @initialize({privateFunction: true}) // função privada

  **init**(_owner_id) {

    **this**.owner_id = _owner_id;

  }

}
Enter fullscreen mode Exit fullscreen mode

No contrato, você também pode tornar obrigatória a função de inicialização. Isso evitará que outras funções de "alteração de estado" sejam executadas antes de você inicializar o estado. Para isso, você precisa modificar o decorador @NearBindgen({}) e adicionar o parâmetro requireInit:


@NearBindgen({requireInit: true})

**export** **class** **voteContract** {

    **constructor**() {

    this.vote_map = **new** **UnorderedMap**("vote-map");

    this.owner_id = "";

  }

  @initialize({privateFunction: true})

  **init**(_owner_id) {

    this.owner_id = _owner_id;

  }

}
Enter fullscreen mode Exit fullscreen mode

Adicionando as funções

  • Depois de definirmos os valores default, podemos trabalhar no restante das funções. Nos contratos inteligentes NEAR, além do que acabamos de ver (constructor(), init()), há dois outros tipos de funções, que são:
  • Funções de chamada
  • Funções de visualização

Uma função de chamada é o que você chama de função de "alteração de estado". Essas funções contêm o código para manipular o estado. Essas funções também podem chamar outros contratos. Uma função de visualização só pode ler os dados de estado e não pode causar nenhuma alteração neles. O acesso a essas funções é gratuito para todos e os usuários podem chamar uma função de visualização sem precisar de uma conta NEAR.

As funções de chamada e visualização são decoradas usando os decoradores @call({}) e @view({}), respectivamente.

Portanto, conforme discutido, vamos escrever as funções para adicionar e visualizar candidatos, votar e visualizar os resultados:


//adicionar novos candidatos

  @call({})

  addCandidate({ _candidate_name }) {

    //obter o ID da conta do usuário que chamou essa função

    let caller_id = near.predecessorAccountId();

    //garantir que somente o proprietário possa adicionar novos candidatos

    assert(caller_id == **this**.owner_id,"Only owner can add candidates")

    //adicionar o nome do candidato ao mapa,

    //juntamente com o número de votos padrão (o)

    **this**.vote_map.**set**(_candidate_name,0);

  }



  //visualizar a lista de candidatos

  @view({})

  listCandidates({ }) {

    //obter as chaves (nomes dos candidatos) do unorderedmap 

    **return** **this**.vote_map.keys.toArray();

  }





  @call({})

  castVote({_candidate_name}) {

    //obter o número de votos obtidos pelo candidato

    //e atualizá-lo

    let currentVote = **this**.vote_map.**get**(_candidate_name) + 1;

    //incrementar a votação e adicioná-la ao mapa

    **this**.vote_map.**set**(_candidate_name,currentVote);

  }

  @view({})

  showResult(){

    //converter o mapa em um array e retorná-lo

    **return** **this**.vote_map.toArray();

  }
Enter fullscreen mode Exit fullscreen mode

Aqui, você pode ver que as funções addCandidate() e castVote() são decoradas com @call({}), enquanto as outras são decoradas com @view({}). Isso basicamente indica a natureza dessas funções, ou seja, se elas são uma função de chamada ou uma função de visualização.

A função addCandidate() recebe o nome do candidato como parâmetro (_candidate_name) e, usando a instrução assert(), certifica-se de que o ID da conta da pessoa que está chamando a função (caller_id) e o ID da conta da pessoa que implantou o contrato (this.owner_id) são os mesmos. Se não forem iguais, a função lançará um erro e interromperá sua execução. A ideia é evitar que qualquer pessoa que não seja o proprietário do contrato chame essa função.

A função castVote() usa o nome do candidato como parâmetro e obtém o número de votos (usando a função get(<key>)) que é armazenado em relação ao nome do candidato em nosso UnorderedMap. O valor é então atualizado e adicionado novamente ao UnorderedMap usando a função set(<key>,<value>). As funções get() e set() são parte da estrutura do mapa.

A função listCandidates() é uma função de exibição que obtém as chaves (nomes dos candidatos) de nosso UnorderedMap e as retorna. A função showResult() converte o UnorderedMap em uma lista de listas (contendo o nome e os votos do candidato) e as retorna.

Depois de adicionar todas as funções, o contrato inteligente concluído deverá ter a seguinte aparência:


**import** {

  NearBindgen,

  call,

  view,

  near,

  UnorderedMap,

  initialize,

  assert,

} from "near-sdk-js";

@NearBindgen({requireInit: true}) / configurar a inicialização mandatória

export **class** **voteContract** {

  **constructor**() {

    // criando um mapa para rastrear os candidatos 

    // e o número de votos que eles recebem

    **this**.vote_map = new UnorderedMap("vote-map");

    //criar uma variável para armazenar o ID da conta do proprietário do contato

    **this**.owner_id = "";

  }



  // inicializar o ID da conta do proprietário do contrato

  @initialize({privateFunction: true})

  **init**({_owner_id}) {

    **this**.owner_id = _owner_id;

  }

  //adicionar novos candidatos

  @call({})

  addCandidate({ _candidate_name }) {

    //obter o ID da conta do usuário que chamou essa função

    let caller_id = near.predecessorAccountId();

    //garantir que somente o proprietário possa adicionar novos candidatos

    assert(caller_id == **this**.owner_id,"Only owner can add candidates")

    //adicionar o nome dos candidatos ao mapa,

    //juntamente com o número de votos padrão (o)

    **this**.vote_map.**set**(_candidate_name,0);

  }



  //visualizar a lista de candidatos

  @view({})

  listCandidates({ }) {

    //obter as chaves (nomes dos candidatos) do unorderedmap

    **return** **this**.vote_map.keys.toArray();

  }





  @call({})

  castVote({_candidate_name}) {

    //obter o número de votos obtidos por um candidato

    //e atualizá-lo

    let currentVote = **this**.vote_map.**get**(_candidate_name) + 1;

    //incrementar a votação e adicioná-la ao mapa

    **this**.vote_map.**set**(_candidate_name,currentVote);

  }

  @view({})

  showResult(){

    //converter o mapa em um array e retorná-lo

    **return** **this**.vote_map.toArray();

  }

}

Enter fullscreen mode Exit fullscreen mode

Para implantar o contrato na rede, você deve criá-lo (compilar) e criar o arquivo WebAssembly correspondente. Para compilar seu contrato, abra um terminal no diretório raiz de seu projeto e use o seguinte comando:

npm run build
Enter fullscreen mode Exit fullscreen mode

Isso compilará seu contrato e armazenará todos os arquivos de saída no diretório contract/build. Dentro do diretório, você encontrará o arquivo WebAssembly correspondente do seu contrato (<project-name>.wasm), juntamente com outros artefatos.

Portanto, agora que temos um contrato compilado, vamos implantá-lo.

Implantação do contrato inteligente

Para implantar o contrato, usaremos a ferramenta CLI da NEAR. Essa ferramenta permite aos usuários:

  • Acessar contas
  • Implementar e interagir com contratos

Essa ferramenta é instalada automaticamente pelo pacote create-near-app e você pode usá-la abrindo um terminal no diretório raiz do projeto e digitando o seguinte comando:

npx near
Enter fullscreen mode Exit fullscreen mode

Isso exibirá todos os comandos da CLI da NEAR disponíveis e seu uso:

Image description

Comandos e Uso da CLI da NEAR

Você pode instalar a ferramenta CLI da NEAR globalmente em seu sistema usando o seguinte:
npm install -g near-cl
Enter fullscreen mode Exit fullscreen mode
Isso permitirá que você execute a ferramenta diretamente de qualquer lugar do seu sistema sem o prefixo npx. Neste artigo, estamos usando e executando o pacote CLI da NEAR de dentro do diretório do projeto, por isso o prefixo npx.

Para implantar um contrato inteligente na rede NEAR, você também precisará de uma conta NEAR válida (uma conta da rede de teste NEAR será suficiente).

Você pode usar o tutorial anterior para aprender como configurar uma conta NEAR.

Acessando a conta

Uma vez que você tenha a ferramenta CLI e a conta, a primeira coisa que você precisa fazer é entrar na sua conta. Você pode usar o seguinte comando da CLI da NEAR para acessar sua conta da rede de teste:

npx near login
Enter fullscreen mode Exit fullscreen mode

Isso abrirá automaticamente uma página no seu navegador que solicitará que você selecione a conta que deseja acessar:

Image description

Acessando sua conta da rede de teste NEAR

 

Depois que você autorizar o acesso à conta, a ferramenta CLI fará login na sua conta.

Implantar o contrato

Agora, podemos implantar o contrato usando o seguinte comando:


npx near deploy --wasmFile contract/build/<**file-name**>.wasm --accountId <**your-account-id**> --initFunction init --initArgs '{"_owner_id":"<**account-id**>"}'
Enter fullscreen mode Exit fullscreen mode

O comando recebe o caminho para o arquivo WebAssembly (.wasm), o ID da conta do usuário, o nome da função de inicialização (init(), no nosso caso) e o valor do parâmetro para a função init(). Depois de implantar o contrato e inicializá-lo com o valor, o comando emitirá o ID da transação.


Como estamos executando os comandos da CLI de dentro do diretório do projeto, o caminho para o arquivo do WebAssembly é mantido em relação ao diretório. Se estiver executando-o de qualquer outro local, certifique-se de fornecer o caminho correto (absoluto) para o arquivo do WebAssembly.

Depois que o contrato é implantado, podemos usar a ferramenta CLI e o ID do contrato (o ID da conta do contrato) para interagir com ele.

Veja como você pode chamar a função addCandidates() usando a CLI da NEAR:

npx near call <contract-id> addCandidate '{"_candidate_name":"alice"}' --accountId <user-account-id>
Enter fullscreen mode Exit fullscreen mode

Aqui, o comando recebe o ID do contrato (ID da conta do contrato), o nome da função, os valores dos parâmetros necessários e o ID da conta do usuário que invoca a função. Como addCandidate() é uma função de chamada, temos de usar a palavra-chave call no início do comando.

Para visualizar a lista de candidatos, podemos usar o seguinte comando:


npx near view <**contract-id**> listCandidates
Enter fullscreen mode Exit fullscreen mode

Como você pode ver, com funções de visualização como listCandidates(), não é necessário adicionar o ID da conta do usuário que a invoca, pois essas funções são de acesso livre para todos.

Todas as outras funções de chamada e exibição no contrato seguem o mesmo padrão de comando.

E, com isso, criamos, implantamos e interagimos com sucesso com um contrato inteligente NEAR baseado em JavaScript.

Conclusão

O objetivo deste artigo é fornecer uma visão geral dos vários elementos envolvidos em um contrato inteligente NEAR e mostrar como usá-los para criar contratos complexos. Nos próximos tutoriais, exploraremos ainda mais os contratos inteligentes NEAR e escreveremos scripts reais para testar, implantar e interagir com os contratos, e faremos tudo isso em seu próprio nó NEAR, hospedado na plataforma Chainstack.

Você já explorou o que pode conseguir com a Chainstack? Comece a usar gratuitamente hoje mesmo.

Esse artigo foi escrito por Sethu Raman Omanakuttan e traduzido por Fátima Lima. O original pode ser lido aqui.

Latest comments (0)