NEAR x Flutter Hola Match Guide

(-2 nL)
11 min read
NEAR x Flutter Logos
To Share and +4 nLEARNs

Primera Parte – Contrato Inteligente para conectarnos.

Hola, en esta guía vamos a crear un Hola + Nombre usando el patrón match de RUST, para que nos lo de en diferentes lenguajes.

Después usaremos la NEAR-RPC-API para comunicarnos con esta función desde una aplicación multiplataforma hecha en Flutter, demostrando así que podemos llamar esta función desde cualquier dispositivo y así construir sobre NEAR para cualquier plataforma.

-La cuarta instalación es VS CODE, pero tu puedes usar tu Editor de Textos/Código favorito”-

Para esto necesitamos tener instalados:

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

Lo primero que haremos es abrir una terminal para crear el proyecto, recomiendo abrir la terminal desde adentro de VS CODE por comodidad, pero puedes usar una externa.

En NEAR creamos los contratos inteligentes con Librerías, no con Binarios, entonces el comando que nos creará un archivo lib en lugar de main será:

cargo new --lib <nombre_de_tu_elección>

En este caso lo llamaré: near_hola_match

El siguiente paso es agregar la dependencia del SDK de NEAR, y unos ajustes para especiales dentro del TOML, ya comenté en el código lo que hace la mayoría de estos ajustes, los ajustes son los siguientes:

[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 la versión más reciente (Hasta la fecha – 15/04) del SDK de NEAR.

En el archivo lib.rs tendremos una función de ejemplo, la cual vamos a borrar, para construir desde cero nuestro Smart Contract.

Empezaremos escribiendo lo más básico, lo que son la struct del Contrato y la impl del Contrato:
He añadido el macro #[near_bindgen] que es propio del NEAR SDK, este incluye todo lo necesario internamente para generar el contrato y sus conexiones.

use near_sdk::{ near_bindgen };
 
#[near_bindgen]
pub struct Contrato {
 
}
 
#[near_bindgen]
impl Contrato {
 
}

Para este ejemplo incluiré una función de inicialización del contrato, y un owner del mismo, aunque no son necesarios para la función Hola Nombre, nos servirán para mostrarte como incluír llamadas View y Call en un contrato, ya que Hola Nombre no modifica el estado de la blockchain, sin embargo la función Call de inicialización si lo hace.

Para esto incluiré unas importaciones más, como son borsh, la cual nos sirve para la serialización y deserialización al momento de comunicarnos con NEAR, al igual que AccountId y PanicOnDefault, las cuales se encargan de revisar que el AccountId tenga un formato válido, y de que en caso de no inicializar el contrato este entre en Pánico, respectivamente.

Además he creado la función de inicialización del contrato, recibe una variable de tipo AccountId, la cual asignará como la owner al momento de inicializar, esta llamada debe hacerse solamente una 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
   }
}

Comenzaremos a crear la función que nos devolverá el saludo, dependiendo qué lenguaje queramos:

La función se llamará hello_name, recibe de parámetros el mismo contrato, el nombre para unir al String, y el lenguaje deseado. Ponemos en un patrón match los lenguajes posibles, y hasta abajo un último elemento que será usado como default en caso de no haber match.

   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),
       }
   }

Lo siguiente será agregar el target wasm32-unknown-unknown si aun no lo hemos hecho:

rustup target add wasm32-unknown-unknown

Y entonces hagamos el build y veamos si todo va correcto hasta ahora:

cargo build --target wasm32-unknown-unknown --release

Se habrá creado una nueva carpeta, el archivo específico que buscamos es near_hola_match.wasm , y ahí está:

Al principio les dije que era necesario instalar near-cli, y este es el momento en el que nos va a servir.

Primer paso:

near login

Le estaremos dando acceso completo a nuestra cuenta de TESTNET, con esto procederemos a crear una subcuenta y desplegar el contrato.

Segundo paso:

near create-account <subcuenta>.<nuestra_cuenta_de_testnet> --masterAccount <nuestra_cuenta_de_testnet>

Ejemplo:

near create-account contrato1.jeph.testnet --masterAccount jeph.testnet

Este comando no nos dará una frase semilla, sino que almacenará la key directamente en nuestra computadora.

El siguiente comando desplegará el contrato a testnet para que pueda ser accedido por cualquier persona:

near deploy --wasmFile target/wasm32-unknown-unknown/release/<nombre_del_archivo_wasm>.wasm --accountId <nombre_de_la_cuenta>

Ejemplo:

near deploy --wasmFile target/wasm32-unknown-unknown/release/near_hola_match.wasm --accountId contrato1.jeph.testnet

Tx ID

Como vemos se ha desplegado sin problemas, y podemos ver la transacción en el explorador:

Entonces para llamar a la función VIEW usamos:

near view <id_del_contrato> <nombre_de_la_función> '{"name": "<cualquier_nombre>", "language": "<alguno_de_los_lenguajes_que_pusimos>"}'

