# Lista de Boda II + NFT

WARNING

Seguimos con el ejemplo de Guestbook enmarcado en una lista de bodas donde hasta el momento hemos podido mandar un mensaje a los novios y un regalo económico (XRD). El ejemplo de hoy no es más que una vuelta de rosca al código que ya hemos escrito anteriormente 😎.

Opinión

  • Al final el código es repetitivo ya que las soluciones suelen tener características comunes. Lo mismo que una casa siempre tiene puertas, ventanas, paredes, luces... las aplicaciones siempre tienen elementos comunes que debemos conocer e incorporar de la forma mas útil, esto lo podemos lograr respetando y utilizando patrones de diseño.

# Análisis

Problema/Análisis:

Muchos novios dan detalles/regalos a sus invitados para que recuerden ese día. Nos piden incorporar a nuestro componente la capacidad de generar un recuerdo NFT a todos aquellos que registren un mensaje. Y como en muchas ocasiones, pasado un tiempo, las bodas terminan en separaciones, queremos poder registrar ese dato de fin del enlace y que los NFT que se dieron como regalo pudieran actualizar la información. Además nos piden mejorar algo el sistema de retirada de fondos para que de alguna manera la pareja llegue a un consenso para dicha retirada.

  • Poder generar NFT con datos mutables e inmutables.
  • Permitir guardar fecha de fin del enlace.

# Diseño

Nota: En muchas ocasiones podemos realizar el diseño con psudocódigo que es una representación escrita de un algoritmo, es decir, muestra en forma de texto los pasos a seguir para solucionar un problema.

  • Función constructora

    • Crear insignia para administradores (novios)
    • Creer definición NFT
    • Autorización acuñar NFT
    • Crear Tabla de registros
    • Crear deposito XRD (regalos)
    • Fecha boda inicio y fin
  • Agregar registros

    • Insertar a la tabla registros: nombre, comentario y regalo
    • Guardar regalo en deposito
    • Acuñar NFT personalizado con el nombre del registro
    • Pasar a la cuenta del invitado
  • Leer registros

    • Sacar por pantalla listado de registros
  • Sacar XRD de componente

    • Buscar consenso para mandar el monto total de XRD (restringido al admin)
  • Actualizar fecha fin boda

    • Método para introducir fecha fin de boda (restringido al admin)
  • Actualizar NFT

    • Método para cambiar datos mutables en NFT (fecha boda fin)

# Programación

Como en ocasiones anteriores solo nos centraremos en los cambios realizados en el código con respecto a la unidad anterior y las nuevas mejoras.

# La estructura:

#[derive(NonFungibleData)]
struct DatosNft {
    mensaje: String,
    fecha_boda: String,
    #[scrypto(mutable)]
    fecha_fin: String
}

Cuando creamos NFT podemos crear una estructura donde se guardaran los metadatos mutables e inmutables. Para ello Scrypto nos proporciona un macro específico: #[derive(NonFungibleData)].

TIP

  • Por defecto todos los datos serán inmutables.
  • Si queremos que los datos sean mutables debemos escribir delante (en la línea anterior) del dato #[scrypto(mutable)]

En este caso el NFT que daran los novios como detalle por asistir a su boda y dejar un mensaje en este libro de visitas, contendrá un mensaje de los novios, la fecha del enlace, y la fecha de fin del matrimonio. Si te fijaste, este último dato, el de la fecha de fin, es mutable 🙄.

