Guía completa de frontend para dapps Stellar
Requisitos previos:
- Conocimiento básico de React, Tailwind CSS y tecnologías web relacionadas
- Comprensión básica de la blockchain Stellar
- Node.js y npm instalados
- Navegador web con la extensión Freighter Wallet instalada
1. Introducción
El papel del frontend en las dapps Stellar
El desarrollo de frontend juega un papel crucial en las aplicaciones descentralizadas (dapps) creadas en la red Stellar. Sirve como la interfaz principal entre los usuarios y la tecnología blockchain subyacente. Un frontend bien diseñado no solo hace que tu dapp sea accesible y fácil de usar, sino que también ayuda a los usuarios a interactuar sin problemas con operaciones complejas de blockchain.
En las dapps Stellar, el frontend es responsable de:
- Presentar datos de blockchain en un formato legible por humanos
- Facilitar interacciones de usuario con contratos inteligentes y operaciones de Stellar
- Gestionar cuentas y claves de usuario de forma segura
- Proporcionar actualizaciones en tiempo real sobre el estado de las transacciones y los saldos de las cuentas
- Guiar a los usuarios a través de procesos complejos como transacciones de múltiples firmas u operaciones de saldos reclamables
Importancia de la interfaz de usuario y la experiencia del usuario
La importancia de una buena interfaz de usuario (UI) y experiencia de usuario (UX) en dapps Stellar no puede ser exagerada. La tecnología blockchain puede ser intimidante para muchos usuarios, y un diseño de UI/UX bien hecho puede marcar la diferencia entre una dapp exitosa y una que los usuarios consideran frustrante o confusa.
Aspectos clave de UI/UX en dapps Stellar incluyen:
- Simplicidad: Presentar conceptos complejos de blockchain de manera fácil de entender
- Transparencia: Proporcionar información clara sobre tarifas de transacción, estado de la red y resultados de operaciones
- Retroalimentación: Ofrecer retroalimentación inmediata y clara sobre las acciones del usuario y el progreso de la transacción
- Manejo de Errores: Gestionar y explicar errores de manera amistosa para el usuario
- Rendimiento: Asegurar tiempos de carga rápidos e interacciones receptivas, incluso cuando se trata de operaciones de blockchain
Al enfocarte en estos aspectos, puedes crear dapps Stellar que no solo sean funcionales, sino también agradables de usar, fomentando una adopción más amplia de tu aplicación y de la red Stellar en su conjunto.
2. Configurando el entorno de desarrollo
Antes de comenzar a crear nuestra dapp Stellar, necesitamos configurar nuestro entorno de desarrollo. Usaremos React con Next.js como nuestro marco de frontend, Tailwind CSS para el estilo y el Stellar SDK para interactuar con la red Stellar.
Instalando Node.js y npm
Primero, asegúrate de tener Node.js y npm (Node Package Manager) instalados en tu sistema. Puedes descargarlos e instalarlos desde el sitio web oficial de Node.js: https://nodejs.org/
Para verificar tu instalación, abre una terminal y ejecuta:
node --version
npm --version
Ambos comandos deberían devolver números de versión si la instalación fue exitosa.
Configurando un proyecto de Next.js
Next.js es un marco de React que proporciona características como el renderizado del lado del servidor y enrutamiento desde el principio. Para crear un nuevo proyecto de Next.js, ejecuta los siguientes comandos en tu terminal:
npx create-next-app@latest stellar-dapp
cd stellar-dapp
Cuando se te pregunte, elige las siguientes opciones:
- ¿Te gustaría usar TypeScript? Sí
- ¿Te gustaría usar ESLint? Sí
- ¿Te gustaría usar Tailwind CSS? Sí
- ¿Te gustaría usar el directorio
src/
? No - ¿Te gustaría usar App Router? Sí
- ¿Te gustaría personalizar el alias de importación predeterminado? No
Configurar HTTPS en Localhost
La billetera Freighter requiere una conexión segura (HTTPS) para interactuar con tu dapp. Para habilitar HTTPS en localhost, puedes usar una herramienta como mkcert
. Afortunadamente, Next.js proporciona soporte integrado para HTTPS.
Para habilitar HTTPS en tu proyecto de Next.js, abre el archivo package.json
y edita la sección scripts
de la siguiente manera:
"scripts": {
"dev": "next dev --experimental-https",
}
Instalando Stellar SDK y otras dependencias
Para interactuar con la red Stellar, necesitaremos instalar el Stellar SDK y algunas dependencias adicionales:
npm install stellar-sdk @stellar/freighter-api bignumber.js
stellar-sdk
: El SDK oficial de Stellar para interactuar con la red Stellar@stellar/freighter-api
: Una biblioteca para integrar con la billetera Freighter (una popular extensión de navegador para billeteras Stellar)bignumber.js
: Una biblioteca para aritmética decimal y no decimal de precisión arbitraria
Ahora que tenemos nuestro entorno de desarrollo configurado, ¡estamos listos para comenzar a crear nuestra dapp Stellar!
3. Creando elementos básicos de la interfaz
En esta sección, crearemos componentes reutilizables para nuestra dapp Stellar e implementaremos formularios y entradas utilizando React y Tailwind CSS.
Creando componentes reutilizables
Comencemos creando un componente de botón que utilizaremos en toda nuestra aplicación. Crea un nuevo archivo components/Button.tsx
:
import React from "react";
interface ButtonProps {
onClick: () => void;
children: React.ReactNode;
disabled?: boolean;
className?: string;
}
const Button: React.FC<ButtonProps> = ({
onClick,
children,
disabled = false,
className = "",
}) => {
return (
<button
onClick={onClick}
disabled={disabled}
className={`px-4 py-2 font-bold text-white bg-blue-500 rounded hover:bg-blue-700 focus:outline-none focus:shadow-outline ${
disabled ? "opacity-50 cursor-not-allowed" : ""
} ${className}`}
>
{children}
</button>
);
};
export default Button;
Este componente de botón utiliza clases de Tailwind CSS para el estilo y acepta props para la personalización.
A continuación, crearemos otro botón auxiliar para ayudar a conectar la billetera Freighter. Crea un nuevo archivo components/ConnectWalletButton.tsx
:
"use client";
import React from "react";
import { setAllowed } from "@stellar/freighter-api";
export interface ConnectButtonProps {
label: string;
isHigher?: boolean;
}
export function ConnectButton({ label }: ConnectButtonProps) {
return (
<button
className={`bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded`}
onClick={setAllowed}
>
{label}
</button>
);
}
Este componente de botón utiliza la función setAllowed
de la biblioteca @stellar/freighter-api
para conectar la billetera Freighter cuando se hace clic.
Implementando formularios y entradas
A continuación, crearemos un componente de entrada reutilizable. Crea un nuevo archivo components/Input.tsx
:
import React from "react";
interface InputProps {
type: string;
placeholder: string;
value: string;
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
className?: string;
}
const Input: React.FC<InputProps> = ({
type,
placeholder,
value,
onChange,
className = "",
}) => {
return (
<input
type={type}
placeholder={placeholder}
value={value}
onChange={onChange}
className={`shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline ${className}`}
/>
);
};
export default Input;
Ahora, vamos a crear un componente de formulario que utilice estos componentes reutilizables. Crea un nuevo archivo components/SendPaymentForm.tsx
:
"use client";
import React, { useState } from "react";
import Button from "./Button";
import Input from "./Input";
interface SendPaymentFormProps {
onSubmit: (destination: string, amount: string) => void;
}
const SendPaymentForm: React.FC<SendPaymentFormProps> = ({ onSubmit }) => {
const [destination, setDestination] = useState("");
const [amount, setAmount] = useState("");
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
onSubmit(destination, amount);
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label
htmlFor="destination"
className="block text-sm font-medium text-gray-700"
>
Destination Address
</label>
<Input
type="text"
placeholder="GXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
value={destination}
required
onChange={(e) => setDestination(e.target.value)}
/>
</div>
<div>
<label
htmlFor="amount"
className="block text-sm font-medium text-gray-700"
>
Amount (XLM)
</label>
<Input
type="number"
placeholder="0.0"
value={amount}
required
onChange={(e) => setAmount(e.target.value)}
/>
</div>
<Button type="submit" disabled={!destination || !amount}>
Send Payment
</Button>
</form>
);
};
export default SendPaymentForm;
Diseñando diseños responsivos con Tailwind CSS
Para crear un diseño responsivo para nuestra dapp, utilizaremos clases de utilidad de Tailwind CSS. Creamos un componente de diseño que podamos usar en nuestras páginas. Edita el archivo app/layout.tsx
:
import "./globals.css";
import { Inter } from "next/font/google";
import type { Metadata } from "next";
import React from "react";
export const metadata: Metadata = {
title: "Stellar Payment DApp",
description: "Payment DApp built on Stellar",
};
interface LayoutProps {
children: React.ReactNode;
}
const Layout: React.FC<LayoutProps> = ({ children }) => {
return (
<html lang="en">
<body>
<div className="min-h-screen bg-gray-100">
<header className="bg-white shadow">
<div className="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
<h1 className="text-3xl font-bold text-gray-900">Stellar Dapp</h1>
</div>
</header>
<main>
<div className="max-w-7xl mx-auto py-6 sm:px-6 lg:px-8">
<div className="px-4 py-6 sm:px-0">
<div className="border-4 border-dashed border-gray-200 rounded-lg h-96">
{children}
</div>
</div>
</div>
</main>
</div>
</body>
</html>
);
};
export default Layout;
Ahora, actualicemos nuestro archivo app/page.tsx
para usar este diseño y nuestros nuevos componentes:
"use client";
import React from "react";
import SendPaymentForm from "../components/SendPaymentForm";
export default function Home() {
const handleSendPayment = (destination: string, amount: string) => {
// We'll implement this function in the next section
console.log(`Sending ${amount} XLM to ${destination}`);
};
return (
<div className="max-w-md mx-auto">
<h2 className="text-2xl font-bold mb-4">Send Payment</h2>
<SendPaymentForm onSubmit={handleSendPayment} />
</div>
);
}
Esta configuración proporciona una base sólida para construir la interfaz de usuario de nuestra dapp Stellar. En la próxima sección, integraremos estos componentes con el Stellar SDK para realizar operaciones reales de blockchain.
4. Conceptos básicos de los contratos Soroban
Antes de comenzar a integrar la funcionalidad de contratos inteligentes en nuestra dapp Stellar, entendamos los conceptos básicos de los contratos Soroban.
Soroban es una plataforma de contratos inteligentes construida sobre la red Stellar. Permite a los desarrolladores escribir y desplegar contratos inteligentes que se ejecutan en la blockchain Stellar. Los contratos de Soroban están escritos en Rust y se compilan a XDR (Representación de Datos Externa) para su ejecución en la red Stellar.
Tipos de datos
Stellar admite algunos tipos de datos que pueden ser utilizados en contratos Soroban y necesitamos realizar conversiones de vez en cuando entre esos tipos y los tipos que tenemos en Javascript. La lista completa de tipos de datos primitivos se explica aquí.
XDR
Los valores que se pasan a los contratos y se devuelven de los contratos se serializan usando XDR. XDR es un formato estándar de serialización de datos utilizado en Stellar para representar estructuras de datos complejas. Se utiliza para codificar y decodificar datos para su transmisión a través de la red Stellar. XDR se representa principalmente en Javascript como cadena y se puede convertir a otros tipos usando el Stellar SDK.
Tarifas
Las tarifas de gas, como se les llama en la red Ethereum, se cobran de manera diferente aquí. Al enviar diferentes tipos de transacciones, el tipo de tarifas pagadas es diferente. Al enviar una transacción a la red que interactúa con ella, se paga una tarifa base junto con la tarifa de recursos para la operación. La tarifa base es una tarifa fija que se paga por cada transacción en la red. La tarifa de recursos es una tarifa que se paga por los recursos utilizados por la operación y se calcula en función de los recursos utilizados por la operación y el precio actual de recursos de la red.
Calcular estas tarifas puede ser complicado, pero el Stellar SDK proporciona una manera de calcular estas tarifas utilizando el método server.prepareTransaction
que simula la transacción, obtiene las tarifas apropiadas y agrega la configuración correcta de tarifas a la transacción.
ABI o Spec
El ABI o spec es un archivo json que contiene la interfaz del contrato. Define las funciones que se pueden llamar en el contrato, sus parámetros y tipos de retorno. El ABI es utilizado por los clientes para interactuar con el contrato y ejecutar sus funciones. El ABI se genera a partir del código fuente del contrato y se utiliza para compilar el contrato en XDR para su ejecución en la red Stellar.
ABI se puede generar para un contrato usando el comando stellar contract bindings
y se puede usar para interactuar con el contrato.
Este ABI también puede ser generado como una biblioteca de TypeScript para facilitar el desarrollo en tu DApp y lo veremos más adelante en esta guía
5. Integrando con la blockchain Stellar
Ahora que tenemos nuestros componentes básicos de UI en su lugar, integremoslos con la blockchain Stellar usando el Stellar SDK. Es imperativo saber que la blockchain Stellar tiene tres redes principales:
- Red pública (también llamada Mainnet): Esta es la red Stellar principal donde ocurren transacciones reales.
- Red de prueba: Este es un entorno de prueba para desarrolladores para probar sus aplicaciones sin usar lúmenes reales.
- Red Futurenet: Esta es una red para probar nuevas características antes de que se implementen en la red pública.
Para esta guía, utilizaremos la red de prueba para evitar usar lúmenes reales durante el desarrollo. Lee más sobre las redes Stellar aquí.
Configurando el Stellar SDK
A continuación se muestra un fragmento que muestra cómo configurar el Stellar SDK en tu proyecto. Usaremos partes de él mucho en esta guía así que intentemos entenderlo.
import * as StellarSdk from "@stellar/stellar-sdk";
import { SorobanRpc } from "@stellar/stellar-sdk";
export const server = new SorobanRpc.Server("https://soroban-testnet.stellar.org"); // soroban testnet server
const transaction = new StellarSdk.TransactionBuilder(account, {
fee: StellarSdk.BASE_FEE
networkPassphrase: StellarSdk.Networks.TESTNET, // Use appropriate network
})
En el fragmento anterior, importamos el Stellar SDK y creamos una instancia del servidor para el Stellar Testnet.
También creamos una instancia de constructor de transacciones con la frase de paso de red apropiada. La BASE_FEE
es la tarifa mínima requerida para una transacción en la red Stellar. mientras que la Networks.TESTNET
es la frase de paso de red para el Stellar Testnet. Al usar la red pública, puedes reemplazar TESTNET
por PUBLIC
o FUTURENET
para la red Futurenet.
Interaccionando con la red Stellar
Si estás codificando a lo largo de esta guía en este punto, deberías tener una aplicación web mínima con un formulario para enviar pagos. Ahora, integremos este formulario con el Stellar SDK para enviar pagos reales en la red Stellar.
Antes de continuar, asegúrate de haber configurado la extensión de billetera Freighter en tu navegador. Puedes descargarla aquí. Además, asegúrate de tener lúmenes en la cuenta de Testnet que estás usando. Puedes obtener lúmenes gratis del Stellar Friendbot.
Ahora, actualicemos nuestro componente app/page
para interactuar con la red Stellar:
"use client";
import React, { useState, useEffect } from "react";
import SendPaymentForm from "../components/SendPaymentForm";
import * as StellarSdk from "@stellar/stellar-sdk";
import { SorobanRpc } from "@stellar/stellar-sdk";
import {
isConnected,
setAllowed,
getPublicKey,
signTransaction,
} from "@stellar/freighter-api";
export default function Home() {
const [publicKey, setPublicKey] = useState<string | null>(null);
useEffect(() => {
const checkFreighter = async () => {
try {
const connected = await isConnected();
if (connected) {
const pubKey = await getPublicKey();
setPublicKey(pubKey);
}
} catch (error) {
console.error("Error checking Freighter connection:", error);
}
};
checkFreighter();
}, []);
const handleConnectWallet = async () => {
try {
await setAllowed();
const pubKey = await getPublicKey();
setPublicKey(pubKey);
} catch (error) {
console.error("Error connecting to Freighter:", error);
}
};
const handleSendPayment = async (destination: string, amount: string) => {
if (!publicKey) {
console.error("Wallet not connected");
return;
}
try {
const server = new SorobanRpc.Server(
"https://soroban-testnet.stellar.org",
);
const sourceAccount = await server.getAccount(publicKey);
const transaction = new StellarSdk.TransactionBuilder(sourceAccount, {
fee: StellarSdk.BASE_FEE,
networkPassphrase: StellarSdk.Networks.TESTNET,
})
.addOperation(
StellarSdk.Operation.payment({
destination: destination,
asset: StellarSdk.Asset.native(),
amount: amount,
}),
)
.setTimeout(30)
.build();
const signedTransaction = await signTransaction(transaction.toXDR(), {
networkPassphrase: StellarSdk.Networks.TESTNET,
});
const transactionResult = await server.sendTransaction(
StellarSdk.TransactionBuilder.fromXDR(
signedTransaction,
StellarSdk.Networks.TESTNET,
),
);
console.log("Transaction successful:", transactionResult);
alert("Payment sent successfully!");
} catch (error) {
console.error("Error sending payment:", error);
alert("Error sending payment. Please check the console for details.");
}
};
return (
<div className="max-w-md mx-auto">
<h2 className="text-2xl font-bold mb-4">Send Payment</h2>
{publicKey ? (
<>
<p className="mb-4">Connected: {publicKey}</p>
<SendPaymentForm onSubmit={handleSendPayment} />
</>
) : (
<button
onClick={handleConnectWallet}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
Connect Freighter Wallet
</button>
)}
</div>
);
}
En el componente app/page
actualizado, agregamos un hook useEffect
para verificar si la billetera Freighter está conectada y recuperar la clave pública. También agregamos una función handleConnectWallet
para conectar la billetera si no está ya conectada.
La función handleSendPayment
ahora interactúa con la red Stellar para enviar un pago. Recupera los detalles de la cuenta de origen, crea una transacción de pago, firma la transacción usando la billetera Freighter, y envía la transacción a la red Stellar. Si la transacción es exitosa, muestra una alerta al usuario.
Observa cómo importamos las funciones isConnected
, setAllowed
, getPublicKey
, y signTransaction
de la biblioteca @stellar/freighter-api
. Estas funciones se utilizan para interactuar con la extensión de billetera Freighter y firmar transacciones de forma segura.
¡Hurra! Has integrado con éxito tu dapp Stellar con la red Stellar. Ahora puedes enviar pagos usando la extensión de billetera Freighter en el Stellar Testnet.
Interactuando con contratos inteligentes
En el ejemplo anterior, enviamos una transacción de pago simple utilizando la operación StellarSdk.Operation.payment
. Sin embargo, Stellar también soporta muchas otras operaciones, una de las cuales es la operación invokeHostFunction
que se utiliza para interactuar con contratos inteligentes en la red Stellar.
Trabajaremos con una versión desplegada del contrato inteligente contador en el Stellar Testnet. El contrato inteligente tiene una única función increment
que incrementa un valor contador almacenado en la red Stellar.
Crea un nuevo archivo app/counter/page.tsx
:
"use client";
import {
BASE_FEE,
Contract,
Networks,
SorobanRpc,
Transaction,
TransactionBuilder,
xdr,
} from "@stellar/stellar-sdk";
import React, { useEffect, useState } from "react";
import {
getPublicKey,
isConnected,
signTransaction,
} from "@stellar/freighter-api";
import { ConnectButton } from "@/components/ConnectButton";
// Replace with your actual contract ID and network details
const CONTRACT_ID = "CC6MWZMG2JPQEENRL7XVICAY5RNMHJ2OORMUHXKRDID6MNGXSSOJZLLF";
const NETWORK_PASSPHRASE = Networks.TESTNET;
const SOROBAN_URL = "https://soroban-testnet.stellar.org:443";
export default function CounterPage() {
const [publicKey, setPublicKey] = useState<string | null>(null);
const [count, setCount] = useState<number | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const checkWallet = async () => {
const connected = await isConnected();
if (connected) {
const pubKey = await getPublicKey();
setPublicKey(pubKey);
}
};
checkWallet();
}, []);
const handleIncrement = async () => {
if (!publicKey) {
console.error("Wallet not connected");
return;
}
setLoading(true);
try {
const server = new SorobanRpc.Server(SOROBAN_URL);
const account = await server.getAccount(publicKey);
const contract = new Contract(CONTRACT_ID);
// const instance = contract.getFootprint();
const tx = new TransactionBuilder(account, {
fee: BASE_FEE,
networkPassphrase: NETWORK_PASSPHRASE,
})
.addOperation(contract.call("increment"))
.setTimeout(30)
.build();
const preparedTx = await server.prepareTransaction(tx);
const signedXdr = await signTransaction(
preparedTx.toEnvelope().toXDR("base64"),
{
networkPassphrase: NETWORK_PASSPHRASE,
},
);
const signedTx = TransactionBuilder.fromXDR(
signedXdr,
NETWORK_PASSPHRASE,
) as Transaction;
const txResult = await server.sendTransaction(signedTx);
if (txResult.status !== "PENDING") {
throw new Error("Something went Wrong");
}
const hash = txResult.hash;
let getResponse = await server.getTransaction(hash);
// Poll `getTransaction` until the status is not "NOT_FOUND"
while (getResponse.status === "NOT_FOUND") {
console.log("Waiting for transaction confirmation...");
getResponse = await server.getTransaction(hash);
await new Promise((resolve) => setTimeout(resolve, 1000));
}
if (getResponse.status === "SUCCESS") {
// Make sure the transaction's resultMetaXDR is not empty
if (!getResponse.resultMetaXdr) {
throw "Empty resultMetaXDR in getTransaction response";
}
} else {
throw `Transaction failed: ${getResponse.resultXdr}`;
}
// Extract the new count from the transaction result
const returnValue = getResponse.resultMetaXdr
.v3()
.sorobanMeta()
?.returnValue();
if (returnValue) {
const newCount = returnValue.u32();
setCount(newCount);
}
} catch (error) {
console.error("Error incrementing counter:", error);
alert(
"Error incrementing counter. Please check the console for details.",
);
} finally {
setLoading(false);
}
};
return (
<div className="max-w-md mx-auto mt-10">
<h1 className="text-2xl font-bold mb-4">
Stellar Smart Contract Counter
</h1>
{publicKey ? (
<div>
<p className="mb-4">Connected: {publicKey}</p>
<p className="mb-4">
Current Count: {count === null ? "Unknown" : count}
</p>
<button
onClick={handleIncrement}
disabled={loading}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded disabled:opacity-50"
>
{loading ? (
<span className="flex items-center">
<svg
className="animate-spin -ml-1 mr-3 h-5 w-5 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
></path>
</svg>
Processing...
</span>
) : (
"Increment Counter"
)}
</button>
</div>
) : (
<>
<p>Please connect your Freighter wallet to use this app.</p>
<ConnectButton label="Connect Wallet" />
</>
)}
</div>
);
}
En el fragmento de código anterior, creamos un nuevo componente de página CounterPage
que interactúa con el contrato inteligente contador en la red Stellar. La función handleIncrement
envía una transacción al contrato inteligente para incrementar el valor del contador. Luego recupera el valor actualizado del contador del resultado de la transacción y lo muestra al usuario.
Observa cómo utilizamos una operación diferente contract.call("increment")
para interactuar con el contrato inteligente. Esta operación es una fachada para la operación invokeHostFunction
en la red Stellar. También utilizamos los métodos prepareTransaction
y sendTransaction
para enviar la transacción a la red y recuperar el resultado de la transacción.
La llamada prepareTransaction
se utiliza para preparar una transacción para su presentación a la red. Simula la transacción, luego utiliza las tarifas de recursos apropiadas y otros parámetros para crear una transacción que esté lista para enviarse a la red.
<ConnectButton>
es un botón auxiliar que conecta la billetera Freighter cuando se hace clic, como describimos anteriormente.
Después de llamar a la transacción para incrementar exitosamente, necesitamos hacer polling y luego llamar a otro método server.getTransaction
para obtener el resultado de la transacción. El resultado de la transacción contiene el nuevo valor del contador que extraemos y mostramos al usuario.
El valor obtenido del resultado de la transacción se almacena en una forma llamada scval
debido a la cantidad limitada de tipos que existen en soroban hoy. This value is transmitted about in the form of xdr
which can be converted to an scVal
using the sdk. El valor se analiza luego como u32
que es un entero sin signo de 32 bits. Aprenderemos más sobre cómo convertir estos tipos en esta colección de guías.
¡Felicidades! Has interactuado exitosamente con un contrato inteligente en la red Stellar usando tu dapp.
Leyendo eventos de la red Stellar
Los eventos en Stellar son valores emitidos por contratos agrupados por temas. Estos eventos se emiten cuando se llama a un contrato y pueden ser leídos por cualquier cliente interesado en el contrato. Los eventos se almacenan en el ledger de Stellar y pueden ser leídos por cualquier cliente interesado en el contrato.
Además de enviar transacciones e interactuar con contratos inteligentes, también puedes leer eventos de la red Stellar utilizando el Stellar SDK. Esto te permite monitorear cambios en cuentas, estado de transacciones y otros eventos de red en tiempo real.
Esto es posible utilizando el método server.getEvents
, que te permite consultar eventos basados en temas. Consideraremos un ejemplo simple donde leemos eventos del contrato inteligente contador con el que interactuamos anteriormente.
Estaremos editando el componente CounterPage
para leer eventos del contrato inteligente contador inmediatamente cuando se cargue la página para obtener el valor inicial del contador y actualizar en lugar de usar "Desconocido". Antes de continuar, por favor revisa el código del contrato. En el código del contrato, un evento llamado increment
se emite cada vez que se llama a la función increment
. Se publica en 2 temas, increment
y COUNTER
, y necesitamos escuchar estos temas para obtener los eventos.
Los temas se almacenan en un tipo de datos llamado symbol
y necesitaremos convertir tanto increment
como COUNTER
a symbol
antes de poder usarlos en el método server.getEvents
. Como máximo, los RPC de stellar mantienen un seguimiento de eventos durante 7 días y puedes consultar eventos que ocurrieron dentro de los últimos 7 días, así que si necesitas almacenar eventos por más tiempo, puede que necesites hacer uso de un indexador.
Para usar eventos, editamos nuestra página de contador y añadimos el siguiente código:
useEffect(() => {
const checkWallet = async () => {
const connected = await isConnected();
if (connected) {
const pubKey = await getPublicKey();
setPublicKey(pubKey);
}
};
checkWallet();
getInitialCount();
}, []);
const getInitialCount = async () => {
try {
const topic1 = xdr.ScVal.scvSymbol("COUNTER").toXDR("base64");
const topic2 = xdr.ScVal.scvSymbol("increment").toXDR("base64");
const latestLedger = await server.getLatestLedger();
const events = await server.getEvents({
startLedger: latestLedger.sequence - 2000,
filters: [
{
type: "contract",
contractIds: [CONTRACT_ID],
topics: [[topic1, topic2]],
},
],
limit: 20,
});
setCount(events.events.map((e) => e.value.u32()).pop() || null);
} catch (error) {
console.error(error);
}
};
En el código anterior, usamos un método RPC llamado getEvents
para consultar eventos de la red Stellar. Pasamos el ID del contrato, los temas y otros filtros como el ledger para comenzar la búsqueda desde 2000 ledgers atrás para obtener los eventos que nos interesan. Luego extraemos el valor del contador de los eventos y actualizamos el estado en consecuencia.
Sin embargo, tuvimos que convertir los temas COUNTER
e increment
a symbol
usando el xdr.ScVal.scvSymbol
y luego convertirlos a XDR antes de poder usarlos en el método getEvents
. Esto se debe a que los temas se almacenan en forma de symbol
y se transmiten en formato XDR a la red, como discutimos anteriormente. De manera similar, el valor del nuevo conteo se envía a través de la red como XDR, pero el SDK nos ayudó a convertirlo a un scVal
y luego lo parseamos como un u32
para obtener el valor real.
El código fuente para este programa está disponible aquí
Ahora, cuando la página se carga, consultará los eventos de la red Stellar y actualizará el valor del contador en consecuencia.
6. Usar las vinculaciones de Typescript
El Stellar CLI viene con soporte para exportar una especificación de contrato a una biblioteca de typescript. Esta biblioteca está optimizada para importar como un módulo basado en npm en tu aplicación.
La biblioteca generada contiene métodos auxiliares para llamar a cada contrato y también realiza una conversión automática de tipos para cualquier método de contrato.
Cómo crear la biblioteca de vinculación
Para lograr esto, necesitas
- Tener el Stellar CLI instalado.
- Tener el código fuente o el ID del contrato desplegado del contrato.
- Conocer la red a la que se desplegó.
Escenario 1: Tengo el ID del contrato pero no el código
En este escenario, necesitamos usar el comando stellar contract fetch
para obtener el código wasm del contrato
Escenario 2: Tengo el código
El siguiente paso aquí es crear un archivo wasm usando el comando stellar contract build
.
Generando la biblioteca
Después de obtener el wasm, ahora podemos ejecutar stellar contract bindings typescript
para generar la biblioteca que está lista para ser publicada en NPM. La biblioteca generada es adecuada para trabajar con contratos complejos.
7. Trampas comunes
A continuación se presentan algunas trampas comunes que los desarrolladores de DApp de Soroban podrían encontrar
Conversiones de tipo de datos
Los tipos de datos en Javascript son diferentes de los tipos de datos en Soroban. Al trabajar con contratos Soroban, debes ser consciente de los tipos de datos y cómo convertirlos a los tipos que conoces en Javascript. El Stellar SDK proporciona métodos auxiliares para convertir entre tipos XDR y Javascript en el espacio de nombres xdr
.
Honorarios
Calcular tarifas para transacciones puede ser complicado. El Stellar SDK proporciona una manera de calcular tarifas utilizando el método server.prepareTransaction
, que simula la transacción y devuelve las tarifas apropiadas. Las tarifas se calculan en stroops
Enviar transacciones y obtener transacciones
Los resultados de la ejecución de un contrato inteligente o de cualquier transacción válida en soroban no son inmediatos. Se mantienen en un estado PENDING
hasta que se confirmen. Necesitas consultar el método getTransaction
para obtener el resultado final de la transacción.
Archivado de estado
El archivado de estado es una característica de los contratos Soroban donde algunos datos almacenados en el ledger sobre el contrato podrían ser archivados. Estas guías ayudan a entender cómo trabajar con el archivado de estado en DApps.
Retención de datos
Varias cosas a tener en cuenta sobre la retención de datos en contratos Soroban:
- Los datos de eventos pueden ser consultados dentro de los 7 días de que ocurra el evento. Así que puedes necesitar un indexador para almacenar eventos por períodos más largos.
- Los datos de transacciones se almacenan con un período de retención de 1440 ledgers. Esto significa que después de 1440 ledgers, los datos de transacciones no pueden ser consultados usando el RPC. Nuevamente, puedes necesitar un indexador para almacenar datos de transacciones por períodos más largos.
Guías en esta categoría:
📄️ Usar Docker para crear y ejecutar dapps
Entender Docker y usarlo para crear aplicaciones
📄️ Guía completa de frontend para dapps Stellar
Aprende a crear interfaces de frontend funcionales para dapps Stellar usando React, Tailwind CSS y el Stellar SDK.
📄️ Inicializa una dapp usando scripts
Configura la inicialización correctamente para asegurar un proceso fluido para tu dapp
📄️ Crear un frontend para tu dapp usando React
Conectar frontends de dapp a contratos y la billetera Freighter usando @soroban-react
📄️ Desarrollar plantillas de interfaz de usuario para la inicialización de contratos
Entender, encontrar y crear tus propias plantillas de interfaz de usuario para usarlas con el comando `stellar contract init` de Stellar CLI
📄️ Implementar archivo de estado en dapps
Aprender cómo implementar archivo de estado en tu dapp
📄️ Trabajar con especificaciones de contrato en Java, Python y PHP
Una guía para entender e interactuar con los contratos inteligentes de Soroban en diferentes lenguajes de programación