Ejemplo:

near view contrato1.jeph.testnet hello_name '{"name": "jeph.testnet", "language": "es"}'

Sin embargo nos encontramos con esto, el contrato no ha sido inicializado, y como no quisimos dejar un default, entró en panico, ¿Qué hacemos?, inicializarlo:

Esta será una llamada CALL, no VIEW y entonces le añadimos un parámetro al final –account-id <cuenta_a_la_que_poseamos_acceso>.

Ejemplo:

near call contrato1.jeph.testnet new '{"owner_id": "contrato1.jeph.testnet"}' --account-id contrato1.jeph.testnet

Tx ID

Se ha inicializado con éxito.

Y podemos proceder con la llamada VIEW anterior:

near view contrato1.jeph.testnet hello_name '{"name": "jeph.testnet", "language": "es"}'

(Podemos poner cualquiera de los idiomas, e incluso cambiar el nombre que le mandamos)

Segunda Parte – Conexión Flutter a NEAR

Continuamos con la parte de Flutter, recordemos que este no es un curso de Flutter, sino de como conectar tu aplicación hecha en Flutter y hacer llamadas a los Nodos RPC de NEAR Protocol, por lo que es un requisito el ya tener instalado Flutter.

Crearemos un nuevo proyecto en Flutter con (command + shift + p) o (control + shift + p).

Nos preguntará si será una Aplicación, un Módulo, etc… Elegimos Aplicación.

Elegimos el nombre deseado.

Nos creará un archivo principal muy largo, al cual le he eliminado: 

  • Todos los comentarios.
  • La función “increment”.
  • El body.
  • El fab (Floating Action Button – Botón Flotante).

Lo único que he agregado es el String “title”, y lo he pasado como argumento en 2 lugares.

Igual tendremos el archivo “pubspec.yaml”, es como el “package.json” de JS/TS, aquí declaramos los paquetes a usar, la versión de la aplicación y varios ajustes más.

En este archivo añadiremos una sola linea, y esto será para añadir un paquete:

“ http: ^0.13.4“.

  • Este paquete nos servirá para hacer peticiones http sencillas, existen varias un poco más personalizables como DIO, pero para este ejemplo no necesitamos las opciones extras, además de que http suele ser suficiente.

Ojo. Tener cuidado en mantener la misma sangría tal como “flutter” o “cupertino_icons”.

Crearemos un archivo llamado “name_and_language.dart” dentro de una nueva carpeta a la que he llamado “models” dentro de “lib”.

En este archivo crearemos solamente una Clase, para pasar solamente un objeto en las siguientes funciones.

Crearemos un archivo llamado “rpc_fun.dart” dentro de una nueva carpeta a la que llamaré “utils” dentro de “lib”.

En este archivo pondremos la petición hacia el Nodo RPC de NEAR Protocol, que desde ya te menciono que es una petición http de tipo post, por ello lleva 3 requisitos: url, headers y body.

Iniciamos el archivo con las importaciones, necesitamos importar a “convert” para JSON, importar a “math” para crear un id único aleatorio para las peticiones a la blockchain (En este caso no es muy importante que el ID sea único y funcionaría sin ello pero aun así vamos a considerarlo), igual he importado a “foundation” para hacer esta petición en un hilo separado del principal (Dart generalmente corre en un solo hilo, y necesitas Isolates si deseas usar otro) (Para esta petición quizás es innecesario, pero supongamos que algún día decides pedir datos de 100 NFTs a la blockchain, muy seguramente haría que la pantalla de tu usuario se ponga lenta o incluso se detenga por algunos segundos), importamos a http para hacer las peticiones.

De igual manera hasta arriba declaramos las constantes de los headers, la URL del Nodo RPC de Testnet, el nombre del contrato que creamos en la parte 1 de esta guía, el método del contrato al que llamaremos y declaramos la instancia de Random.

Fijate que yo nunca uso “var” para declarar variables, y tu que trabajas con RUST tampoco deberías, además haciendo constantes los valores que no cambias hacemos un poco más rápida la ejecución de la aplicación.

Como bien sabes, las peticiones http llevan igual un body, dicho body lleva:

  • El nombre del contrato.
  • El método del contrato.
  • La finalidad que necesitamos, puede ser optimista o final.
  • Los argumentos que la función requiere, van en un Mapa, pero convertido a Base64.
  • El ID de la petición.

Los Nodos RPC nos devuelven muchos datos, pero el que más nos importa es el que se encuentra en “result” (Dentro de otro “result”), pero lo devuelve en forma de buffer (Que es una lista de u8 (números)) que tienen un mensaje que ni tu ni yo podemos entender a simple vista.

(Hay una extensión para que puedas descifrarlo desde tu navegador – RPC Parser (Hecha por mi)).

Pero bueno, nuestra app debe entenderlo, para devolverle su saludo al usuario, y esa función es la siguiente:

