Red de Stellar
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:
- TypeScript
let account = wal.stellar().account();
Ahora podemos crear un par de claves:
- TypeScript
let accountKeyPair = account.createKeypair();
Si usas react-native, createKeypair
no funcionará. En su lugar, usa el método auxiliar createKeypairFromRandom
así:
- TypeScript
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).
- TypeScript
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.
- TypeScript
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.
- TypeScript
const newSignerKeyPair = account.createKeypair();
const tx = txBuilder.addAccountSigner(newSignerKeyPair, 10).build();
Eliminar un firmante de la cuenta.
- TypeScript
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.
- TypeScript
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.
- TypeScript
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).
- TypeScript
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.
- TypeScript
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.
- TypeScript
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".
- TypeScript
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.
- TypeScript
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
- TypeScript
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.
- TypeScript
// 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.
- TypeScript
const createTxn = txBuilder.createAccount(newKeyPair).build();
Esta transacción debe enviarse al firmante externo (titular de externalKeyPair
) para ser firmada.
- TypeScript
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);
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.
- TypeScript
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:
- TypeScript
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".
- TypeScript
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
:
- TypeScript
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);
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:
- TypeScript
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).
- TypeScript
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.
- TypeScript
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.
- TypeScript
sponsorKeyPair.sign(transaction);
sponsoredKeyPair.sign(transaction);
A continuación, crea un fee bump, apuntando a la transacción.
- TypeScript
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.
- TypeScript
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:
- TypeScript
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:
- TypeScript
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:
- TypeScript
// 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:
- TypeScript
const signedTransaction = stellar.decodeTransaction(xdrString);
await wallet.stellar().submitTransaction(signedTransaction);
Enviar Transacció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.
- TypeScript
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:
- TypeScript
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:
- TypeScript
const server = wallet.stellar().server;
Y puedes trabajar con la instancia del servidor Horizon:
- TypeScript
const stellarTransaction = server
.transactions()
.forAccount("account_id")
.call();