NEAR Revisão de Contrato ao Vivo | Parte 3: Whitelist e Fábrica de Staking Pool

20 min read
To Share and +4 nLEARNs

Introdução

Olá pessoal. Hoje vamos revisar 2 contratos em vez de 1. Incluiremos contratos que envolvem chamadas de contratos cruzados e falaremos sobre promessas e como elas funcionam na NEAR. Se você quiser saber mais sobre como funciona a composição, esta é uma boa sessão para ler. O primeiro contrato que veremos é chamado de contrato de lista de permissões e é usado na rede principal para colocar pools de staking na lista de permissões. Isso é importante porque a distribuição de tokens é feita por meio de contratos inteligentes. Usamos contratos de bloqueio que eventualmente revisaremos nesta série, mas a maneira como funciona é que o bloqueio é um contrato autônomo que contém tokens que devem ser liberados durante um período de tempo. Digamos que os tokens sejam lançados ao longo de 2 anos e sejam alocados linearmente a cada bloco. O que queríamos fazer é dar a capacidade de stake desses tokens, incluindo os tokens que ainda não foram lançados. Isso significa que você deve poder delegar todos os tokens que bloqueou por 2 anos, por exemplo, e começar a ganhar recompensas com eles. Isso é feito em um contrato inteligente, e um contrato de bloqueio está essencialmente chamando o contrato de staking pool que analisamos antes e transferindo tokens de um contrato para outro. Os tokens do contrato de bloqueio saem da conta e vão para a conta do staking pool. Se um staking pool não fornecer as garantias necessárias, como a capacidade de retornar esses tokens, isso significa que haverá problemas. Digamos que eu construa um staking pool personalizado que me permite não apenas fazer stake dos tokens, mas também retirá-los para qualquer conta. Esse tipo de operação permitirá que você obtenha ativos líquidos antes do término do período de liberação. Então você poderá se retirar, e esse não é o comportamento desejado.

É por isso que introduzimos a whitelist onde implementações personalizadas dos staking pools aprovados pela NEAR Foundation podem ser usadas por contratos de bloqueio. Ao mesmo tempo, queríamos dar a capacidade de criar novos staking pools que já fossem aprovados por qualquer pessoa sem passar pela aprovação da NEAR Foundation. Isso permite que qualquer pessoa crie um novo staking pool por meio de uma Staking Pool Factory (Fábrica de Staking Pool). A fábrica de staking pool é o segundo contrato que analisaremos hoje. A maneira como funciona é quando um contrato de lockup deseja delegar, antes que eles possam transferir fundos para este contrato, eles primeiro precisam selecionar um staking pool. Quando você seleciona o staking pool, o lockup emite uma transação para verificar se um determinado ID de conta está na lista de permissões em um contrato de lista de permissões e se retornar verdadeiro, o que significa que a conta está na lista de permissões, o contrato de bloqueio pode continuar com delegação. 

Ele permite que o lock-up seja realmente transferido para este contrato. Isso significa que o contrato de staking pool tem algumas garantias e APIs que o contrato local espera e não bloqueará os tokens do proprietário ou roubará tokens do contrato de bloqueio. Isso também foi importante para os cronogramas de investimento dos funcionários da NEAR. Foi em um cronograma de investimento de quatro anos, e isso permite que a Fundação emita uma transação específica para o bloqueio dessa pessoa, a fim de liberar tudo do staking pool e devolver o valor investido de volta à fundação NEAR no caso de um funcionário sair o trabalho ou for demitido. Esse é o pano de fundo sobre bloqueios e o contrato da lista de permissões.

O repositório original desses contratos pode ser encontrado no NEAR Github. Aqui está o vídeo original no qual este guia se baseia:

Contrato de lista de permissões

Estrutura principal

Vejamos o contrato da lista de permissões. Na verdade, é um contrato bastante simples, e não tem muita lógica, já sabemos a maioria das coisas.

Ele usa uma API NEAR chamada LookupSet. É semelhante a um conjunto não ordenado. É uma coleção persistente, mas não possui iteradores, então você não pode iterar sobre as chaves dos elementos do conjunto. Você só pode verificar se um determinado elemento está presente ou não, e pode adicioná-lo ao conjunto. Você não pode verificar quais elementos estão presentes neste conjunto. Ao fazer isso, melhora a eficiência do armazenamento e o acesso de várias leituras a algumas leituras. O contrato contém alguns campos. O primeiro é o foundation_account_id. Este é o ID da conta que controla a lista de permissões. Isso significa que essa conta pode colocar staking pools na lista de permissões por 1 e também pode listar fábricas de staking pool. 