Le pasamos una Lista de valores Dinámicos (¿Qué significa eso? – Que solo Dios sabe que tipo de dato está recibiendo), nosotros sabemos que seguramente sean números, pero Dart/Flutter no, y personalmente he experimentado errores, a veces cree que son String, a veces Números y otras simplemente no lo entiende.

Entonces la anterior función pide esa lista dinámica, cada uno de los elementos es convertido a String (Cadena de texto), después borra los espacios en blanco y de último los pasa a Int (Numero), entonces vuelve a generar una lista nueva en la que ya está seguro que son números. Luego convierte esos números a nuestro esperado mensaje.

Pero bueno, para ello necesitamos una respuesta, y para eso necesitamos una petición, la siguiente función hace:

  • Pasa la URL de String a Uri.
  • Crea el Mapa con los argumentos.
  • Pasa los argumentos a Base64 después de pasarlos a utf8 después de pasarlos a json.
  • Llama a la función que nos genera el body con los datos requeridos y lo pasa a json.
  • Finalmente realiza la petición http post pasando los tres parámetros que mencioné al principio (url, headers, body).
  • En caso de que haya respuesta la pasamos a un Mapa.
  • Llamamos a la función que decodifica el buffer (Y hace más cosas que ya expliqué más arriba).
  • Y el resultado es nada más y nada menos que el mensaje que esperábamos.

Muy bonito y todo, pero… Son muchos pasos y a lo mejor algún dispositivo presente un bajón de fps, especialmente si tu petición fuera aún más grande.

¿Qué tal si la llamamos a través de otro hilo?

Entonces no llamamos directamente a “fetchRpc” sino a “callFetch”, el cual mandará llamar a “fetchRpc” a través de otro hilo.

Muy bien, ya tenemos lo más difícil, y ahora, ¿En donde lo vemos?,necesitamos una interfaz sencilla que nos deje ingresar un nombre, elegir un idioma y darle click a enviar. Todo eso lo vamos a hacer en el archivo “main.dart”. En la parte de hasta arriba añadiremos 2 “imports”:

(Material ya estaba, solo añadimos “name_and_language.dart” y “rpc_fun.dart”)

No tocaremos la función main ni la clase MyApp, pero en MyHomePage si haremos muchos cambios:

Crearemos un TextEditingController para poder tomar el valor del nombre, una lista de los lenguajes aceptados (Los mismos que pusimos en el Contrato), un simple valor booleano que nos indica que el usuario no ha clickeado, y lo más importante, una nueva instancia de la clase NameAndLanguage que anteriormente creamos… ¿Por qué es final?, Porque a pesar de que los valores internos no son finales, la instancia si lo es.

Inicializamos el TextEditingController en “initState” y nos deshacemos de él en “dispose”.

Después añadiremos un input para que podamos añadir un nombre, un un dropdown para elegir entre los lenguajes posibles (Para ello pasamos la constante accepted), dicho Dropdown no se abrirá si no hemos incluído un nombre, y un botón para realizar la primera petición (Las siguientes se realizarán ni bien se cambie el lenguaje en el dropdown), dicho botón sólo será activado si hemos elegido un lenguaje en el dropdown​.

Aquí he añadido dos “Text”, uno que nos saludará y mostrará nuestro nombre y otro que en caso de no haber elegido un lenguaje nos pedirá que lo ingresemos, y en caso de que ya, nos dirá el lenguaje que hablamos (Hasta acá todavía no enviamos nada a NEAR).

Aquí el usuario ingresa su nombre, este se almacena en la instancia de la clase llamada “nameAndLanguage” y llamamos un “setState”.

En el DropDown elegimos un lenguaje de los que hemos puesto en la lista y llamamos otro “setState”.

Un botón simple para asignar el valor booleano true en “clicked”, ya que solo al ser true llamamos al siguiente “FutureBuilder”.

Este “FutureBuilder” llama a callFetch pasando “nameAndLanguage”, y lo que retorna es mostrado en pantalla, fijate en la velocidad de carga, ¿Bastante rápida una petición al Nodo RPC de NEAR verdad?

¿Quieres verlo en acción?

https://youtube.com/shorts/9r4jmVBZRec?feature=share

¿Quieres ver el código completo?

https://github.com/JuEnPeHa/near_hola_match_flutter/tree/main/lib

¿En cuál de las siguientes plataformas podemos construir con NEAR?

Correct! Wrong!

¿Podemos hacer peticiones al Nodo RPC de NEAR para pedir información sobre NFTs y/o FTs?

Correct! Wrong!

¿En qué formato envíamos el httpBody (O simplemente el Body de la petición) al RPC?

Correct! Wrong!

¿Las peticiones VIEW qué NO modifican el estado de la blockchain (Pero que sí pueden hacer cierto procesamiento) nos cuestan?

Correct! Wrong!

 

 

Generate comment with AI 2 nL
6

Deja un comentario


To leave a comment you should to:


Ir arriba
Report a bug👀