Saltar al contenido principal

Red de Stellar

información

Esta guía está disponible en cuatro lenguajes de programación diferentes: Typescript, Kotlin, Flutter (Dart) y Swift. Puedes cambiar la versión mostrada en cada página mediante los botones de arriba.

En la sección anterior aprendimos cómo crear una billetera y un objeto Stellar que proporciona una conexión a Horizon. En esta sección, examinaremos los usos de esta clase.

Cuentas

La entidad más básica en la red Stellar es una cuenta. Miremos el AccountService que proporciona la capacidad de trabajar con cuentas:

let account = wal.stellar().account();

Ahora podemos crear un par de claves:

let accountKeyPair = account.createKeypair();
información

Si usas react-native, createKeypair no funcionará. En su lugar, usa el método auxiliar createKeypairFromRandom así:

import * as Random from "expo-crypto";
const rand = Random.randomBytes(32);
const kp = account.createKeypairFromRandom(Buffer.from(rand));

Construir Transacción

El constructor de transacciones te permite crear varias transacciones que pueden ser firmadas y enviadas a la red Stellar. Algunas transacciones pueden ser patrocinadas.

Construyendo Transacciones Básicas

Primero, veamos cómo construir transacciones básicas.

Crear Cuenta

La transacción de crear cuenta activa/crea una cuenta con un saldo inicial de XLM (1 XLM por defecto).

const txBuilder = await stellar.transaction({
sourceAddress: sourceAccountKeyPair,
});
const tx = txBuilder.createAccount(destinationAccountKeyPair).build();

Modificar Cuenta

Puedes bloquear la clave maestra de la cuenta estableciendo su peso en 0. Ten cuidado al bloquear la clave maestra de la cuenta. Asegúrate de haber establecido los firmantes y pesos correctos. De lo contrario, bloquearás la cuenta de forma irreversible.

const txBuilder = await stellar.transaction({
sourceAddress: sourceAccountKeyPair,
});

const tx = txBuilder.lockAccountMasterKey().build();

Añadir un nuevo firmante a la cuenta. Ten cuidado al añadir nuevos firmantes y asegúrate de establecer el peso del firmante correcto. De lo contrario, bloquearás la cuenta de forma irreversible.

const newSignerKeyPair = account.createKeypair();

const tx = txBuilder.addAccountSigner(newSignerKeyPair, 10).build();

Eliminar un firmante de la cuenta.

const tx = txBuilder.removeAccountSigner(newSignerKeyPair).build();

Modificar los umbrales de la cuenta (útil cuando se asignan varios firmantes a la cuenta). Esto te permite restringir el acceso a ciertas operaciones cuando no se alcanza el límite.

const tx = txBuilder.setThreshold({ low: 1, medium: 10, high: 30 }).build();

Modificar Activos (Trustlines)

Añadir un activo (trustline) a la cuenta. Esto permite a la cuenta recibir transferencias del activo.

const asset = new IssuedAssetId(
"USDC",
"GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
);

const tx = txBuilder.addAssetSupport(asset).build();

Eliminar un activo de la cuenta (el saldo del activo debe ser 0).

const tx = txBuilder.removeAssetSupport(asset).build();

Intercambiar

Intercambiar un activo de la cuenta por otro activo diferente. La cuenta debe tener una trustline para el activo de destino.

const txBuilder = await stellar.transaction({
sourceAddress: sourceKp,
});
const usdcAsset = new IssuedAssetId(
"USDC",
"GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
);
const txn = txBuilder.swap(new NativeAssetId(), usdcAsset, ".1").build();

Path Pay

Enviar un activo de la cuenta de origen y recibir un activo diferente en la cuenta de destino.

const txBuilder = await stellar.transaction({
sourceAddress: sourceKp,
});
const usdcAsset = new IssuedAssetId(
"USDC",
"GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
);
const txn = txBuilder
.pathPay({
destinationAddress: receivingKp.publicKey,
sendAsset: new NativeAssetId(),
destAsset: usdcAsset,
sendAmount: "5",
})
.build();