A fábrica é um contrato que pode criar uma nova instância de staking pool. A maneira como funciona é quando você emite uma transação para a fábrica de staking que está na lista de permissões por este contrato, ela cria uma nova conta como uma subconta da fábrica. No nosso caso na rede principal é chamado poolv1.near que é a fábrica de staking pool que usamos. Ele cria um contrato, por exemplo, bisontrails.poolv1.near, no qual implanta o código de pool de staking na lista de permissões que essa fábrica pode produzir. Entraremos na fábrica de staking pool mais tarde, mas, ao mesmo tempo, ela também pode colocar na whitelist esse staking pool. Aqui está como funciona. A inicialização do contrato aceita apenas um argumento foundation_account_id. Uma conta de base tem permissões mais altas neste contrato.

Getters

Há um monte de getters.

Você pode verificar se uma determinada entrada de stake está na lista de permissões. É assim que um contrato de bloqueio verifica se o pool está na lista de permissões. Basicamente, apenas verifica se um elemento está presente em um conjunto. A segunda maneira que ele também pode verificar é se a fábrica está na lista branca, então isso não é realmente necessário e ninguém a chama.

Adicionar método de staking pool

Este é um método que pode ser chamado tanto por uma fábrica quanto pela fundação. O que fazemos aqui é verificar se esse método de staking adiciona um novo staking_pool_account_id a uma lista de permissões. Ele verifica se o id da conta é válido e, se passar, verificamos se é uma fábrica. Verificamos em um conjunto que o chamador deste método está presente na lista de permissões das fábricas. Se for chamado pela fábrica de staking pool, tudo bem. Caso contrário, deve ser um ID de conta de fundação, caso contrário, o contrato falhará e esse método entrará em pânico. 

Se passarmos na verificação de permissão, apenas adicionamos esse staking pool à lista de permissões. No momento, temos apenas uma implementação para o staking pool, mas em teoria podemos modificar essa implementação quando introduzimos o slashing, por exemplo, e um staking pool precisa ter a permissão necessária. Ele precisa manter algum saldo mínimo. Há alguma outra mudança em que precisamos modificar um contrato validado durante o período de investimento de todos é de 4 anos. Muitas coisas podem acontecer na rede e precisamos ter a capacidade de alterar potencialmente os staking pools. Se a lógica do staking pool mudar, por exemplo. 

Ele permite a criação de uma nova fábrica que é uma versão melhor ou suporta algo que não era suportado antes. Ele não permite alterar uma taxa de recompensa instantaneamente, mas apenas permite que ela seja alterada após um período de espera de 7 dias, ou alguma outra ideia para uma modificação que seja um fator diferente.

O próximo remove_staking_pool só pode ser chamado pela fundação, de modo que um staking pool só pode ser eliminado pela fundação.

Além disso, add_factory também pode ser chamado apenas pela fundação. É básico, apenas adiciona a uma lista branca das contas de fábrica.

Finalmente, remove_factory também só pode ser chamado pela fundação e remove o factory_account_id. Digamos que a primeira fábrica expire, então a fundação pode basicamente remover a fábrica da lista de permissões, bem como remover todos os pools anteriores da lista de permissões. Agora, você não poderá selecionar uma das pesquisas de staking anteriores de um contrato de bloqueio e, finalmente, há uma verificação de que isso é chamado pela fundação, que é uma comparação. Este é um contrato muito simples, e ele opera apenas em conjuntos internos e o único método verdadeiro que é visível por fora é is_whitelisted. É bastante simples, é apenas um monte de setters e getters.

Imutabilidade dos Contratos Inteligentes

