Saltar al contenido principal

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:

  1. Presentar datos de blockchain en un formato legible por humanos
  2. Facilitar interacciones de usuario con contratos inteligentes y operaciones de Stellar
  3. Gestionar cuentas y claves de usuario de forma segura
  4. Proporcionar actualizaciones en tiempo real sobre el estado de las transacciones y los saldos de las cuentas
  5. 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:

  1. Simplicidad: Presentar conceptos complejos de blockchain de manera fácil de entender
  2. Transparencia: Proporcionar información clara sobre tarifas de transacción, estado de la red y resultados de operaciones
  3. Retroalimentación: Ofrecer retroalimentación inmediata y clara sobre las acciones del usuario y el progreso de la transacción
  4. Manejo de Errores: Gestionar y explicar errores de manera amistosa para el usuario
  5. 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.

consejo

¡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.

consejo

¡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.