En este tutorial, aprenderás a desarrollar una aplicación descentralizada simple, que realiza la compra y venta de un NFT. Múltiples instancias de esta aplicación representan un NFT Marketplace, donde los usuarios pueden acuñar, comprar y vender NFT. Inicialmente, un usuario acuñará el NFT y lo vinculará a un Stateful Smart Contract. Este contrato contendrá la lógica de implementación de las interacciones entre el vendedor y el comprador. Además, un contrato inteligente sin estado independiente es responsable de transferir el NFT al propietario legítimo. Además de la lógica de la aplicación, se tratan de presentar algunas prácticas para desarrollar dApps que pueden ser útiles para el desarrollo de otros contratos inteligentes. Esas prácticas tienen como objetivo hacer que el código sea más comprensible, fácilmente extensible e interactuable con otras aplicaciones.
Todo el código fuente de la aplicación está disponible en el siguiente repositorio de github.
Requisitos:
Debes instalar los siguientes paquetes en tu entorno de Python:
Además, debes crear un archivo config.yml. Allí deberás poner tus credenciales para conectarse con el cliente Algorand. Puedes leer este tutorial para obtener ayuda.
Background
Antes de leer el tutorial, se sugiere que veas el siguiente vídeo. En una aplicación web simple, se muestra cómo implementar e interactuar con NFTMarketplace. Este tutorial explicará todos los detalles que están sucediendo detrás de la escena.
Pasos
Definir la interfaz del Stateful Smart Contract
Implementación del Stateful Smart Contract
Implementación del Stateless Smart Contract
Servicios de comunicación
Implementación de la Testnet de Algorand
Definir la interfaz del Stateful Smart Contract
Un contrato inteligente con estado representa una aplicación que vive en la cadena de bloques de Algorand. Varias entidades pueden comunicarse con la aplicación a través de transacciones de llamada de aplicación. Actualmente, una aplicación de interfaz de usuario (una aplicación móvil, una aplicación web, CLI) crea las transacciones y las envía a la red. Sin embargo, en el futuro la mayor parte de la comunicación ocurrirá entre los contratos inteligentes. Esto indica que los contratos inteligentes iniciarán las transacciones de llamada de la aplicación. Se debe tener en cuenta que esta función aún no está disponible en la cadena de bloques de Algorand, se espera que esté disponible más adelante.
El siguiente documento sugiere buenas prácticas sobre cómo estructurar correctamente la comunicación con los contratos inteligentes con el estado. Introduce los conceptos de una interfaz y un método. Una sola aplicación implementa una o varias interfaces.
Una interfaz es un conjunto de métodos agrupados lógicamente. Un método es una sección de código destinada a ser invocada externamente con una transacción de llamada de aplicación.
Volviendo a la aplicación, se siguen esas sugerencias y se define primero un NFTMarketplaceInterface que contiene todos los métodos necesarios. Esos métodos definen la comunicación entre el contrato inteligente con estado y las aplicaciones externas.
La interfaz se representa como una clase de python abstracta. El contrato inteligente que quiera ajustarse a esta interfaz deberá implementar todos los métodos definidos en él. Es importante tener en cuenta que múltiples contratos inteligentes pueden ajustarse a esta interfaz, mientras que cada uno de los contratos inteligentes puede tener una lógica de implementación separada. A pesar de la diferente lógica de implementación, la comunicación externa seguirá siendo la misma.
Definimos cuatro métodos en NFTMarketplaceInterace:
initialize_escrow(escrow_address): inicializa la dirección de depósito que es responsable de transferir el NFT. Incluso podemos extraer esta función en una interfaz separada.
make_sell_offer(sell_price) - hace una oferta de venta para el NFT por un precio de venta particular. El contrato inteligente en este tutorial venderá directamente el NFT por el precio de venta particular, mientras que otro contrato que cumpla con este protocolo puede decidir vender el NFT a través de un proceso de licitación. Puedes consultar la siguiente solución que explica cómo puedes vender tu NFT a través de una subasta.
buy(): un usuario compra el NFT que está en la oferta de venta.
stop_sell_offer(): finaliza la oferta de venta actual para el NFT.
Implementación de un Smart Contract con estado
El siguiente paso es crear una clase concreta NFTMarketplaceASC1 que implemente NFTMarketplaceInterace. Esta clase contendrá toda la lógica de PyTeal para el contrato inteligente con estado. En esta sección, se explicará cada bloque de código del contrato inteligente NFTMarketplaceASC1.
Definiendo las constantes
Cada contrato inteligente con estado puede contener un estado local y global, conformando los datos de estado de la aplicación. Esos estados se representan como pares clave-valor. Es una buena práctica definir las claves como constantes. Esto hace que el código sea más legible y menos propenso a errores. Además, un esquema define el número de enteros y bytes en los pares clave-valor. Un contrato inteligente tiene un esquema global para el estado global y un esquema local para el estado local.
Para que el código sea más comprensible y fácil de mantener, es recomedable separar las constantes en varias clases:
Variables: define las claves para las variables globales utilizadas en el contrato inteligente. Esas variables representan el estado global de la aplicación.
LocalVariables: define las claves para las variables locales utilizadas en el contrato inteligente. El NFTMarketplaceASC1 no tiene ninguna variable local, por eso no tenemos esta clase.
AppMethods: define todos los métodos que permiten la comunicación entre el contrato inteligente y las aplicaciones externas. Cada constante en esta clase debe asignarse a un método único definido en una interfaz particular.
Variables
El contrato tiene seis variables globales, tres enteros y tres bytes.
escrow_address: contiene la dirección del contrato inteligente sin estado que es responsable de la transferencia del ASA, es decir, NFT.
asa_id: un identificador único para el NFT. El contrato inteligente es responsable de la compra y venta del NFT que coincide con esta identificación.
asa_price: representa el precio del NFT cuando una oferta de venta está activa. asa_owner: contiene la dirección del propietario actual del NFT. Sólo esta dirección puede hacer una oferta de venta para el NFT.
app_state: representa uno de los posibles estados de aplicación definidos en la clase AppState. Esos son los tres estados posibles:
not_initialized: el contrato inteligente se implementa en la red pero el depósito en garantía no se ha inicializado. Cuando el contrato se encuentra en este estado, solo se puede ejecutar el método initialize_escrow.
active: el asa_owner y el NFT se han vinculado al contrato. Sin embargo, el NFT aún no está a la venta, lo que significa que el propietario aún no ha iniciado una oferta de venta.
selling_in_progress: el propietario ha puesto a la venta el NFT. Cuando la aplicación está en este estado, un comprador puede ejecutar el método de compra y comprar el NFT.
app_admin: contiene la dirección del administrador. Solo esta dirección puede configurar escrow_address a través de una llamada al método initialize_escrow.
Métodos de aplicación
El contrato inteligente con estado implementa NFTMarketplaceInterace que contiene cuatro métodos. Por lo tanto, creamos una clase AppMethods que identifica de forma única esos métodos:
initialize_escrow: configura la dirección de depósito en garantía en el contrato inteligente. La lógica permite invocar el método sólo a través de una llamada de aplicación realizada por la dirección app_admin. Además, escrow_address se puede inicializar sólo una vez.
make_sell_offer: la dirección asa_owner puede invocar este método para establecer el NFT a la venta por un precio fijo de micro algos asa_price.
buy: cuando el NFT está a la venta, un comprador puede invocar este método y comprar el NFT.
stop_sell_offer: asa_owner puede invocar este método para detener la oferta de venta actual para el NFT.
Además, tenemos un método especial app_initialization. Llamamos a este método cuando implementamos por primera vez el contrato inteligente en la red mediante una transacción de creación de aplicaciones. Puede pensar en este método como el constructor del contrato inteligente, se llama sólo cuando la aplicación se implementa en la red.
Inicialización de la aplicación
Para implementar la aplicación en la red, necesitamos tres argumentos requeridos: la dirección que actualmente contiene el NFT, la dirección que tiene derechos de administrador en la aplicación y la ID del NFT. Pasamos asa_owner y app_admin como argumentos de la aplicación, mientras que asa_id se envía en el campo Foreign_assets de ApplicationCreateTxn.
Puede leer más sobre el campo de activos en la documentación de PyTeal. Esta es una característica realmente poderosa, puede permitir que los contratos inteligentes con estado busquen información sobre activos y saldos de cuentas. Puedes leer más al respecto en el siguiente enlace.
Inicializando el escrow
Después de la implementación exitosa de la aplicación, el administrador primero debe llamar al método initialize_escrow para configurar el contrato sin estado que será responsable de transferir el NFT. Esto se logra configurando la dirección del contrato inteligente sin estado como la dirección de recuperación del NFT.
Para tener una lógica persistente en la aplicación, también congelamos el NFT. Esto elimina la capacidad del usuario de poder enviar el NFT a una dirección arbitraria. Una forma de eliminar esta restricción sería agregar un método gift en el contrato inteligente NFTMarketplaceASC1, que permitirá que asa_owner pueda enviar el NFT de forma gratuita a cualquier persona.
Cuando llamamos a este método, queremos verificar si todas las propiedades en el NFT son como esperamos que sean.
No queremos que el NFT tenga una dirección de administrador. Con este requisito eliminamos la posibilidad de cambiar la dirección de recuperación del NFT. Queremos que la dirección de recuperación sea constante porque la usamos y almacenamos en el contrato inteligente con estado.
Comprobamos si la dirección de recuperación del NFT es igual a la dirección de depósito en garantía que pasamos como argumento.
Queremos que el NFT se congele. Si este no fuera el caso, uno podría enviar el NFT a alguna dirección mientras la variable de estado asa_owner no cambiaría.
En el código a continuación, uso el grupo de expresiones AssetParam, puede leer más sobre esto en la documentación oficial de PyTeal.
Después de la inicialización exitosa del escrow por parte del administrador, la aplicación pasa a un estado AppState.active. Ahora, el asa_owner podrá hacer ofertas de venta para el NFT.
Haciendo la oferta de venta
Vendemos el NFT a través de ofertas de venta. Sólo el propietario de NFT puede llamar a la aplicación para iniciar una oferta de venta. La transacción de llamada de la aplicación contiene dos argumentos: el nombre del método y el precio NFT. Esta llamada de aplicación actualizará el estado interno de AppState.active a AppState.selling_in_progress.