Establecer Memo

Establece un memo en la transacción. El objeto memo se puede importar de "@stellar/stellar-sdk".

import { Memo } from "@stellar/stellar-sdk";

const tx = txBuilder.setMemo(new Memo("text", "Memo string")).build();

Fusión de Cuentas

Fusiona la cuenta en una cuenta de destino.

const txBuilder = await stellar.transaction({
sourceAddress: accountKp,
baseFee: 1000,
});
const mergeTxn = txBuilder
.accountMerge(accountKp.publicKey, sourceKp.publicKey)
.build();

Fondos de Cuenta de Test Net

Financia una cuenta en la red de pruebas Stellar

wallet.stellar().fundTestnetAccount(accountKp.publicKey);

Construyendo Transacciones Avanzadas

En algunos casos, es posible que no se conozca una clave privada antes de formar una transacción. Por ejemplo, se debe financiar una nueva cuenta para que exista y la billetera puede no tener la clave de la cuenta, por lo que puede solicitar que la transacción de creación de la cuenta sea patrocinada por un tercero.

// Third-party key that will sponsor creating new account
const externalKeyPair = new PublicKeypair.fromPublicKey("GC5GD...");
const newKeyPair = account.createKeypair();

Primero, se debe crear la cuenta.

const createTxn = txBuilder.createAccount(newKeyPair).build();

Esta transacción debe enviarse al firmante externo (titular de externalKeyPair) para ser firmada.

const xdrString = createTxn.toXDR();
// Send xdr encoded transaction to your backend server to sign
const xdrStringFromBackend = await sendTransactionToBackend(xdrString);

// Decode xdr to get the signed transaction
const signedTransaction = stellar.decodeTransaction(xdrStringFromBackend);
nota

Puedes leer más sobre cómo enviar transacciones XDR al servidor en el capítulo siguiente.

La transacción firmada puede ser presentada por la billetera.

await wallet.stellar().submitTransaction(signedTransaction);

Ahora, después de crear la cuenta, puede realizar operaciones. Por ejemplo, podemos desactivar el keypair maestro y reemplazarlo con uno nuevo (llamémoslo keypair del dispositivo) atómicamente en una sola transacción:

const deviceKeyPair = account.createKeypair();

const txBuilder = await stellar.transaction({ sourceAddress: newKeyPair });
const modifyAccountTransaction = txBuilder
.addAccountSigner(deviceKeyPair, 1)
.lockAccountMasterKey()
.build();
newKeyPair.sign(modifyAccountTransaction);

await wallet.stellar().submitTransaction(modifyAccountTransaction);

Añadiendo una Operación

Agrega una Operación personalizada a una transacción. Esto puede ser cualquier Operación admitida por la red Stellar. El objeto Operación se puede importar de "@stellar/stellar-sdk".

import { Operation } from "@stellar/stellar-sdk";

const txBuilder = await stellar.transaction({
sourceAddress: sourceAccountKeyPair,
});
const tx = txBuilder.addOperation(
Operation.manageData({
name: "web_auth_domain",
value: new URL(authServer).hostname,
source: sourceAccountKeyPair,
}),
);

Patrocinando Transacciones

Operaciones de Patrocinio

Algunas operaciones que modifican las reservas de cuentas pueden ser patrocinadas. Para las operaciones patrocinadas, la cuenta patrocinadora pagará las reservas en lugar de la cuenta que está siendo patrocinada. Esto te permite realizar algunas operaciones, incluso si la cuenta no tiene suficientes fondos para realizar tales operaciones. Para patrocinar una transacción, simplemente crea una función de construcción (describiendo qué operaciones van a ser patrocinadas) y pásala al método sponsoring:

const txBuilder = await stellar.transaction({
sourceAddress: sponsoredKeyPair,
});

const buildingFunction = (bldr) => bldr.addAssetSupport(asset);
const transaction = txBuilder
.sponsoring(sponsorKeyPair, buildingFunction)
.build();