A maneira como os contratos inteligentes geralmente se comportam, o que é verdade no Ethereum, e em outras plataformas, é que eles são imutáveis ​​em si mesmos. No Ethereum, todo interior de contrato é imutável, mas eles usam um contrato de proxy que permite atualizar o endpoint para determinados contratos que são muito críticos para tokens. Nesse caso, nossos contratos principais são essencialmente completamente imutáveis, e pensamos neles como se fôssemos implantá-los uma vez e provavelmente não pudéssemos alterá-los, porque senão você terá que fazer um hard fork e convencer todos os validadores a fazer algum tipo de migração de código. É importante, porque o controle sobre eles deve ser feito no nível do contrato e não em algumas entidades centralizadas. Por exemplo, enquanto a fundação ainda mantém grande controle sobre os staking pools tendo a capacidade de remover staking pools aqui, ele não tem o controle para colocar uma entidade específica no mundo real na lista negra de implantar um staking pool. 

Eles ainda podem criar um staking pool com o máximo de anonimato possível e criar um staking pool sem pedir permissão para se tornar um validador na rede principal. Algumas coisas vêm da descentralização, outras vêm da limitação do controle. Embora a fundação deva dar suporte à rede, é possível que, em alguns cenários, a fundação seja forçada a fazer algo ruim para a rede. Digamos que o governo venha e tente forçá-los. Se eles tiverem menos capacidade de fazer isso, haverá maior segurança na rede. Quando desenhamos um contrato, pensamos: “Qual é a quantidade de valor neste contrato?” ou “Qual é a capacidade deste contrato de influenciar a rede em geral?”. Se for um valor pequeno, então não há problema em manter a credibilidade contanto que a comunidade confie, se for um valor grande, então não está certo. Quando chegarmos ao contrato de bloqueio, e da maneira como ele foi projetado, você pode ver como, por exemplo, a aquisição de direitos foi projetada para, por um lado, permitir que a fundação retire os fundos restantes, mas ao mesmo tempo impedem que a fundação retire os fundos investidos. 

É uma maneira legal de fazer as coisas, exceto que está escrito no código. A lista branca é um contrato muito crítico, porque antes que os fundos sejam bloqueados, a maioria dos fundos está controlando a rede por meio de contratos locais em staking pools por meio dessa lista branca específica, por isso era importante projetá-la de forma a manter a descentralização, e segurança da rede sem dar controle à fundação. Digamos que algo aconteceu, e a fundação começou a agir maliciosamente. Digamos que você conseguiu criar um novo staking pool por meio de uma fábrica e delegar para o staking pool, agora a fundação não pode realmente impedir que você delegue para esse staking pool.

Contrato Staking Pool Factory

Incluir Macro Bytes

O contrato staking_pool_factory é um contrato que possui internamente o código de um contrato de staking pool. No Rust você pode fazer isso usando a macro include_bytes. Ele basicamente pega um arquivo local e o incorpora no binário como um vetor. O que acontece é que dentro desse binário do WebAssembly podemos ter algum pedaço de memória alocado que representa um binário desse staking pool específico. Vamos voltar ao topo.

Estrutura

Mais uma vez esta é a estrutura.

Havia algumas informações sobre o gás, voltaremos a isso mais tarde.

Há uma reward_fee_fraction que acabou de ser copiado do contrato de staking pool que analisamos antes.

Contratos Externos

Existem os argumentos do staking pool, e há características, e também contratos externos, então essa é a interface de alto nível que usamos para chamar certos contratos.

Temos dois deles, o primeiro pode ser qualquer nome. Nós o chamamos de ExtSelf, porque ele representa nosso próprio contrato e contém um retorno de chamada para um método on_staking_pool_create. A segunda característica é para o contrato whitelist que acabamos de ver chamado add_staking_pool. Aqui está.

É exatamente a mesma interface, exceto que as características em Rust são como interfaces em java, por exemplo. Acabamos de definir a interface de um contrato remoto.

Inicialização

Vamos primeiro pelo cenário. Quando uma fábrica é criada, verificamos que ela não foi inicializada e verificamos o staking_pool_whitelist_account_id, o ID da conta do contrato da lista de permissões, durante a inicialização do StakingPoolFactory. Precisamos saber o staking_pool_whitelist_account_id. É aqui que o contrato de lista de permissões é implantado para colocar na lista de permissões nossas instâncias de pool de staking recém-criadas. Lembramos disso e também criamos um conjunto de contas já criadas no final deste trecho.

Método principal

