La blockchain de Stellar es una plataforma descentralizada, lo que significa que no depende de un solo servidor o entidad. En cambio, una red de computadoras (o nodos) trabaja en conjunto para verificar y registrar todas las transacciones de forma transparente. Esto hace que cada operación sea muy rápida (generalmente en 2 a 5 segundos) y que las tarifas sean mínimas (¡prácticamente simbólicas!). ⚡️🔒
Un poco de su historia
Stellar fue creada en 2014 por Jed McCaleb y Joyce Kim. Ambos ya tenían experiencia en el mundo de las criptomonedas (McCaleb, por ejemplo, estuvo involucrado en proyectos como Ripple y Mt. Gox). La idea era sencilla pero poderosa: crear una plataforma que facilitara el envío de dinero de forma global, especialmente para aquellos que no tienen acceso a los servicios bancarios tradicionales. Para lograrlo, se diseñó una red en la que se pudiera usar una moneda digital propia llamada Lumens (XLM), la cual se usa para pagar las pequeñas tarifas de transacción y para mantener el sistema en marcha. 🚀🌟
¿Por qué es importante?
Inclusión financiera: Stellar busca que más personas en el mundo puedan acceder a servicios financieros sin tener que depender de bancos tradicionales.
Velocidad y bajo costo: Con Stellar, las transacciones internacionales son casi instantáneas y con comisiones muy bajas.
Transparencia y seguridad: Al ser una red descentralizada, todas las operaciones se registran de manera pública y segura, lo que reduce el riesgo de fraudes.
Inicio
¡Bienvenido a Stellar Español! 🚀✨
Este es tu nuevo punto de referencia en español para todo lo relacionado con Stellar. Diseñado por desarrolladores para desarrolladores, nuestro portal se centra inicialmente en los contratos inteligentes de Soroban y en cómo interactuar con ellos a través de interfaces web intuitivas. 💻🤖
¿Qué encontrarás en Stellar Español?
Documentación de Soroban:
Descubre guías paso a paso, ejemplos de código y recursos técnicos para crear y desplegar contratos inteligentes en Stellar con Soroban. ¡Aprende a sacarle el máximo provecho a esta tecnología revolucionaria! 📚🚀
Interfaz web para contratos:
Explora cómo utilizar herramientas web diseñadas para interactuar de manera fácil y rápida con tus contratos inteligentes. Desde la gestión de cuentas hasta la ejecución de transacciones, te mostramos todo lo que necesitas saber. 🌐🖱️
Recursos en constante evolución:
Nuestro portal se actualizará continuamente. A futuro, complementaremos esta información con fundamentos de Stellar, un blog con noticias y análisis, y muchos temas afines al universo Stellar. 🔄📈
NVM (Node Version Manager) es una herramienta que permite instalar, gestionar y cambiar entre múltiples versiones de Node.js en un mismo sistema. Es útil para desarrolladores que trabajan en proyectos con diferentes versiones de Node.js, ya que facilita la instalación, actualización y cambio de versiones sin afectar otras configuraciones del sistema. Funciona en macOS, Linux y, con herramientas adicionales, en Windows.
Instalación
Mac y linux
Se ingresa a este link para ver las últimas versiones
Abrimos la terminal y ejecutamos lo siguiente:
Windows
Nos dirigimos al siguiente link y bajamos la última versión
Node Js
Node.js es un entorno de ejecución de JavaScript basado en el motor V8 de Chrome, que permite desarrollar aplicaciones escalables del lado del servidor con un modelo asíncrono y no bloqueante. Utiliza un solo hilo con eventos para manejar múltiples conexiones de manera eficiente, es altamente escalable y cuenta con un extenso ecosistema de paquetes a través de npm. Es ideal para APIs, microservicios, aplicaciones en tiempo real y servidores web.
Para nosotros es relevante la instalación de nodejs para la interacción web con los contratos de la red Stellar
Instalación
Nos dirijimos a para ver la última version LTS
Abrimos la terminal y ejecutamos:
Está es la última version el 12 de Junio de 2025
Fácil no? asi podemos instalar y gestionar varias versiones de Node js mucho más fácil 😊
Visual studio code
Visual studio code es el editor por excelencia para los desarrollos de contratos y programación en general
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env};
#[contracttype]
pub enum DataKey {
Counter(Address),
}
#[contract]
pub struct IncrementContract;
#[contractimpl]
impl IncrementContract {
/// Increment increments a counter for the user, and returns the value.
pub fn increment(env: Env, user: Address, value: u32) -> (Address, u32) {
// Requires user to have authorized call of the increment of this
// contract with all the arguments passed to increment, i.e. user
// and value. This will panic if auth fails for any reason.
// When this is called, Soroban host performs the necessary
// authentication, manages replay prevention and enforces the user's
// authorization policies.
// The contracts normally shouldn't worry about these details and just
// write code in generic fashion using Address and require_auth (or
// require_auth_for_args).
user.require_auth();
// This call is equilvalent to the above:
// user.require_auth_for_args((&user, value).into_val(&env));
// The following has less arguments but is equivalent in authorization
// scope to the above calls (the user address doesn't have to be
// included in args as it's guaranteed to be authenticated).
// user.require_auth_for_args((value,).into_val(&env));
// Construct a key for the data being stored. Use an enum to set the
// contract up well for adding other types of data to be stored.
let key = DataKey::Counter(user.clone());
// Get the current count for the invoker.
let mut count: u32 = env.storage().persistent().get(&key).unwrap_or_default();
// Increment the count.
count += value;
// Save the count.
env.storage().persistent().set(&key, &count);
// Return the count to the caller.
(user, count)
}
}
Explicación del código
Timelock
Cliente Stellar
El cliente de Stellar es super importante en la escritura de contratos en Stellar, ya que se encarga de la creación de la estructura básica y archivos, su compilación, despliegue y pruebas respectivas.
Una vez instalado en la terminal ejecutamos el comando stellar ( en la terminal).
Ejecución de prueba
Rust
Instalación de Rust en Mac/Linux y Windows
Rust es el lenguaje de propósito general en el cual se escriben los contratos para la red de Stellar a continuación un paso a paso para su instalación.
Abre tu aplicación de terminal (Terminal.app en macOS o la terminal de tu distribución Linux).
Paso 2: Descargar e instalar Rust con rustup
Este comando descargará un script y te guiará por el proceso de instalación. Generalmente se recomienda usar la opción predeterminada para instalar la versión estable.
Paso 3: Configurar el entorno
Una vez finalizada la instalación, cierra y vuelve a abrir la terminal o ejecuta:
Esto garantiza que el directorio de Cargo (donde se instalan las herramientas, por ejemplo, rustc, cargo y rustup) se añada a tu variable de entorno PATH.
Paso 4: Verificar la instalación
Comprueba que Rust se instaló correctamente ejecutando:
Paso 5: Instalar un compilador de C (si es necesario)
Rust requiere un enlazador para compilar correctamente:
En macOS: Instala las herramientas de línea de comandos de Xcode
En Linux (por ejemplo, Ubuntu): Asegúrate de tener instalado el paquete build-essential
Fedora
Arch
Paso 6: Agregar lo siguiente a Rust, para poder generar archivos WebAssembly
Ejecutamos lo siguente para saver laversión de Rust:
Si el rust es anterior a la versión 1.85
Si el rust es igual o mayor a la versión 1.85
Instalación en Windows
Paso 1: Descargar el instalador
Visita la y descarga el instalador para Windows (rustup-init.exe). Elige la versión de 32 o 64 bits según tu sistema.
Paso 2: Ejecutar el instalador
Ejecuta el archivo descargado y sigue las instrucciones en pantalla. Durante la instalación, es posible que se te pida instalar las Visual Studio C++ Build Tools (necesarias para compilar Rust en Windows). También debemos instalar el windows SDK acorde a la versión de windows que tengamos.
Paso 3: Verificar la instalación
Abre una terminal (CMD o PowerShell) y ejecuta:
Paso 4: Agregamos el paquete que genera el WebAsembly
Ejecutamos lo siguente para saver laversión de Rust:
Si el rust es anterior a la versión 1.85
Si el rust es igual o mayor a la versión 1.85
Segundos pasos
Una vez visto lo básico del cliente, miremos lo pertinente enfocado a los contratos inteligentes, como es:
Creación de un contratos.
Compilación de contratos.
Despliegue.
Ejecución de contrato.
Creación de un contrato
Syntaxis:
stellar contract init "nombre_contrato"
stellar contract init hola_mundo
Estructura y archivos creados
Dentro de lib.rs está el contrato por defecto
Compilación del contrato
Se ejecuta el siguiente comando:
Podemos ver que se creo una carpeta llamada release, más internamente vemos que está creado el archivo hello_world.wasm
💡el nombre del archivos en web assembly es el que automáticamente se pone dentro del archivo Cargo.toml
Despliegue del contrato
Mac/linux
Windows
Felicidades, ya tienes el contratro deplegado en testnet 🥳
Interactuando con los contratosSintaxisstellar contract invoke--id "contrato"--source "identidad"--network testnet--"función"--"parametro" "dato del parámetro"
Linux y MAC
Windows
El resultado es:
Primeros pasos
El cliente de Stellar es super importante en la escritura de contratos en Stellar, ya que se encarga de la creación de la estructura básica y archivos, su compilación, despliegue y pruebas respectivas.
En esta primera pare veremos lo siguiente:
Creación de una cuenta (identidad)
Obtención de la llave pública (address)
helloworld
Clásico hello world autogenerado por el cliente de Stellar
Nos vamos a una ruta donde queramos crear nuestro proyecto y ponemos el siguiente comando:
Como podemos observar el proyecto como tal se llama helloworld y en la carpeta contracts se creó una carpeta llamada hello-world, este contrato se genera por defecto cuando creamos un proyecto pero no indicamos como se llama el contrato como tal
Análisis del código generado
1 Declaración de No-Standard Library
brew install stellar-cli
lsb_release -a
# Caso de x86_64
wget https://github.com/stellar/stellar-cli/releases/download/v22.8.1/stellar-cli-22.8.1-x86_64-unknown-linux-gnu.tar.gz
# Caso de arch64
wget https://github.com/stellar/stellar-cli/releases/download/v22.8.1/stellar-cli-22.8.1-aarch64-unknown-linux-gnu.tar.gz
#caso x86_64
tar -xvf stellar-cli-22.8.1-x86_64-unknown-linux-gnu.tar.gz
# Caso de arch64
tar -xvf stellar-cli-22.8.1-aarch64-unknown-linux-gnu.tar.gz
Acá vemos que la cuenta viene con 10.000 lumens, lo suficiente para seguir con los ejercicios
Para los amantes de la consola. 😉
Averiguamos en testnet cual es el id de XLM stellar contract id asset --asset native --network testnet
obtenemos esta respuesta: CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC
Obtenemos la llave pública de la identidad developer
stellar keys address developer
obtenemos:
GD45T2VRMYBSGRHLMVTS4QQZVXAM7WD6IYWKYRS7DFURRR2EKWCNGOAN
Como es de observar no hemos creado la identidad developer1 por lo tanto ejecutamos:
stellar keys generate --global developer1 --network testnetstellar keys address developer1
obtenemos la siguiente llave pública :
Esta directiva le indica al compilador que el contrato se compilará sin la biblioteca estándar de Rust. Esto con el fin de hacer lo más liviano posible.
2 Importación de Elementos del SDK
use soroban_sdk::{contract, contractimpl, vec, Env, String, Vec};
contract
Es un macro atributo que se aplica a una estructura para marcarla como el contenedor del contrato. Esto permite que el entorno de Soroban reconozca el contrato y genere la metadata necesaria.
contractimpl
Es otro macro atributo que se utiliza en el bloque impl para indicar que las funciones definidas en ese bloque serán exportadas y podrán ser invocadas desde el entorno de ejecución del contrato.
vec
Es un macro propio de Soroban que crea un vector (colección
dinámica) adaptado al entorno de contratos. Permite inicializar vectores de forma similar a vec! de Rust, pero considerando las particularidades del entorno.
Env
Es una estructura que representa el entorno en el que se ejecuta el contrato. A través de ella se accede a funciones esenciales como el almacenamiento, emisión de eventos, logs, etc.
String
Es una implementación propia del tipo cadena optimizada para entornos sin estándar (no_std) y adaptada a las limitaciones de ejecución de los contratos inteligentes en Soroban.
Vec
Es el tipo vector (colección dinámica) proporcionado por Soroban, similar al Vec estándar de Rust pero diseñado para trabajar de forma segura y eficiente en contratos inteligentes.
Definición del Contrato
Implementación de las Funciones del Contrato
La anotación #[contractimpl]
sobre el bloque impl Contract indica que las funciones definidas serán expuestas para que puedan ser llamadas por el entorno de Soroban.
Función hello:
Firma:
Recibe dos argumentos:
env: Env: El entorno de ejecución, que se usa para acceder a funcionalidades propias del runtime de Soroban.
to: String: Una cadena que se pasa como parámetro.
Retorna un Vec, es decir, un vector de cadenas.
Cuerpo de la función:
Se usa el macro vec! propio de Soroban para crear un vector.
Elementos del vector:
&env: Se pasa el entorno para que el macro pueda asignar memoria o gestionar la colección en el contexto de Soroban.
String::from_str(&env, "Hello"): Convierte el literal "Hello" en un objeto de tipo String propio de Soroban, usando el entorno env para la conversión.
to: Se agrega la cadena que se recibió como argumento.
Al no tener ";" al final es el valor a retornar que se especifica en la función
Compilación:
Despliegue:
Mac/Linux
Windows
Pruebas del contrato
Linux y MAC
Windows
Obtenemos lo siguiente:
Estructuras de archivos generados
archivo lib.rs dentro de la carpeta hello-world
Estructura de archivos generada bajo la carpeta release
Git
🗂️ ¿Qué es un Repositorio de Código?
Imagínate que estás escribiendo un libro con tus amigos. Necesitas un lugar donde guardar todas las páginas, ver quién escribió qué, y poder volver a versiones anteriores si algo sale mal. ¡Un repositorio de código es exactamente eso, pero para tu código! 📚✨
Un repositorio (o "repo" como le dicen los cool 😎) es como una carpeta mágica que:
💾 Guarda todo tu código de forma segura
📝 Recuerda cada cambio que haces (¡como una máquina del tiempo!)
👥 Permite que varios programadores trabajen juntos sin hacer un desastre
🔄 Te deja volver atrás si rompes algo (¡todos lo hacemos!)
🎯 ¿Para qué sirve exactamente?
🕰️ Control de versiones
Es como tener un "deshacer" súper poderoso. Cada vez que guardas cambios, Git crea una "foto" de tu proyecto. Si algo se rompe, ¡simplemente vuelves a una foto anterior!
👥 Colaboración
Imagina que tú y tus amigos están pintando un mural gigante. Sin organización, todos pintarían encima del trabajo de otros. ¡Un repositorio evita este caos digital!
🛡️ Respaldo automático
Tu código vive en la nube, así que aunque tu computadora se rompa, tu trabajo está seguro. ¡Es como tener un backup automático súper inteligente!
📖 Documentación
Puedes guardar instrucciones, notas y explicaciones junto con tu código. ¡Tu futuro yo te lo agradecerá!
🌟 Las Plataformas Más Populares
🐙 GitHub - El Rey de los Repos
GitHub es como el Instagram de los programadores. Es donde vive el código más cool del mundo:
🔥 Más de 100 millones de repositorios
🆓 Gratis para proyectos públicos
🤝 Perfecto para mostrar tu trabajo a empleadores
¿Por qué es genial para principiantes?
Interface súper amigable 😊
Toneladas de tutoriales y documentación
GitHub Pages (¡puedes hacer sitios web gratis!)
🦊 GitLab - El Todoterreno
GitLab es como el Swiss Army knife del desarrollo:
🛠️ No solo guarda código, también lo prueba y despliega
🏢 Perfecto para empresas
🔧 Herramientas integradas de CI/CD (no te preocupes, lo aprenderás después)
🪣 GitBucket - El Independiente
GitBucket es como el rebelde cool:
🆓 100% gratis y de código abierto
🏠 Lo instalas en tu propio servidor
🎮 Perfecto para aprender sin depender de otros
💻 Git en Consola - Tu Nueva Herramienta Favorita
Git es como el motor que hace funcionar todos estos repositorios. La consola (terminal) es donde ocurre la magia real. ¡No te asustes, es más fácil de lo que parece! 🪄
📥 ¿Cómo instalar Git?
🍎 Para Mac (macOS)
Siguiendo las instrucciones de:
Opción 1:
Si tienes instalado brew, se corre la instrucción en consola
Opción 2:
Si tienes instalado MacPorts, se corre la instrucción en consola
🐧 Para Linux
Ubuntu/Debian (Los más comunes) 📦
Fedora/Red Hat 🎩
Arch Linux (Para los aventureros) 🏔️
🪟 Para Windows
📥 Ve a
🖱️ Descarga el instalador
🎉 ¡Tu Primer Paso!
Una vez instalado, abre tu terminal y escribe:
Si ves algo como git version 2.x.x, ¡felicidades! 🎊 Git está listo para usar.
⚙️ Configuración inicial (Súper importante)
Dile a Git quién eres:
¡Y listo! Ya estás preparado para comenzar tu aventura en el mundo de Git y los repositorios. 🚀
get set helloworld
Contrato típico donde podemos poner y extraer un mensaje
Nos vamos a una ruta donde queramos crear nuestro proyecto y ponemos el siguiente comando:
Borramos el contrato generado por defecto y lo sustituimos por el siguiente código
Análisis del código:
Importaciones del SDK de Soroban
Symbol y symbol_short:
Se usan para trabajar con identificadores cortos y
optimizados en el entorno de Soroban.
Symbol Es un string de 32 caracteres, viene ocupa 64 bit (a-z A-Z 0-9)
symbol_short! Es un string de 9 caracteres (a-z A-Z 0- 9)
String:
Es una versión adaptada del tipo cadena de texto para el entorno sin estándar (no_std) y específicamente optimizada para contratos inteligentes en Soroban.
Definición de una Constanteconst MESSAGE: Symbol = symbol_short!("Message");
🛠 Explicación de las funciones
Todas las funciones trabajan con env: Env, que proporciona acceso al almacenamiento y otras funcionalidades del entorno Soroban.
1️⃣ set_message(env: Env, message: String)
✅ Guarda un mensaje en el almacenamiento.
📌 Paso a paso:
Toma como entrada un mensaje de tipo String.
Usa env.storage().instance().set() para almacenar el mensaje en la blockchain usando la clave MESSAGE.
2️⃣ get_message(env: Env) -> String
✅ Obtiene el mensaje almacenado.
📌 Paso a paso:
Intenta recuperar el valor asociado a MESSAGE en el almacenamiento.
Si la clave no existe, usa .unwrap_or() para devolver un mensaje por defecto: "Default Message".
Devuelve el mensaje almacenado o el mensaje por defecto.
📌 Resumen
Este contrato inteligente permite almacenar y recuperar un mensaje de la blockchain de Soroban:
set_message(): Guarda un mensaje en la blockchain.
get_message(): Obtiene el mensaje guardado o un mensaje por defecto si aún no se ha establecido.
Compilación del contrato
la compilación y creación del webassembly es con el siguiente comando:
stellar contract build
Despliegue del contratro
Para Mac y Linux el salto de línea es con el caractér " \" y en Windows con el caracter " ´ "
Reemplaze el simbolo * por el respectivo caractér de salto de linea a su sistema operativo.
Pruebas del contratro
Para linux y Mac el salto de línea de la instrucción es con el caracter " \ " para Windows con el caracter " ` "
Invocación de la función set_message
Invocación de la función get_message
Pruebas
🧪 Guía Completa de Testing en Stellar Soroban
🔍 ¿Por qué necesitamos tests? (detallado)
🛡️ Prevención de bugs críticos: detecta errores en lógica de negocio antes de deployment que podrían resultar en pérdida de fondos o estados inconsistentes. 🎯 Validación de edge cases: simula condiciones extremas (overflow, underflow, fondos insuficientes) para asegurar comportamiento robusto. 💰 Protección de inversión: evita costos de re-deployment y pérdida de confianza por bugs en mainnet. 🔄 Refactoring seguro: permite cambiar implementaciones manteniendo garantías de comportamiento. 🧪 Simulación de ataques: prueba vectores de ataque conocidos (reentrancy, front-running, privilege escalation) de forma controlada.
🌟 ¿Qué ofrece Soroban para testing? (explicación técnica)
📦 soroban_sdk::testutils: módulo con herramientas para crear entornos de prueba aislados y mockear comportamientos. 🏗️ Env::default(): crea un entorno de testing limpio con ledger simulado, storage independiente y control total sobre el tiempo. 👤 MockAuth: permite simular firmas y autorizaciones sin claves privadas reales. 📊 Budget tracking: monitorea consumo de CPU y memoria durante tests para optimizar gas costs. 🎭 Address generation: crea direcciones determinísticas para tests reproducibles.
🔧 Herramientas y conceptos clave (más allá de la sintaxis)
Env::default() — qué hace y efectos
Crea un entorno aislado donde cada test ejecuta independientemente
Simula un ledger con timestamp, sequence numbers y storage limpio
Permite control sobre authorizaciones y mock data
MockAuth patterns
MockAuth::authorize(): simula que una dirección específica autorizó la transacción
MockAuthInvoke: para casos complejos con múltiples contratos
Cuidado: solo para testing; en mainnet require_auth() usa firmas reales
Testing strategies
Unit tests: funciones individuales con inputs específicos
Integration tests: flujos completos entre múltiples funciones
Fuzzing: inputs aleatorios para encontrar casos no contemplados
Error handling validation
Usa should_panic o assert!(result.is_err()) para validar fallos esperados
Verifica que error codes específicos se generen correctamente
Simula condiciones de falla (fondos insuficientes, permisos incorrectos)
📋 Estrategias y patrones prácticos (con por qué y cuándo usarlas)
Arrange-Act-Assert pattern
Arrange: configura estado inicial (addresses, balances, roles)
Act: ejecuta la función bajo prueba
Assert: verifica resultados esperados
Test data management
Usa direcciones determinísticas para reproducibilidad
Crea helpers para setup común (inicializar contratos, crear usuarios)
Mantén tests independientes — no dependas de estado previo
Mocking strategies
Mock external contracts durante unit tests
Simula condiciones de red (timeouts, failures) en integration tests
Usa MockAuth para probar flujos sin manejar claves privadas
Performance testing
Mide budget consumption para optimizar gas costs
Prueba límites de storage y memoria
Valida que operaciones batch no excedan límites de transacción
🚀 ¿Qué es una Cripto Wallet? Una cripto wallet (billetera de criptomonedas) es como tu billetera digital personal para guardar, enviar y recibir criptomonedas. A diferencia de una billetera física qu
🚀 ¿Qué es una Cripto Wallet?
Una cripto wallet (billetera de criptomonedas) es como tu billetera digital personal para guardar, enviar y recibir criptomonedas. A diferencia de una billetera física que guarda dinero en efectivo, una cripto wallet almacena las claves privadas que te dan acceso y control sobre tus activos digitales en la blockchain.
Las wallets NO guardan las criptomonedas físicamente - tus monedas siempre están en la blockchain. Lo que realmente guarda tu wallet son:
🔐 Clave privada: Tu "contraseña maestra" que demuestra que eres el dueño
🆔 Clave pública: Tu "dirección" donde otros pueden enviarte criptomonedas
📊 Historial de transacciones: Registro de todas tus operaciones
🌐 Wallets de Navegador (Browser Wallets)
Las wallets de navegador son extensiones que se instalan directamente en tu navegador web (Chrome, Firefox, Edge, etc.). Son muy populares porque:
✅ Ventajas:
🚀 Fácil acceso: Siempre disponibles mientras navegas
🔗 Conexión directa: Se conectan automáticamente con aplicaciones web (DeFi, NFTs, etc.)
💻 Interfaz familiar: Funcionan como cualquier extensión de navegador
⚡ Transacciones rápidas: Confirmas operaciones con un clic
⚠️ Consideraciones:
🔒 Dependen de la seguridad de tu navegador
💻 Solo funcionan en el dispositivo donde están instaladas
🌐 Requieren conexión a internet
🌟 Freighter Wallet: Tu Puerta de Entrada a Stellar
Freighter es la wallet de navegador más popular y confiable para la red Stellar ⭐. Desarrollada por el Stellar Development Foundation (SDF), es la herramienta oficial recomendada para interactuar con el ecosistema Stellar.
🎯 ¿Qué es Stellar?
Stellar es una blockchain ultrarrápida y económica, diseñada especialmente para:
💸 Pagos internacionales: Transferencias casi instantáneas y con comisiones de centavos
🔍 Verás el ícono de Freighter en tu barra de herramientas
Paso 3: Configuración Inicial ⚙️
🚀 Haz clic en el ícono de Freighter para abrirla
🆕 Selecciona "Create new wallet" (Crear nueva wallet)
🔐 Crea una contraseña segura para proteger tu wallet localmente
📝 ¡MUY IMPORTANTE! Guarda tu frase de recuperación (seed phrase) de 12 palabras:
✍️ Escríbela en papel
🔒 Guárdala en un lugar seguro
❌ NUNCA la compartas con nadie
Paso 4: ¡Listo para Usar! 🎉
Una vez configurada, podrás:
👀 Ver tu dirección pública para recibir XLM
💰 Consultar tu balance de XLM y otros activos Stellar
📤 Enviar transacciones a otras direcciones
🔗 Conectarte a DApps del ecosistema Stellar
🛡️ Consejos de Seguridad Importantes
🔒 Protege tu Frase de Recuperación:
✍️ Escríbela a mano, nunca digitalmente
📦 Guárdala en múltiples lugares seguros
❌ Nunca la compartas ni la ingreses en sitios web sospechosos
🚫 Ningún soporte técnico legítimo te la pedirá
🌐 Navegación Segura:
🔍 Siempre verifica las URLs de las DApps
🛡️ Solo conecta tu wallet a sitios confiables
📖 Lee cuidadosamente antes de firmar transacciones
🌟 ¿Por qué Elegir Stellar y Freighter?
Ventajas de Stellar:
⚡ Transacciones súper rápidas: 3-5 segundos
💵 Comisiones mínimas: Menos de $0.01 por transacción
🌍 Alcance global: Diseñada para pagos internacionales
🏗️ Ecosistema creciente: Cada vez más DApps y servicios
Ventajas de Freighter:
🏆 Oficial: Respaldada por Stellar Development Foundation
🔄 Actualizaciones constantes: Siempre compatible con las últimas funcionalidades
🆓 Completamente gratuita: Sin costos ocultos
👥 Comunidad activa: Soporte y documentación excelente
¡Freighter Wallet es tu mejor compañera para explorar el increíble mundo de Stellar! 🚀⭐ Con transacciones ultrarrápidas y económicas, estarás listo para aprovechar todo el potencial de esta innovadora blockchain.
Tipos de datos primitivos
Tipos de datos que no necesitan ninguna importación, se manejan primitivamente en el contrato
Nos vamos a una ruta donde queramos crear nuestro proyecto y ponemos el siguiente comando:
Borramos el contrato generado en lib.rs y ponemos el siguiente código
📌 Explicación general
Usa #![no_std], lo que indica que no usa la biblioteca estándar de Rust.
Importa módulos de soroban_sdk, necesarios para ejecutar el contrato en Soroban.
Define un contrato llamado DataTypesContract.
Implementa varias funciones que realizan suma de números con diferentes tipos (u32, i32, u64, i64, u128, i128).
Incluye una función para negar un booleano.
🛠 Explicación de las funciones
Todas las funciones reciben _env: Env, aunque no lo utilizan directamente. Es un requisito en Soroban para la ejecución de contratos.
1️⃣ Funciones de suma
Cada una de estas funciones suma dos números del mismo tipo y retorna el resultado.
📌 Ejemplo de uso:
Si llamamos a add_u32(5, 10), devuelve 15.
Si llamamos a add_i64(-20, 50), devuelve 30.
2️⃣ negate_bool(_env: Env, flag: bool) -> bool
✅ Invierte un valor booleano (true ↔ false).
📌 Paso a paso:
Recibe un valor booleano (flag).
Usa el operador lógico ! para invertirlo.
Retorna el valor opuesto.
📌 Ejemplo de uso:
Si llamamos negate_bool(true), devuelve false.
Si llamamos negate_bool(false), devuelve true.
📌 Resumen
Este contrato define funciones matemáticas y lógicas en Soroban:
Funciones de suma (add_u32, add_i32, add_u64, add_i64, add_u128, add_i128): Permiten sumar números de diferentes tamaños y signos.
Función booleana
Compilación del contrato
Ejecutamos lo siguiente:
Despliegue del contrato
Para Mac y Linux el salto de línea es con el carácter " \" y en Windows con el carácter " ´ "
Reemplaze el simbolo * por el respectivo carácter de salto de linea a su sistema operativo.
Pruebas del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
Función add_u32
Función negate_bool
counter
Ejemplo de un contador
Para crear el contrato ejecutamos lo siguiente:
Dentro de la carpeta counter, en el archivo lib.rs borramos todo el código y ponemos el siguiente código:
Básicamente es un contrato con un contador y 3 funciones:
Análisis del código:
Importaciones del SDK de Soroban
Symbol y symbol_short:
Se usan para trabajar con identificadores cortos y
optimizados en el entorno de Soroban.
Symbol Es un string de 32 caracteres, viene ocupa 64 bit (a-z A-Z 0-9)
symbol_short! Es un string de 9 caracteres (a-z A-Z 0- 9)
🛠 Explicación de las funciones
Todas las funciones trabajan con env: Env, que proporciona acceso al almacenamiento y otras funcionalidades del entorno Soroban.
1️⃣ add_to_counter(env: Env, increment: u32)
✅ Suma un número arbitrario al contador.
📌 Paso a paso:
Recupera el valor actual de COUNTER desde el almacenamiento, usando .unwrap_or(0), lo que garantiza que si la clave no existe, se toma como 0.
Suma el increment recibido como parámetro.
2️⃣ inc_counter(env: Env)
✅ Incrementa el contador en 1.
📌 Paso a paso:
Esta función es similar a add_to_counter, pero en lugar de recibir un parámetro, simplemente suma 1 al contador.
3️⃣ get_counter(env: Env) -> u32
✅ Devuelve el valor actual del contador.
📌 Paso a paso:
Recupera el valor de COUNTER del almacenamiento.
Si la clave no existe, devuelve 0.
Retorna el valor del contador.
📌 Resumen
Este contrato mantiene un contador en la blockchain de Soroban con:
add_to_counter(): Incrementa el contador en un valor específico.
inc_counter(): Incrementa el contador en 1.
get_counter(): Obtiene el valor actual del contador.
Compilación del contrato:
Despliegue del contratro
Para Mac y Linux el salto de línea es con el carácter " \" y en Windows con el carácter " ´ "
Reemplaze el simbolo * por el respectivo carácter de salto de linea a su sistema operativo.
Pruebas del contratro
Para linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
Invocación de la función add_to_counter
Invocación de la función inc_counter
Invocación de la función get_counter
String
Tipo de dato string que viene dentro de la libreria soroban
Los strings pueden pasarse a contratos y almacenes utilizando el tipo Bytes.
Tenga en cuenta que los bytes contenidos en los strings no se ajustan necesariamente a ninguna codificación de texto estándar, como ASCII o Unicode UTF-8. Son bytes sin interpretar. Se trata de bytes sin interpretar, y los usuarios que esperen una codificación determinada deberán aplicarla manualmente.
Nos vamos a una ruta donde queramos crear nuestro proyecto y ponemos el siguiente comando:
Borramos el contrato generado en lib.rs y ponemos el siguiente código
📌 Ejemplo:
Si llamamos concatenate("Hola", "Mundo"), devuelve ["Hola", "Mundo"].
🔹 Nota:
Soroban aún no soporta concatenación directa con +, por lo que se usa un vector para unir los valores.
📌 Resumen
Este contrato proporciona funciones útiles para manipular strings en Soroban:
create_string(): Retorna el string recibido.
get_length(): Retorna la longitud del string.
compare_strings(): Compara si dos strings son iguales.
is_empty(): Verifica si un string está vacío.
concatenate(): Une dos strings en un vector (ya que la concatenación directa no está disponible en Soroban).
Este contrato permite manejar textos dentro de la blockchain de manera eficiente. 🚀
Compilación del contrato
Ejecutamos lo siguiente:
Despliegue del contrato
Para Mac y Linux el salto de línea es con el carácter " \" y en Windows con el carácter " ´ "
Reemplaze el simbolo * por el respectivo carácter de salto de linea a su sistema operativo.
Ejecución de prueba
Pruebas del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
Funcióncreate_string
Ejecución de prueba
Función get_length
Ejecución de prueba
Función compare_strings
ejecución de prueba
Función is_empty
Ejecución de prueba
Función concatenate
Ejecuciónd e prueba
Sentencias condicionales
📚 Teoría sobre if, else y match en Rust
Rust ofrece dos estructuras de control clave para la toma de decisiones: if-else y match.
✅ if-else
Se usa para tomar decisiones en función de una condición booleana.
📌 Sintaxis básica:
📌 Ejemplo en Rust:
📌 Cuándo usar if-else
Cuando se necesita verificar una condición booleana única.
Cuando solo hay dos opciones (true o false).
✅ match
Se usa para comparar un valor con múltiples posibles casos, similar a switch en otros lenguajes.
📌 Sintaxis básica:
📌 Ejemplo en Rust:
📌 Cuándo usar match
Cuando hay varias condiciones posibles.
Cuando se trabaja con enum, strings o patrones complejos.
Código en Soroban
Nos vamos a una ruta donde queramos crear nuestro proyecto y ponemos el siguiente comando:
Borramos el contrato generado en lib.rs y ponemos el siguiente código
📌 Explicación general del código
Usa #![no_std], lo que significa que no usa la biblioteca estándar de Rust.
Importa módulos de soroban_sdk, necesarios para escribir contratos inteligentes en Soroban.
Define una estructura de contrato
🛠 Explicación de las Funciones
1️⃣ check_number
Descripción:
Determina si un número es positivo o negativo.
Mecanismo:
Recibe un número entero (i32) como entrada.
Emplea una estructura condicional if-else para evaluar el valor de num.
Si num es mayor o igual a 0, retorna un String
2️⃣ check_role
Descripción:
Asigna permisos según el rol del usuario.
Mecanismo:
Define tres roles fijos utilizando el macro symbol_short!:
Admin
User
📌 Resumen General
Este contrato inteligente demuestra el uso de condicionales y estructuras de control en Soroban. A través de check_number se evalúa la positividad o negatividad de un número, mientras que check_role asigna permisos basándose en roles específicos.
Compilación del contrato
Ejecutamos lo siguiente:
Despliegue del contrato
Para Mac y Linux el salto de línea es con el carácter " \" y en Windows con el carácter " ´ "
Reemplaze el simbolo * por el respectivo carácter de salto de linea a su sistema operativo.
Pruebas del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
Función get_length
Función check_role
Option
A pesar de ser un tipo Enum, dado su uso lo vamos a tratar aparte para una mejor claridad
En Rust, el tipo Option es una forma segura y explícita de representar que un valor puede estar presente o no. Es como decir "puede haber algo o no" 🔍. En lugar de usar null (que puede causar errores difíciles de encontrar), Rust usa Option para obligarte a manejar ambos casos.
La definición de Option es muy sencilla:
Some(valor): Indica que sí hay un valor. 😊
Loops
📚 Loops en Rust: Repetición de código de forma eficiente
En programación, los loops (o bucles) permiten repetir la ejecución de un bloque de código automáticamente. Son útiles para realizar tareas repetitivas, recorrer colecciones de datos o ejecutar acciones hasta que se cumpla una condición.
⚠️ En contratos inteligentes, es importante que los loops tengan límites claros para evitar ciclos infinitos y un consumo excesivo de gas.
✅ loop (bucle infinito controlado) Se ejecuta indefinidamente hasta que se encuentra una condición de salida.
📌 Sintaxis básica:
📌 Ejemplo en Rust:
📌 Cuándo usar loop Cuando no se sabe cuántas iteraciones se necesitarán y se desea un control manual de salida.
stellar contract init strings --name strings
#![no_std]
use soroban_sdk::{contract, contractimpl, vec, Env, String, Vec};
#[contract]
pub struct Strings;
#[contractimpl]
impl Strings {
// Crear un nuevo string
pub fn create_string(env: Env, text: String) -> String {
text
}
// Obtener la longitud de un string
pub fn get_length(env: Env, text: String) -> u32 {
text.len()
}
// Comparar dos strings
pub fn compare_strings(env: Env, text1: String, text2: String) -> bool {
text1 == text2
}
// detecta si un strig está vacio
pub fn is_empty(env: Env, text1: String) -> bool {
text1.is_empty()
}
/*está pendiente en las funcioalidades de soroban_sdk::String la funcionalidad directa
de apend o una cobre carga del operador +, por lo tanto usaremos un vector */
pub fn concatenate(env: Env, text1: String, text2: String) -> Vec<String> {
vec![&env, text1, text2]
}
}
#![no_std]
use soroban_sdk::{contract, contractimpl, symbol_short, Env, String, Symbol};
#[contract]
pub struct ConditionalSt;
#[contractimpl]
impl ConditionalSt {
// Función que usa if-else para determinar si un número es positivo o negativo.
pub fn check_number(env: Env, num: i32) -> String {
if num >= 0 {
String::from_str(&env, "Numero positivo")
} else {
String::from_str(&env, "Numero negativo")
}
}
// Función que usa match para asignar permisos según el rol del usuario.
pub fn check_role(env: Env, role: Symbol) -> String {
let admin: Symbol = symbol_short!("Admin");
let user: Symbol = symbol_short!("User");
let guest: Symbol = symbol_short!("Guest");
match role {
admin => String::from_str(&env, "Acceso total"),
user => String::from_str(&env, "Acceso limitado"),
guest => String::from_str(&env, "Solo lectura"),
_ => String::from_str(&env, "Rol no reconocido"),
}
}
}
Imagina que queremos dividir dos números, pero tenemos que evitar dividir por cero. Podemos usar Option para devolver un resultado solo cuando la división tiene sentido:
Contratos inteligentes ejemplo:
Abrimos la consola en la ruta donde deseamos crear el proyecto y ejecutamos.
Borramos todo el código y ponemos lo siguiente:
Explicación del contrato
📌 Estructuras y Tipos del Contrato
Atributo #![no_std]
Indica que el contrato no utiliza la biblioteca estándar de Rust, lo cual es común en entornos embebidos o en contratos inteligentes para reducir dependencias y adaptarse a restricciones de recursos.
Macros de Contrato#[contract] y #[contractimpl]
Estas macros son proporcionadas por el soroban_sdk y se usan para marcar:
#[contract]: Define la estructura principal del contrato.
#[contractimpl]: Implementa la lógica de negocio del contrato.
Para más detalles, consulta la documentación de contratos Soroban.
🛠 Funciones del Contrato
1️⃣ set_message
Descripción:
Establece (o elimina) un mensaje en el almacenamiento del contrato. Si se pasa Some(mensaje), se guarda el mensaje; si se pasa None, se elimina el mensaje existente en el storage.
Mecanismo:
Se define una clave (key) con el símbolo "msg".
Se utiliza la estructura de control match para evaluar el parámetro message:
Caso Some(msg): Se guarda el mensaje en el storage.
Caso None: Se elimina el mensaje del storage.
2️⃣ get_message
Descripción:
Recupera el mensaje almacenado en el storage del contrato. Devuelve Some(mensaje) si existe un mensaje, o None si no se ha establecido ninguno.
Mecanismo:
Se define la misma clave "msg" con un Symbol.
Se accede al storage del contrato para obtener el mensaje asociado a esa clave.
3️⃣ greet
Descripción:
Genera un saludo utilizando un parámetro opcional. Si se proporciona un nombre (Some(nombre)), se saluda a esa persona; si no se proporciona ningún nombre (None), se saluda por defecto a "amigo".
Mecanismo:
Se utiliza match para evaluar el parámetro name:
Caso Some(n): Devuelve el nombre proporcionado como saludo.
Caso None: Devuelve el saludo predeterminado "¡Hola, amigo! 😃" mediante String::from_str.
📌 Resumen General
Este contrato inteligente demuestra cómo utilizar el tipo Option para gestionar valores que pueden estar presentes o no. Permite:
Establecer o eliminar datos en el storage mediante la función set_message.
Recuperar datos del storage con get_message, retornando un valor opcional.
Generar respuestas predeterminadas en caso de ausencia de valor mediante la función greet.
Compilación del contrato
Ejecutamos lo siguiente:
Despliegue del contrato
Para Mac y Linux el salto de línea es con el carácter " \" y en Windows con el carácter " ´ "
Reemplaze el simbolo * por el respectivo carácter de salto de linea a su sistema operativo.
Ejecución de prueba
Pruebas del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
Función set_message:
Ejecución de prueba
Función get_message:
Ejecución de prueba
Función get_message:
Vamos a hacer que se ejecute el código por la opción none
Prueba de ejecución
✅ while (bucle condicional) Ejecuta el bloque de código mientras la condición sea verdadera.
📌 Sintaxis básica:
📌 Ejemplo en Rust:
📌 Cuándo usar while Cuando no se conoce el número exacto de iteraciones, pero se tiene una condición de parada.
✅ for (bucle iterativo) Recorre colecciones de datos de manera segura y controlada.
📌 Sintaxis básica:
📌 Ejemplo con array:
📌 Ejemplo con vector:
📌 Cuándo usar for Cuando se necesita iterar sobre una colección con un número finito de elementos.
⚡ El bucle for es el más recomendado en contratos inteligentes, ya que garantiza que el número de iteraciones es limitado.
Código en Soroban
Nos vamos a una ruta donde queramos crear nuestro proyecto y ponemos el siguiente comando:
Borramos el contrato generado en lib.rs y ponemos el siguiente código
📌 Descripción General del Contrato
Este contrato inteligente, denominado LoopContract, está desarrollado en Rust para la plataforma Soroban. Utiliza la directiva #![no_std] y módulos de soroban_sdk para operar sin la biblioteca estándar de Rust.
El contrato define tres funciones principales: ejemplo_loop, ejemplo_while y ejemplo_for, cada una demostrando diferentes estructuras de control de flujo para iteración en Rust.
🛠 Explicación de las Funciones
1️⃣ ejemplo_loop
Descripción:
Genera una lista de números enteros desde 0 hasta 4 utilizando un bucle infinito controlado por una condición interna.
Mecanismo:
Inicializa la variable count en 0 y crea un vector vacío result para almacenar los resultados.
Utiliza un bucle loop que se ejecuta indefinidamente hasta que se cumpla una condición de salida.
En cada iteración, añade el valor actual de count al final del vector result usando push_back.
Incrementa count en 1.
Si count alcanza el valor de 5, se ejecuta la sentencia break para salir del bucle.
Finalmente, retorna el vector result con los valores acumulados.
2️⃣ ejemplo_while
Descripción:
Genera una lista de números enteros desde 0 hasta 4 utilizando un bucle while que se ejecuta mientras se cumpla una condición específica.
Mecanismo:
Inicializa count en 0 y crea un vector vacío result para almacenar los resultados.
Emplea un bucle while que continúa ejecutándose mientras count sea menor que 5.
En cada iteración, añade el valor actual de count al vector result y luego incrementa count en 1.
Cuando count alcanza 5, la condición del while deja de cumplirse y el bucle termina.
Retorna el vector result con los valores acumulados.
3️⃣ ejemplo_for
Descripción:
Itera sobre un array de cadenas de texto y las almacena en un vector, demostrando el uso del bucle for en Rust.
Mecanismo:
Define un array elementos con tres cadenas de texto: "a", "b" y "c".
Crea un vector vacío result para almacenar las cadenas convertidas.
Utiliza un bucle for para iterar sobre cada elemento del array elementos.
Dentro del bucle, convierte cada &str en un String utilizando String::from_str y el entorno env, y luego añade el resultado al vector result con push_back.
Después de procesar todos los elementos, retorna el vector result que contiene las cadenas "a", "b" y "c" como objetos String.
📌 Resumen General
El contrato LoopContract ejemplifica el uso de diferentes estructuras de control de flujo en Rust dentro del contexto de contratos inteligentes en Soroban. Las funciones demostradas —loop, while y for— ofrecen distintas maneras de iterar y manipular datos, permitiendo a los desarrolladores elegir la estructura más adecuada según las necesidades específicas de su lógica de negocio.
Compilación del contrato
Ejecutamos lo siguiente:
Despliegue del contrato
Para Mac y Linux el salto de línea es con el carácter " \" y en Windows con el carácter " ´ "
Reemplaze el simbolo * por el respectivo carácter de salto de linea a su sistema operativo.
Ejecución de prueba
Pruebas del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
Link del contrato desplegado en la red testnet de Stellar
Saldo de una dirección en XLM
A continuación vamos a realizar nuestro primer ejercicio en react 😃
Consiste en realizar una conexión usando la libreria de que nos permite integrarnos a varias billeteras y el uso del servicio api rest de el cual ofrece varios servicios, para este caso en particular la lectura de saldo de una dirección en particular.
Por recomendaciones de la documentación de react, como indican usar un framework, usaremos el de , que es uno de los más populares.
Creación del proyecto
fn dividir(a: f64, b: f64) -> Option<f64> {
if b == 0.0 {
// Si b es 0, no podemos dividir, así que devolvemos None 😞
None
} else {
// Si b no es 0, devolvemos el resultado dentro de Some 😊
Some(a / b)
}
}
fn main() {
let resultado = dividir(10.0, 2.0);
// Usamos pattern matching para ver si hay un resultado
match resultado {
Some(valor) => println!("El resultado es: {}", valor), // Caso cuando hay valor 😊
None => println!("No se puede dividir por cero 😞"), // Caso cuando no hay valor
}
}
stellar contract init option --name option
#![no_std]
use soroban_sdk::{contract, contractimpl, symbol_short, Env, String, Symbol};
#[contract]
pub struct OptionContract;
#[contractimpl]
impl OptionContract {
// Establece (o elimina) un mensaje en el almacenamiento del contrato.
// Si se pasa Some(mensaje), se guarda. Si se pasa None, se borra.
pub fn set_message(env: Env, message: Option<String>) {
const key: Symbol = symbol_short!("msg");
match message {
Some(msg) => {
// Guardamos el mensaje en el storage
env.storage().instance().set(&key, &msg)
}
None => {
// Si se pasa None, eliminamos el mensaje del storage
env.storage().instance().remove(&key)
}
}
}
// Recupera el mensaje almacenado (si existe) del storage.
// Devuelve Some(mensaje) si hay un mensaje, o None si no hay ninguno.
pub fn get_message(env: Env) -> Option<String> {
let key: Symbol = symbol_short!("msg");
env.storage().instance().get(&key)
}
// Método de saludo que usa un parámetro opcional.
// Si se proporciona un nombre, saluda a esa persona; si no, saluda a "amigo".
pub fn greet(env: Env, name: Option<String>) -> String {
match name {
Some(n) => n,
None => String::from_str(&env, "¡Hola, amigo! 😃"),
}
}
}
rustCopiarEditarpub fn set_message(env: Env, message: Option<String>) {
const key: Symbol = symbol_short!("msg");
match message {
Some(msg) => {
// Guardamos el mensaje en el storage
env.storage().instance().set(&key, &msg)
}
None => {
// Si se pasa None, eliminamos el mensaje del storage
env.storage().instance().remove(&key)
}
}
}
rustCopiarEditarpub fn get_message(env: Env) -> Option<String> {
let key: Symbol = symbol_short!("msg");
env.storage().instance().get(&key)
}
rustCopiarEditarpub fn greet(env: Env, name: Option<String>) -> String {
match name {
Some(n) => n,
None => String::from_str(&env, "¡Hola, amigo! 😃"),
}
}
for elemento in coleccion.iter() {
// Código que usa cada elemento
}
fn main() {
let numeros = [1, 2, 3, 4, 5];
for numero in numeros.iter() {
println!("Número: {} 💡", numero);
}
}
fn main() {
let elementos = vec!["a", "b", "c"];
for elemento in elementos.iter() {
println!("Elemento: {} 🔥", elemento);
}
}
stellar contract init loops --name loops
#![no_std]
use soroban_sdk::{contract, contractimpl, Env, String, Vec};
#[contract]
pub struct LoopContract;
#[contractimpl]
impl LoopContract {
pub fn ejemplo_loop(env: Env) -> Vec<u32> {
let mut count = 0;
let mut result:Vec<u32> = Vec::new(&env);
loop {
result.push_back( count);
count += 1;
if count == 5 {
break;
}
}
result
}
pub fn ejemplo_while(env: Env) -> Vec<u32> {
let mut count = 0;
let mut result:Vec<u32> = Vec::new(&env);
while count < 5 {
result.push_back(count);
count += 1;
}
result
}
pub fn ejemplo_for(env: Env) -> Vec<String> {
let elementos: [&str; 3] = ["a", "b", "c"];
let mut result = Vec::new(&env);
for elemento in elementos.iter() {
result.push_back(String::from_str( &env, elemento));
}
result
}
}
rustCopiarEditarpub fn ejemplo_loop(env: Env) -> Vec<u32> {
let mut count = 0;
let mut result: Vec<u32> = Vec::new(&env);
loop {
result.push_back(count);
count += 1;
if count == 5 {
break;
}
}
result
}
rustCopiarEditarpub fn ejemplo_while(env: Env) -> Vec<u32> {
let mut count = 0;
let mut result: Vec<u32> = Vec::new(&env);
while count < 5 {
result.push_back(count);
count += 1;
}
result
}
rustCopiarEditarpub fn ejemplo_for(env: Env) -> Vec<String> {
let elementos: [&str; 3] = ["a", "b", "c"];
let mut result = Vec::new(&env);
for elemento in elementos.iter() {
result.push_back(String::from_str(&env, elemento));
}
result
}
Es el almacenamiento más duradero y confiable de Soroban. Los datos aquí se mantienen "para siempre" (hasta que explícitamente los borres). Es como guardar algo en una caja fuerte 🔒.
✨ Características:
🔄 Permanente: Los datos NO se borran automáticamente
💰 Más costoso: Requiere más fees porque ocupa espacio indefinidamente
🛡️ Más seguro: Ideal para datos críticos del contrato
🎯 ¿Cuándo usarlo?
👤 Información de usuarios (balances, perfiles)
⚙️ Configuraciones del contrato que no cambian
📊 Datos históricos importantes
💡 Ejemplo Práctico:
Compilación del contrato:
Despliegue del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
Pruebas del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
Función set_balance
2. 🏃♂️ INSTANCE STORAGE (Almacenamiento de Instancia)
🧠 ¿Qué es?
Es el almacenamiento intermedio de Soroban. Los datos aquí duran bastante tiempo, pero pueden "caducar" si no se usan. Es como tu escritorio de trabajo 📝 - mantienes las cosas que usas regularmente.
✨ Características:
⏳ Semi-permanente: Los datos pueden expirar si no se acceden
💸 Costo medio: Más barato que persistent, más caro que temporary
🔄 Auto-renovable: Cada acceso extiende su tiempo de vida
🎯 ¿Cuándo usarlo?
⚙️ Configuraciones del contrato que pueden cambiar
📊 Metadatos del contrato (nombre, símbolo, decimales)
🎮 Estados de juego que duran varias partidas
💡 Ejemplo Práctico:
Compilación del contrato:
Despliegue del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
Pruebas del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
Función init_token
3. ⚡ TEMPORARY STORAGE (Almacenamiento Temporal)
🧠 ¿Qué es?
Es el almacenamiento más barato y temporal de Soroban. Los datos aquí se borran automáticamente después de un tiempo. Es como usar notas adhesivas 📝 para recordatorios rápidos.
✨ Características:
⏰ Temporal: Los datos se borran automáticamente (aprox. 24 horas)
💰 Más barato: Ideal para optimizar costos
🚀 Rápido: Perfecto para operaciones que no necesitan persistir
🎯 ¿Cuándo usarlo?
🔢 Cálculos intermedios en transacciones complejas
🎫 Nonces y tokens de sesión
📊 Caché de datos temporales
💡 Ejemplo Práctico:
Compilación del contrato:
Despliegue del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
🎯 Comparación Rápida
Aspecto
💾 Persistent
🏃♂️ Instance
⚡ Temporary
🎨 Consejos de Mejores Prácticas
💾 Para PERSISTENT:
✅ Usa para balances de usuarios
✅ Datos que nunca deben perderse
❌ Evita para datos temporales (caro)
🏃♂️ Para INSTANCE:
✅ Configuraciones del contrato
✅ Metadatos que cambian ocasionalmente
❌ Evita para datos de usuarios individuales
⚡ Para TEMPORARY:
✅ Cálculos intermedios
✅ Sistema de cooldowns
✅ Cache temporal
❌ Evita para datos importantes
Errores
📚 Manejo de errores
🎯 Parte Teórica: Fundamentos del Manejo de Errores
¿Por qué es importante el manejo de errores? 🤔
En los contratos inteligentes de Soroban, el manejo de errores es crítico porque:
🛡️ Seguridad: Evita comportamientos inesperados que podrían ser explotados
💰 Protección de fondos: Previene pérdidas de tokens o activos
🎯 Experiencia del usuario: Proporciona mensajes claros sobre qué salió mal
🔧 Tipos de Errores en Soroban
1. Errores del Sistema (Host Errors) 🏠
Son errores que maneja automáticamente el entorno de Soroban:
Falta de memoria
Límites de gas excedidos
Operaciones matemáticas inválidas (división por cero)
2. Errores Personalizados (Custom Errors) ✨
Son errores que tú defines para tu lógica de negocio:
Permisos insuficientes
Parámetros inválidos
Estados de contrato incorrectos
🛠️ Herramientas para Manejar Errores
Result<T, E> - Tu mejor amigo 🤝
Macros útiles 🎨
panic!() - Para errores irrecuperables (¡usar con cuidado!)
assert!() - Para validaciones críticas
log!() - Para debugging (solo en testnet)
📋 Estrategias de Manejo de Errores
1. Validación Temprana ⚡
Valida todos los parámetros al inicio de la función
2. Errores Específicos 🎯
Crea tipos de error específicos para cada situación
3. Propagación Controlada 📤
Usa ? para propagar errores de forma limpia
4. Logging Inteligente 📝
Registra errores para facilitar el debugging
💡 Ejemplos Prácticos:
🌱 Ejemplo : Contrato Básico SIN Errores Personalizados
Empecemos con algo simple que usa solo los errores que Soroban ya tiene integrados:
🎯 Conceptos Clave de este Ejemplo:
✅ Herramientas Básicas de Manejo de Errores:
Option<T> 📦
Some(value) cuando todo está bien
None
📋 Cuándo Usar Cada Herramienta:
Option: Para datos que pueden o no existir
assert!: Para condiciones que NUNCA deberían ser falsas
checked_*
🏗️ Ejemplo : Contrato Básico con Errores Personalizados
Este ejemplo muestra un contrato simple para gestionar un contador con validaciones:
Lecciones Importantes del Ejemplo :
1. Manejo de Estado Explícito 🎯
2. Validaciones Paso a Paso 🛡️
3. Errores Específicos y Útiles 📋
🎉 Conclusión
El manejo de errores en Soroban es fundamental para crear contratos seguros y confiables. Recuerda:
🎯 Sé específico con tus errores
🛡️ Valida todo lo que puedas
🧪 Prueba todos los escenarios
Registro de actividades (logs)
📚 Logging en Soroban: Cómo registrar información útil durante la ejecución de contratos inteligentes
El logging es una herramienta esencial para los desarrolladores de contratos inteligentes, ya que permite registrar información clave durante la ejecución del contrato. Estos registros pueden ser útiles para depuración, seguimiento del comportamiento del contrato o para proveer información de auditoría.
⚠️ En Soroban, los logs no almacenan datos permanentes, pero pueden verse en el historial de ejecución. Son eventos diagnósticos y no afectan el estado del contrato.
npx create-next-app@latest
√ What is your project named? ... wallet-balance
√ Would you like to use TypeScript? ... No / Yes
√ Would you like to use ESLint? ... No / Yes
√ Would you like to use Tailwind CSS? ... No / Yes
√ Would you like your code inside a src/ directory? ... No / Yes
√ Would you like to use App Router? (recommended) ... No / Yes
√ Would you like to use Turbopack for next dev? ... No / Yes
√ Would you like to customize the import alias (@/* by default)? ... No / Yes
npm install @creit.tech/stellar-wallets-kit
export const metadata: Metadata = {
title: "Wallet balance",
description: "Mi primera daap gracias a Stellar español",
};
// Archivo principal de la aplicación: muestra la interfaz principal y conecta los componentes clave
// "use client" indica que este archivo se ejecuta del lado del cliente en Next.js
'use client'
import React, { useState } from "react";
import WalletButton from "./components/WalletButton";
import Balance from "./components/Balance";
/**
* Componente principal Home
* Este componente representa la página principal de la aplicación.
* Permite al usuario conectar su wallet de Stellar y ver el saldo de su cuenta.
* Utiliza estados locales para manejar la conexión y la dirección de la cuenta.
*/
export default function Home() {
// Estado que indica si la wallet está conectada (true/false)
const [isConnected, setIsConnected] = useState(false);
// Estado que almacena la dirección pública de la cuenta Stellar conectada
const [address, setAddress] = useState<string | null>(null);
// Renderiza la interfaz principal de la aplicación
return (
<>
<main className="flex justify-center items-start min-h-screen bg-black py-12">
<div className="bg-black bg-opacity-90 rounded-xl shadow-[0_4px_32px_0_rgba(255,255,255,0.25)] border border-white/20 px-8 py-10 max-w-xl w-full text-center">
{/* Título principal */}
<h1 className="text-3xl md:text-4xl font-bold text-white mb-4">Bienvenido a Stellar en español</h1>
{/* Descripción de la aplicación */}
<p className="text-base md:text-lg text-gray-200">
Esta aplicación te permite consultar de forma rápida y sencilla el saldo de tu cuenta en la red Stellar. Conecta tu wallet y visualiza al instante cuántos XLM tienes disponibles en tu cuenta.
</p>
{/* Botón para conectar/desconectar la wallet */}
<WalletButton isConnected={isConnected} address={address} setIsConnected={setIsConnected} setAddress={setAddress} />
{/* Componente que muestra el saldo si la wallet está conectada */}
<Balance isConnected={isConnected} address={address} />
</div>
</main>
</>
);
}
// Componente WalletButton: permite conectar y desconectar una wallet de Stellar
// "use client" indica que este componente se ejecuta del lado del cliente en Next.js
'use client'
import React from "react";
import {
StellarWalletsKit,
WalletNetwork,
allowAllModules,
FREIGHTER_ID,
ISupportedWallet
} from '@creit.tech/stellar-wallets-kit';
/**
* Props que recibe el componente WalletButton
* @property {boolean} isConnected - Indica si la wallet está conectada
* @property {string | null} address - Dirección de la cuenta Stellar
* @property {function} setIsConnected - Función para actualizar el estado de conexión
* @property {function} setAddress - Función para actualizar la dirección
*/
interface WalletButtonProps {
isConnected: boolean; // Indica si la wallet está conectada
address: string | null; // Dirección de la cuenta Stellar
setIsConnected: React.Dispatch<React.SetStateAction<boolean>>; // Función para actualizar el estado de conexión
setAddress: React.Dispatch<React.SetStateAction<string | null>>; // Función para actualizar la dirección
}
/**
* Componente funcional que maneja la conexión y desconexión de la wallet de Stellar.
* Permite al usuario conectar su wallet, ver la dirección y desconectarla.
* Utiliza el kit de wallets de Stellar para facilitar la integración.
*/
const WalletButton: React.FC<WalletButtonProps> = ({ isConnected, address, setIsConnected, setAddress }) => {
// Inicializa el kit de wallets de Stellar para la red de prueba (TESTNET)
const kit: StellarWalletsKit = new StellarWalletsKit({
network: WalletNetwork.TESTNET,
selectedWalletId: FREIGHTER_ID,
modules: allowAllModules(),
});
/**
* Función para conectar la wallet
* Abre un modal para seleccionar la wallet y obtiene la dirección de la cuenta
*/
const handleConnect = async () => {
// Abre el modal para seleccionar la wallet
await kit.openModal({
onWalletSelected: async (option: ISupportedWallet) => {
kit.setWallet(option.id); // Selecciona la wallet elegida
const { address } = await kit.getAddress(); // Obtiene la dirección de la cuenta
if (address) {
setAddress(address); // Actualiza la dirección en el estado
setIsConnected(true); // Marca como conectada
}
}
});
};
/**
* Función para desconectar la wallet
* Limpia el estado de conexión y la dirección
*/
const handleDisconnect = () => {
setIsConnected(false); // Marca como desconectada
setAddress(null); // Limpia la dirección
};
// Renderiza el botón correspondiente según el estado de conexión
return (
<div className="mt-6 flex flex-col items-center">
{isConnected ? (
<>
{/* Muestra la dirección conectada y botón para desconectar */}
<p className="text-white mb-2">Conectado como:</p>
<p className="text-white font-mono mb-4">{address}</p>
<button
className="bg-blue-400 hover:bg-blue-500 text-white px-4 py-2 rounded"
onClick={handleDisconnect}
>
Desconectar Wallet
</button>
</>
) : (
// Botón para conectar la wallet
<button
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded"
onClick={handleConnect}
>
Conectar Wallet
</button>
)}
</div>
);
};
// Exporta el componente para que pueda ser usado en otras partes de la app
export default WalletButton;
/ Componente Balance: muestra el saldo de XLM de la cuenta conectada
// "use client" indica que este componente se ejecuta del lado del cliente en Next.js
'use client'
// Importamos React y useEffect para manejar el ciclo de vida del componente
import React, { useEffect } from "react";
// Importamos la función GetBalance que consulta el saldo en la blockchain
import GetBalance from "../utils/balance";
// Definición de las propiedades (props) que recibe el componente Balance
// Estas props permiten saber si la wallet está conectada y cuál es la dirección de la cuenta
interface BalanceProps {
isConnected: boolean; // Indica si la wallet está conectada
address: string | null; // Dirección de la cuenta Stellar
}
// Componente funcional que recibe las props definidas arriba
const Balance: React.FC<BalanceProps> = ({ isConnected, address }) => {
// Estado local para guardar el saldo obtenido de la cuenta
// useState inicializa el saldo en 0
const [balance, setBalance] = React.useState(0);
// useEffect se ejecuta cada vez que cambian isConnected o address
// Sirve para actualizar el saldo cuando la wallet se conecta o cambia de cuenta
useEffect(() => {
// Si la wallet está conectada y hay dirección, consulta el saldo
if (isConnected && address) {
// Llama a la función GetBalance y actualiza el estado con el resultado
GetBalance(address).then((result) => {
setBalance(result); // Actualiza el saldo en el estado
});
}
}, [isConnected, address]); // Dependencias: se ejecuta cuando cambian estos valores
// Si no está conectada la wallet o no hay dirección, no muestra nada
if (!isConnected || !address) {
return null; // No renderiza nada
}
// Renderiza el saldo en pantalla si la wallet está conectada
return (
<div className="mt-4 text-white text-lg font-semibold">
{/* Muestra el saldo en XLM */}
Su saldo en XLM es: {balance}
</div>
);
};
// Exporta el componente para que pueda ser usado en otras partes de la app
export default Balance;
// Función asíncrona que obtiene el saldo de XLM de una cuenta Stellar
// Esta función es fundamental para consultar el saldo de una cuenta en la blockchain de Stellar.
// Recibe como parámetro la dirección de la cuenta y devuelve el saldo en XLM.
// Si la dirección es inválida o no se encuentra saldo, retorna 0.
//
// Parámetros:
// address (string): Dirección pública de la cuenta Stellar a consultar.
//
// Retorna:
// Promise<number>: Saldo de la cuenta en XLM (puede ser 0 si no hay fondos o la cuenta no existe).
async function GetBalance(address: string) {
// Verifica que la dirección no sea nula o vacía
if (address) {
// Realiza una petición HTTP a la API de Stellar para obtener los datos de la cuenta
const response = await fetch(`https://horizon-testnet.stellar.org/accounts/${address}`);
// Si la respuesta es exitosa (status 200)
if (response.ok) {
// Convierte la respuesta a formato JSON
const data = await response.json();
// Busca el objeto que representa el saldo nativo (XLM) dentro del array de balances
const xlmBalance = data.balances.find((b: any) => b.asset_type === "native");
// Retorna el saldo si es mayor a 0, si no, retorna 0
return xlmBalance.balance > 0 ? xlmBalance.balance : 0;
}
}
// Si la dirección es inválida o la petición falla, retorna 0
return 0;
}
// Exporta la función para que pueda ser utilizada en otros archivos
export default GetBalance;
npm run dev
⚡ Acceso rápido: Optimizado para lecturas frecuentes
🔑 Claves y identificadores únicos
📈 Eficiente: Perfecto para datos que usas con frecuencia
📝 Información que se actualiza regularmente
🧹 Auto-limpieza: No necesitas borrar manualmente
🔄 Estados de transacciones en progreso
Auto-borrado
❌ No
🔄 Si no se usa
✅ Automático
Duración
♾️ Permanente
⏳ Semi-permanente
⏰ ~24 horas
Costo
💰💰💰 Alto
💰💰 Medio
💰 Bajo
Uso ideal
👤 Datos críticos
⚙️ Configuraciones
Resultado de la compilación
Resultado del despliegue
Resultado del llamado al contrato
Resultado de la compilación
Resultado del despliegue
Resultado del llamado al contrato
Resultado de la compilación
Resultado del despliegue
🔢 Cálculos temporales
📊 Debugging: Facilita la identificación y solución de problemas
cuando algo falla o no existe
Perfecto para "encontrado/no encontrado"
assert!(condición, mensaje) ⚡
Valida condiciones críticas
Si falla, causa panic automático
Ideal para validaciones que nunca deberían fallar
checked_add(), checked_sub() 🧮
Operaciones matemáticas seguras
Devuelven Option: Some(result) o None si hay overflow
Previenen errores numéricos silenciosos
Operador ? con Option 🎯
Si es Some, continúa con el valor
Si es None, termina la función devolviendo None
Hace el código más limpio
unwrap_or(default) 🛡️
Proporciona un valor por defecto si es None
Evita crashes cuando un valor faltante es aceptable
: Para operaciones matemáticas que pueden overflow
?: Para propagar "falta de datos" de forma limpia
unwrap_or: Cuando un valor faltante tiene un reemplazo lógico
📝 Documenta tu código
🚀 Itera y mejora continuamente
#![no_std]
use soroban_sdk::{contract, contractimpl, symbol_short, Address, Env};
#[contract]
pub struct TokenContract;
#[contractimpl]
impl TokenContract {
pub fn set_balance(env: Env, user: Address, amount: i128) {
let key = symbol_short!("balance");
// Este dato debe persistir siempre 🔒
env.storage().persistent().set(&(key, user), &amount);
}
pub fn get_balance(env: Env, user: Address) -> i128 {
let key = symbol_short!("balance");
env.storage().persistent().get(&(key, user)).unwrap_or(0)
}
}
// 🔍 Cada get() se maneja explícitamente
let mut data: CounterData = match env.storage().persistent().get(&data_key) {
Some(d) => d, // ✅ Datos encontrados
None => return Err(CounterError::NotInitialized), // ❌ Error específico
};
// 🔐 Validación 1: Permisos
if data.owner != caller {
return Err(CounterError::Unauthorized);
}
// 🧮 Validación 2: Overflow
let new_value = match data.value.checked_add(amount) {
Some(v) => v,
None => return Err(CounterError::ValueTooHigh),
};
// 📊 Validación 3: Límites de negocio
if new_value > data.max_value {
return Err(CounterError::ValueTooHigh);
}
#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum CounterError {
ValueTooHigh = 1, // 🚫 Para overflow O límites excedidos
Unauthorized = 2, // 🔒 Para problemas de permisos
NotInitialized = 3, // ❌ Para contrato no inicializado
}
Código en Soroban
Nos vamos a una ruta donde queramos crear nuestro proyecto y ponemos el siguiente comando:
Borramos el contrato generado en lib.rs y ponemos el siguiente código
📌 Código del contrato: lib.rs
🛠 Explicación de la función hello
🔍 Descripción:
La función hello utiliza el macro log! de Soroban para registrar un mensaje que incluye un valor dinámico.
📌 Mecanismo:
log!(&env, "Hello {}", value); → Utiliza el macro log!, que acepta el entorno (env) y una cadena formateada estilo Rust.
{} es un marcador de posición que se reemplaza por el valor pasado, en este caso, un Symbol.
Este log no modifica el estado del contrato y es accesible mediante las herramientas de análisis de Soroban.
🧠 Importante: Los logs son visibles cuando se ejecutan pruebas o cuando se hace seguimiento en una red (por ejemplo, testnet) con herramientas como el CLI de Soroban.
Borramos el contrato generado en test.rs y ponemos el siguiente código
🧪 Código de prueba: test.rs
🧪 ¿Qué hace esta prueba?
1️⃣ Configura un entorno (env) local de pruebas.
2️⃣ Crea una dirección y registra el contrato.
3️⃣ Llama a la función hello pasando el símbolo "Dev".
4️⃣ Verifica que se haya registrado correctamente el log esperado.
📝 Resultado esperado del log:
📌 Este log muestra:
El ID del contrato que generó el log.
El tipo de evento: log.
Los datos logueados, en este caso "Hello {}" y el símbolo Dev.
🧠 Cuándo usar log!
✅ Para seguimiento de ejecución en diferentes entornos (testnet, futurenet).
✅ Para depurar comportamientos inesperados.
✅ Para mantener trazabilidad durante el desarrollo.
❌ No usar para almacenar información crítica del negocio, ya que los logs no son persistentes ni recuperables dentro del contrato.
Compilación y prueba
resultado de la ejecución
🧪 ¿Por qué se necesita -- --nocapture?
Por defecto, cuando ejecutas cargo test, Rust captura la salida estándar (stdout) y la salida de error (stderr) de cada prueba para mantener la salida de la terminal limpia y enfocada en los resultados de las pruebas. Esto significa que cualquier salida generada por macros como println! o log! no se mostrará en la terminal, a menos que una prueba falle.
El flag --nocapture le indica al ejecutable de pruebas que no capture estas salidas, permitiendo que cualquier mensaje impreso se muestre directamente en la terminal, independientemente de si la prueba pasa o falla.
Colecciones
📚 Colecciones
Las colecciones son estructuras de datos que te permiten almacenar y manipular múltiples valores. En Soroban, están optimizadas para el entorno blockchain y difieren de las implementaciones estándar de Rust.
Vec (soroban_sdk::vec::Vec): Implementación propia de Soroban para vectores dinámicos.
Map (soroban_sdk::map::Map): Implementación propia de Soroban para colecciones de pares clave-valor.
1. Vec en Soroban 🌟
El Vec en Soroban es una colección dinámica que permite almacenar una secuencia de elementos de un mismo tipo. Al igual que en Rust, su tamaño puede crecer de forma dinámica. En el contexto de Soroban se trabaja con él a través del entorno (env), lo que facilita su integración en contratos inteligentes.
2. Map en Soroban 🔄
El Map en Soroban es una colección de pares clave-valor, muy útil para asociar claves únicas a valores, similar a un diccionario. También requiere el entorno (env) para su creación y manipulación, lo que lo hace ideal para el manejo de datos en contratos inteligentes.
Contratos inteligentes ejemplo:
Abrimos la consola en la ruta donde deseamos crear el proyecto y ejecutamos.
Borramos todo el código y ponemos lo siguiente:
Explicación del contrato
📌 Estructuras y Tipos del Contrato
Atributo #![no_std]
Indica que el contrato no utiliza la biblioteca estándar de Rust, lo cual es común en entornos embebidos o en contratos inteligentes para reducir dependencias y adaptarse a restricciones de recursos.
Macros de Contrato#[contract] y #[contractimpl]
Estas macros son proporcionadas por el soroban_sdk y se usan para marcar:
#[contract]: Define la estructura principal del contrato.
#[contractimpl]: Implementa la lógica de negocio del contrato.
Para más detalles, consulta la documentación de contratos Soroban en:
Constantes del Contrato
VEC_KEY: Clave (tipo Symbol) para almacenar y recuperar el vector (Vec<String>) en el storage.
MAP_KEY: Clave (tipo Symbol) para almacenar y recuperar el mapa (Map<Symbol, String>
🛠 Funciones del Contrato
1️⃣ get_vec
Descripción:
Recupera el vector almacenado en el contrato. Si no existe, devuelve un vector vacío.
Mecanismo:
Accede al storage utilizando la clave VEC_KEY.
Utiliza unwrap_or para devolver un Vec nuevo en caso de que no se encuentre ningún valor.
2️⃣ set_vec
Descripción:
Almacena el vector proporcionado en el storage del contrato, usando la clave VEC_KEY.
Mecanismo:
Se guarda el Vec de cadenas en el storage para persistir los cambios.
3️⃣ add_vec
Descripción:
Agrega un elemento al final del vector y devuelve la nueva longitud del mismo.
Mecanismo:
Recupera el vector actual mediante get_vec.
Usa push_back para agregar el valor (tipo String).
Actualiza el storage con el vector modificado mediante
4️⃣ get_vec_element
Descripción:
Obtiene el elemento ubicado en el índice especificado del vector.
Devuelve Some(elemento) si existe o None si el índice es inválido.
Mecanismo:
Recupera el vector desde el storage.
Usa el método get para obtener el elemento en la posición indicada.
5️⃣ remove_vec_element
Descripción:
Elimina el elemento en el índice indicado del vector.
Retorna Some(()) si la eliminación fue exitosa o None si el índice no es válido.
Mecanismo:
Verifica si el índice es válido utilizando get.
Si existe el elemento, se elimina mediante remove.
Se actualiza el vector en el storage usando set_vec.
6️⃣ get_map
Descripción:
Recupera el mapa almacenado en el contrato.
Si no existe, devuelve un mapa nuevo y vacío.
Mecanismo:
Accede al storage usando la clave MAP_KEY.
Utiliza unwrap_or para retornar un Map vacío si no se encuentra ningún valor.
7️⃣ set_map
Descripción:
Almacena el mapa proporcionado en el storage del contrato usando la clave MAP_KEY.
Mecanismo:
Se guarda el Map en el storage para persistir los cambios.
8️⃣ add_map_entry
Descripción:
Agrega una entrada al mapa, utilizando una clave (Symbol) y su valor asociado (String).
Mecanismo:
Recupera el mapa actual desde el storage.
Utiliza el método set del Map para agregar la nueva entrada.
Actualiza el storage con el mapa modificado.
9️⃣ get_map_value
Descripción:
Obtiene el valor asociado a la clave especificada en el mapa.
Retorna Some(valor) si existe o None si la clave no se encuentra.
Mecanismo:
Recupera el mapa desde el storage.
Utiliza get para buscar el valor asociado a la clave dada.
🔟 remove_map_entry
Descripción:
Elimina la entrada del mapa correspondiente a la clave especificada.
Retorna Some(()) si la operación fue exitosa o None si la clave no existía en el mapa.
Mecanismo:
Recupera el mapa actual desde el storage.
Utiliza el método remove para eliminar la entrada asociada a la clave.
Si se elimina, actualiza el storage con el mapa modificado mediante set_map.
📌 Resumen General
Este contrato inteligente demuestra cómo manejar estructuras de datos dinámicas en Soroban utilizando Vec y Map para gestionar colecciones de elementos de manera persistente en el storage.
Permite:
Manipulación de Vectores (Vec):
Agregar, obtener y eliminar elementos del vector almacenado.
Manipulación de Mapas (Map):
Agregar, recuperar y eliminar entradas del mapa almacenado.
Compilación del contrato
Ejecutamos lo siguiente:
Despliegue del contrato
Para Mac y Linux el salto de línea es con el carácter " \" y en Windows con el carácter " ´ "
Reemplaze el simbolo * por el respectivo carácter de salto de linea a su sistema operativo.
Pruebas del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
Función add_vec:
Función get_vec_element:
Función remove_vec_element:
Función add_map_entry:
Función get_map_value:
Función remove_map_entry:
get set helloworld
Esta es la interfaz del contrato MessageContract. La cual es una operación de poner un valor tipo string a un contrato y capturar tambien este valor.
el contrato desplegado es el :
para realizar los pasos iniciales , es exactamente los mismos pasos iniciales del la dapp Saldo de una dirección en XLM.
Una vez instalado Next o el entorno favorito, y dejarlo listo, procedemos a abrir la terminal e instalar los siguientes paquetes:
react-hot-toast:Sirve para dar mensajes al usuario acerca de alguna operación
@stellar/stellar-sdk: Libreria en javascript para interactuar con la red de stellar, en esta ocasión la usaremos para poder convertir nuestros valores de los mensajes al entandar de mensajes de stellar XDR
Los amigos de la empresa 🥑, una vez más al rescate, extendiendo la libreria de permitiendo interaractuar con los contratos y la operación de conexión a la billetera de una forma más sencilla ( developer friendly 🤓)
Archivos a crear o modificar
StellarWalletProvider.tsx: En la ruta src/app
Sirve para la configuración inicial de la billetera, red por defecto, el contrato que vamos a usar, esta configuración la vamos a necesitar a través de toda la aplicación
layout.tsx: En la ruta src/app
Es nuestro "template" de la aplicación, allí indicamos que toda la aplicación va a tener acceso a los datos de la billetera, billetera conectada, red, servidor al que está conectado, etc...
deployments.json: En la ruta src/app
array json donde podemos almacenar los diferentes contratos con que podemos interactuar.
page.tsx: En la ruta src/app
Página principal de la aplicación, a resaltar allí la interactividad es nula, ya que está se da en el componente Header y TablaValores .
📝 A continuación se crea la carpeta components en la ruta src/app
Header.tsx: En la ruta src/app/components
Es el encargado de la conexión y desconexión de la billetera que tengamos instalada al navegador de la red de stellar
TablaValores.tsx: En la ruta src/app/components
Es el componente estrella de la daap ⭐, sobre todo las funciones:
readMessage: Invoca el metodo get_message del contrato
Algo relevante es que pasamos el mensaje de su
writeMessageInvoca el metodo set_message del contrato.
Pasamos el mensaje de nativo a
Para ejecutar el programa ejecutamos:
Todo el proyecto esta en un repositorio
Tipos de Datos Estructurados
🧱 Tipos de Datos Estructurados
Los tipos de datos estructurados son construcciones que te permiten organizar y agrupar datos relacionados bajo una misma entidad. Son fundamentales para modelar conceptos complejos en tus contratos inteligentes.
Structs: Estructuras que agrupan campos con nombre y tipos diferentes.
use soroban_sdk::{Env, Vec};
pub fn ejemplo_vec(env: &Env) {
let mut numeros = Vec::new(env); // Crea un Vec vacío
numeros.push(10);
numeros.push(20);
// Puedes registrar el contenido usando el log del entorno
env.log().info("Contenido del Vec:", numeros);
}
use soroban_sdk::{Env, Map};
pub fn ejemplo_map(env: &Env) {
let mut mi_mapa = Map::new(env); // Crea un Map vacío
mi_mapa.set(1, "uno");
mi_mapa.set(2, "dos");
// Registra el contenido del Map
env.log().info("Contenido del Map:", mi_mapa);
}
stellar contract init collections --name vecmap
#![no_std]
use soroban_sdk::{contract, contractimpl, symbol_short, Env, Map, String, Symbol, Vec};
#[contract]
pub struct VecMapContract;
#[contractimpl]
impl VecMapContract {
const VEC_KEY: Symbol = symbol_short!("vec");
const MAP_KEY: Symbol = symbol_short!("map");
fn get_vec(env: &Env) -> Vec<String> {
env.storage()
.instance()
.get(&Self::VEC_KEY)
.unwrap_or(Vec::new(&env))
}
fn set_vec(env: &Env, vec: &Vec<String>) {
env.storage().instance().set(&Self::VEC_KEY, vec);
}
// Funciones para manejo de Vec
pub fn add_vec(env: Env, value: String) -> u32 {
let mut my_vec: Vec<String> = Self::get_vec(&env);
my_vec.push_back(value);
Self::set_vec(&env, &my_vec);
my_vec.len()
}
//Obtenemos un elemento del vector
pub fn get_vec_element(env: Env, index: u32) -> Option<String> {
let my_vec: Vec<String> = Self::get_vec(&env);
my_vec.get(index)
}
pub fn remove_vec_element(env: Env, index: u32) -> Option<()> {
let mut my_vec = Self::get_vec(&env);
// Verificamos primero si el índice es válido
if my_vec.get(index).is_some() {
let element: Option<()> = my_vec.remove(index);
Self::set_vec(&env, &my_vec);
Some(())
} else {
None
}
}
fn get_map(env: &Env) -> Map<Symbol, String> {
env.storage()
.instance()
.get(&Self::MAP_KEY)
.unwrap_or(Map::new(env))
}
fn set_map(env: &Env, map: &Map<Symbol, String>) {
env.storage().instance().set(&Self::MAP_KEY, map);
}
// Funciones para manejo de Map
// Agregamos un elemento al map
pub fn add_map_entry(env: Env, key: Symbol, value: String) {
let mut my_map = Self::get_map(&env);
my_map.set(key, value);
Self::set_map(&env, &my_map);
}
//Obtener algun valor del map
pub fn get_map_value(env: Env, key: Symbol) -> Option<String> {
let my_map = Self::get_map(&env);
my_map.get(key)
}
// Eliminar un elemento del map
pub fn remove_map_entry(env: Env, key: Symbol) -> Option<()> {
let env_ref = &env;
let mut my_map = Self::get_map(env_ref);
if my_map.remove(key).is_some() {
Self::set_map(&env, &my_map);
Some(()) // Éxito
} else {
None // La clave no existía
}
}
}
// Este archivo define un Provider personalizado para conectar una aplicación Next.js con la red de Stellar usando Soroban React.
'use client'; // Indica que este componente se ejecuta del lado del cliente en Next.js
import { ReactNode } from "react";
import { NetworkDetails, SorobanReactProvider, WalletNetwork } from "stellar-react";
import deployments from './deployments.json'; // Importa la configuración de despliegue de contratos
// Componente Provider que envuelve la aplicación y provee acceso a la red de Stellar
const StellarWalletProvider = ({ children }: { children: ReactNode }) => {
// Definimos los detalles de la red de prueba (testnet) de Stellar
// Puedes cambiar estos valores para conectar a otra red si lo necesitas
const testnetNetworkDetails: NetworkDetails = {
network: WalletNetwork.TESTNET, // Selecciona la red de prueba de Stellar
sorobanRpcUrl: 'https://soroban-testnet.stellar.org/', // URL del nodo Soroban para testnet
horizonRpcUrl: 'https://horizon-testnet.stellar.org' // URL de Horizon para testnet
}
return (
// SorobanReactProvider provee el contexto de Stellar a toda la app
<SorobanReactProvider
appName={"Hello World Stellar App"} // Nombre de la app que se muestra en la wallet
allowedNetworkDetails={[testnetNetworkDetails]} // Lista de redes permitidas (aquí solo testnet)
activeNetwork={WalletNetwork.TESTNET} // Red activa por defecto
deployments={deployments} // Información de despliegue de contratos inteligentes
>
{/* children representa los componentes hijos que tendrán acceso al contexto de Stellar */}
{children}
</SorobanReactProvider>
)
}
// Exportamos el Provider para usarlo en el layout principal de la app
export default StellarWalletProvider;
// Este archivo define el layout principal de la aplicación Next.js.
// Aquí se configuran los estilos globales, fuentes, providers y la estructura base de la app.
import type { Metadata } from "next"; // Importa el tipo Metadata para definir metadatos de la página
import { Geist, Geist_Mono } from "next/font/google"; // Importa fuentes de Google para mejorar la apariencia
import "./globals.css"; // Importa los estilos globales de la aplicación
import StellarWalletProvider from "./StellarWalletProvider"; // Importa el provider personalizado para conectar con la red de Stellar
import { Toaster } from "react-hot-toast"; // Importa el componente para mostrar notificaciones tipo toast
// Configuración de la fuente sans-serif personalizada usando Geist
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
// Configuración de la fuente monoespaciada personalizada usando Geist Mono
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
// Definición de los metadatos de la aplicación (título y descripción)
export const metadata: Metadata = {
title: "Get set messaje",
description: "get y set de una variable de contratos",
};
// Componente principal del layout
// children representa el contenido de cada página que se renderiza dentro del layout
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{/*
StellarWalletProvider envuelve toda la aplicación y provee el contexto de conexión a la red de Stellar.
Esto permite que cualquier componente hijo pueda interactuar con la blockchain de Stellar fácilmente.
*/}
<StellarWalletProvider>{children}</StellarWalletProvider>
{/*
Toaster se utiliza para mostrar notificaciones emergentes (toast) en la interfaz.
El div con id="toast-root" es el contenedor donde se renderizan estos mensajes.
*/}
<div id="toast-root"><Toaster/></div>
</body>
</html>
);
}
// Este archivo define la página principal de la aplicación, integrando los componentes de encabezado y tabla de valores.
import Header from "./components/Header";
import TablaValores from "./components/TablaValores";
import React from "react";
// Componente principal de la página Home
export default function Home() {
return (
// Estructura principal de la página con estilos y disposición
<main className="min-h-screen bg-black flex flex-col items-center justify-start">
{/* Componente de encabezado que gestiona la conexión de la billetera */}
<Header />
<section className="w-full max-w-2xl mt-10 px-4">
<div className="bg-gray-900 bg-opacity-80 rounded-xl shadow-[0_4px_24px_0_rgba(255,255,255,0.15)] border border-gray-700 p-8">
<h2 className="text-3xl font-bold text-white mb-2 text-center">Get Set Message Stellar Español</h2>
<p className="text-gray-300 text-center mb-8">
Esta aplicación permite recuperar y establecer un valor tipo string en un contrato inteligente sobre la red Stellar.
</p>
<p className="text-gray-400 text-center mb-8 font-mono">
Contrato: CBLHRYKD6UPJA75PJ5CCTCY6LO4H3ZY7HFW2SS2JP4US5VRYDEO3I67H
</p>
{/* Componente que muestra y permite actualizar el valor del contrato */}
<TablaValores />
</div>
</section>
</main>
);
}
// Este componente Header muestra el título de la aplicación y permite conectar o desconectar la billetera Stellar.
'use client';
import React, { useState } from "react";
import { useSorobanReact } from "stellar-react";
// Componente principal del encabezado
const Header: React.FC = () => {
// Extraemos la dirección de la billetera, y las funciones para conectar y desconectar usando el hook de Soroban React
const { address, connect, disconnect } = useSorobanReact();
return (
// Encabezado visual con estilos y título
<header className="w-full flex justify-between items-center px-6 py-4 bg-black bg-opacity-80 border-b border-gray-800">
<h1 className="text-2xl font-bold text-white">Stellar Wallet</h1>
{/* Si no hay dirección, mostramos el botón para conectar la billetera */}
{!address ? (
<button
className="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded transition"
onClick={connect}
>
Conectar billetera
</button>
// Si hay dirección, mostramos la dirección y el botón para desconectar */}
) : (
<div className="flex items-center gap-4">
{/* Mostramos la dirección de la billetera conectada */}
<span className="text-gray-300 text-sm">{address}</span>
<button
className="bg-blue-400 hover:bg-blue-500 text-white px-4 py-2 rounded transition shadow-lg shadow-white/30"
onClick={disconnect}
>
Desconectar
</button>
</div>
)}
</header>
);
};
// Exportamos el componente para su uso en otras partes de la app
export default Header;
// Obtenemos la dirección del contrato y llamamos al método get_message
const addr = contract?.deploymentInfo?.contractAddress;
const result = await contract?.invoke({ method: "get_message", args: [] });
setMessage(scValToNative(result as xdr.ScVal) as string)
// Llamamos al método set_message del contrato, firmando la transacción
const result = await contract?.invoke({
method: "set_message",
args: [nativeToScVal(newMessage, { type: "string" })],
signAndSend: true,
reconnectAfterTx: false
});
// Este componente permite leer y escribir un mensaje en un contrato inteligente de Stellar.
'use client';
import { nativeToScVal, scValToNative, xdr } from "@stellar/stellar-sdk";
import React, { useEffect, useState } from "react";
import toast from "react-hot-toast";
import { useRegisteredContract, useSorobanReact } from "stellar-react";
// Componente principal de la tabla de valores
const TablaValores: React.FC = () => {
// Obtenemos información de la red, el servidor y la dirección de la billetera
const { activeNetwork, sorobanServer, address } = useSorobanReact();
// Obtenemos el contrato registrado con el nombre "hello_world"
const contract = useRegisteredContract("hello_world");
// Estado para el mensaje actual y el nuevo mensaje a enviar
const [message, setMessage] = useState("");
const [newMessage, setNewMessage] = useState("");
// Función para leer el mensaje almacenado en el contrato inteligente
const readMessage = async () => {
if (!contract) {
return;
}
try {
// Obtenemos la dirección del contrato y llamamos al método get_message
const addr = contract?.deploymentInfo?.contractAddress;
const result = await contract?.invoke({ method: "get_message", args: [] });
setMessage(scValToNative(result as xdr.ScVal) as string)
}
catch (error) {
console.log(error);
}
}
// Función para escribir un nuevo mensaje en el contrato inteligente
const writeMessage = async () => {
// Validamos que la billetera esté conectada
if (address === undefined) {
toast.error("Wallet no conectada");
return;
}
// Validamos que el campo no esté vacío
if (newMessage === "") {
toast.error("Introduce un mensaje");
return;
}
try {
// Llamamos al método set_message del contrato, firmando la transacción
const result = await contract?.invoke({
method: "set_message",
args: [nativeToScVal(newMessage, { type: "string" })],
signAndSend: true,
reconnectAfterTx: false
});
if (result) {
toast.success("mensaje actualizado");
readMessage(); // Actualizamos el mensaje mostrado
} else {
toast.error("Actuliazacion fallida");
}
} catch (error) {
if (error instanceof Error) {
console.log(`Error: ${error.message}`);
}
else {
console.log('ocurrio un error desconocido');
}
}
}
// Hook useEffect para leer el mensaje cada vez que cambia el servidor o el contrato
useEffect(() => {
readMessage();
}, [sorobanServer, contract]);
return (
// Tabla visual para mostrar y actualizar el valor del contrato
<div className="w-full max-w-md mx-auto mt-8 bg-gray-900 bg-opacity-80 rounded-lg shadow-lg p-6">
<table className="w-full text-white border-separate border-spacing-y-4">
<tbody>
<tr>
<td className="font-semibold">Valor actual</td>
<td className="bg-gray-800 rounded px-4 py-2 text-center">{message}</td>
</tr>
<tr>
<td className="font-semibold">Valor nuevo</td>
<td>
<input
type="text"
className="w-full px-3 py-2 rounded bg-gray-700 text-white focus:outline-none focus:ring-2 focus:ring-blue-600"
value={newMessage}
onChange={e => setNewMessage(e.target.value)}
placeholder="Introduce un nuevo valor"
/>
</td>
</tr>
<tr>
<td></td>
<td>
<button
className="w-full bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded transition mt-2"
onClick={writeMessage}
>
Enviar
</button>
</td>
</tr>
</tbody>
</table>
</div>
);
};
// Exportamos el componente para su uso en otras partes de la app
export default TablaValores;
npm run dev
Enums: Tipos que pueden contener uno de varios valores posibles predefinidos.
Tuplas: Colecciones ordenadas y de tamaño fijo que pueden contener elementos de diferentes tipos.
Arrays: Colecciones de elementos del mismo tipo con longitud fija.
1. Structs 🏗️
Las structs en Rust te permiten agrupar campos con nombres y tipos diferentes. Es como crear tu propio "molde" para agrupar datos relacionados.
2. Enums 🎨
Los enums definen un tipo que puede ser uno de varios valores predefinidos. Son muy útiles para representar estados o variantes.
3. Tuplas 🔗
Las tuplas son colecciones ordenadas de tamaño fijo que pueden contener elementos de diferentes tipos. ¡Son perfectas para agrupar datos relacionados sin necesidad de nombres!
4. Arrays 📚
Los arrays son colecciones de elementos del mismo tipo con una longitud fija. Se usan cuando conoces la cantidad exacta de elementos y estos son del mismo tipo.
Contratos inteligentes ejemplo:
En esta ocasión vamos a crear un contrato independiente por cada tipo de dato de la siguiente manera.
Abrimos la consola en la ruta donde deseamos crear el proyecto y ejecutamos.
Borramos todo el código y ponemos lo siguiente:
Explicación del contrato
📌 Estructuras y Tipos del Contrato
1. Atributo #![no_std]
Indica que el contrato no utiliza la biblioteca estándar de Rust, lo que es común en entornos embebidos o en contratos inteligentes para reducir dependencias y adaptarse a restricciones de recursos.
2. Macros de Contrato
#[contract], #[contractimpl] y #[contracttype]
Estas macros son proporcionadas por el soroban_sdk y se usan para marcar:
#[contract]: Define la estructura principal del contrato.
#[contractimpl]: Implementa la lógica de negocio del contrato.
#[contracttype]: Declara tipos (enums o structs) que se utilizarán en la interfaz del contrato.
3. Enum TaskStatus
Define los posibles estados de una tarea:
Open (valor 0)
InProgress (valor 1)
Completed (valor 2)
Se deriva de Clone, Debug, Eq y PartialEq para facilitar la clonación, la depuración y la comparación de valores.
4. Struct Task
Representa una tarea y contiene:
id: u32: Identificador único de la tarea.
description: String: Descripción de la tarea.
status: TaskStatus: Estado actual de la tarea.
assignee: Address: Dirección del usuario asignado a la tarea.
Address es un tipo que representa direcciones de cuentas o contratos en Soroban.
Descripción:
Busca en un arreglo de 5 frutas (como cadenas) la que coincida con la cadena de entrada fruit y devuelve su posición (índice).
Mecanismo:
Se crea un arreglo estático de frutas.
Se recorre el arreglo utilizando enumerate(), que proporciona el índice y el elemento.
Si se encuentra una coincidencia (usando if), se retorna el índice.
Si no hay coincidencia, se retorna -1.
2. create_task
Descripción:
Crea una nueva tarea con un identificador, descripción y usuario asignado. El estado inicial de la tarea se establece en TaskStatus::Open.
Mecanismo:
Se construye una instancia de la estructura Task con los valores proporcionados.
Se asigna el estado inicial de la tarea como abierta.
3. get_info
Descripción:
Devuelve una tupla que contiene un mensaje y un número entero.
Mecanismo:
Se crea una cadena "Ejemplo Simple".
Se retorna junto a un entero fijo 123.
4. get_status_description
Descripción:
Devuelve una cadena descriptiva según el estado de una tarea.
Mecanismo:
Se utiliza la estructura de control match para evaluar el valor del TaskStatus.
Según el caso (Open, InProgress, Completed), se retorna la cadena correspondiente: "Abierta", "En Progreso" o "Completada".
Contexto Teórico del match:
En Rust, match es similar a la instrucción switch de otros lenguajes, pero es más poderoso, permitiendo comparar contra patrones y asegurando que todos los casos sean tratados o manejados mediante un caso por defecto. Esto proporciona una forma segura y expresiva de controlar el flujo del programa.
📌 Resumen General
Este contrato inteligente demuestra:
Definición de Tipos Personalizados:
Con el enum TaskStatus y la estructura Task, se modelan estados y tareas para un posible sistema de gestión.
Búsqueda en Arreglos:
La función find_fruit muestra cómo recorrer un arreglo de cadenas y buscar un elemento.
Creación y Manejo de Estructuras:
La función create_task crea una tarea inicializada con un estado predeterminado.
Uso de Tuplas y match:
La función get_info devuelve información empaquetada en una tupla y get_status_description utiliza match para retornar descripciones basadas en el estado.
Compilación del contrato
Ejecutamos lo siguiente:
Despliegue del contrato
Para Mac y Linux el salto de línea es con el carácter " \" y en Windows con el carácter " ´ "
Reemplaze el simbolo * por el respectivo carácter de salto de linea a su sistema operativo.
Ejecución de prueba
Pruebas del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
función find_fruit
Ejecución de prueba
Función create_task
Ejecución de ejemplo
Función get_info
Ejecución de prueba
Función get_status_description
Ejecución de prueba
rustCopiarEditarstruct Persona {
nombre: String,
edad: u32,
}
fn main() {
let persona = Persona {
nombre: String::from("Juan"),
edad: 30,
};
println!("{} tiene {} años 👤", persona.nombre, persona.edad);
}
rustCopiarEditarenum Estado {
Activo,
Inactivo,
Desconocido,
}
fn main() {
let estado = Estado::Activo;
match estado {
Estado::Activo => println!("¡Está activo! 😊"),
Estado::Inactivo => println!("Está inactivo. 😴"),
Estado::Desconocido => println!("Estado desconocido. 🤔"),
}
}
rustCopiarEditarfn main() {
let persona: (&str, u32) = ("Ana", 25);
println!("{} tiene {} años 👩", persona.0, persona.1);
}
rustCopiarEditarfn main() {
let numeros: [i32; 5] = [1, 2, 3, 4, 5];
println!("El primer número es: {} ", numeros[0]);
}
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, Address, Env, String, };
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TaskStatus {
Open = 0,
InProgress = 1,
Completed = 2,
}
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Task {
pub id: u32,
pub description: String,
pub status: TaskStatus,
pub assignee: Address,
}
#[contract]
pub struct SimpleContract;
#[contractimpl]
impl SimpleContract {
// Función que busca una fruta en un array y devuelve su posición o -1 si no la encuentra.
pub fn find_fruit(env: Env,fruit: String) -> i32 {
let fruits: [String; 5] = [
String::from_str(&env,"manzana"),
String::from_str(&env,"banana"),
String::from_str(&env,"naranja"),
String::from_str(&env,"uva"),
String::from_str(&env,"fresa"),
];
for (index, f) in fruits.iter().enumerate() {
if f == &fruit {
// Compara la fruta dada con cada elemento del array.
return index as i32; // Devuelve la posición (índice) si la encuentra.
}
}
-1 // Devuelve -1 si la fruta no está en el array.
}
// Función que crea una tarea y devuelve el struct
pub fn create_task(env: Env, id: u32, description: String, assignee: Address) -> Task {
Task {
id,
description,
status: TaskStatus::Open,
assignee,
}
}
// Función que devuelve una tupla con información
pub fn get_info(env: Env) -> (String, i32) {
(String::from_str(&env,"Ejemplo Simple"), 123)
}
// Función que devuelve la descripción del estado de la tarea
pub fn get_status_description(env: Env,status: TaskStatus) -> String {
match status {
TaskStatus::Open => String::from_str(&env,"Abierta"),
TaskStatus::InProgress => String::from_str(&env,"En Progreso"),
TaskStatus::Completed => String::from_str(&env,"Completada"),
}
}
}
pub fn find_fruit(env: Env, fruit: String) -> i32 {
let fruits: [String; 5] = [
String::from_str(&env,"manzana"),
String::from_str(&env,"banana"),
String::from_str(&env,"naranja"),
String::from_str(&env,"uva"),
String::from_str(&env,"fresa"),
];
for (index, f) in fruits.iter().enumerate() {
if f == &fruit {
// Si encuentra la fruta, retorna el índice convertido a i32.
return index as i32;
}
}
// Si no se encuentra, retorna -1.
-1
}
Un Token No Fungible (NFT) es como un certificado digital único e irrepetible. Imagínate que tienes una obra de arte física: no puedes dividirla en pedacitos y cada una es única. Los NFTs funcionan igual pero en el mundo digital.
🤔 ¿Qué significa "No Fungible"?
💰 Fungible: Como el dinero. Un billete de $1000 es igual a otro billete de $1000, son intercambiables.
🎨 No Fungible: Como una pintura original. La Mona Lisa es única, no puedes cambiarla por otra pintura y que valga exactamente lo mismo.
🌟 ¿Por Qué son Importantes los NFTs?
Los NFTs permiten:
✅ Propiedad digital verificable: Puedes demostrar que algo digital es tuyo
🎨 Arte digital: Artistas pueden vender obras digitales únicas
🎮 Gaming: Objetos únicos en videojuegos
📚 Diferencias clave con Tokens Fungibles
Tokens Fungibles 🪙
NFTs 🎨
🏗️ Estructura de un NFT - Los Metadatos
🧠 ¿Qué significa que un NFT use un "archivo JSON en lugar de la ubicación directa de la imagen"?
Cuando creas un NFT, es común pensar que la imagen (como un JPG o PNG) se guarda directamente en la blockchain, pero eso no es así. Por razones de costo y eficiencia, no se suben archivos pesados a la blockchain, sino que se usa un sistema de doble enlace (también llamado "two-step metadata linking").
🔗 ¿Qué es el sistema de doble enlace?
Es una estructura donde el contrato del NFT no apunta directamente a la imagen, sino a un archivo .json que contiene los metadatos del NFT. Ese archivo .json, a su vez, contiene un enlace a la imagen u otros recursos.
Veámoslo como una cadena:
📁 ¿Por qué no enlazar directamente la imagen desde el contrato?
Porque al enlazar a un archivo .json, tienes varias ventajas importantes:
Puedes incluir mucha más información, no solo la imagen.
Es más flexible: si luego quieres mostrar un video, animación o cambiar la descripción, lo puedes hacer sin tocar el contrato.
Es más económico y limpio: solo apuntas a un archivo liviano con metadatos.
📄 Ejemplo claro de un archivo metadata.json
Este archivo puede estar alojado en IPFS u otro sistema descentralizado, y contiene:
name: El nombre visible del NFT.
description: Una breve descripción o historia.
url: El enlace real a la imagen del NFT.
✅ Ventajas clave de esta arquitectura
Beneficio
Descripción
Para este ejemplo usamos el servicio de , primero se sube la imagen como tal, para tener su CID , ese lo incluimos en el archivo de metadata.json y luego subimos el archivo metadata.json
🧙♂️ OpenZeppelin Wizard para Stellar
OpenZeppelin ha creado un wizard (asistente) súper útil para generar tokens SEP-41: 🔗
🌟 ¿Qué hace el wizard por ti?
🎨 Interfaz visual: Sin necesidad de escribir código desde cero
⚙️ Configuración simple: Solo selecciona las funciones que necesitas
📋 Código generado: Listo para usar en minutos
⚠️ Importante: Limitaciones Actuales
Aunque el wizard es genial para empezar, todavía tiene algunos errores 🐛. Por eso, es mejor usar los ejemplos oficiales como referencia:
🔗
Instructivo para poder utilizar el código de OpenZeppelin
Pre-requisitos
Tener instalado Rust.
Tener instalado El cliente Stellar
tener instalado el cliente de GIt
Copiar el repositorio de github de OpenZeppelin en una carpeta previamente seleccionada con la siguiente instrucción.
Con el editor favorito de código abrimos el folder stellar-contracts.
En el directorio raiz abrir el archivo Cargo.toml
Para el ejemplo vamos a crear una subcarpeta dentro de examples
Ejemplo :NFT con consecutivo.
Una vez creado el contrato del NFT como tal , se hace el proceso de acuñación del NFT se genera un consecutivo.
Primero se copia la carpeta nft-consecutive
Copiamos la carpeta y la nueva carpeta lo nombramos como mynft
Dentro de mynft editados el archivo Cargo.toml
editamos el archivo.
Antes de proceder con el código añadimos la ruta de mynft dentro del archivo Cargo.toml en el directorio raiz
Escribimos lo siguiente dentro de lib.rs
Código de contract.rs
🖼️ Explicación del Contrato mynft (NFT Consecutivo)
¿Qué es este contrato?
Este contrato crea un token no fungible (NFT) en la blockchain de Stellar, usando el módulo Consecutive, que permite crear muchos NFTs de forma eficiente en una sola transacción. Además, los NFTs pueden quemarse (eliminarlos para siempre).
🧰 1. Importaciones: Herramientas necesarias
¿Qué significa?
soroban_sdk: Caja de herramientas básica para contratos Soroban.
stellar_non_fungible: Librería oficial de Stellar para trabajar con NFTs.
burnable
🧱 2. Definición del contrato
Para qué sirve esto?
Define una clave personalizada (Owner) para guardar el dueño del contrato en el almacenamiento interno del contrato.
Es útil para que solo ese dueño pueda ejecutar ciertas funciones como "mint".
🧱 3. Declaración del contrato
mynft es el nombre del contrato.
Con #[contract] le decimos a Soroban que esto es un contrato inteligente.
🏗️ 4. Constructor (__constructor)
¿Qué hace?
Guarda el dueño del contrato (quien puede mintear NFTs).
Define la metadata básica:
"My NFT": nombre del token.
🧯 5. Función batch_mint (minteo en lote)
¿Qué hace?
Solo el dueño original del contrato puede mintear NFTs.
Permite crear varios NFTs (según el amount) de forma eficiente.
Usa la función batch_mint del módulo Consecutive
💡 Ejemplo:
Si llamas batch_mint(to, 5), se crean 5 NFTs y se asignan al usuario to.
🧾 6. Implementación estándar de NonFungibleToken
Esta parte define todas las funciones necesarias para que el NFT funcione según los estándares:
¿Qué funciones se incluyen?
🔎 Consultas:
balance(): ¿Cuántos NFTs tiene un usuario?
owner_of(token_id): ¿Quién es el dueño de un NFT específico?
name(), symbol()
🔄 Transferencias:
transfer(): Transferir un NFT.
transfer_from(): Transferencia con aprobación previa.
approve(), approve_for_all()
🔥 7. Funciones para quemar NFTs (Burnable)
¿Qué hacen?
burn(from, token_id): El dueño puede destruir su NFT.
burn_from(spender, from, token_id): Un tercero autorizado puede quemar un NFT en nombre del dueño.
💡 Esto sirve para eliminar NFTs obsoletos o por decisión del usuario.
✅ 8. Consecutive extension
Esto indica que el contrato utiliza el módulo Consecutive, que permite:
Mintear muchos NFTs seguidos.
Optimizar almacenamiento y eficiencia.
🧠 Analogía general
Piensa en este contrato como una fábrica de obras de arte digitales que puede crear muchas piezas a la vez, entregarlas a quien tú quieras, y también destruirlas si ya no quieres que existan.
Todo está bajo el control del creador original (owner).
Compilación del contrato
Nos ubicamos dentro de ../stellar-contracts/examples/mynft allí en consola ejecutamos:
Despliegue del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
📚 Autenticación y Autorizaciones en Stellar Soroban
🎯 Parte Teórica: ¿Qué son las autorizaciones?
Las autorizaciones son el conjunto de mecanismos y patrones que garantizan que solo quien tiene derecho pueda ejecutar acciones concretas en un contrato inteligente. En Soroban esto se traduce principalmente a comprobar quién firmó la transacción y comparar esa identidad con lo que el contrato espera (owner, admin, etc.).
🤔 Analogía: imagina una cerradura inteligente que solo abre con la llave correcta. require_auth() es la comprobación de llave; el almacenamiento del contrato (env.storage()) contiene la información sobre quién tiene la llave (admin, owner, roles).
🛡️ Prevención de ataques: bloquea la ejecución de funciones críticas por parte de actores no autorizados (ej.: transferir fondos, cambiar parámetros).
🎛️ Control de acceso fino: separa responsabilidades (admin vs. usuario) y reduce la superficie de riesgo.
💰 Protección de activos: evita que fondos o estados sensibles sean alterados por quien no debe.
🌟 ¿Qué ofrece Soroban para autorizaciones? (explicación técnica)
Address::require_auth(): solicita al entorno (host) que verifique que la dirección proporcionada autorizó la transacción. Si la firma/ autorización no está presente, la ejecución aborta. Esto se usa para garantizar que el titular de Address realmente firmó.
Storage para roles: env.storage().instance().set/get(...) típico para guardar direcciones que representan roles (admin, oracle, treasury).
Nota práctica: require_auth verifica la autorización del Address pasado; si tu interfaz front-end intenta pasar otra Address sin su firma, require_auth() fallará. Esto protege contra “param tampering”.
🔧 Herramientas y conceptos clave (más allá de la sintaxis)
require_auth() — qué hace y efectos
Pide al host que valide que el signature set de la transacción incluye autorización para esa Address.
📋 Estrategias y patrones prácticos (con por qué y cuándo usarlas)
Funciones públicas vs privadas
Públicas: lecturas o endpoints no sensibles. Riesgo: nadie con malos fines puede cambiar estado.
Privadas/autenticadas: toda escritura importante debe exigir require_auth.
#![no_std]: estándar para contratos Soroban (sin std).
use ...: importa macros y tipos.
#[contract] pub struct PublicContract; declara el contrato.
🔑 Ejemplo 2 — Autenticación básica con require_auth
Explicación profunda del ejemplo 2 (punto por punto)
pub fn secure_action(env: Env, user: Address) -> Symbol
user: Address: el contrato recibe la dirección que debe autorizar la operación.
👑 Ejemplo 3 — Contrato con Roles (Admin + Usuario)
Explicación profunda del ejemplo 3 (punto por punto)
initialize
admin.require_auth() garantiza que quien firma la transacción es la dirección admin indicada. Esto evita que alguien inicialice el contrato con un admin inexistente sin su firma.
❗ Errores comunes y cómo evitarlos
Confiar en parámetros sin require_auth: nunca confíes en que el parámetro user enviado por la UI está autorizado. Usa require_auth.
Usar unwrap() en production: causa abortos no controlados. Preferir Result.
🎉 Conclusión (resumen práctico)
require_auth() es la herramienta central para comprobar identidad en Soroban.
Guarda roles en storage y siempre verifica firma + almacenamiento antes de acciones sensibles.
Prefiere Result y errores explícitos frente a panic!()
Eventos
📚¿Qué son los Eventos?
Los eventos en Soroban son como notificaciones que tu contrato inteligente envía al mundo exterior. Imagínate que tu contrato es como una caja negra 📦, y los eventos son como notas que saca por una ranura para contarte qué está pasando adentro.
🏢 Gestión de roles: posibilita auditorías, rotación de llaves y políticas como “solo admin puede pausar contrato”.
🧪 Mejor testeo y trazabilidad: con autorizaciones claras puedes simular y probar casos de abuso (attack vectors) y asegurar que mecanismos fallan de forma controlada.
Logs (log!): registrar quién hizo qué ayuda en auditoría y debugging en testnet.
Patrones de diseño: Owner pattern, Role-based Access Control (RBAC), Two-step transfer (pending_admin → accept), multisig delegados vía contratos.
Si falla, la ejecución termina en host error: el estado no cambia.
Úsalo siempre antes de ejecutar acciones irreversibles (transferencias, cambios de roles, mint/burn).
Storage: instance() vs persistent() (uso práctico)
instance() es útil para datos del contrato en el contexto de su instancia (clave por Symbol). En muchos ejemplos se usa para roles.
persistent() se usa para esquemas de almacenamiento más genéricos (aunque ambos son persistentes entre ejecuciones). Mantén consistencia en tu elección.
Preferir Result<..., ContractError> vs panic!()
Result permite devolver errores legibles y manejables por callers.
panic!() aborta la transacción abruptamente (útil para invariantes que no deberían romperse, pero menos amigable para testing/UX).
Diseños de seguridad
Minimiza el poder del admin: reduce funciones sensibles que solo admin puede ejecutar.
Two-step admin transfer: evita pérdida accidental de control.
Auditoría & logging: crucial durante pruebas.
Gas & storage: cada dirección que guardes consume espacio — planifica cambios de roles y rotaciones para minimizar writes innecesarios.
Owner/Admin pattern
Guardar admin en storage y validar en funciones críticas. Sencillo y efectivo.
Para operaciones críticas, delegar autorización a un contrato multisig o sistema de aprobación en varias firmas.
Principio de menor privilegio
Da lo mínimo necesario a cada rol. Si solo necesita pausar, crea un rol “pauser” en vez de usar al admin para todo.
pub fn hello(env: Env) -> Symbol
¿Por qué devuelve Symbol?: Symbol es un tipo barato y pequeño (clave textual) ideal para mensajes o claves. Aquí ilustramos una función pública que devuelve un identificador estático.
log!(&env, "..."): escribe un mensaje que es útil en testnet para debugging. No confíes en logs para seguridad, son solo para observabilidad.
Cuándo usar este patrón: endpoints informativos (version, status, lectura de métricas públicas).
Riesgos: nunca lances operaciones que cambien estado/tokenes desde una función pública. Si necesitas una función pública que cambie estado, añade validaciones fuertes.
user.require_auth()
: instruye al host a verificar que la transacción incluye la autorización de
user
.
Efecto: si el remitente de la transacción no posee la firma de user, la llamada falla y no hay cambios de estado.
Por qué es seguro: aun si un atacante intenta pasar otra dirección como user, no podrá firmar por esa dirección sin la llave privada.
Casos de uso típicos:
Cambios realizados por el propietario de una cuenta (ej.: reclamar fondos, actualizar perfil).
Acciones donde el contrato necesita confirmar que la persona indicada dio su consentimiento explícito.
Puntos a cuidar:
Inyección de user maliciosa: si tu front-end pasa la dirección de otro usuario pero ese otro no firmó, require_auth fallará — es correcto.
Confusión caller vs. user: el caller puede ser quien invoca (p. ej. una cuenta distinta o un relayer). La verificación real es la firma del user. Si esperas que caller sea el que firma, usa caller.require_auth() o no pases user como parámetro.
Relayers / meta-transactions: si usas relayers, estructura tu contrato para que verifique firmas o pruebas off-chain y no confíes solo en require_auth sin el patrón correcto.
env.storage().instance().set(&Symbol::new(&env, "admin"), &admin); guarda la dirección del admin bajo la clave "admin".
Mejora recomendada: proteger initialize para que solo pueda ejecutarse una vez (ej.: comprobar get("admin") y devolver error si ya existe).
user_action
Similar a secure_action del ejemplo 2: patrón para acciones que un usuario hace sobre su propia cuenta.
admin_action
caller.require_auth() exige que la persona que presenta la transacción sea quien dice ser.
let admin: Address = ...get(...).unwrap();riesgo:unwrap() hará panic!() si no hay admin guardado. Si el contrato no fue inicializado, esto aborta la transacción.
Recomendación: manejar ese caso con Result y un error tipo NotInitialized para devolver un mensaje claro.
if caller != admin { panic!(...) } compara direcciones y aborta si no coincide.
Alternativa segura: retornar Err(ContractError::Unauthorized) para manejo más limpio en llamadas compositoras.
transfer_admin
Patrón sencillo: el admin actual firma y reemplaza el admin con new_admin.
Problemas potenciales: si new_admin es equivocada, se pierde control.
Mejor patrón (recomendado): two-step transfer:
set_pending_admin(env, new_admin) (firma del admin actual).
accept_admin(env)
Permitir re-initialization: añade guardas para que initialize solo corra una vez.
No contemplar relayers: si usas relayers, implementa patrones de meta-transaction o firmados off-chain.
Dar demasiado poder al admin: separa roles y reduce privilegios.
para mejorar UX y testeo.
Implementa patrones seguros (two-step transfer, multisig, timelocks) para producción.
Documenta, prueba y audita tus funciones de autorización — son la primera línea de defensa contra exploits.
🎯 ¿Para qué sirven?
Transparencia: Permiten que aplicaciones externas sepan qué ha ocurrido
Debugging: Te ayudan a rastrear qué hace tu contrato
Integración: Otras apps pueden "escuchar" estos eventos y reaccionar
Auditoría: Crean un registro inmutable de las acciones
🔍 Tipos de Eventos
System Events 🔧: Generados automáticamente por Soroban
Contract Events 📝: Los que tú defines en tu contrato
Código en Soroban
Nos vamos a una ruta donde queramos crear nuestro proyecto y ponemos el siguiente comando:
Borramos el contrato generado en lib.rs y ponemos el siguiente código
📌 Código del contrato: lib.rs
🎯 Explicación Detallada de los Conceptos
📢 ¿Cómo Funcionan los Eventos?
Los eventos en Soroban funcionan como un sistema de notificaciones:
Topics 🏷️: Son como "etiquetas" que permiten filtrar eventos
Primer topic: Identifica el contrato
Segundo topic: Identifica el tipo de evento
Data 📦: La información específica del evento
Puede ser un struct, tupla, o valor simple
Se serializa automáticamente
🔧 Componentes Clave
🛠 Explicación de la función initialize
🔍 Descripción:
Inicializa el valor del contador y emite un evento indicando su creación.
📌 Mecanismo:
env.storage().instance().set(...) → Guarda el valor inicial del contador en el almacenamiento persistente del contrato.
env.events().publish(...) → Publica un evento del tipo CounterCreated, que incluye el valor inicial y el símbolo del creador.
(symbol_short!("counter"), symbol_short!("created")) → Define los topics del evento, útiles para filtrarlo desde herramientas de análisis.
🧠 Importante:
Este evento puede ser consultado luego en herramientas como el explorador de eventos de Soroban para auditar quién creó el contador y con qué valor.
🛠 Explicación de la función increment
🔍 Descripción:
Incrementa el valor del contador en 1 y emite un evento con los detalles del cambio.
📌 Mecanismo:
Obtiene el valor actual del contador, lo incrementa, lo guarda nuevamente y emite un evento CounterUpdated con:
old_value: valor antes del incremento.
new_value: valor luego del incremento.
operation: símbolo "increment".
🧠 Importante:
Este evento permite rastrear cada operación que afectó el estado del contador.
🛠 Explicación de la función decrement
🔍 Descripción:
Disminuye el valor del contador (si es mayor a cero) y registra el cambio con un evento.
📌 Mecanismo:
Se protege contra valores negativos con una verificación.
El evento publicado incluye la operación "decrement".
🧠 Importante:
El estado nunca bajará de 0, manteniéndose válido en todo momento.
🛠 Explicación de la función reset
🔍 Descripción:
Establece el contador en cero y emite un evento indicando quién lo reseteó.
📌 Mecanismo:
Guarda el valor anterior para registrarlo en el evento CounterReset.
reset_by permite rastrear al responsable del reinicio.
🧠 Importante:
Este evento es útil para auditoría o lógica de negocio que responda a resets.
🛠 Explicación de la función get_counter
🔍 Descripción:
Consulta el valor actual del contador sin modificar el estado del contrato.
📌 Mecanismo:
Solo lectura: no genera logs ni eventos.
Devuelve 0 si el contador nunca fue inicializado.
🧠 Importante:
Función ideal para mostrar el estado actual en una interfaz de usuario o al cliente.
🛠 Explicación de la función log_milestone
🔍 Descripción:
Permite emitir un evento especial para marcar hitos del contador.
📌 Mecanismo:
El evento contiene una tupla con el valor actual, el hito alcanzado, y un mensaje descriptivo.
No altera el estado del contrato.
🧠 Importante:
Útil para casos de uso como alcanzar un récord, pasar un umbral o anunciar logros.
Compilación del contrarto:
Resultado de la compilación
Despliegue del contrato
Mac/Linux
Windows
Contrato desplegado
Pruebas de ejecución y visualización de los eventos
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
También observamos evento en la parte de "raised event" con el contenido que le hemos pasado.
Otra opción de ver el evento por el cliente de Stellar
1 por lumenscan sacamos el ledger donde está la operación:, esto lo obtenemos por el historial de operacione del contrato y con que billetera interactuó. En esta caso la billetera "developer"
ledger
2 en la consola ejecutamos:
Resultados de la operación
Si vemos la parte Value vemos ambos valores del evento el 42 y el valor developer.
Tokens
🌟 Introducción a los Tokens SEP-41 en Stellar
🤔 ¿Qué es un Token SEP-41?
El SEP-41 es un estándar para tokens fungibles en la red Stellar que utiliza contratos inteligentes Soroban. Pero, ¿qué significa "fungible"? 🤷♂️
🪙 Fungibilidad Explicada de Forma Simple
Imagina que tienes billetes de $10 pesos en tu billetera. Cada billete de $10 es intercambiable por cualquier otro billete de $10 - todos tienen el mismo valor y función. Esto es fungibilidad: cada unidad es idéntica e intercambiable con otra del mismo tipo.
Pero aquí viene algo súper importante: Los tokens fungibles también son divisibles ✂️
🔢 Divisibilidad y Unidad Mínima
Al igual que el dinero físico, los tokens fungibles pueden dividirse en partes más pequeñas, pero siempre hay un límite:
💰 Ejemplo con dinero físico:
1 peso se puede dividir en 100 centavos
El centavo es la unidad mínima - no puedes tener 0.5 centavos físicos
🪙 Ejemplo con tokens digitales:
1 token puede dividirse según sus decimales configurados
Si un token tiene 6 decimales: 1 token = 1,000,000 unidades mínimas
Si un token tiene 2 decimales: 1 token = 100 unidades mínimas
Tu token: Tú decides cuántos decimales → flexibilidad total
⚠️ Regla de oro: No puedes tener fracciones de la unidad mínima. Si tu token tiene 2 decimales, no puedes enviar 0.001 tokens (necesitarías al menos 0.01).
Ejemplos de tokens fungibles:
💰 Monedas digitales (como USDC, Bitcoin)
💳 Puntos de recompensa
🎮 Monedas de videojuegos
📈 Tokens de inversión
¿Por qué son importantes? Los tokens fungibles son perfectos para crear:
💱 Monedas digitales
🏦 Sistemas de pagos
💸 Programas de lealtad
📊 Activos financieros
🧙♂️ OpenZeppelin: Tu Escudo de Seguridad
🛡️ ¿Por qué usar OpenZeppelin?
OpenZeppelin es como tener un equipo de expertos en seguridad trabajando para ti 24/7. Aquí te explico por qué es crucial:
🔐 Contratos Ultra-Seguros:
⚔️ Batalla-probados: Millones de dólares protegidos en producción
🧪 Testeo exhaustivo: Cada línea de código probada por expertos
🏆 Estándar de la industria: Usado por los proyectos más grandes
💰 ¿Sabes cuánto dinero se ha perdido por contratos inseguros?
🔥 +$3 billones USD perdidos en hacks de DeFi desde 2020
💸 The DAO Hack (2016): $60 millones robados
🚨 Poly Network (2021): $600 millones hackeados
🛠️ El Trabajo Pesado Ya Está Hecho: OpenZeppelin hace el trabajo que tú no quieres (ni debes) hacer:
🔒 Protección contra reentrancy attacks
🛡️ Validación de permisos y roles
⚡ Optimización de gas
🧙♂️ OpenZeppelin Wizard para Stellar
OpenZeppelin ha creado un wizard (asistente) súper útil para generar tokens SEP-41: 🔗
🌟 ¿Qué hace el wizard por ti?
🎨 Interfaz visual: Sin necesidad de escribir código desde cero
⚙️ Configuración simple: Solo selecciona las funciones que necesitas
📋 Código generado: Listo para usar en minutos
⚠️ Importante: Limitaciones Actuales
Aunque el wizard es genial para empezar, todavía tiene algunos errores 🐛. Por eso, es mejor usar los ejemplos oficiales como referencia:
🔗
Instructivo para poder utilizar el código de OpenZeppelin
Pre-requisitos
Tener instalado Rust.
Tener instalado El cliente Stellar
tener instalado el cliente de GIt
Copiar el repositorio de github de OpenZeppelin en una carpeta previamente seleccionada con la siguiente instrucción.
Con el editor favorito de código abrimos el folder stellar-contracts.
En el directorio raiz abrir el archivo Cargo.toml
Para cada ejemplo, por facilidad vamos a crear una subcarpeta
Ejemplo 1: Un token sencillo
Vamos a crear el token MYT (My token).
MyToken usa la funcionalidad limitada ( capped ) de un token fungible, en esta ocasión la función set_cap, en esta le indicamos cual es el maximo de monedas posibles a generar del token.
En el archivo Cargo.toml del directorio raiz. en members, agregamos
Dentro de la carpeta examples creamos la carpeta "myt"
En el folder myt creamos el archivo Cargo.toml con lo siguiente
En el caso de ser un token fungible es con
En el caso de ser un NFT se pone
Dentro de myt creamos una carpeta llamada src con los siguientes archivos:
contract.rs ( Contrato del token)
lib.rs ( archivo que engancha el contrato y su respectivo test)
Escribimos lo siguiente dentro de lib.rs
Código de contract.rs
Explicación del Contrato MyToken
¿Qué es este contrato?
Este es un token fungible (como una moneda digital) que funciona en la blockchain de Stellar. Piensa en él como crear tu propia criptomoneda, pero con reglas específicas que tú defines.
Estructura General del Código
1. Importaciones (Las herramientas que necesitamos)
¿Qué significa esto?
soroban_sdk: Es como la "caja de herramientas" básica para crear contratos en Stellar
stellar_fungible: Son las funciones pre-construidas para crear tokens (como plantillas ya hechas)
capped: Funciones para limitar la cantidad máxima de tokens que se pueden crear
2. Definición del Contrato
Explicación simple:
Myt es el nombre de nuestro contrato (puedes cambiarlo por el que quieras)
#[contract] le dice a Stellar "esto es un contrato inteligente"
Es como crear una "fábrica" que va a producir tokens
Partes Más Importantes
🏗️ Constructor (La función que inicializa todo)
¿Qué hace?
Base::set_metadata(...): Define las características básicas del token:
2: Decimales (como los centavos de las monedas)
"MyToken": El nombre completo del token
Analogía: Es como registrar una nueva moneda en el banco central, definiendo su nombre, símbolo y cuántas unidades máximo pueden existir.
🪙 Función Mint (Crear nuevos tokens)
¿Qué hace?
check_cap(e, amount): Verifica que no se exceda el límite máximo
Base::mint(...): Crea los tokens y los asigna a una cuenta específica
Analogía: Es como una máquina impresora de billetes, pero que primero verifica que no imprimas más de lo permitido.
🔄 Implementación de FungibleToken (Las funciones estándar)
Esta parte implementa todas las funciones que cualquier token debe tener:
Funciones de Consulta (Solo leen información):
total_supply(): ¿Cuántos tokens existen en total?
balance(): ¿Cuántos tokens tiene una cuenta específica?
decimals()
Funciones de Transferencia:
transfer(): Enviar tokens de una cuenta a otra
transfer_from(): Permitir que alguien más mueva tus tokens (con permiso previo)
¿Por qué este diseño es inteligente?
1. Reutilización de código
En lugar de escribir todas las funciones desde cero, usa Base que ya tiene todo implementado y probado.
2. Seguridad con límites
La función check_cap asegura que nunca se puedan crear más tokens de los permitidos.
3. Estándar compatible
Al implementar FungibleToken, tu token funciona con todas las aplicaciones que esperan tokens estándar.
Compilación del contrato
Nos ubicamos dentro de ../stellar-contracts/examples/myt allí en consola ejecutamos:
Despliegue del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
Nota:Para un millón o cualquier cifra se ponen 2 ceros de más que son los centavos
Acá podemos ver que se llama al constuctor del contrato y estamos poniendo que el máximo de tokens son 1 millón.
No obstante, a pesar que tenemos como máximo 1 millón, no hemos acuñado ninguna moneda.
A continuación vamos a invocar la función mint
Para ver el balance
Si queremos ver el saldo de una forma visual en nuestra billetera 😃
Primero ejecutamos el siguiente comando
En nuetos caso el alias de cuenta o entidad es developer, mandamos a descansar a Bob o Alice 😉
Esto nos da la llave secreta, esta llave secreta la añadimos en nuestra billetera favorita, en nuestro caso la billetera freigther
Una vez importada la billetera importamos el contrato de token
Al añadir el contrato del token vemos lo siguiente en nuestra billetera:
⚠️ OJO: Si eres detallista, ves un agujero ENORME de seguridad, cualquiera puede hacer la operación de mint, este ejemplo sólo ha sido con fines ilustrativos 😅.
Ejemplo 2: Un token sencillo, pero sólo el dueño puede acuñarlo
Lo primero que hacemos es copiar la carpeta myt y la renombramos con el nombre mytsf ( My Token Safe version)
dentro de la carpeta mytsv cambiamos el contenido de name en el archivo Cargo.toml
En la carpeta raiz agregamos en el archivo Cargo.toml dentro de members la carpeta que se acabo de crear
El código en en contract.rs
Explicación del Contrato MyToken con Control de Propietario 🔐
Estructura General del Código
1. Importaciones (Las herramientas que necesitamos)
¿Qué significa esto?
soroban_sdk: Es como la "caja de herramientas" básica para crear contratos en Stellar
stellar_fungible: Son las funciones pre-construidas para crear tokens (como plantillas ya hechas)
capped: Funciones para limitar la cantidad máxima de tokens que se pueden crear
2. Definición de Constantes (Los valores que no cambian)
¿Qué es esto?
Es como crear una "etiqueta" que identifica quién es el propietario del contrato
symbol_short!("OWNER"): Es una forma eficiente de almacenar esta información en Stellar
Piénsalo como la "llave maestra" del contrato 🗝️
2. Definición del Contrato
Explicación simple:
MyTSV es el nombre de nuestro contrato (cambió de Myt a MyTSV)
#[contract] le dice a Stellar "esto es un contrato inteligente"
Partes Más Importantes
🏗️ Constructor (La función que inicializa todo) - ¡MEJORADO!
¿Qué hace ahora? 🎯
Base::set_metadata(...): Define las características básicas del token:
2: Decimales ( como los centavos del peso)
"My Token Safe Version"
Analogía: Es como registrar una nueva moneda en el banco central, pero ahora también registras oficialmente quién es el director del banco que puede autorizar la impresión de nuevos billetes. 🏦
🚨 CAMBIO IMPORTANTE: Control de Propietario
🪙 Función Mint (Crear nuevos tokens) - ¡AHORA MÁS SEGURA!
¿Qué hace ahora? 🔍
🆕 let owner: Address = e.storage().instance().get(&OWNER)...:
Busca quién es el propietario registrado del contrato
🆕 owner.require_auth()
Analogía: Antes era como una máquina impresora de billetes que cualquiera podía usar. Ahora es como una máquina que requiere la huella dactilar del director del banco para funcionar. 👆
¿Por qué es una EXCELENTE práctica? 🌟
🛡️ Seguridad Crítica
Antes: Cualquier persona podía crear tokens nuevos
Ahora: Solo el propietario puede crear tokens
💰 Control de Inflación
Sin control: Los tokens podrían volverse sin valor si cualquiera los crea
Con control: El propietario decide cuándo y cuántos tokens crear
🎯 Casos de Uso Reales
Token de empresa: Solo el CEO puede autorizar nuevas emisiones
Token de recompensas: Solo el sistema de la app puede crear tokens por logros
Token de comunidad: Solo el comité puede crear tokens para nuevos miembros
⚖️ Transparencia
Todo el mundo puede ver quién es el propietario
Todas las operaciones de mint quedan registradas en la blockchain
La comunidad puede auditar quién y cuándo se crean nuevos tokens
Ejemplo de Ataque Prevenido 🚫
Escenario peligroso sin control de propietario:
Con control de propietario:
🔄 Implementación de FungibleToken (Las funciones estándar)
Esta parte implementa todas las funciones que cualquier token debe tener (sin cambios, pero ahora más seguro porque el mint está protegido):
Funciones de Consulta (Solo leen información):
total_supply(): ¿Cuántos tokens existen en total?
balance(): ¿Cuántos tokens tiene una cuenta específica?
decimals()
Funciones de Transferencia:
transfer(): Enviar tokens de una cuenta a otra
transfer_from(): Permitir que alguien más mueva tus tokens (con permiso previo)
¿Por qué este diseño es inteligente? 🧠
1. Reutilización de código
En lugar de escribir todas las funciones desde cero, usa Base que ya tiene todo implementado y probado.
2. Seguridad multinivel 🔒
Nivel 1: check_cap asegura que no se excedan los límites
Nivel 2: require_auth asegura que solo el propietario pueda crear tokens
Nivel 3: Todo queda registrado en la blockchain (inmutable y auditable)
3. Estándar compatible
Al implementar FungibleToken, tu token funciona con todas las aplicaciones que esperan tokens estándar.
Compilación del contrato
Nos ubicamos dentro de ../stellar-contracts\examples\mytsv allí en consola ejecutamos:
Despliegue del contrato
Para Linux y Mac el salto de línea de la instrucción es con el carácter " \ " para Windows con el carácter " ` "
Nota:Para un millón o cualquier cifra se ponen 2 ceros de más que son los centavos
Haciendo un mint con una cuenta no valida
Haciendo un mint con una cuenta valida
⚠️ OJO: Si eres detallista, ves que se revela la wallet del owner 🙈, esto lo podemos solucionar, si comparamos la billetera que nos envian si es la misma del owner 😉, si es la misma ok, si no es la misma mensaje de error. Además de la instruccion de la verificación si la firma digital es del owner owner.require_auth().
#![no_std]
use soroban_sdk::{contract, contractimpl, log, Env, Symbol};
// 📦 Contrato simple sin autenticación
#[contract]
pub struct PublicContract;
#[contractimpl]
impl PublicContract {
pub fn hello(env: Env) -> Symbol {
// 📢 Cualquiera puede ejecutar esta función
log!(&env, "Función pública ejecutada sin autorización.");
Symbol::new(&env, "COUNTER")
}
}
#![no_std]
use soroban_sdk::{contract, contractimpl, Env, Symbol, log, Address};
#[contract]
pub struct AuthContract;
#[contractimpl]
impl AuthContract {
pub fn secure_action(env: Env, user: Address) -> Symbol {
// 🔐 Usuario debe firmar la transacción
user.require_auth();
log!(&env, "Acción segura ejecutada por un usuario autorizado.");
Symbol::new(&env, "ok")
}
}
#![no_std]
use soroban_sdk::{contract, contractimpl, log, Env, Symbol, Address};
#[contract]
pub struct RoleBasedContract;
#[contractimpl]
impl RoleBasedContract {
// 🏗️ Inicializar contrato con un administrador
pub fn initialize(env: Env, admin: Address) -> Symbol {
admin.require_auth();
env.storage().instance().set(&Symbol::new(&env, "admin"), &admin);
log!(&env, "Contrato inicializado con administrador.");
Symbol::new(&env, "INITIALIZED")
}
// 👤 Acción para usuarios normales
pub fn user_action(env: Env, user: Address) -> Symbol {
user.require_auth();
log!(&env, "Acción de usuario ejecutada.");
Symbol::new(&env, "USER_OK")
}
// 👑 Acción solo para administradores
pub fn admin_action(env: Env, caller: Address) -> Symbol {
caller.require_auth();
let admin: Address = env.storage()
.instance()
.get(&Symbol::new(&env, "admin"))
.unwrap();
if caller != admin {
panic!("Solo el administrador puede ejecutar esta función.");
}
log!(&env, "Acción administrativa ejecutada por el admin.");
Symbol::new(&env, "ADMIN_OK")
}
// 🔄 Transferir rol de administrador
pub fn transfer_admin(env: Env, current_admin: Address, new_admin: Address) -> Symbol {
current_admin.require_auth();
let stored_admin: Address = env.storage()
.instance()
.get(&Symbol::new(&env, "admin"))
.unwrap();
if current_admin != stored_admin {
panic!("Solo el administrador actual puede transferir el rol.");
}
env.storage().instance().set(&Symbol::new(&env, "admin"), &new_admin);
log!(&env, "Administrador transferido exitosamente.");
Symbol::new(&env, "ADMIN_TRANSFERRED")
}
}
stellar contract init events --name events
#![no_std]
use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Env, Symbol};
// 📝 Definimos los tipos de datos para nuestros eventos
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CounterCreated {
pub initial_value: u32,
pub creator: Symbol,
}
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CounterUpdated {
pub old_value: u32,
pub new_value: u32,
pub operation: Symbol,
}
#[contracttype]
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CounterReset {
pub previous_value: u32,
pub reset_by: Symbol,
}
// 🏗️ Definimos las claves para almacenar datos
const COUNTER: Symbol = symbol_short!("COUNTER");
#[contract]
pub struct CounterContract;
#[contractimpl]
impl CounterContract {
/// 🎯 Inicializa el contador y emite evento de creación
pub fn initialize(env: Env, initial_value: u32, creator: Symbol) {
// Guardamos el valor inicial
env.storage().instance().set(&COUNTER, &initial_value);
// 📢 ¡Emitimos nuestro primer evento!
env.events().publish(
(symbol_short!("counter"), symbol_short!("created")), // Topics para filtrar
CounterCreated {
initial_value,
creator,
},
);
}
/// ➕ Incrementa el contador
pub fn increment(env: Env) -> u32 {
let current: u32 = env.storage().instance().get(&COUNTER).unwrap_or(0);
let new_value = current + 1;
env.storage().instance().set(&COUNTER, &new_value);
// 📢 Evento de actualización
env.events().publish(
(symbol_short!("counter"), symbol_short!("updated")),
CounterUpdated {
old_value: current,
new_value,
operation: symbol_short!("increment"),
},
);
new_value
}
/// ➖ Decrementa el contador
pub fn decrement(env: Env) -> u32 {
let current: u32 = env.storage().instance().get(&COUNTER).unwrap_or(0);
let new_value = if current > 0 { current - 1 } else { 0 };
env.storage().instance().set(&COUNTER, &new_value);
// 📢 Evento de actualización
env.events().publish(
(symbol_short!("counter"), symbol_short!("updated")),
CounterUpdated {
old_value: current,
new_value,
operation: symbol_short!("decrement"),
},
);
new_value
}
/// 🔄 Resetea el contador a cero
pub fn reset(env: Env, reset_by: Symbol) {
let current: u32 = env.storage().instance().get(&COUNTER).unwrap_or(0);
env.storage().instance().set(&COUNTER, &0u32);
// 📢 Evento de reset
env.events().publish(
(symbol_short!("counter"), symbol_short!("reset")),
CounterReset {
previous_value: current,
reset_by,
},
);
}
/// 👀 Obtiene el valor actual (sin eventos, es solo lectura)
pub fn get_counter(env: Env) -> u32 {
env.storage().instance().get(&COUNTER).unwrap_or(0)
}
/// 📊 Función que emite evento personalizado con datos múltiples
pub fn log_milestone(env: Env, milestone: u32, message: Symbol) {
let current: u32 = env.storage().instance().get(&COUNTER).unwrap_or(0);
// 📢 Evento complejo con múltiples datos
env.events().publish(
(symbol_short!("counter"), symbol_short!("milestone")),
(current, milestone, message),
);
}
}
rustenv.events().publish( (topic1, topic2), // 🏷️ Topics para filtrar event_data // 📦 Datos del evento);
pub fn mint(e: &Env, account: Address, amount: i128) {
let owner: Address = e.storage().instance().get(&OWNER).expect("owner should be set");
owner.require_auth();
check_cap(e, amount);
Base::mint(e, &account, amount);
}
// ❌ Cualquiera podía hacer esto:contract.mint(mi_cuenta, 1_000_000); // ¡Crear un millón de tokens!
// ✅ Solo el owner puede hacer esto:contract.mint(cuenta_destino, 1000); // Y debe firmar la transacción
1. Hacker descubre el contrato.
2. Llama a mint(su_cuenta, 999_999_999).
3. Se vuelve millonario instantáneamente.
4. El valor del token se colapsa.
5. Todos los holders pierden dinero
1. Hacker intenta llamar mint().
2. El contrato verifica: "¿Eres el propietario?".
3. Respuesta: "No".
4. Transacción rechazada automáticamente ✋.
5. El token mantiene su integridad