blueprint! {
    struct ListaBoda {
        registro: HashMap<u128, (String,String,Decimal)>,
        regalos: Vault,
        nft_address: ResourceAddress,
        admin: Vault,
        fecha_boda: String,
        fecha_fin: String,
    }

Con respecto a la unidad anterior, hemos agregado: 1. Un contendedor (Vault) para guardar la insignia (admin) que tiene la autorización para mintear nft en este componente. 2. Declaramos dos variables para guardar las fechas de inicio y fin de boda, son de tipo String para ser mas sencillo su implementación desde el simulador. Cuando tengamos un front-end podremos cambiar este tipo de dato a uno mas concreto para guardar fechas. 3. Y guardamos la dirección del no fungible que vamos a regalar.

# Función constructora new

pub fn new(descripcion_nft: String, fecha_boda: String) -> (ComponentAddress, Bucket) {

    let arras: Bucket = ResourceBuilder::new_fungible()
        .metadata("name", "Admin Lista de Boda")
        .initial_supply(2);

    let admin: Bucket = ResourceBuilder::new_fungible()
        .initial_supply(1);

    let nft_address = ResourceBuilder::new_non_fungible(NonFungibleIdType::UUID)
        .metadata("name", descripcion_nft)
        .mintable(rule!(require(admin.resource_address())), AccessRule::DenyAll)
        .updateable_non_fungible_data(rule!(require(admin.resource_address())), AccessRule::DenyAll)
        .no_initial_supply();

    let access_rules: AccessRules = AccessRules::new()
        .method("nuevo", rule!(allow_all), AccessRule::DenyAll)
        .method("actualizar_nft", rule!(allow_all), AccessRule::DenyAll)
        .method("leer_registros", rule!(allow_all))  
        .method("fin_boda", rule!(require(arras.resource_address())), AccessRule::DenyAll)
        .method("sacar_todo", rule!(require_amount(dec!(2), arras.resource_address())), AccessRule::DenyAll);
    
    let mut componente = Self {
        registro: HashMap::new(),
        regalos: Vault::new(RADIX_TOKEN),
        nft_address,
        admin: Vault::with_bucket(admin),
        fecha_boda: String::from(fecha_boda),
        fecha_fin: String::new(),
    }
    .instantiate();

    componente.add_access_check(access_rules);

    (componente.globalize(), arras)
}

En realidad en esta función constructora no hay nada que antes no hayamos codificado: 1. Creamos un recurso fungible (arras) como control de acceso a ciertos métodos que solo podrán accionar los administradores de este componente. 2. Creamos otro recurso fungible (admin) que, en esta ocasión, actuará como seguridad para la acuñación y cambio de los metadatos de los NFT, este recurso se guarda en el propio componente. 3. Definiremos las reglas de acceso a los diferentes métodos, con las reglas allow_all todo el mundo puede acceder, con la regla require definimos el recurso que debemos presentar para acceder y con require_amount definimos la cantidad de unidades de cierto recurso que debemos presentar para el acceso al método. En este caso hemos diseñado que solo si se posee las dos arras se podran sacar los fondos. 4. Inicializamos las variables de la estructura. 5. Al componente le añadimos add_access_check para agregar las reglas de acceso.

::: tip 
Recuerda declarar la variable que define el componente como mutable para poder, antes de globalizar, agregar las reglas.
:::

# Método nuevo

pub fn nuevo(&mut self, nombre: String, comentario: String, regalo: Bucket) -> Bucket {
    let mensaje = "Gracias por asistir a nuestra boda ".to_string() + &nombre;

    self.registro.insert(Runtime::generate_uuid(),(nombre,comentario, regalo.amount()));
    self.regalos.put(regalo);

    let data: DatosNft = DatosNft {mensaje, fecha_boda: self.fecha_boda.to_string(), fecha_fin: String::new()};
    
    self.admin.authorize(|| {
        borrow_resource_manager!(self.nft_address).mint_non_fungible(&NonFungibleId::random(), data)
    })
}

Este método permite a los invitados crear un registro de visitas junto con un comentario y un regalo (XRD). Tales datos y recursos serán enviados con la petición, la cual recibirá de vuelta un NFT si todo va bien!!!

let mensaje = "Gracias por asistir a nuestra boda ".to_string() + &nombre;

Guardamos en una variable mensaje un mensaje prefijado junto con el nombre que la persona ha pasado como dato.

self.registro.insert(Runtime::generate_uuid(),(nombre,comentario, regalo.amount()));

Guardamos los datos que nos han pasado en la petición como valor y como clave generamos un identificador único Uuid.

TIP

  • Para agregar nuevos registros en una colección como HashMap utilizamos el método insert.
self.regalos.put(regalo);

Tomamos todo el Bucket regalo y lo pasamos al Vault regalos con el método .put, una vez pasamos todo el Bucket ya este desaparece.

 let data: DatosNft = DatosNft {
        mensaje, 
        fecha_boda: self.fecha_boda.to_string(), 
        fecha_fin: String::new()
};
            
self.admin.authorize(|| {
    borrow_resource_manager!(self.nft_address).mint_non_fungible(&NonFungibleId::random(), data)
})

Guardamos los datos con los que crearemos el NFT en la variable data, esta variable sera del tipo DatosNft que fue la estructura que creamos al inicio del componente. Una vez tenemos los datos utilizamos la macro borrow_resource_manager! envuelta en la badge admin como ya vimos en la unidad anterior. Finalmente utilizamos el método random() pra el tipo NonFungibleId que nos dara un número aleatorio para cada nuevo NFT.

# Método fin_boda

pub fn fin_boda(&mut self, fin: String) {
    self.fecha_fin = fin.to_string();
} 

Este método toma como parametro de entrada un string que contendra una 'fecha' (ojo que no se está haciendo ninguna verificación en esta capa) y se escribe como dato en el componente. Si recuerdas al instanciar el componente asignamos una regla de acceso a este componente:

 .method("fin_boda", rule!(require(arras.resource_address())), AccessRule::DenyAll)

La insignia que valida la regla se debe presentar como prueba (proof) en la AuthZone, desde resim la declararemos con --proofs. Ya hablaremos de como se presenta desde el manifiesto de transación.

# Método actualizar_nft

pub fn actualizar_nft(&mut self, nft_boda: Proof) {
    let nft_proof = nft_boda.validate_proof(self.nft_address).expect("Este no es el NFT de la Boda");
    let mut nft_data: DatosNft = nft_proof.non_fungible::<DatosNft>().data();

    nft_data.fecha_fin = self.fecha_fin.to_string();

    self.admin.authorize(|| {
        borrow_resource_manager!(self.nft_address).update_non_fungible_data(nft_proof.non_fungible::<DatosNft>().id(), nft_data)
    });

    info!("Tu nft se ha actualizado.");
}

Si el el componente ha actualizado el dato de fecha_fin de la boda, los NFT estarán desactualizados, con este método creamos un mecanismo por el cual alguien que quiera actualizarlos solo lo debe de enseñar su NFT (proof) para que el componente lo actualice. Recuerda que solo podremos actualizar los datos mutables de los recursos no fungibles.

let nft_proof = nft_boda.validate_proof(self.nft_address).expect("Este no es el NFT de la Boda");
let mut nft_data: DatosNft = nft_proof.non_fungible::<DatosNft>().data();

Como ya hicimos en unidades anteriores, primero validamos que la prueba que se pasa corresponde con la esperada. Seguidamente, si el nft es el correcto, extraemos los datos del mismo, identificador y datos.

 datos_nft.fecha_fin = self.fecha_fin.to_string();

Actualizamos el dato de fecha_fin que es el único mutable.

    self.admin.authorize(|| {
        borrow_resource_manager!(self.nft_address).update_non_fungible_data(nft_proof.non_fungible::<DatosNft>().id(), nft_data)
    });

Finalmente actualizamos el NFT, para ello como ya hemos visto en unidades anteriores.

# Método sacar_todo

pub fn sacar_todo(&mut self) -> Bucket {
    self.regalos.take_all()
}

Este método aparentemente no ha cambiado, pero si le hemos agregado un regla de acceso al instanciar el componente:

.method("sacar_todo", rule!(require_amount(dec!(2), arras.resource_address())),AccessRule::DenyAll);

Donde de forma sencilla requerimos que aquel que quiera acceder al método sacar_todo debe presentar dos tokens de tipo arras.

# Código completo:

use scrypto::prelude::*;

#[derive(NonFungibleData)]
struct DatosNft {
    mensaje: String,
    fecha_boda: String,
    #[scrypto(mutable)]
    fecha_fin: String
}

blueprint! {
    struct ListaBoda {
        registro: HashMap<u128, (String,String,Decimal)>,
        regalos: Vault,
        nft_address: ResourceAddress,
        admin: Vault,
        fecha_boda: String,
        fecha_fin: String,
    }

    impl ListaBoda {
        pub fn new(descripcion_nft: String, fecha_boda: String) -> (ComponentAddress, Bucket) {

            let arras: Bucket = ResourceBuilder::new_fungible()
                .metadata("name", "Admin Lista de Boda")
                .initial_supply(2);

            let admin: Bucket = ResourceBuilder::new_fungible()
                .initial_supply(1);

            let nft_address = ResourceBuilder::new_non_fungible(NonFungibleIdType::UUID)
                .metadata("name", descripcion_nft)
                .mintable(rule!(require(admin.resource_address())), AccessRule::DenyAll)
                .updateable_non_fungible_data(rule!(require(admin.resource_address())), AccessRule::DenyAll)
                .no_initial_supply();

            let access_rules: AccessRules = AccessRules::new()
                .method("nuevo", rule!(allow_all), AccessRule::DenyAll)
                .method("actualizar_nft", rule!(allow_all), AccessRule::DenyAll)
                .method("leer_registros", rule!(allow_all), AccessRule::DenyAll)  
                .method("fin_boda", rule!(require(arras.resource_address())), AccessRule::DenyAll)
                .method("sacar_todo", rule!(require_amount(dec!(2), arras.resource_address())), AccessRule::DenyAll);
            
            let mut componente = Self {
                registro: HashMap::new(),
                regalos: Vault::new(RADIX_TOKEN),
                nft_address,
                admin: Vault::with_bucket(admin),
                fecha_boda: String::from(fecha_boda),
                fecha_fin: String::new(),
            }
            .instantiate();

            componente.add_access_check(access_rules);

            (componente.globalize(), arras)
        }

        pub fn nuevo(&mut self, nombre: String, comentario: String, regalo: Bucket) -> Bucket {
            let mensaje = "Gracias por asistir a nuestra boda ".to_string() + &nombre;

            self.registro.insert(Runtime::generate_uuid(),(nombre,comentario, regalo.amount()));
            self.regalos.put(regalo);

            let data: DatosNft = DatosNft {mensaje, fecha_boda: self.fecha_boda.to_string(), fecha_fin: String::new()};
            
            self.admin.authorize(|| {
                borrow_resource_manager!(self.nft_address).mint_non_fungible(&NonFungibleId::random(), data)
            })
        }
        
        pub fn fin_boda(&mut self, fin: String) {
            self.fecha_fin = fin.to_string();
        } 

        pub fn actualizar_nft(&mut self, nft_boda: Proof) {
            let nft_proof = nft_boda.validate_proof(self.nft_address).expect("Este no es el NFT de la Boda");
            let mut nft_data: DatosNft = nft_proof.non_fungible::<DatosNft>().data();

            nft_data.fecha_fin = self.fecha_fin.to_string();

            self.admin.authorize(|| {
                borrow_resource_manager!(self.nft_address).update_non_fungible_data(nft_proof.non_fungible::<DatosNft>().id(), nft_data)
            });
 
            info!("Tu nft se ha actualizado.");
        }

        pub fn leer_registros(&self) {
            let mut total_regalos = Decimal::zero();
            info!("Comentarios:");
            info!("--------------------------------------------------");
            for (_uuid, comentario) in &self.registro {
                info!("{:?}", comentario);
                total_regalos += comentario.2;
            };
            info!("--------------------------------------------------");
            info!("Total: {} xrd regalados" , total_regalos);
        }
 
        pub fn sacar_todo(&mut self) -> Bucket {
            self.regalos.take_all()
        }
    }
}

# Compilación y ejecución

A estas alturas seguro que ya sabes publicar el package, instanciar el component y llamar a las funciones pasando un parámetro.

Pero... por si eres un Homo Emilius te dejo una ayudita 🙈.
  1. Limpiar el simulador
resim reset
  1. Crear un Package
scrypto new-package LibroBoda
cd LibroBoda
  1. Crear una cuenta (recuerda copiar la dirección de los XRD de tu cuenta)
resim new-account
set acct [Address de la cuenta que acabamos de crear]
set privada [Guardamos la clave pública de esta cuenta]
set xrd xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
resim new-simple-badge
set badge [NFAddress]
  1. Copiar o escribir el código (recuerda guardar ctrl + s)
  • Recuerda guardar el código de este ejercicio dentro del archivo lib.rs que has creado en la carpeta \radixdlt-scrypto\LibroBoda\src\lib.rs
  1. Publicar y guardamos la dirección del Package
//resim publish . --owner-badge <owner_NFAddress>
resim publish . --owner-badge $badge
set pack [New Package Reference]
  1. Instanciar componente (recuerda que en este caso hay que añadir el argumento del precio) y guardar la dirección del componente.
resim call-function $pack ListaBoda new "Boda de Miriam y Esther" "Dos de febrero de 2020"   
set comp [dirección del componente]
  1. Probar método nuevo
resim call-method $comp nuevo "Emilio Bitcoin" "Que boda mas bonita, os deseo muchos hijos..." 100,$xrd
// Ojo que cuando pasamos un dato de tipo string y tiene mas de una palabra debemos entrecomillarlo.
// Podemos comprobar el NFT que nos han regalado
resim show $acct
  1. Probar método leer_registros
resim call-method $comp leer_registros
  1. Sacar los regalos del componente a la billetera que lo ejecuta
resim call-method $comp sacar_todo --proofs 2,[definición del recurso fungible arras]
  1. Comprobamos que hemos recibido los xrd
resim show $acct
  1. Actualizamos la fecha de fin de boda
resim call-method $comp fin_boda "22 de febrero de 2025" 1,[definición del recurso fungible arras]
  1. Actualizamos el NFT
resim call-method $comp actualizar_nft 1,[definición del nft que nos han regalado]

😎 ¿Qué tal? ¿Como te sientes después de 10 unidades de aprendizaje? Yo solo puede decir que para mi ha sido un verdadero honor haberte ayudado a emprender una ruta fantástica. El camino de la programación es un sendero de continuo aprendizaje de mucha practica e ilusión por logra vencer los retos que nos marcamos. Hoy puedes decir que conoces los fundamentos de Radix y Scrypto, puedes decir que puedes leer, ejecutar e incluso escribir tus propios componentes. Hoy puedes decir que te has iniciado en la programación funcional de la mano de Rust. De verdad que me emociona pensar que esta academia ha sido tu punto de partida para amar como yo la programación y el diseño de tecnología aplicada.

¿Te atreves? a seguir aprendiendo, e incluso realizando tus primeras prácticas. Solo cuando empieces por ti mismo a realizar tus propios algoritmos podrás decir que eres un programador junior en Scrypto.

¿Te apetece? Pues te invito a resolver una pequeña prueba de nivel que demuestre que has asimilado algunos conocimientos de programación y Scrypto. Solo así podrás acceder al nivel intermedio donde empezará lo verdaderamente emocionante!!!! No te arrepentirás...

Hagas lo que hagas... gracias, mil gracias por llegar hasta aquí. Solo las personas verdaderamente valientes terminan lo que empiezan y tu, sin duda, eres uno de ellos. De verdad gracias, para mi fue un placer.