Red de Stellar
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:
- TypeScript
let account = wal.stellar().account();
Ahora podemos crear un keypair:
- 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 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).
- 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 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.
- TypeScript
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.
- 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 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)
Agregar un activo (trustline) a la cuenta. Esto permite que la cuenta reciba 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 un activo diferente. La cuenta debe tener un 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();
Pago por Ruta
Envía un activo de la cuenta de origen y recibe 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
Establecer un memo en la transacción. El objeto memo puede ser importado de "@stellar/stellar-sdk".
- TypeScript
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.
- TypeScript
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
- TypeScript
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.
- TypeScript
// 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.
- TypeScript
const createTxn = txBuilder.createAccount(newKeyPair).build();
Esta transacción debe ser enviada a un 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 pasar la transacción XDR al servidor en el capítulo a continuación.
La transacción firmada puede ser presentada por la billetera.
- TypeScript
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:
- 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);
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".
- 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 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:
- 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 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:
- 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 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).
- 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 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.
- 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();
Segundo, firma la transacción con ambas claves.
- TypeScript
sponsorKeyPair.sign(transaction);
sponsoredKeyPair.sign(transaction);
A continuación, crea un aumento de tarifa, apuntando a la transacción.
- TypeScript
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.
- 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 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:
- TypeScript
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:
- 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írmatelo 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 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.
- TypeScript
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:
- 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ó 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:
- TypeScript
const server = wallet.stellar().server;
Y puedes trabajar con la instancia del servidor de Horizon:
- TypeScript
const stellarTransaction = server
.transactions()
.forAccount("account_id")
.call();