Saltar al contenido principal

Saldos reclamables

Los saldos reclamables fueron introducidos en CAP-0023 y se usan para dividir un pago en dos partes.

  • Parte 1: la cuenta que envía crea un pago, o ClaimableBalanceEntry, usando la operación Create Claimable Balance
  • Parte 2: la(s) cuenta(s) destinataria(s), o reclamante(s), acepta(n) el ClaimableBalanceEntry usando la operación Claim Claimable Balance

Los saldos reclamables permiten que una cuenta envíe un pago a otra cuenta que no necesariamente está preparada para recibir el pago. Pueden usarse cuando envías un activo no nativo a una cuenta que aún no ha establecido una trustline, lo cual puede ser útil para anchors que integran nuevos usuarios. El reclamante debe establecer una trustline para el activo antes de poder reclamar el saldo reclamable; de lo contrario, la reclamación resultará en un error op_no_trust.

Es importante notar que si un saldo reclamable no es reclamado, permanece en el ledger para siempre, ocupando espacio y, en última instancia, haciendo que la red sea menos eficiente. Por esta razón, es buena idea poner una de tus propias cuentas como reclamante de un saldo reclamable. Así puedes aceptar tu propio saldo reclamable si es necesario, liberando espacio en la red.

Cada ClaimableBalanceEntry es una entrada del ledger, y cada reclamante en esa entrada incrementa el balance mínimo de la cuenta fuente en una reserva base.

Una vez que un ClaimableBalanceEntry ha sido reclamado, es eliminado.

Operaciones

Crear saldo reclamable

Para parámetros básicos, consulta la entrada Crear saldo reclamable en nuestra sección Lista de operaciones.

Parámetros adicionales

Claim_Predicate_ Reclamante — un objeto que contiene tanto la cuenta destino que puede reclamar el ClaimableBalanceEntry como un ClaimPredicate que debe evaluarse como verdadero para que la reclamación tenga éxito.