Agora que a fábrica de staking pool foi criada, ela é chamada poolv1.near, por exemplo, e a fundação colocou a fábrica na lista de permissões em um contrato de lista de permissões emitindo outra transação. Agora que esta fábrica de staking pool está na lista de permissões, significa que ela tem permissão para listar os novos staking pools que ela cria. Então agora um validador entra e eles querem criar um staking pool para si mesmos. A maneira como funciona é que eles chamam create_staking_pool, e são necessários vários argumentos. O primeiro argumento é um prefixo. Digamos que seja bisontrails sem o sufixo deste id de conta atual, e isso vem dos nomes das contas NEAR

Uma conta só pode criar uma subconta, ou uma conta muito longa, então a fábrica de staking pool cria uma subconta sob si mesma que será bisontrails.poolv1.near. O owner_id é o id da conta do proprietário do staking pool, conforme discutimos anteriormente. Todos esses três itens são essencialmente argumentos que você passaria para um staking pool ao criá-lo pela primeira vez. É um argumento que você pode fazer proxy para o staking pool. Por exemplo, staking_pool_id pode ser bisontrails.near. O stake_public_key pode ser a chave de staking da execução de um nó validador e reward_fee_fraction pode ser 10%, por exemplo. Observe que este método também é pagável, significa que leva um depósito anexado de entrada, e a primeira coisa que pergunta é: “Você anexou depósito suficiente?” O depósito que você precisa anexar é 30 NEAR, e isso tem muitos zeros, mas isso é porque está em yocto NEAR. Você precisa anexar 30 NEAR principalmente porque precisa cobrir o estado do próprio contrato em um staking pool recém-criado. O contrato é bastante grande, são 250 kilobytes e você precisa de pelo menos 25 NEAR para isso, mas também é preciso algum dinheiro extra para cobrir o fundo de garantia de preço. 

Este é um daqueles casos em que você precisa ter um depósito anexado, porque você não pode anexar tanto gás a essa transação. Além disso, não podemos converter gás para NEAR dentro de um contrato, então, idealmente, a separação de gás permanece a mesma, é usada apenas para computação, algumas operações de leitura/gravação e chamadas de contrato cruzado. O saldo é usado para armazenamento e transferências de estado. Portanto, no nosso caso, isso criará uma nova conta, e criar uma nova conta no NEAR exige que você pague pelo armazenamento dessa conta. O armazenamento, no nosso caso, não será apenas a conta em si, mas também o contrato dessa conta. No nosso caso, este é o código do contrato de staking pool.

A próxima coisa que fazemos é verificar se o prefixo não tem um ponto, o que significa que não é uma subconta em si. Em seguida, criamos um novo staking_pool_account_id concatenando nosso ponto de identificação de conta (.) esse novo prefixo. Verificamos se o novo ID da conta do staking pool é válido. Basicamente, se qualquer uma dessas declarações falhar no protocolo NEAR devolverá os tokens. Se uma transação falhar com um depósito anexado, o depósito anexado retornará ao remetente ou predecessor, porque somente o predecessor pode anexar um saldo. É seguro fazer como um monte de afirmações aqui. Em seguida, verificamos se o owner_id do staking pool é válido. Isso é basicamente apenas um monte de ajudantes extras que também são verificados no staking pool. 

Isso é para ter certeza de que se você não passar os argumentos corretos, ou argumentos um pouco incorretos, é melhor você falhar antes que tudo seja executado para evitar queimar gás e travar os tokens que você gastou. Finalmente, verificamos usando insert que o staking pool não existe. Basicamente, insert retornará verdadeiro se for um novo elemento exclusivo e retornará falso se o elemento já existir em um conjunto. É assim que o hashset Rust funciona da mesma forma que um conjunto ordenado funciona. Portanto, se o nome do pool já existir, não adicionaremos esse staking pool ou tentaremos criar essa conta novamente. Insert faz duas coisas: adiciona esse elemento ao armazenamento, além de retornar verdadeiro se o elemento for exclusivo e não existir antes ou retornar false se o elemento já estiver presente.  Se o conjunto não tinha este valor presente verdadeiro é retornado, se este conjunto tinha este valor presente falso é retornado.

