Saltar al contenido principal

Red de Stellar

información

Esta guía está disponible en tres lenguajes de programación diferentes: Typescript, Kotlin y Flutter (Dart). 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 keypair:

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 generador de transacciones te permite crear varias transacciones que pueden ser firmadas y enviadas a la red Stellar. Algunas transacciones pueden ser patrocinadas.

Construcción de Transacciones Básicas

Primero, miremos 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 a 0. Usa precaución 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();

Agrega un nuevo firmante a la cuenta. Usa precaución al agregar nuevos firmantes y asegúrate de establecer el peso de 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 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)

Agregar un activo (trustline) a la cuenta. Esto permite que la cuenta reciba 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 un activo diferente. La cuenta debe tener un 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();

Pago por Ruta

Envía un activo de la cuenta de origen y recibe 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

Establecer un memo en la transacción. El objeto memo puede ser importado de "@stellar/stellar-sdk".

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

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

Fusión de Cuenta

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();

Financiar Cuenta Testnet

Financiar una cuenta en la red de prueba de Stellar

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

Construcción de Transacciones Avanzadas

En algunos casos, es posible que una clave privada no se conozca antes de formar una transacción. Por ejemplo, una nueva cuenta debe financiarse para existir y la billetera puede no tener la clave para la cuenta, por lo que puede solicitar que la transacción de crear 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, la cuenta debe ser creada.

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

Esta transacción debe ser enviada a un 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 pasar la transacción XDR al servidor en el capítulo a continuación.

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

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

Ahora, después de que la cuenta es creada, puede realizar operaciones. Por ejemplo, podemos desactivar el keypair maestro y reemplazarlo con uno nuevo (llámalo keypair del dispositivo) atómicamente en una 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);

Agregar 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 puede ser importado 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 Patrocinadas

Algunas operaciones que modifican las reservas de cuenta pueden ser patrocinadas. Para 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 llevar a cabo tales operaciones. Para patrocinar una transacción, simplemente crea una función de construcción (describiendo qué operaciones deben ser patrocinadas) y pásala al sponsoring method:

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 builder patrocinador tiene un conjunto ligeramente diferente de funciones disponibles en comparación con el TransactionBuilder regular. Nota que una transacción debe ser firmada tanto por la cuenta patrocinadora (sponsoringKeyPair) como por la cuenta que está siendo patrocinada (sponsoredKeyPair).

Creación de Cuenta Patrocinadora

Una de las cosas que se pueden hacer a través del patrocinio es crear una cuenta con un saldo inicial de 0. Esta creación de cuenta puede ser realizada 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 de origen de la transacción se establece en sponsoredKeyPair. Debido a esto, no necesitamos pasar un valor de cuenta patrocinada al method. Dado que cuando se omite, la cuenta patrocinada se establece por defecto en la cuenta de origen de la transacción (sponsoredKeyPair).

Sin embargo, esta vez, la cuenta patrocinada (el newKeyPair recién creado) es diferente de la cuenta de origen de la transacción. Por lo tanto, es necesario especificarlo. De lo contrario, la transacción contendrá una operación malformada. 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 sponsoring method (newKeyPair a continuación). Si este argumento está presente, todas las operaciones dentro del bloque patrocinado se originarán de esta sponsoredAccount. (Excepto la creación de cuenta, que siempre se origina desde 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 de Aumento de Tarifa

Si deseas modificar una cuenta recién creada con un saldo de 0, también es posible hacerlo a través de FeeBump. Puede combinarse con un method de patrocinio 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, creemos 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();

Segundo, firma la transacción con ambas claves.

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

A continuación, crea un aumento de tarifa, apuntando a la transacción.

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

Finalmente, presenta una transacción de aumento de tarifa. La ejecución de esta transacción será completamente cubierta por sponsorKeyPair y sponsoredKeyPair y puede no tener ni siquiera 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 a la billetera. Utilicemos el ejemplo anterior de creación de cuenta patrocinada, pero esta vez con la clave del patrocinador siendo desconocida para la billetera. El primer paso es definir la clave pública de la clave del patrocinador:

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

A continuación, crea una cuenta de la misma manera que antes y fírmatela 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írmatelo 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 de manera elegante los tiempos de espera y las excepciones por falta de tarifas.

Finalmente, enviemos 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 enviar automáticamente en el error 504 de Horizon (tiempo de espera), lo que indica un aumento repentino de la actividad de la red.

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

Sin embargo, el método anterior no maneja con elegancia el aumento de precios de tarifas en la red. Si la tarifa requerida para que una transacción sea incluida en el ledger se vuelve demasiado alta y la transacción caduca antes de ser incluida en el ledger, este método lanzará 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ó en 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 el envío. Una vez que la transacción tenga éxito, la función devolverá el cuerpo de la transacción. Nota que cualquier otro error terminará el ciclo de reintentos y se lanzará una excepción.

Accediendo al SDK de Horizon

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

const server = wallet.stellar().server;

Y puedes trabajar con la instancia del servidor de Horizon:

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