Construir un Frontend de Dapp
Esta es una continuación del tutorial de Comenzar, donde deberías haber desplegado dos contratos inteligentes en la red pública. En esta sección, crearemos una aplicación web que interactúe con los contratos a través de llamadas RPC.
Vamos a comenzar.
Inicializar una herramienta frontend
Puedes crear una aplicación Soroban con cualquier herramienta frontend o integrarla en cualquier aplicación de pila completa existente. Para este tutorial, vamos a usar Astro. Astro funciona con React, Vue, Svelte, cualquier otra biblioteca de interfaz de usuario, o ninguna biblioteca de interfaz de usuario en absoluto. En este tutorial, no estamos usando una biblioteca de interfaz de usuario. Las partes específicas de Soroban de este tutorial serán similares sin importar qué herramienta frontend uses.
Si eres nuevo en el frontend, no te preocupes. No profundizaremos demasiado. Pero será útil para ti ver y experimentar el proceso de desarrollo frontend utilizado por las aplicaciones Soroban. Cubriremos las partes relevantes de JavaScript y Astro, pero enseñar todo el desarrollo frontend y Astro está más allá del alcance de este tutorial.
Vamos a comenzar.
Vas a necesitar Node.js v18.14.1 o superior. Si aún no lo has hecho, instálalo ahora.
Queremos inicializar nuestro proyecto actual como un proyecto de Astro. Para hacer esto, podemos clonar una plantilla. Puedes encontrar plantillas de Soroban en GitHub buscando repositorios que comiencen con "soroban-template-". Para este tutorial, usaremos stellar/soroban-template-astro. También utilizaremos una herramienta llamada degit para clonar la plantilla sin su historial de git. Esto nos permitirá configurarla como nuestro propio proyecto git.
Dado que tienes node
y su administrador de paquetes npm
instalados, también tienes npx
. Asegúrate de que ya no estés en tu directorio soroban-hello-world
y luego ejecuta:
npx degit stellar/soroban-template-astro first-soroban-app
cd first-soroban-app
git init
git add .
git commit -m "first commit: initialize from stellar/soroban-template-astro"
Este proyecto tiene la siguiente estructura de directorios, que detallaremos más abajo.
├── contracts
│ ├── hello_world
│ └── increment
├── CONTRIBUTING.md
├── Cargo.toml
├── Cargo.lock
├── initialize.js
├── package-lock.json
├── package.json
├── packages
├── public
├── src
│ ├── components
│ │ └── Card.astro
│ ├── env.d.ts
│ ├── layouts
│ │ └── Layout.astro
│ └── pages
│ └── index.astro
└── tsconfig.json
Los contratos
son los mismos que revisaste en los pasos anteriores del tutorial.
Generar un paquete NPM para el contrato Hello World
Antes de abrir los nuevos archivos frontend, generemos un paquete NPM para el contrato Hello World. Esta es nuestra forma sugerida de interactuar con los contratos desde los frontend. Estas bibliotecas generadas funcionan con cualquier proyecto de JavaScript (no un UI específico como React), y facilitan el trabajo con algunas de las partes más difíciles de Soroban, como la codificación de XDR.
Esto va a utilizar el comando CLI stellar contract bindings typescript
:
stellar contract bindings typescript \
--network testnet \
--contract-id $(cat .stellar/contract-ids/hello_world.txt) \
--output-dir packages/hello_world
Este proyecto está configurado como un Workspace de NPM, por lo que la biblioteca cliente hello_world
se generó en el directorio packages
en packages/hello_world
.
Intentamos mantener el código en estas bibliotecas generadas legible, así que ve y observa. Abre el nuevo directorio packages/hello_world
en tu editor. Si has construido o contribuido a proyectos de Node, todo debería parecer familiar. Verás un archivo package.json
, un directorio src
, un tsconfig.json
, e incluso un README.
Generar un paquete NPM para el contrato Increment
Aunque podemos ejecutar soroban contract bindings typescript
para cada uno de nuestros contratos individualmente, la plantilla soroban-template-astro que utilizamos como nuestra plantilla incluye un script muy útil initialize.js
que se encargará de esto para todos los contratos en nuestro directorio contracts
.
Además de generar los paquetes NPM, initialize.js
también:
- Generar y financiar nuestra cuenta Stellar
- Construir todos los contratos en el directorio
contracts
- Desplegar nuestros contratos
- Crear clientes de contrato útiles para cada contrato
Ya nos hemos ocupado de los primeros tres puntos en pasos anteriores de este tutorial, por lo que esas tareas serán noops cuando ejecutemos initialize.js
.
Configurar initialize.js
Necesitamos asegurarnos de que initialize.js
tenga todas las variables de entorno que necesita antes de hacer nada más. Copia el archivo .env.example
a .env
. Las variables de entorno establecidas en .env
son utilizadas por el script initialize.js
.
cp .env.example .env
Veamos el contenido del archivo .env
:
# Prefix with "PUBLIC_" to make available in Astro frontend files
PUBLIC_STELLAR_NETWORK_PASSPHRASE="Standalone Network ; February 2017"
PUBLIC_STELLAR_RPC_URL="http://localhost:8000/soroban/rpc"
STELLAR_ACCOUNT="me"
STELLAR_NETWORK="standalone"
Este archivo .env
está configurado para conectarse a una red que se ejecuta localmente, pero queremos configurar nuestro proyecto para comunicarse con Testnet, ya que ahí es donde desplegamos nuestros contratos. Para hacer eso, actualicemos el archivo .env
para que se vea así:
# Prefix with "PUBLIC_" to make available in Astro frontend files
-PUBLIC_STELLAR_NETWORK_PASSPHRASE="Standalone Network ; February 2017"
+PUBLIC_STELLAR_NETWORK_PASSPHRASE="Test SDF Network ; September 2015"
-PUBLIC_STELLAR_RPC_URL="http://localhost:8000/soroban/rpc"
+PUBLIC_STELLAR_RPC_URL="https://soroban-testnet.stellar.org:443"
-STELLAR_ACCOUNT="me"
+STELLAR_ACCOUNT="alice"
-STELLAR_NETWORK="standalone"
+STELLAR_NETWORK="testnet"
Este archivo .env
se utiliza en el script initialize.js
. Al usar la CLI, aún podemos utilizar la configuración de red que establecimos en el paso de Configuración, o pasando las banderas --rpc-url
y --network-passphrase
.
Ejecutar initialize.js
Primero, instalemos las dependencias de JavaScript:
npm install
Y luego ejecutemos initialize.js
:
npm run init
Como se mencionó anteriormente, este script intenta construir y desplegar nuestros contratos, lo cual ya hemos hecho. El script es lo suficientemente inteligente como para comprobar si un paso ya ha sido tratado, y es un no-op en ese caso, por lo que es seguro ejecutarlo más de una vez.
Llamar al contrato desde el frontend
Ahora abramos src/pages/index.astro
y veamos cómo el código frontend se integra con el paquete NPM que creamos para nuestros contratos.
Aquí podemos ver que estamos importando nuestro cliente helloWorld
generado desde ../contracts/hello_world
. Luego, estamos invocando el método hello
y agregando el resultado a la página.
---
import Layout from "../layouts/Layout.astro";
import Card from "../components/Card.astro";
import helloWorld from "../contracts/hello_world";
const { result } = await helloWorld.hello({ to: "you" });
const greeting = result.join(" ");
---
...
<h1>{greeting}</h1>
¡Veamos esto en acción! Inicia el servidor de desarrollo:
npm run dev
Y abre localhost:4321 en tu navegador. ¡Deberías ver el saludo del contrato!
Puedes intentar actualizar el argumento { to: 'Soroban' }
. Cuando guardes el archivo, la página se actualizará automáticamente.
Cuando inicies el servidor de desarrollo con npm run dev
, verás una salida similar en tu terminal a la que obtuviste cuando ejecutaste npm run init
. Esto se debe a que el script dev
en package.json está configurado para ejecutar npm run init
y astro dev
, para que puedas asegurarte de que tu contrato desplegado y tu paquete NPM generado estén siempre sincronizados. Si solo quieres iniciar el servidor de desarrollo sin el script initialize.js, puedes ejecutar npm run astro dev
.
¿Qué está pasando aquí?
Si inspeccionas la página (clic derecho, inspeccionar) y actualizas, verás un par de cosas interesantes:
- La pestaña "Red" muestra que no se han realizado solicitudes Fetch/XHR. ¡Pero las llamadas RPC ocurren a través de Fetch/XHR! ¿Entonces, cómo está llamando el frontend al contrato?
- No hay JavaScript en la página. ¡Pero acabamos de escribir algo de JavaScript! ¿Cómo está funcionando?
Esta es parte de la filosofía de Astro: el frontend debe enviarse con la menor cantidad de activos posible. Prefiriendo cero JavaScript. Cuando pones JavaScript en el frontmatter, Astro lo ejecutará en el momento de la construcción y luego reemplazará cualquier cosa en los corchetes {...}
con la salida.
Al usar el servidor de desarrollo con npm run dev
, se ejecuta el código del frontmatter en el servidor e inyecta los valores resultantes en la página en el cliente.
Puedes intentar construir para ver esto de manera más evidente:
npm run build
Luego verifica la carpeta dist
. Verás que se generó un archivo HTML y CSS, pero no JavaScript. Y si miras el archivo HTML, verás un "Hola Soroban" estático en el <h1>
.
Durante la construcción, Astro realizó una única llamada a tu contrato, luego inyectó el resultado estático en la página. Esto es genial para métodos de contrato que no cambian, pero probablemente no funcionará para la mayoría de los métodos de contrato. Vamos a integrar con el contrato incrementor
para ver cómo manejar métodos interactivos en Astro. -->
Llamar al contrato incrementor desde el frontend
Mientras hello
es un método simple de solo lectura, increment
cambia el estado on-chain. Esto significa que alguien necesita firmar la transacción. Así que necesitaremos agregar capacidades de firma de transacciones al frontend.
La forma en que funciona la firma en un navegador es con una billetera. Las billeteras pueden ser aplicaciones web, extensiones de navegador, aplicaciones independientes, o incluso dispositivos de hardware separados.
Instalar la extensión Freighter
En este momento, la billetera que mejor admite Soroban es Freighter. Está disponible como un complemento para Firefox, así como extensiones para Chrome y Brave. Ve y instálalo ahora.
Una vez instalado, ábrelo haciendo clic en el ícono de la extensión. Si esta es tu primera vez usando Freighter, necesitarás crear una nueva billetera. Sigue las indicaciones para crear una contraseña y guardar tu frase de recuperación.
Ve a Configuración (el ícono de la tuerca) → Preferencias y activa la opción para Habilitar Modo Experimental. Luego regresa a su pantalla de inicio y selecciona "Test Net" en el menú desplegable de la esquina superior derecha. Finalmente, si muestra el mensaje de que tu dirección Stellar no está financiada, ve y haz clic en el botón "Financiar con Friendbot".
Ahora estás completamente configurado para usar Freighter como usuario, y puedes agregarlo a tu aplicación.
Agregar StellarWalletsKit y configurarlo
Aunque estamos usando Freighter para probar nuestra aplicación, hay más billeteras que admiten la firma de transacciones de contratos inteligentes. Para facilitar su integración, estamos utilizando la biblioteca StellarWalletsKit
que nos permite soportar todas las billeteras Stellar con una sola biblioteca.
Para instalar este kit, vamos a incluir el siguiente paquete:
npm install @creit.tech/stellar-wallets-kit
Con el paquete instalado, vamos a crear un nuevo archivo simple donde se encontrarán nuestro kit instanciado y estado simple. Crea el archivo src/stellar-wallets-kit.ts
y pega esto:
import {
allowAllModules,
FREIGHTER_ID,
StellarWalletsKit,
} from "@creit.tech/stellar-wallets-kit";
const SELECTED_WALLET_ID = "selectedWalletId";
function getSelectedWalletId() {
return localStorage.getItem(SELECTED_WALLET_ID);
}
const kit = new StellarWalletsKit({
modules: allowAllModules(),
network: import.meta.env.PUBLIC_STELLAR_NETWORK_PASSPHRASE,
// StellarWalletsKit forces you to specify a wallet, even if the user didn't
// select one yet, so we default to Freighter.
// We'll work around this later in `getPublicKey`.
selectedWalletId: getSelectedWalletId() ?? FREIGHTER_ID,
});
export const signTransaction = kit.signTransaction.bind(kit);
export async function getPublicKey() {
if (!getSelectedWalletId()) return null;
const { address } = await kit.getAddress();
return address;
}
export async function setWallet(walletId: string) {
localStorage.setItem(SELECTED_WALLET_ID, walletId);
kit.setWallet(walletId);
}
export async function disconnect(callback?: () => Promise<void>) {
localStorage.removeItem(SELECTED_WALLET_ID);
kit.disconnect();
if (callback) await callback();
}
export async function connect(callback?: () => Promise<void>) {
await kit.openModal({
onWalletSelected: async (option) => {
try {
await setWallet(option.id);
if (callback) await callback();
} catch (e) {
console.error(e);
}
return option.id;
},
});
}
En el código anterior, instanciamos el kit con la configuración deseada y lo exportamos. También envolvemos algunas funciones del kit y añadimos funcionalidad personalizada, como aumentar el kit permitiendo que recuerde qué opciones de billetera fueron seleccionadas entre actualizaciones de página (esa es la parte de localStorage
). El kit requiere un selectedWalletId
incluso antes de que el usuario seleccione uno, así que también encontramos una solución a esta limitación, como explica el comentario de código. Puedes aprender más sobre cómo funciona el kit en la documentación de StellarWalletsKit
Ahora vamos a añadir un botón "Conectar" a la página que abrirá el modal incorporado del kit e invitará al usuario a usar su billetera preferida. Una vez que el usuario elija su billetera preferida y otorgue permiso para aceptar solicitudes del sitio web, recuperaremos la clave pública y el botón de "Conectar" será reemplazado con un mensaje que dice: "Conectado como [su clave pública]".
Ahora agreguemos un nuevo componente al directorio src/components
llamado ConnectWallet.astro
con el siguiente contenido:
<div id="connect-wrap" class="wrap" aria-live="polite">
&nbsp;
<div class="ellipsis"></div>
<button style="display:none" data-connect aria-controls="connect-wrap">
Connect
</button>
<button style="display:none" data-disconnect aria-controls="connect-wrap">
Disconnect
</button>
</div>
<style>
.wrap {
text-align: center;
display: flex;
width: 18em;
margin: auto;
justify-content: center;
line-height: 2.7rem;
gap: 0.5rem;
}
.ellipsis {
overflow: hidden;
text-overflow: ellipsis;
text-align: center;
white-space: nowrap;
}
</style>
<script>
import { getPublicKey, connect, disconnect } from "../stellar-wallets-kit";
const ellipsis = document.querySelector(
"#connect-wrap .ellipsis",
) as HTMLElement;
const connectButton = document.querySelector("[data-connect]") as HTMLButtonElement;
const disconnectButton = document.querySelector(
"[data-disconnect]",
) as HTMLButtonElement;
async function showDisconnected() {
ellipsis.innerHTML = "";
ellipsis.removeAttribute("title");
connectButton.style.removeProperty("display");
disconnectButton.style.display = "none";
}
async function showConnected() {
const publicKey = await getPublicKey();
if (publicKey) {
ellipsis.innerHTML = ``;
ellipsis.title = publicKey ?? "";
connectButton.style.display = "none";
disconnectButton.style.removeProperty("display");
} else {
showDisconnected();
}
}
connectButton.addEventListener("click", async () => {
await connect(showConnected);
});
disconnectButton.addEventListener("click", async () => {
disconnect(showDisconnected);
});
if (await getPublicKey()) {
showConnected();
} else {
showDisconnected();
}
</script>
Algunas de estas cosas pueden parecer sorprendentes. ¿<style>
y <script>
en medio de la página? ¿Nombres de clase poco creativos como wrap
? ¿Declaraciones de import
en un <script>
? ¿await
de nivel superior? ¿Qué está pasando aquí?
Astro automáticamente delimita los estilos dentro de un componente a ese componente, así que no hay razón para que tengamos que idear nombres ingeniosos para nuestras clases.
Y todas las declaraciones de script
se agrupan y se incluyen inteligentemente en la página. Incluso si usas el mismo componente múltiples veces, el script solo se incluirá una vez. Y sí, puedes usar await
de nivel superior.
Puedes leer más sobre esto en la página de Astro sobre scripts del lado del cliente.
El código en sí aquí es bastante autocontenido. Importamos kit
desde el archivo que creamos anteriormente. Entonces, cuando el usuario haga clic en el botón de inicio de sesión, llamamos a la función connect
que creamos en nuestro archivo stellar-wallets-kit.ts
anterior. Esto lanzará el modal incorporado de StellarWalletsKit, que permite al usuario elegir entre las opciones de billetera que configuramos (configuramos todas, con allowAllModules
). Pasamos nuestra propia función setLoggedIn
como callback, que será llamada en la función onWalletSelected
en stellar-wallets-kit.ts
. Terminamos actualizando la interfaz de usuario, según si el usuario está conectado o no.
Ahora podemos importar el componente en el frontmatter de pages/index.astro
:
---
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
import helloWorld from "../contracts/hello_world";
+import ConnectWallet from '../components/ConnectWallet.astro'
...
Y agregarlo justo debajo del <h1>
:
<h1>{greeting}</h1>
+<ConnectWallet />
Si ya no estás ejecutando tu servidor de dev, ve y reinícialo:
npm run dev
Luego abre la página y haz clic en el botón "Conectar". You should see Freighter pop up and ask you to sign in. Una vez que lo hagas, el botón debería ser reemplazado con un mensaje que dice: "Conectado como [tu clave pública]".
¡Ahora estás listo para firmar la llamada a increment
!
Llamar a increment
Ahora podemos importar el cliente del contrato increment
desde soroban_increment_contract
y comenzar a usarlo. Nuevamente, crearemos un nuevo componente Astro. Crea un nuevo archivo en src/components/Counter.astro
con el siguiente contenido:
<strong>Incrementor</strong><br />
Current value: <strong id="current-value" aria-live="polite">???</strong><br />
<br />
<button data-increment aria-controls="current-value">Increment</button>
<script>
import { getPublicKey, kit } from "../stellar-wallets-kit";
import incrementor from "../contracts/soroban_increment_contract";
const button = document.querySelector(
"[data-increment]",
) as HTMLButtonElement;
const currentValue = document.querySelector("#current-value") as HTMLElement;
button.addEventListener("click", async () => {
const publicKey = await getPublicKey();
if (!publicKey) {
alert("Please connect your wallet first");
return;
} else {
incrementor.options.publicKey = publicKey;
incrementor.options.signTransaction = signTransaction;
}
button.disabled = true;
button.classList.add("loading");
currentValue.innerHTML =
currentValue.innerHTML +
'<span class="visually-hidden"> – updating…</span>';
try {
const tx = await incrementor.increment();
const { result } = await tx.signAndSend();
// Only use `innerHTML` with contract values you trust!
// Blindly using values from an untrusted contract opens your users to script injection attacks!
currentValue.innerHTML = result.toString();
} catch (e) {
console.error(e);
} finally {
button.disabled = false;
button.classList.remove("loading");
}
});
</script>
Esto debería ser algo familiar para ahora. Tenemos un script
que, gracias al sistema de construcción de Astro, puede importar
módulos directamente. Usamos document.querySelector
para encontrar los elementos definidos arriba. Y agregamos un manejador de click
al botón, que llama a increment
y actualiza el valor en la página. También establece el botón como deshabilitado
y agrega una clase de cargando
mientras la llamada está en progreso para evitar que el usuario haga clic nuevamente y comunicar visualmente que algo está sucediendo. Para las personas que utilizan lectores de pantalla, el estado de carga se comunica con la etiqueta visualmente oculta, que será anunciada gracias a las etiquetas aria
que vimos antes.
La mayor diferencia con la llamada a greeter.hello
es que esta transacción se ejecuta en dos pasos. La llamada inicial a increment
construye una transacción de Soroban y luego realiza una llamada RPC para simularla. Para llamadas de solo lectura como hello
, esto es todo lo que necesitas, por lo que puedes obtener el resultado
de inmediato. Para llamadas de escritura como increment
, debes signAndSend
antes de que la transacción sea realmente incluida en el ledger. También necesitas asegurarte de que establezcas un publicKey
válido y un método signTransaction
.
Desestructuración { result }
: Si eres nuevo en JavaScript, puede que no sepas lo que está sucediendo con esas líneas const { result }
. Esto está utilizando la característica de desestructuración de JavaScript. Si la cosa a la derecha del signo igual es un objeto, entonces puedes usar este patrón para obtener rápidamente claves específicas de ese objeto y asignarlas a variables. También puedes nombrar la variable de otra manera, si lo deseas. Por ejemplo, intenta cambiar el código de arriba a:
const { result: newValue } = ...
También, ten en cuenta que no necesitas especificar manualmente a Freighter como la billetera en la llamada a increment
. Esto puede cambiar en el futuro, pero mientras Freighter sea la única opción disponible, estas bibliotecas generadas la utilizan automáticamente. Si deseas anular este comportamiento, puedes pasar una opción wallet
; consulta la última interfaz Wallet
en la fuente de la plantilla para más detalles.
Ahora, usemos este componente. En pages/index.astro
, primero impórtalo:
---
import Layout from '../layouts/Layout.astro';
import Card from '../components/Card.astro';
import helloWorld from "../contracts/hello_world";
import ConnectFreighter from '../components/ConnectFreighter.astro';
+import Counter from '../components/Counter.astro';
...
Luego úsalo. Reemplacemos el contenido del párrafo instrucciones
con él:
<p class="instructions">
- To get started, open the directory <code>src/pages</code> in your project.<br />
- <strong>Code Challenge:</strong> Tweak the "Welcome to Astro" message above.
+ <Counter />
</p>
Revisa la página; si aún estás ejecutando tu servidor de desarrollo, debería haberse actualizado ya. Haz clic en el botón "Incrementar"; deberías ver una confirmación de Freighter. Confirma, y... ¡el valor se actualiza! 🎉
Obviamente, falta algo de funcionalidad. Por ejemplo, ese ???
es frustrante. Pero nuestro contrato incrementar
no nos da una manera de consultar el valor actual sin actualizarlo también.
Antes de intentar actualizarlo, simplifiquemos el proceso de creación, implementación y generación de clientes para los contratos.
Llévalo más allá
Si quieres llevarlo un poco más lejos y asegurarte de entender todas las piezas aquí, prueba lo siguiente:
- Crea una carpeta
src/contracts
con ungreeter.ts
y unincrementor.ts
. Mueve la lógica denew Contract({ ... })
a esos archivos. También querrás extraer la variablerpcUrl
a un archivosrc/contracts/utils.ts
. - Agrega un método
get_value
al contratoincrementar
, y úsalo para mostrar el valor actual en el componenteContador
. Cuando ejecutesnpm run dev
, el scriptinitialize
se ejecutará y actualizará el contrato y el cliente generado. - Agrega un botón "Decrementar" al componente
Contador
. - Implementa tu frontend. Puedes hacerlo rápido y gratis con GitHub. Si tienes problemas instalando stellar-cli y desplegando contratos en GitHub, consulta cómo lo hicimos.
- En lugar de usar scripts de NPM para todo, intenta usar un corredor de scripts más elegante como just. Los scripts de npm existentes pueden llamar a
just
, como"setup": "just setup"
. - Actualiza el README para explicar qué es este proyecto y cómo usarlo a posibles colaboradores y empleadores 😉
Resolución de problemas
A veces las cosas salen mal. Como primer paso al resolver problemas, es posible que desees clonar nuestro repositorio del tutorial y ver si el problema ocurre allí también. Si ocurre allí también, entonces puede ser un problema temporal con la red de Soroban.
Aquí hay algunas incidencias comunes y cómo corregirlas.
La llamada a hello
falla
A veces la llamada a hello
puede comenzar a fallar. Obviamente puedes simular la llamada y definir result
de otra manera para solucionar problemas.
Uno de los problemas comunes aquí es que el contrato se vuelve archivado. Para verificar si este es el problema, puedes ejecutar de nuevo npm run init
.
Si sigues teniendo problemas, únete a nuestro Discord (enlace de arriba) o abre un problema en GitHub.
Todas las llamadas a contratos comienzan a lanzar errores 403
Esto significa que Testnet está fuera de servicio, y probablemente solo necesites esperar un poco y volver a intentarlo.
Conclusión
Algunas de las cosas que hicimos en esta sección:
- Aprendimos sobre el enfoque sin JS por defecto de Astro
- Agregamos componentes de Astro y aprendimos cómo funcionan sus etiquetas
script
ystyle
- Vimos lo fácil que es interactuar con contratos inteligentes desde JavaScript generando bibliotecas de clientes usando
stellar contract bindings typescript
- Aprendimos sobre billeteras y Freighter
¡En este punto, has visto un ejemplo completo de inicio a fin de la creación de un contrato en Stellar! ¿Qué sigue? ¡Tú decides! Puedes:
- Ver contratos de ejemplo más complejos en la sección Contratos Ejemplo.
- Aprender más sobre la arquitectura interna y diseño de Soroban.
- Aprende cómo encontrar varias plantillas además de stellar/soroban-template-astro, y cómo crear las tuyas: Desarrollar plantillas frontend de inicialización de contratos