Saltar al contenido principal

Depósito y retirada hospedados

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.

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:

const sep24 = await anchor.sep24();

Primero, obtengamos la información sobre el soporte del anchor para SEP-24. Esta solicitud no requiere autenticación y devolverá información genérica, como las monedas admitidas y las características admitidas por el anchor. Puedes obtener una lista completa de los campos devueltos en la especificación SEP-24.

const getAnchorServices = async (): Promise<AnchorServiceInfo> => {
return await anchor.getServicesInfo();
};

Flujos Interactivos

Antes de empezar, asegúrate de que te has conectado al anchor y has recibido un token de autenticación, como se describe en la guía de billetera [Stellar Authentication]. 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 directamente, o obtenerlo dinámicamente del archivo de información del anchor, como se muestra a continuación (para USDC):

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);
información

Antes de comenzar con el flujo de depósito, asegúrate de que la cuenta del usuario ha establecido una trustline para el activo con el que estás trabajando.

Flujo Básico

Comencemos con un depósito básico:

const deposit = await sep24.deposit({
assetCode: asset.code,
authToken,
});

Como resultado, recibirás una respuesta interactiva del anchor.

Abre la URL recibida en un iframe y guarda el ID de la transacción para futuras referencias:

const url = deposit.url;
const id = deposit.id;

De manera similar al flujo de depósito, un flujo de retirada básico tiene la misma firma de método y tipo de respuesta:

const withdrawal = await sep24.withdraw({
assetCode: asset.code,
authToken,
});
const url = withdrawal.url;
const id = withdrawal.id;

Proporcionando información de KYC

Para mejorar la experiencia del usuario, el estándar SEP-24 admite el envío de KYC de usuario al anchor a través de SEP-9. A su vez, el anchor completará esta información en el popup interactivo.

información

Mientras que SEP-9 admite el envío de datos binarios, la versión actual del SDK no ofrece tal funcionalidad.

nota

En este momento, el SEP-9 aceptado no está tipado de manera estricta aún. Una tipificación mejorada será ofrecida en versiones futuras.

const deposit = await sep24.deposit({
assetCode: asset.code,
authToken,
extraFields: { email_address: "[email protected]" },
});

Cambiando la cuenta de transferencia Stellar

De forma predeterminada, la transferencia Stellar se enviará a la cuenta autenticada (con un memo) que inició el depósito.

Mientras que 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):

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 una retirada, se podría cambiar la cuenta de origen de la transacción Stellar:

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 transacción para notificar a los usuarios sobre actualizaciones de estado. Esto se hace a través del SEP-24 GET /transaction y GET /transactions endpoint.

Alternativamente, algunos anchors admiten webhooks para notificaciones. Ten en cuenta que esta característica aún no se ha adoptado ampliamente.

Rastreo de transacciones

Veamos cómo usar el SDK de billetera para rastrear cambios en el estado de la transacción. Usaremos la clase Watcher para este propósito. Primero, inicialicemos el observador y empecemos a rastrear una transacción.

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.

const watcher = sep24.watcher();

const { stop, refresh } = watcher.watchAllTransactions({
authToken,
assetCode: asset.code,
onMessage,
onError,
});

Obteniendo transacción

Mientras la clase Watcher ofrece poderosas capacidades de rastreo, 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:

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 la transacción por el activo

const transactions = await sep24.getTransactionsForAsset({
authToken,
assetCode: asset.code,
});

Presentando transferencia de retirada

Anteriormente, revisamos cómo iniciar el flujo de retirada. Ahora, echemos un vistazo a un ejemplo completo.

Primero, inicia la retirada:

const withdrawal = await sep24.withdraw({
assetCode: asset.code,
authToken,
});

Next, open an interactive url :

const url = withdrawal.url;
// open the url

Después de eso, necesitamos esperar hasta que el anchor esté listo para recibir fondos. Para hacerlo, estaremos esperando hasta que la transacción alcance el estado pending_user_transfer_start. Este código utiliza un mecanismo de observación simple (polling) sin una condición de salida. El código de la aplicación debería ser más robusto.

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:

// 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 SEP-10. Si deseas transferir fondos desde una dirección diferente, consulta la sección Cambiando la cuenta de transferencia Stellar.

El código para enviar transacciones a Stellar debería desarrollarse cuidadosamente. El SDF tiene una página de documentación dedicada a enviar transacciones y manejar errores de manera adecuada. Aquí hay algunas cosas que debes tener en cuenta:

  • Ofrece una tarifa alta. Tu tarifa debería ser tan alta como la que ofrecerías 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 ofreces a menos que todos los demás estén ofreciendo la misma cantidad o mayor. De lo contrario, pagarás la tarifa más baja ofrecida en el conjunto de transacciones incluidas en el ledger.
  • Establece un tiempo máximo para la transacción. Esto garantiza que si tu transacción no se incluye en un ledger antes del tiempo establecido, podrás reconstruir la transacción con una tarifa ofrecida más alta y enviarla de nuevo con mejores posibilidades de inclusión.
  • Reenvía la transacción cuando recibas códigos de estado 504. Los códigos de estado 504 solo te están diciendo que tu transacción aún está pendiente -- no que ha sido cancelada o que tu solicitud fue inválida. Simplemente deberías hacer la solicitud de nuevo con la misma transacción para obtener un estado final (ya sea incluida o caducada).

Finalmente, rastreemos las actualizaciones del estado de la transacción. En este ejemplo, simplemente verificamos si la transacción ha sido completada:

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,
});