Por fim, usamos uma API de nível médio, não usamos nossos métodos de custo bruto, mas ao mesmo tempo não usamos uma interface de alto nível. A forma como funciona é que criamos uma nova promessa, que cria uma estrutura temporária em nosso NEAR SDK e lembra o receptor dessa promessa. Você pode pensar nisso como se o contrato fosse emitir a transação para esse determinado id de conta. Chamaremos um id de conta de staking pool inexistente. Claro que não é uma transação e sim um recibo, mas é um detalhe técnico. A próxima coisa é a primeira ação em cima dessa promessa. Começamos a agrupar ações nessa promessa. A primeira ação é o create_account. Esta ação vai criar uma nova conta ou ela falhará se a conta já existir. Em seguida, depositamos o saldo em anexo. Depositamos todo o depósito que nos foi repassado, para que não fiquemos com ele nesta fábrica, e ele irá com o mesmo recibo na conta remota. 

Em seguida, implantamos um contrato. Como explicado anteriormente, include_bytes é uma macro que cria uma fatia estática que convertemos em um vetor que passará para uma ação de implantação. Isso implantará o código na conta remota. Você só pode implantar código na conta que controla, mas create_account dá a você permissão para agir como se fosse o proprietário dessa conta apenas para essa transação específica. Você pode usar o método deploy_contract, você pode fazer stake e fazer outras coisas em nome deste contrato na primeira transação que você fizer. Por fim, inicializamos o contrato do staking pool usando a API serda. Pegamos essa estrutura e serializamos isso em JSON, o método é chamado de novo. O primeiro argumento é o depósito anexado a esta chamada de função. Não precisamos dele, porque ele não espera. A próxima é a quantidade de gás que você retira da sua quantidade atual de gás e imediatamente a retira, após o que vai para a promessa. Digamos que nosso método foi chamado de 100 tera gás, tera gás é um tipo de unidade que é mais ou menos compreensível para humanos.

Você tem 100 tera de gás quando entra e dizemos que vamos passar a base (25 tera de gás) multiplicada por 2. Passaremos imediatamente 50 tera de gás para a chamada de função deste método, então esse gás de 50 tera significa que ficamos com menos de 50 tera de gás, pois já queimamos alguns na lógica antes. Além disso, cada ação que você incluir nessa promessa também lhe custará um pouco de gas. Por exemplo, uma ação de implantação custará a você transferir os bytes de uma conta para outra. Finalmente usamos Então (then). Then é semelhante a como o javascript funciona, ele anexa a dependência da promessa anterior. Esta é a primeira promessa, e dizemos que, uma vez concluída, faça a segunda promessa. 

A maneira como funciona é você, digamos bisontrails.near ter chamado este contrato (poolv1.near) para criar bisontrails.poolv1.near. Primeiro, criamos uma promessa para bisontrails.poolv1.near e, em seguida, anexamos um retorno de chamada a essa API, o que não é ótimo em termos de usar argumentos posicionais para duas coisas diferentes. De qualquer forma, ele chama de volta o ID da conta atual. Portanto, ele chamará poolv1.near após a execução da primeira promessa. Aqui está a estrutura: bisontrails.near chama poolv1.near criando uma promessa de staking pool. Agora isso cria uma promessa para bisontrails.poolv1.near, e também cria uma promessa para si mesmo no método on_staking_pool.

Ele precisa do resultado deste método antes que este método seja iniciado e passa três argumentos aqui. Ele passa o staking_pool_account_id, o attach_deposit e o predecessor_account_id. Então foi quem nos ligou, qual conta tentou ser criada e quantos tokens foram anexados caso precisemos fazer um reembolso. Agora, se bisontrails.poolv1.near for executado com sucesso, on_staking_pool_create receberá o resultado da execução. Digamos que houve alguma configuração incorreta de que esse método também será chamado, mas receberá um retorno de chamada dizendo que falhou. Retornamos a promessa principal depois, significa que retornamos on_staing_pool_create primeiro. É importante, porque o resultado do método create_staking_pool depende do resultado da promessa on_staking_pool_create. A transação não começa completamente em paralelo,

Chamada de Retorno

Vejamos a chamada de retorno.

A primeira coisa que fazemos é dizer que ele só pode ser chamado pelo contrato atual usando assert_self, o que significa que ninguém mais pode chamar essa promessa.

A próxima coisa que fazemos é usar outro método auxiliar de utilitário que diz se a dependência, a criação do staking pool, foi bem-sucedida ou falhou.