Un ClaimPredicate es una estructura de datos recursiva que puede usarse para construir condicionales complejos usando diferentes ClaimPredicateTypes. A continuación, algunos ejemplos con el prefijo Claim_Predicate_ removido para mayor legibilidad. Ten en cuenta que los SDKs esperan que las marcas de tiempo Unix estén expresadas en segundos.

  • Puede reclamar en cualquier momento - UNCONDITIONAL
  • Puede reclamar si el tiempo de cierre del ledger, incluyendo la reclamación, es antes de X segundos + el tiempo de cierre del ledger en el que se creó el ClaimableBalanceEntry - BEFORE_RELATIVE_TIME(X)
  • Puede reclamar si el tiempo de cierre del ledger incluyendo la reclamación es antes de X (marca de tiempo Unix) - BEFORE_ABSOLUTE_TIME(X)
  • Puede reclamar si el tiempo de cierre del ledger, incluyendo la reclamación, es en o después de X segundos + el tiempo de cierre del ledger en el que se creó el ClaimableBalanceEntry - NOT(BEFORE_RELATIVE_TIME(X))
  • Puede reclamar si el tiempo de cierre del ledger, incluyendo la reclamación, es en o después de X (marca de tiempo Unix) - NOT(BEFORE_ABSOLUTE_TIME(X))
  • Puede reclamar entre las marcas de tiempo Unix X e Y (dado que X < Y) - AND(NOT(BEFORE_ABSOLUTE_TIME(X)), BEFORE_ABSOLUTE_TIME(Y))
  • Puede reclamar fuera de las marcas de tiempo Unix X e Y (dado que X < Y) - OR(BEFORE_ABSOLUTE_TIME(X), NOT(BEFORE_ABSOLUTE_TIME(Y))

ClaimableBalanceID ClaimableBalanceID es una unión con un posible tipo (CLAIMABLE_BALANCE_ID_TYPE_V0). Contiene un hash SHA-256 del OperationID para Saldos Reclamables.

Una operación Crear saldo reclamable exitosa devolverá un ID de Balance, que es requerido al reclamar el ClaimableBalanceEntry con la operación Claim Claimable Balance.

Reclamar saldo reclamable

Para parámetros básicos, consulta la entrada Reclamar saldo reclamable en nuestra sección Lista de operaciones.

Esta operación cargará el ClaimableBalanceEntry que corresponde al Balance ID y luego buscará la cuenta fuente de esta operación en la lista de reclamantes de la entrada. Si se encuentra una coincidencia en el reclamante, y el ClaimPredicate evalúa a verdadero, entonces el ClaimableBalanceEntry puede ser reclamado. El saldo en la entrada será transferido a la cuenta fuente si no hay problemas de límite o trustline (para activos no nativos), lo que significa que el reclamante debe establecer una trustline para el activo antes de reclamarlo.

Recuperar saldo reclamable

Esta operación recupera un saldo reclamable, devolviendo el activo a la cuenta emisora, quemándolo. Debes recuperar el saldo reclamable completo, no solo una parte. Una vez que un saldo reclamable ha sido reclamado, usa la operación regular de recuperación para recuperarlo.

Las recuperaciones de saldos reclamables requieren el ID del saldo reclamable.

Aprende más sobre recuperaciones en nuestra Guía de recuperaciones.

Ejemplo

El código a continuación demuestra, mediante los SDKs de JavaScript y Go, cómo una cuenta (Cuenta A) crea un ClaimableBalanceEntry con dos reclamantes: Cuenta A (ella misma) y Cuenta B (otro destinatario).

Cada una de estas cuentas solo puede reclamar el saldo bajo condiciones únicas. La Cuenta B tiene un minuto completo para reclamar el saldo antes de que la Cuenta A pueda recuperarlo para sí misma.

Nota: en general no existe un mecanismo de recuperación para un saldo reclamable — si ninguno de los predicados puede cumplirse, el saldo no puede recuperarse. El ejemplo de recuperación a continuación funciona como una red de seguridad para esta situación.

const sdk = require("stellar-sdk");

async function main() {
let server = new sdk.Server("https://horizon-testnet.stellar.org");

let A = sdk.Keypair.fromSecret(
"SAQLZCQA6AYUXK6JSKVPJ2MZ5K5IIABJOEQIG4RVBHX4PG2KMRKWXCHJ",
);
let B = sdk.Keypair.fromPublicKey(
"GAS4V4O2B7DW5T7IQRPEEVCRXMDZESKISR7DVIGKZQYYV3OSQ5SH5LVP",
);

// NOTE: Proper error checks are omitted for brevity; always validate things!

let aAccount = await server.loadAccount(A.publicKey()).catch(function (err) {
console.error(`Failed to load ${A.publicKey()}: ${err}`);
});
if (!aAccount) {
return;
}

// Create a claimable balance with our two above-described conditions.
let soon = Math.ceil(Date.now() / 1000 + 60); // .now() is in ms
let bCanClaim = sdk.Claimant.predicateBeforeRelativeTime("60");
let aCanReclaim = sdk.Claimant.predicateNot(
sdk.Claimant.predicateBeforeAbsoluteTime(soon.toString()),
);

// Create the operation and submit it in a transaction.
let claimableBalanceEntry = sdk.Operation.createClaimableBalance({
claimants: [
new sdk.Claimant(B.publicKey(), bCanClaim),
new sdk.Claimant(A.publicKey(), aCanReclaim),
],
asset: sdk.Asset.native(),
amount: "420",
});

let tx = new sdk.TransactionBuilder(aAccount, { fee: sdk.BASE_FEE })
.addOperation(claimableBalanceEntry)
.setNetworkPassphrase(sdk.Networks.TESTNET)
.setTimeout(180)
.build();

tx.sign(A);
let txResponse = await server
.submitTransaction(tx)
.then(function () {
console.log("Claimable balance created!");
})
.catch(function (err) {
console.error(`Tx submission failed: ${err}`);
});
}

En este punto, el ClaimableBalanceEntry existe en el ledger, pero necesitaremos su Balance ID para reclamarlo, lo cual puede hacerse de varias maneras:

  1. El remitente de la entrada (Cuenta A en este caso) puede recuperar el Balance ID antes de enviar la transacción;
  2. El remitente analiza el XDR del resultado de las operaciones de la transacción; o
  3. Alguien consulta la lista de saldos reclamables.

Cualquiera de las partes también podría revisar los /effects de la transacción o consultar /claimable_balances con diferentes filtros en Horizon. Ten en cuenta que mientras que (1) puede no estar disponible en algunos SDKs ya que es solo una ayuda, los otros métodos son universales.

// Method 1: Suppose `txResponse` comes from the transaction submission
// above on testnet.
const builder = sdk.TransactionBuilder(
txResponse.envelope_xdr,
Networks.TESTNET,
);
console.log("Balance ID (1):", builder.build().getClaimableBalanceId(0));

// Method 2:
let txResult = sdk.xdr.TransactionResult.fromXDR(
txResponse.result_xdr,
"base64",
);
let results = txResult.result().results();

// We look at the first result since our first (and only) operation
// in the transaction was the CreateClaimableBalanceOp.
let operationResult = results[0].value().createClaimableBalanceResult();
let balanceId = operationResult.balanceId().toXDR("hex");
console.log("Balance ID (2):", balanceId);

// Method 3: Account B could alternatively do something like:
let balances = await server
.claimableBalances()
.claimant(B.publicKey())
.limit(1) // there may be many in general
.order("desc") // so always get the latest one
.call()
.catch(function (err) {
console.error(`Claimable balance retrieval failed: ${err}`);
});
if (!balances) {
return;
}

balanceId = balances.records[0].id;
console.log("Balance ID (3):", balanceId);

Con el ID del saldo reclamable adquirido, cualquiera de las cuentas B o A puede realmente enviar una reclamación, dependiendo de cuál predicado se cumpla. Asumiremos aquí que ha pasado un minuto, así que la Cuenta A simplemente recupera el saldo.

let claimBalance = sdk.Operation.claimClaimableBalance({
balanceId: balanceId,
});
console.log(A.publicKey(), "claiming", balanceId);

let tx = new sdk.TransactionBuilder(aAccount, { fee: sdk.BASE_FEE })
.addOperation(claimBalance)
.setNetworkPassphrase(sdk.Networks.TESTNET)
.setTimeout(180)
.build();

tx.sign(A);
await server.submitTransaction(tx).catch(function (err) {
console.error(`Tx submission failed: ${err}`);
});

¡Y eso es todo! Como optamos por la ruta de recuperación, la Cuenta A debería tener el mismo saldo con el que empezó (menos las tarifas), y la Cuenta B debería permanecer sin cambios.

Guías en esta categoría: