Depósito y retirada hospedados
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.
El estándar SEP-24 define la forma estándar en que los anchors y las billeteras interactúan en nombre de los usuarios. Las billeteras utilizan este estándar para facilitar el exchange entre activos on-chain (como stablecoins) y activos off-chain (como fiat, u otros activos de red como BTC).
Durante el flujo, una billetera realiza varias solicitudes al anchor, y finalmente recibe una URL interactiva para abrir en un iframe. Esta URL es utilizada por el usuario para proporcionar una entrada (como KYC) directamente al anchor. Finalmente, la billetera puede obtener información de transacciones usando puntos finales de consulta.
Obtener información del anchor
Comencemos obteniendo una instancia de la clase Sep24
, responsable de todas las interacciones SEP-24:
- TypeScript
const sep24 = await anchor.sep24();
Primero, obten la información sobre el soporte del ancla para SEP-24. Esta solicitud no requiere autenticación y devolverá información genérica, como las divisas admitidas y las características admitidas por el ancla. Puedes obtener una lista completa de los campos devueltos en la especificación SEP-24.
- TypeScript
const getAnchorServices = async (): Promise<AnchorServiceInfo> => {
return await anchor.getServicesInfo();
};
Flujos Interactivos
Antes de comenzar, asegúrate de haberte conectado al ancla y de haber recibido un token de autenticación, como se describe en la guía de billeteras Autenticación Stellar. Usaremos el objeto authToken
en los ejemplos a continuación como el token de autenticación SEP-10 obtenido anteriormente.
Para iniciar una operación, necesitamos conocer un activo. Puedes querer codificarlo de forma fija o obtenerlo dinámicamente del archivo de información del ancla, como se muestra a continuación (para USDC):
- TypeScript
import { IssuedAssetId } from "@stellar/typescript-wallet-sdk";
const assetCode = "USDC";
const info = await anchor.getInfo();
const currency = info.currencies.find(({ code }) => code === assetCode);
if (!currency?.code || !currency?.issuer) {
throw new Error(
`Anchor does not support ${assetCode} asset or is not correctly configured on TOML file`,
);
}
const asset = new IssuedAssetId(currency.code, currency.issuer);
Antes de comenzar con el flujo de depósito, asegúrate de que la cuenta del usuario haya establecido una línea de confianza para el activo con el que estás trabajando.
Flujo Básico
Comencemos con un depósito básico:
- TypeScript
const deposit = await sep24.deposit({
assetCode: asset.code,
authToken,
});
Como resultado, recibirás una respuesta interactiva del ancla.
Abre la URL recibida en un iframe y guarda el ID de la transacción para referencia futura:
- TypeScript
const url = deposit.url;
const id = deposit.id;
De manera similar al flujo de depósito, un flujo de retiro básico tiene la misma firma de método y tipo de respuesta:
- TypeScript
const withdrawal = await sep24.withdraw({
assetCode: asset.code,
authToken,
});
const url = withdrawal.url;
const id = withdrawal.id;
Proporcionando Información KYC
Para mejorar la experiencia del usuario, el estándar SEP-24 admite el envío de KYC del usuario al ancla a través de SEP-9. A su vez, el ancla rellenará esta información en el popup interactivo.
Mientras que SEP-9 admite el envío de datos binarios, la versión actual del SDK no ofrece tal funcionalidad.
En este momento, el SEP-9 aceptado aún no está tipificado de manera estricta. La mejora de la tipificación se ofrecerá en versiones futuras.
- TypeScript
const deposit = await sep24.deposit({
assetCode: asset.code,
authToken,
extraFields: { email_address: "[email protected]" },
});
Cambio de Cuenta de Transferencia Stellar
Por defecto, la transferencia Stellar se enviará a la cuenta autenticada (con un memo) que inició el depósito.
Aunque en la mayoría de los casos es aceptable, algunas billeteras pueden dividir sus cuentas. Para hacerlo, pasa una cuenta adicional (y opcionalmente un memo):
- TypeScript
import { Memo, MemoText } from "stellar-sdk";
const recipientAccount = "G...";
const depositDifferentAccount = async (): Promise<InteractiveFlowResponse> => {
return await sep24.deposit({
destinationAccount: recipientAccount,
destinationMemo: new Memo(MemoText, "some memo"),
assetCode: asset.code,
authToken,
});
};
De manera similar, para un retiro, la cuenta de origen de la transacción Stellar podría cambiarse:
- TypeScript
const originAccount = "G...";
const withdrawal = await sep24.withdraw({
withdrawalAccount: originAccount,
assetCode: asset.code,
authToken,
});
Obteniendo Información de la Transacción
En el flujo típico, la billetera obtendría datos de la transacción para notificar a los usuarios sobre las actualizaciones de estado. Esto se realiza a través de los endpoints GET /transaction
y GET /transactions
de SEP-24.
Alternativamente, algunos anclajes admiten webhooks para notificaciones. Ten en cuenta que esta función aún no está ampliamente adoptada.
Rastreando Transacciones
Veamos cómo usar el SDK de la billetera para rastrear los cambios de estado de las transacciones. Usaremos la clase Watcher
para este propósito. Primero, inicializa el vigilante y comienza a rastrear una transacción.
- TypeScript
const watcher = sep24.watcher();
const { stop, refresh } = watcher.watchOneTransaction({
authToken,
assetCode: asset.code,
id: successfulTransaction.id,
onMessage,
onSuccess,
onError,
});
Alternativamente, podemos rastrear múltiples transacciones para el mismo activo.
- TypeScript
const watcher = sep24.watcher();
const { stop, refresh } = watcher.watchAllTransactions({
authToken,
assetCode: asset.code,
onMessage,
onError,
});
Obteniendo Transacción
Mientras que la clase Watcher
ofrece potentes capacidades de seguimiento, a veces es necesario simplemente obtener una transacción (o transacciones) una vez. La clase Anchor
te permite obtener una transacción por ID, ID de transacción Stellar o ID de transacción externa:
- TypeScript
const transaction = await sep24.getTransactionBy({
authToken,
id: transactionId,
});
// "id" is the actual Anchor transaction id, all transactions should have it.
const transaction = await anchor.sep24().getTransactionBy({
authToken,
id: transactionId,
});
// "stellarTransactionId" (aka "stellar_transaction_id" on the SEP spec)
// is the hash of the Stellar network transaction payment related to this
// Anchor transaction.
// The "stellarTransactionId" has a SHA256 hash format like the below:
// - "a35135d8ed4b29b66d821444f6760f8ca1e77bea1fb49541bebeb2c3d844364a"
// E.g. we'll only have this transaction id field AFTER the wallet sends funds
// to Anchor on the withdrawal flow or receives funds from Anchor on the
// deposit flow.
const transaction = await anchor.sep24().getTransactionBy({
authToken,
stellarTransactionId,
});
// "externalTransactionId" (aka "external_transaction_id" on the SEP spec)
// could refer to some ID of transaction on external network.
// E.g. this could be some "reference number" displayed to the user on
// the last step of the Interactive Flow UI which the user could use in some
// other external place to complete the deposit or withdraw operation.
const transaction = await anchor.sep24().getTransactionBy({
authToken,
externalTransactionId,
});
También es posible obtener transacciones por el activo
- TypeScript
const transactions = await sep24.getTransactionsForAsset({
authToken,
assetCode: asset.code,
});
Enviando Transferencia de Retiro
Antes, revisamos cómo iniciar el flujo de retiro. Ahora, veamos un ejemplo completo.
Primero, inicia el retiro:
- TypeScript
const withdrawal = await sep24.withdraw({
assetCode: asset.code,
authToken,
});
A continuación, abre una URL interactiva:
- TypeScript
const url = withdrawal.url;
// open the url
Después de eso, necesitamos esperar hasta que el ancla esté lista para recibir fondos. Para hacerlo, esperamos hasta que la transacción alcance el estado pending_user_transfer_start
. Este código utiliza un mecanismo de vigilancia simple (polling) sin condición de salida. El código de la aplicación debería ser más robusto.
- TypeScript
const watcher = sep24.watcher();
const onMessage = (transaction) => {
if (transaction.status === "pending_user_transfer_start") {
// begin transfer code
}
};
const onSuccess = (transaction) => {
// transaction comes back as completed / refunded / expired
};
const onError = (transaction) => {
// runtime error, or the transaction comes back as
// no_market / too_small / too_large / error
};
// We can watch for a particular transaction.
const { refresh, stop } = watcher.watchOneTransaction({
authToken,
assetCode: asset.code,
id: successfulTransaction.id,
onMessage,
onSuccess,
onError,
});
// Or watch for ALL transactions of a particular asset.
const { refresh, stop } = watcher.watchAllTransactions({
authToken,
assetCode: asset.code,
onMessage,
onError,
});
A continuación, firma y envía la transferencia Stellar:
- TypeScript
// Import Horizon to get the result codes for error handling
import { Horizon } from "@stellar/stellar-sdk";
// This creates a transaction builder which we'll be using to assemble
// our transfer withdrawal transaction as shown below.
const txBuilder = await stellar.transaction({
sourceAddress: keypair,
baseFee: 10000, // this is 0.001 XLM
timebounds: 180, // in seconds
});
// We can use the transaction object received on the onMessage callback from
// the watcher, or, we can also fetch the transaction object using either
// getTransactionBy or getTransactionsForAsset as illustrated in previous step.
onMessage: (transaction) => {
if (transaction.status === "pending_user_transfer_start") {
// Use the builder to assemble the transfer transaction. Behind the scenes
// it extracts the Stellar account (withdraw_anchor_account), memo (withdraw_memo)
// and amount (amount_in) to use in the Stellar payment transaction that will
// be submitted to the Stellar network.
const transferTransaction = txBuilder
.transferWithdrawalTransaction(transaction, asset)
.build();
// Signs it with the account key pair
transferTransaction.sign(keypair);
// Finally submits it to the stellar network. This stellar.submitTransaction()
// function handles '504' status codes (timeout) by keep retrying it until
// submission succeeds or we get a different error.
try {
const response = await stellar.submitTransaction(transferTransaction);
console.log("Stellar-generated transaction ID: ", response.id);
} catch (error) {
/*
In case it's not a 504 (timeout) error, the application could try some
resolution strategy based on the error kind.
On Stellar docs you can find a page dedicated to error handling:
https://developers.stellar.org/docs/learn/encyclopedia/errors-and-debugging/error-handling
And status/result codes:
https://developers.stellar.org/docs/data/horizon/api-reference/errors
*/
// Let's illustrate here how we could handle an 'invalid sequence number' error.
// We can access all possible result codes through Horizon's API.
const sdkResultCodes = Horizon.HorizonApi.TransactionFailedResultCodes;
// We can access error's response data to check for useful error details.
const errorData = error.response?.data;
/*
Sample of errorData object returned by the Wallet SDK:
{
type: 'https://stellar.org/horizon-errors/transaction_failed',
title: 'Transaction Failed',
status: 400,
detail: 'The transaction failed when submitted to the stellar network.
The `extras.result_codes` field on this response contains further details.
Descriptions of each code can be found at:
https://developers.stellar.org/docs/data/horizon/api-reference/errors/http-status-codes/horizon-specific/transaction-failed',
extras: {
envelope_xdr: 'AAAAAgAAAADBjF7n9gfByOwlnyaJH...k4BRagf/////////8AAAAAAAAAAA==',
result_codes: { transaction: 'tx_bad_seq' },
result_xdr: 'AAAAAAAAAGT////6AAAAAA=='
}
}
*/
/*
Example scenario: invalid sequence numbers.
These errors typically occur when you have an outdated view of an account.
This could be because multiple devices are using this account, you have
concurrent submissions happening, or other reasons. The solution is relatively
simple: retrieve the account details and try again with an updated sequence number.
*/
if (
errorData?.status == 400 &&
errorData?.extras?.result_codes?.transaction ===
sdkResultCodes.TX_BAD_SEQ
) {
// Creating a new transaction builder means retrieving an updated sequence number.
const txBuilder2 = await stellar.transaction({
sourceAddress: keypair,
baseFee: 10000,
timebounds: 180,
});
// ...
// Repeat all the steps until submitting the transaction again.
// ...
const response2 = await stellar.submitTransaction(transferTransaction);
console.log(
"Stellar-generated transaction ID on retry: ",
response2.id,
);
// The application should take care to not resubmit the same transaction
// blindly with an updated sequence number as it could result in more than
// one payment being made when only one was intended.
}
}
}
};
Donde keypair
es la cuenta autenticada por SEP-10. Si deseas transferir fondos desde una dirección diferente, consulta la sección Cambio de Cuenta de Transferencia Stellar.
El código para enviar transacciones a Stellar debería ser desarrollado cuidadosamente. El SDF tiene una página de documentación dedicada a enviar transacciones y manejar errores con gracia. Aquí hay varias cosas que debes tener en cuenta:
- Ofrece una tarifa alta. Tu tarifa debería ser tan alta como estarías dispuesto a ofrecer antes de decidir que la transacción ya no vale la pena enviar. Stellar solo te cobrará lo mínimo necesario para ser incluido en el ledger; no se te cobrará la cantidad que ofrezcas a menos que todos los demás estén ofreciendo la misma cantidad o más. De lo contrario, pagarás la tarifa más baja ofrecida en el conjunto de transacciones incluidas en el ledger.
- Establece un límite de tiempo máximo para la transacción. Esto asegura que si tu transacción no se incluye en un ledger antes del tiempo establecido, puedes reconstruir la transacción con una tarifa ofrecida más alta y volver a enviarla con mejores posibilidades de inclusión.
- Reenvía la transacción cuando obtengas códigos de estado 504. Los códigos de estado 504 solo te están diciendo que tu transacción sigue pendiente; no que ha sido cancelada o que tu solicitud fue inválida. Simplemente debes hacer la solicitud nuevamente con la misma transacción para obtener un estado final (ya sea incluida o caducada).
Por último, rastreemos las actualizaciones de estado de la transacción. En este ejemplo, simplemente verificamos si la transacción se ha completado:
- TypeScript
const watcher = sep24.watcher();
const onSuccess = (transaction) => {
// transaction came back as completed / refunded / expired
console.log("Transaction is completed");
};
const onError = (transaction) => {
// runtime error, or the transaction comes back as
// no_market / too_small / too_large / error
};
const { refresh, stop } = watcher.watchOneTransaction({
authToken,
assetCode: asset.code,
id: successfulTransaction.id,
onMessage,
onSuccess,
onError,
});