Primeira Parte – Contrato Inteligente para se conectar.
Olá, em este guia vamos criar um Olá + Nome, usando o padrão de correspondência RUST, para que ele esteja disponível em vários idiomas.
Usaremos então a NEAR-RPC-API para comunicar com esta função a partir de uma aplicação multiplataforma feita em Flutter, demonstrando assim que podemos acionar esta função desde qualquer dispositivo e assim construir sobre NEAR para qualquer plataforma.
-A quarta instalação é o VS CODE, mas você pode usar seu editor de texto/código favorito”-
Para isso precisamos ter instalado:
RUST = https://www.rust-lang.org/tools/install
NEAR-CLI = https://docs.near.org/docs/tools/near-cli#mac-and-linux
Flutter = https://docs.flutter.dev/get-started/install
VS CODE = https://code.visualstudio.com/#alt-downloads
A primeira coisa que faremos é abrir uma terminal para criar o projeto, recomendo abrir a terminal desde dentro do VS CODE por conveniência, mas você pode usar um externo.
No NEAR criamos contratos inteligentes com Bibliotecas, não com Binários, portanto o comando que irá criar um arquivo lib para nós ao invés do main, será:
cargo new --lib <nombre_de_tu_elección>
Neste caso, vou chamá-lo: near_hola_match
O próximo passo é adicionar a dependência NEAR SDK, e algumas configurações especiais dentro do TOML, já comentei no código o que a maioria dessas configurações fazem, as configurações são as seguintes:
[lib]
crate-type = ["cdylib"]
[dependencies]
near-sdk = "4.0.0-pre.7"
[profile.release]
codegen-units = 1 # Podría mejorar el desempeño, a cambio de ser más lento en compilación.
opt-level = "z" # Optimizaciones de velocidad.
lto = true # Optimizaciones de velocidad.
debug = false # Optimizaciones de velocidad.
panic = "abort" # Detener la ejecución en caso de error.
overflow-checks = true # No permitimos el desbordamiento de ints.
Estamos usando a versão mais recente (da data – 15/04) do NEAR SDK.
No arquivo lib.rs teremos uma função de exemplo, que vamos deletar, para construir nosso Smart Contract do zero.
Começaremos escrevendo as mais básicas, que são a struct (estrutura) do Contrato e a impl (implementação) do Contrato:
Eu adicionei a macro #[near_bindgen] que é típica do NEAR SDK; ela inclui tudo o que é necessário internamente para gerar o contrato e suas conexões.
use near_sdk::{ near_bindgen };
#[near_bindgen]
pub struct Contrato {
}
#[near_bindgen]
impl Contrato {
}
Para este exemplo vou incluir uma função de inicialização de contrato e um proprietário do mesmo, embora não sejam necessários para a função Olá + Nome, eles nos ajudarão a mostrar como incluir chamadas View e Call em um contrato, pois Olá + Nome não modifica o estado do blockchain, mas a função Call de inicialização sim.
Para isso vou incluir mais algumas importações, como o borsh, que é usado para serialização e desserialização na comunicação com o NEAR, bem como AccountId e PanicOnDefault, que são responsáveis por verificar se o AccountId tem um formato válido, e que no caso de não inicializando o contrato entra no Panic, respectivamente.
Também criei a função de inicialização do contrato, ele recebe uma variável do tipo AccountId, que será atribuída como proprietária no momento da inicialização, esta chamada deve ser feita apenas uma vez
use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize};
use near_sdk::{near_bindgen, AccountId, PanicOnDefault};
#[near_bindgen]
#[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)]
pub struct Contrato {
pub owner_id: AccountId,
}
#[near_bindgen]
impl Contrato {
#[init]
pub fn new(owner_id: AccountId) -> Self {
let this = Self { owner_id };
this
}
}
Começaremos a criar a função que devolverá a saudação, dependendo de qual idioma queremos:
A função será chamada hello_name, que recebe de parâmetros o contrato mesmo, o nome a ser vinculado à String e o idioma desejado. Colocamos os idiomas possíveis em um padrão de correspondência, e na parte inferior, um último elemento que será usado como padrão caso não haja correspondência.
pub fn hello_name(&self, name: String, language: String) -> String {
match language.as_str() {
"en" => format!("Hello {}!", name),
"es" => format!("Hola {}!", name),
"fr" => format!("Bonjour {}!", name),
"de" => format!("Hallo {}!", name),
"it" => format!("Ciao {}!", name),
"pt" => format!("Olá {}!", name),
"ru" => format!("Привет {}!", name),
"zh" => format!("你好 {}!", name),
"tr" => format!("Merhaba {}!", name),
"ua" => format!("Привіт {}!", name),
_ => format!("Hello {}!", name),
}
}
A próxima coisa será adicionar o destino wasm32-unknown-unknown se ainda não o tivermos feito:
rustup target add wasm32-unknown-unknown
E então vamos fazer o build e ver se tudo deu certo até agora:
cargo build --target wasm32-unknown-unknown --release
Uma nova pasta terá sido criada, o arquivo específico que estamos procurando é near_hola_match.wasm , e aí está:
No começo eu disse que é necessário instalar near-cli, e este é o momento em que ele vai nos servir.
Primeiro passo:
near login
Estaremos lhe dando acesso total à nossa conta TESTNET, com isso iremos criar uma subconta e implantar o contrato.
Segundo passo:
near create-account <subcuenta>.<nuestra_cuenta_de_testnet> --masterAccount <nuestra_cuenta_de_testnet>
Exemplo:
near create-account contrato1.jeph.testnet --masterAccount jeph.testnet
Este comando não nos dará uma frase-semente, mas armazenará a chave diretamente em nosso computador.
O comando a seguir implantará o contrato na testnet para que possa ser acessado por qualquer pessoa:
near deploy --wasmFile target/wasm32-unknown-unknown/release/<nombre_del_archivo_wasm>.wasm --accountId <nombre_de_la_cuenta>
Exemplo:
near deploy --wasmFile target/wasm32-unknown-unknown/release/near_hola_match.wasm --accountId contrato1.jeph.testnet
Como podemos ver, ele foi implantado sem problemas, e podemos ver a transação no navegador:
Então, para chamar a função VIEW, usamos:
near view <id_del_contrato> <nombre_de_la_función> '{"name": "<cualquier_nombre>", "language": "<alguno_de_los_lenguajes_que_pusimos>"}'
Exemplo:
near view contrato1.jeph.testnet hello_name '{"name": "jeph.testnet", "language": "es"}'
No entanto, encontramos isto, o contrato não foi inicializado e, como não queríamos deixar um padrão, ele entrou em pânico, o que fazemos?, inicialize-o:
Esta será uma CALL, não uma VIEW, então adicionamos um parâmetro ao final –account-id <conta_com_asesso>.
Exemplo:
near call contrato1.jeph.testnet new '{"owner_id": "contrato1.jeph.testnet"}' --account-id contrato1.jeph.testnet
Foi inicializado com sucesso.
E podemos prosseguir com a chamada VIEW anterior:
near view contrato1.jeph.testnet hello_name '{"name": "jeph.testnet", "language": "es"}'
(Podemos colocar qualquer um dos idiomas e até alterar o nome que enviamos)
Segunda Parte – Conexão Flutter para NEAR
Continuamos com a parte do Flutter, lembrando que este não é um curso de Flutter, mas sim como conectar sua aplicação feita em Flutter e fazer chamadas para os Nodos RPC do NEAR, portanto é um requisito já ter o Flutter instalado.
Vamos criar um novo projeto no Flutter com (command + shift + p) ou (control + shift + p).
Ele nos perguntará se será um Aplicativo, um Módulo, etc… Escolhemos o Aplicativo.
Escolhemos o nome desejado.
Ele criará um arquivo principal muito longo, que eu deletei:
- Todos os comentários.
- A função “increment”.
- O body.
- O fab (Floating Action Button – botão de ação flutuante).
A única coisa que adicionei foi a String “title”, e passei como argumento em dois lugares.
Ainda teremos o arquivo “pubspec.yaml”, que é semelhante ao JS/TS “package.json”, aqui declaramos os pacotes a serem usados, a versão da aplicação e várias outras configurações.
Neste arquivo vamos adicionar uma única linha, e esta será para adicionar um pacote:
“ http: ^0.13.4“.
- Este pacote nos ajudará a fazer requisições http simples, existem várias que são um pouco mais personalizáveis, como a DIO, mas para este exemplo não precisamos das opções extras, e normalmente o http é suficiente.
Nota: Tome cuidado para manter o mesmo recuo, como “flutter” ou “cupertino_icons”.
Vamos criar um arquivo chamado “name_and_language.dart” dentro de uma nova pasta que chamei de “models” dentro de “lib”.
Neste arquivo vamos criar apenas uma Classe, para passar somente um objeto nas funções a seguir.
Vamos criar um arquivo chamado “rpc_fun.dart” dentro de uma nova pasta que chamarei de “utils” dentro de “lib”.
Nesse arquivo vamos colocar a requisição para o nodo RPC do NEAR Protocol, vou lhe mencionar de uma vez que é uma requisição http do tipo post, portanto tem três requisitos: url, headers (cabeçalhos) e body (corpo).
Iniciamos o arquivo com as importações, precisamos importar de “convert” para JSON, importar para “math” para criar um id único aleatório para solicitações a blockchain (neste caso, não é muito importante que o ID seja único e funcionaria sem ele, mas ainda o consideraremos), Eu também importei “foundation” para fazer essa solicitação em um thread separado do thread principal (o Dart geralmente é executado em um único thread e você precisa de Isolates se quiser usar outro) (Para esta solicitação, pode ser desnecessário, mas suponha que um dia você decida solicitar dados de 100 NFTs para a blockchain, provavelmente faria a tela do seu usuário desacelerar ou até parar por alguns segundos), importamos http para fazer as solicitações.
Da mesma forma, no topo declaramos as constantes dos headers, a URL do nodo RPC da Testnet, o nome do contrato que criamos na parte 1 deste guia, o método do contrato que chamaremos e declaramos a instância de Random (Aleatório).
Note que eu nunca uso “var” para declarar variáveis, e você que trabalha com RUST também não deve, também tornando os valores que você não altera de constantes, tornamos a execução da aplicação um pouco mais rápida.
Como você bem sabe, as solicitações http têm o mesmo body (corpo) e o referido body tem:
- O nome do contrato.
- O método de contrato.
- O propósito que precisamos pode ser otimista ou final.
- Os argumentos que a função requer, vão para um Map, mas convertidos para Base64.
- O ID da solicitação.
Os nodos RPC nos retornam muitos dados, mas o que mais importa para nós é o encontrado em <result> (dentro de outro <result>), mas ele retorna na forma de um buffer (que é uma lista de u8 (números) e Eles têm uma mensagem que nem você nem eu podemos entender à primeira vista.
Existe uma extensão para que você possa decifrá-lo desde seu navegador – RPC Parser (Feita por mim
Mas tudo bem, nossa app deve entendê-lo, para devolver a saudação ao usuário, e essa função é a seguinte:
Lhe passamos uma lista de valores Dinâmicos (Que significa isso? – Que só Deus sabe quê tipo de dados está recebendo), nós sabemos que seguramente sejam números, mas Dart/Flutter não, e pessoalmente tenho experimentado erros, algumas vezes crê que são strings, número e em outras simplesmente não entende.
É então que a função anterior pede a lista Dinâmica, cada um dos elementos é convertido a String (cadeia de texto), depois apaga os espaços em branco e por último os passa a Int (número) e então volta a gerar uma nova lista na qual está assegurado que são números. Logo após converte esses números em nossa esperada mensagem.
Más também para tal efeito necessitamos uma resposta, e para isso é necessário uma petição, e a seguinte função faz:
Passa a URL de String a Uri.
- Passa a URL de String a Uri.
- Cria o Mapa com os argumentos.
- Passa os argumentos a Base64 depois de passá-los a utf8, depois de passá-los a json.
- Chama a função que nos gera o body com os dados requeridos e o passa a json.
- Finalmente realiza a petição http post passando pelos três parâmetros que mencionei ao princípio (ur, headers e body)
- No caso de haver uma resposta, a passamos para um mapa.
- Chamamos a função que decodifica o buffer (e faz mais coisas as quais expliquei anteriormente)
- E o resultado é nada mais e nada menos que a mensagem que esperávamos
Tudo lindo e maravilhoso mas… mas são muitos passos e é possível que algum dispositivo apresenta uma diminuição de fps, especialmente se a sua petição fora maior ainda.
Que tal se a chamamos através de outra linha?
Então não chamamos diretamente a “fetchRpc” senão a “CallFetch” o qual vai mandar chamar a “fetchRpc” através de outra linha.
Muito bem, já temos o mais difícil e agora, ¿onde o vemos? Necessitamos uma interface simples que nos permita ingressar um nome, escolher um idioma e dar click em enviar. Tudo isso o faremos no arquivo “main.dart.
Na parte até em cima vamos adir 2 “imports:
(O material já estava, somente vamos adir “name_and_languaje.dart” e “rpc_fun.dart”)
Não iremos tocar a função “main” nem a classe “MyApp”, mas em “MyHomePage” sim faremos muitas mudanças:
Vamos Criar um “TextEditingController para poder tomar o valor do nome, uma lista de idiomas aceitados (os mesmos que pusemos no Contrato), um simples valor Booleano que nos indica que o usuário não clickou, e o mais importante, uma nova instância da classe “NameAndLanguaje” que anteriormente criamos.. ¿Porque é o final?; Por que apesar de que os valores internos não ser finais, a instância, sim o é.
Inicializamos o “TextEditingController” no “iniState” e logo nos desfazemos dele no “dispose”.
Logo após incluiremos um input para ser possível adir um nome, um dropdown para escolher entre os idiomas possíveis (para tal fin passamos a constante accepted), dito dropdown não se abrirá si não tivermos incluido um nome e um botão para realizar a primeira petição (As seguintes vão se realizar no momento em que se mude o idioma no dropdown), dito botão só será ativado se tivermos escolhido um idioma no dropdown.
Aqui adicionei dois “Text”, um que nos dará as boas vindas e nos mostrará o nosso nome e outro, que em caso de não haver escolhido um idioma, que pedirá que o ingressemos e em caso de já haver escolhido, nos dirá o idioma que falamos (até aqui ainda não enviamos nada a NEAR.
Aqui o usuário ingressa seu nome, este será armazenado na instância da classe chamada “nameAndLanguage” e chamamos um “setState”.
No Dropdown escolhemos um idioma dos que colocamos na lista e chamamos outro “setState”.
Um simples botão para designar o valor booleano true em “clicked” já que somente ao ser true (verdadeiro) chamamos ao seguinte “FutureBuilder”.
Este “FutureBuilder” chama a callFetch passando “nameAndLanguage” e o que devolve é mostrado na tela, ponha atenção na velocidade de carregamento, Bastante rápida um pedido ao Nodo RCP da Near, ¿certo?
¿Quer vê-lo em ação?
¿Quer ver o código completo?
https://github.com/JuEnPeHa/near_hola_match_flutter/tree/main/lib
¿Em qual das seguintes plataformas podemos construir con a Near?
¿Podemos fazer petições ao Nodo RCP da Near para pedir informação sobre NFTs e ou FTs?
¿Em qual formato enviamos o httpBody (Ou simplesmente o Body da repetição) ao RCP?
¿As petições VIEW que NÃO modificam o estado da blockchain (Mas, sim, podem fazer certo tipo de processamento) tem um custo para nós?