Fazemos isso da seguinte maneira: primeiro usamos dois métodos end para verificar se o número de resultados é 1. Isso é uma invariante porque já sabemos que ninguém pode chamar isso duas vezes, e o segundo é se o resultado for sucesso. Se o método foi executado com sucesso, retornamos verdadeiro se a promessa falhou, então será falso. Então agora sabemos se o staking pool foi criado ou não. Novamente, também anexamos 50 tera de gás ao retorno de chamada, então agora estamos no retorno de chamada, o que significa que temos apenas 50 tera de gás.

Se formos bem-sucedidos, registraremos que o pool foi criado com êxito e, em seguida, colocaremos na lista de permissões. Em seguida, chamamos outra promessa de um retorno de chamada e anexamos 25 tera de gás. Então agora chamamos staking_pool_whitelist_account_id, o contrato da lista de permissões. Agora, podemos colocar na lista de permissões o staking pool que acabamos de criar, porque passamos esse argumento para o retorno de chamada. Retornamos essa promessa para não interromper a execução ainda, porque só queremos concluir a transação inteira quando a lista branca for concluída. Rust não tem retorno, porque se o último valor sem ponto e vírgula for fornecido, então é um valor de retorno. Se a criação falhar, ela pode falhar por apenas um motivo: se você colocar uma chave restrita inválida como discutimos brevemente antes. Se houver alguma curva estranha na qual você criou sua chave de staking, ela falhará. 

 

A razão pela qual ele falha é que significa que o depósito que você passou para o staking pool para criação será reembolsado para nós, não para o antecessor. Temos 30 NEAR neste contrato e precisamos devolvê-los ao remetente para não bloqueá-los. A primeira coisa que fazemos é removê-lo da lista de staking pools que foram criados, porque isso não foi bem-sucedido. Estamos dizendo que a criação falhou e reembolsaremos o depósito anexado. Agora não é um depósito anexado real, porque o retorno de chamada não recebe o depósito anexado. Ele passa por um recibo de reembolso separado que geralmente chega antes do retorno de chamada e também recebe o predecessor_account_id. No nosso caso, se chamarmos o predecessor_account_id, seremos nós porque este é um retorno de chamada. Nós precisamos saber para quem devemos devolver os tokens e a maneira que fazemos é devolvê-los ao remetente para não bloqueá-los. 

A primeira coisa que fazemos é removê-lo da lista de staking pools que foram criados, porque isso não foi bem-sucedido. Estamos dizendo que a criação falhou e reembolsaremos o depósito anexado. 

 

Agora não é um depósito anexado real, porque o retorno de chamada não recebe o depósito anexado. Ele passa por um recibo de reembolso separado que geralmente chega antes do retorno de chamada e também recebe o predecessor_account_id. No nosso caso, se chamarmos o predecessor_account_id, seremos nós porque este é um retorno de chamada. Nós e precisamos devolvê-los ao remetente para não bloqueá-los. A primeira coisa que fazemos é removê-lo da lista de staking pools que foram criados, porque isso não foi bem-sucedido. Estamos dizendo que a criação falhou e reembolsaremos o depósito anexado. Agora não é um depósito anexado real, porque o retorno de chamada não recebe o depósito anexado. Ele passa por um recibo de reembolso separado que geralmente chega antes do retorno de chamada e também recebe o predecessor_account_id. No nosso caso, se chamarmos o predecessor_account_id, seremos nós porque este é um retorno de chamada. Nós e reembolsaremos o depósito em anexo. 

Como você pode ver, não retornamos essa promessa, apenas dizemos que é isso, não nos importamos se ela for bem-sucedida ou falhar, o retorno básico do valor false está dizendo que o pool falhou ao ser criado. O que acontece agora a transação continua sendo executada mas o valor será devolvido ao front end. O front-end é o CLI NEAR. Você saberá imediatamente que a transação falhou. Você pode não receber seu dinheiro de volta ainda, então você ainda está esperando que esse reembolso específico seja executado no próximo bloco, mas você já sabe que o pool não foi criado para que você possa continuar. Este é um exemplo de como você pode fazer uma promessa paralela. É assim que um fator de staking pool funciona. Aqui está um getter que verifica quantos staking pools foram criados que podem ser chamados no NEAR CLI.

Conclusão

Isto conclui a Revisão de Contrato ao Vivo da NEAR | Parte 3: Whitelist e Fábrica de Staking Pool. Obrigado por dedicar um tempo para aprender NEAR, em breve teremos mais conteúdo nesta série.

15
Scroll to Top