sponsoredKeyPair.sign(transaction);
sponsorKeyPair.sign(transaction);
información

Solo algunas operaciones pueden ser patrocinadas, y un constructor de patrocinio tiene un conjunto ligeramente diferente de funciones disponibles en comparación con el TransactionBuilder regular. Nota que una transacción debe ser firmada por ambas cuentas: la cuenta patrocinadora (sponsoringKeyPair) y la cuenta que está siendo patrocinada (sponsoredKeyPair).

Creación de Cuenta Patrocinadora

Una de las cosas que se pueden hacer a través de patrocinio es crear una cuenta con un saldo inicial de 0. Esta creación de cuenta puede hacerse simplemente escribiendo:

const txBuilder = await stellar.transaction({ sourceAddress: sponsorKeyPair });

const newKeyPair = account.createKeypair();

const buildingFunction = (bldr) => bldr.createAccount(newKeyPair);
const transaction = txBuilder
.sponsoring(sponsorKeyPair, buildingFunction, newKeyPair)
.build();

newKeyPair.sign(transaction);
sponsorKeyPair.sign(transaction);

Nota cómo en el primer ejemplo la cuenta fuente de la transacción se establece en sponsoredKeyPair. Debido a esto, no necesitábamos pasar un valor de cuenta patrocinada al bloque método. Ya que cuando se omite, la cuenta patrocinada predetermina a la cuenta fuente de la transacción (sponsoredKeyPair).

Sin embargo, esta vez, la cuenta patrocinada (recientemente creada newKeyPair) es diferente de la cuenta fuente de la transacción. Por lo tanto, es necesario especificarla. De lo contrario, la transacción contendrá una operación mal formada. Como antes, la transacción debe ser firmada por ambas claves.

Creación y Modificación de Cuenta Patrocinadora

Si deseas crear una cuenta y modificarla en una sola transacción, es posible hacerlo pasando un argumento opcional sponsoredAccount al método patrocinador (newKeyPair abajo). Si este argumento está presente, todas las operaciones dentro del bloque patrocinado serán sourcing por esta sponsoredAccount. (Excepto la creación de cuenta, que siempre es sourcing por el patrocinador).

const txBuilder = await stellar.transaction({ sourceAddress: sponsorKeyPair });

const newKeyPair = account.createKeypair();
const replaceWith = account.createKeypair();

const buildingFunction = (bldr) =>
bldr
.createAccount(newKeyPair)
// source account for below operations will be newKeyPair
.addAccountSigner(replaceWith, 1)
.lockAccountMasterKey();

const transaction = txBuilder
.sponsoring(sponsorKeyPair, buildingFunction, newKeyPair)
.build();

newKeyPair.sign(transaction);
sponsorKeyPair.sign(transaction);

Transacción Fee-Bump

Si deseas modificar una cuenta recién creada con un saldo de 0, también es posible hacerlo a través de FeeBump. Se puede combinar con un bloque de patrocinio método para lograr el mismo resultado que en el ejemplo anterior. Sin embargo, con FeeBump, también es posible agregar más operaciones (que no requieren patrocinio), como una transferencia.

Primero, vamos a crear una transacción que reemplazará la clave maestra de una cuenta con un nuevo keypair.

const txBuilder = await stellar.transaction({
sourceAddress: sponsoredKeyPair,
});

const replaceWith = account.createKeypair();

const buildingFunction = (bldr) =>
bldr.lockAccountMasterKey().addAccountSigner(replaceWith, 1);
const transaction = txBuilder
.sponsoring(sponsorKeyPair, buildingFunction)
.build();

Luego, firma la transacción con ambas claves.

sponsorKeyPair.sign(transaction);
sponsoredKeyPair.sign(transaction);

A continuación, crea un fee bump, apuntando a la transacción.

const feeBump = stellar.makeFeeBump({
feeAddress: sponsorKeyPair,
transaction,
});
sponsorKeyPair.sign(feeBump);

Finalmente, presenta una transacción de fee-bump. Ejecutar esta transacción será completamente cubierto por el sponsorKeyPair y sponsoredKeyPair y puede que ni siquiera tenga fondos en XLM en su cuenta.

await wallet.stellar().submitTransaction(feeBump);

Usando XDR para Enviar Datos de Transacción

Nota que una billetera puede no tener una clave de firma para sponsorKeyPair. En ese caso, es necesario convertir la transacción a XDR, enviarla al servidor, conteniendo sponsorKey y devolver la transacción firmada de nuevo a la billetera. Usamos el ejemplo anterior de la creación de cuenta patrocinada, pero esta vez con la clave patrocinadora desconocida para la billetera. El primer paso es definir la clave pública del keypair patrocinador:

const sponsorKeyPair = new PublicKeypair.fromPublicKey("GC5GD...");

A continuación, crea una cuenta de la misma manera que antes y fír mala con newKeyPair. Esta vez, convierte la transacción a XDR:

const txBuilder = await stellar.transaction({ sourceAddress: sponsorKeyPair });

const newKeyPair = account.createKeypair();

const transaction = txBuilder
.sponsoring(sponsorKeyPair, (bldr) => bldr.createAccount(newKeyPair))
.build();
const xdrString = newKeyPair.sign(transaction).toXDR();

Ahora puede ser enviado al servidor. En el servidor, fír mala con una clave privada para la dirección del patrocinador:

// On the server
const sponsorPrivateKey = SigningKeyPair.fromSecret("SD3LH4...");

const signedTransaction = sponsorPrivateKey.sign(
stellar.decodeTransaction(xdrString),
);

return signedTransaction.toXDR();

Cuando el cliente recibe la transacción completamente firmada, puede ser decodificada y enviada a la red Stellar:

const signedTransaction = stellar.decodeTransaction(xdrString);

await wallet.stellar().submitTransaction(signedTransaction);

Enviar Transacción

información

Se recomienda encarecidamente utilizar las funciones de envío de transacciones del SDK de billetera en lugar de las alternativas de Horizon. El SDK de billetera maneja elegantemente las excepciones de tiempo de espera y fuera de tarifa.

Finalmente, vamos a enviar una transacción firmada a la red Stellar. Nota que una transacción patrocinada debe ser firmada tanto por la cuenta como por el patrocinador.

La transacción se vuelve a presentar automáticamente en el error 504 de Horizon (tiempo de espera), que indica un aumento repentino en la actividad de la red.

const signedTxn = transaction.sign(sourceAccountKeyPair);
await wallet.stellar().submitTransaction(signedTxn);

Sin embargo, el método anterior no maneja elegantemente la fijación de precios de tarifas en aumento en la red. Si la tarifa requerida para que una transacción se incluya en el ledger se vuelve demasiado alta y la transacción caduca antes de entrar en el ledger, este método arrojará una excepción.

Así que, en su lugar, el enfoque alternativo es:

const buildingFunction = (builder) =>
builder.transfer(kp.publicKey, new NativeAssetId(), "2");

await stellar.submitWithFeeIncrease({
sourceAddress: kp,
timeout: 30,
baseFeeIncrease: 100,
buildingFunction,
});

Esto creará y firmará la transacción que se originó desde el sourceAccountKeyPair. Cada 30 segundos, esta función volverá a construir esta transacción con una nueva tarifa (aumentada en 100 stroops), repitiendo la firma y la presentación. Una vez que la transacción sea exitosa, la función devolverá el cuerpo de la transacción. Nota que cualquier otro error finalizará el ciclo de reintentos y se lanzará una excepción.

Accediendo al SDK de Horizon

Es muy sencillo utilizar el SDK de Horizon conectándose a la misma instancia de Horizon que una clase Wallet. Para hacerlo, simplemente llama a:

const server = wallet.stellar().server;

Y puedes trabajar con la instancia del servidor Horizon:

const stellarTransaction = server
.transactions()
.forAccount("account_id")
.call();