Recuperaciones
Las recuperaciones fueron introducidas en CAP-0035 y permiten a un emisor de activo quemar una cantidad específica de un activo habilitado para recuperación desde una trustline o un saldo reclamable, destruyéndolo efectivamente y removiéndolo del saldo de un destinatario.
Fueron diseñadas para permitir a los emisores de activos cumplir con regulaciones de valores, que en muchas jurisdicciones requieren que los emisores de activos (o agentes de transferencia designados) tengan la capacidad de revocar activos en caso de una transacción errónea o fraudulenta u otra acción regulatoria relacionada con una persona o activo específico.
Las recuperaciones son útiles para:
- Recuperar activos que han sido obtenidos fraudulentamente
- Responder a acciones regulatorias
- Permitir que personas con identidad verificada recuperen un activo habilitado en caso de pérdida de custodia de clave o robo
Operaciones
Configurar opciones
El emisor configura su cuenta para habilitar las recuperaciones usando la bandera AUTH_CLAWBACK_ENABLED
. Esto causa que cada trustline establecida posteriormente para cualquier activo emitido por esa cuenta tenga automáticamente la bandera TRUSTLINE_CLAWBACK_ENABLED_FLAG
establecida.
Si una cuenta emisora desea establecer la bandera AUTH_CLAWBACK_ENABLED_FLAG
, debe tener establecida la bandera AUTH_REVOCABLE_FLAG
. Esto permite al emisor de un activo recuperar saldos bloqueados en ofertas revocando primero la autorización de una trustline, lo que elimina todas las ofertas que involucran esa trustline. Luego, el emisor puede realizar la recuperación.
Recuperación
La cuenta emisora utiliza esta operación para recuperar parte o la totalidad de un activo. Una vez que una cuenta posee un activo para el cual se han habilitado las recuperaciones, la cuenta emisora puede recuperarlo, quemándolo. Necesitas proporcionar el activo, una cantidad y la cuenta desde la cual estás recuperando el activo. Para más detalles, consulta la operación Recuperación.
Recuperar saldo reclamable
Esta operación recupera un saldo reclamable, devolviendo el activo a la cuenta emisora, quemándolo. Debes recuperar la totalidad del saldo reclamable, no solo una parte de él. Una vez que un saldo reclamable ha sido reclamado, utiliza la operación de recuperación regular para recuperarlo. Las recuperaciones de saldos reclamables requieren el ID del saldo reclamable. Para más detalles, consulta la operación de Recuperar saldo reclamable.
Establecer bandera de trustline
La cuenta emisora utiliza esta operación para eliminar las capacidades de recuperación en una trustline específica removiendo la bandera TRUSTLINE_CLAWBACK_ENABLED_FLAG
vía la operación SetTrustLineFlags.
Solo puedes eliminar una bandera, no establecerla. Por lo tanto, eliminar una bandera de recuperación en una trustline es irreversible. Esto se hace para que no cambies retroactivamente las reglas para los tenedores de tu activo. Si quieres habilitar recuperaciones nuevamente, los tenedores deben reemitir sus trustlines.
Ejemplos
Aquí cubriremos los siguientes enfoques para recuperar un activo.
Ejemplo 1: La cuenta emisora (Cuenta A) crea un activo habilitado para recuperación y lo envía a la Cuenta B. La Cuenta B envía ese activo a la Cuenta C. La Cuenta A luego recupera el activo de C. Ejemplo 2: La Cuenta B crea un saldo reclamable para la Cuenta C, y la Cuenta A recupera el saldo reclamable. Ejemplo 3: La Cuenta A emite un activo habilitado para recuperación a la Cuenta B. A recupera parte del activo de B, luego elimina la bandera habilitada para recuperación de la trustline y ya no puede recuperar el activo.
Preámbulo: Emitiendo un activo recuperable
Primero, configuraremos una cuenta para habilitar recuperaciones y emitir un activo en consecuencia.
Emitir correctamente un activo (con cuentas separadas de emisión y distribución) es un poco más complejo, pero aquí usaremos un método más simple.
Además, ten en cuenta que primero necesitamos habilitar recuperaciones y luego establecer trustlines, ya que no puedes habilitar recuperación retroactivamente en trustlines existentes.
- JavaScript
const sdk = require("stellar-sdk");
let server = new sdk.Server("https://horizon-testnet.stellar.org");
const A = sdk.Keypair.fromSecret(
"SAQLZCQA6AYUXK6JSKVPJ2MZ5K5IIABJOEQIG4RVBHX4PG2KMRKWXCHJ",
);
const B = sdk.Keypair.fromSecret(
"SAAY2H7SANIS3JLFBFPLJRTYNLUYH4UTROIKRVFI4FEYV4LDW5Y7HDZ4",
);
const C = sdk.Keypair.fromSecret(
"SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4",
);
const ASSET = new sdk.Asset("CLAW", A.publicKey());
/// Enables AuthClawbackEnabledFlag on an account.
function enableClawback(account, keys) {
return server.submitTransaction(
buildTx(account, keys, [
sdk.Operation.setOptions({
setFlags: sdk.AuthClawbackEnabledFlag | sdk.AuthRevocableFlag,
}),
]),
);
}
/// Establishes a trustline for `recipient` for ASSET (from above).
const establishTrustline = function (recipient, key) {
return server.submitTransaction(
buildTx(recipient, key, [
sdk.Operation.changeTrust({
asset: ASSET,
limit: "5000", // arbitrary
}),
]),
);
};
/// Retrieves latest account info for all accounts.
function getAccounts() {
return Promise.all([
server.loadAccount(A.publicKey()),
server.loadAccount(B.publicKey()),
server.loadAccount(C.publicKey()),
]);
}
/// Enables clawback on A, and establishes trustlines from C, B -> A.
function preamble() {
return getAccounts().then(function (accounts) {
let [accountA, accountB, accountC] = accounts;
return enableClawback(accountA, A).then(
Promise.all([
establishTrustline(accountB, B),
establishTrustline(accountC, C),
]),
);
});
}
/// Helps simplify creating & signing a transaction.
function buildTx(source, signer, ops) {
var tx = new StellarSdk.TransactionBuilder(source, {
fee: 100,
networkPassphrase: StellarSdk.Networks.TESTNET,
});
ops.forEach((op) => tx.addOperation(op));
tx = tx.setTimeout(30).build();
tx.sign(signer);
return tx;
}
/// Prints the balances of a list of accounts.
function showBalances(accounts) {
accounts.forEach((acc) => {
console.log(`${acc.accountId().substring(0, 5)}: ${getBalance(acc)}`);
});
}
Ejemplo 1: Pagos
Con el código compartido de configuración fuera del camino, ahora podemos demostrar cómo funciona la recuperación para los pagos. Este ejemplo destacará cómo el emisor del activo mantiene el control sobre su activo independientemente de cómo se distribuya al mundo.
En nuestro escenario, la Cuenta A pagará a la Cuenta B con 1000 tokens de su activo personalizado; luego, B pagará a la Cuenta C 500 tokens a su vez. Finalmente, A recuperará la mitad del saldo de C, quemando 250 tokens para siempre. Vamos a profundizar en las funciones auxiliares:
- JavaScript
/// Make a payment to `toAccount` from `fromAccount` for `amount`.
function makePayment(toAccount, fromAccount, fromKey, amount) {
return server.submitTransaction(
buildTx(fromAccount, fromKey, [
sdk.Operation.payment({
destination: toAccount.accountId(),
asset: ASSET, // defined in preamble
amount: amount,
}),
]),
);
}
/// Perform a clawback by `byAccount` of `amount` from `fromAccount`.
function doClawback(byAccount, byKey, fromAccount, amount) {
return server.submitTransaction(
buildTx(byAccount, byKey, [
sdk.Operation.clawback({
from: fromAccount.accountId(),
asset: ASSET, // defined in preamble
amount: amount,
}),
]),
);
}
/// Retrieves the balance of ASSET in `account`.
function getBalance(account) {
const balances = account.balances.filter((balance) => {
return (
balance.asset_code == ASSET.code && balance.asset_issuer == ASSET.issuer
);
});
return balances.length > 0 ? balances[0].balance : "0";
}
Estos fragmentos nos ayudarán con la composición final: hacer algunos pagos para distribuir el activo al mundo y recuperar parte de él.
- JavaScript
function examplePaymentClawback() {
return getAccounts()
.then(function (accounts) {
let [accountA, accountB, accountC] = accounts;
return makePayment(accountB, accountA, A, "1000")
.then(makePayment(accountC, accountB, B, "500"))
.then(doClawback(accountA, A, accountC, "250"));
})
.then(getAccounts)
.then(showBalances);
}
preamble().then(examplePaymentClawback);
Después de ejecutar nuestro ejemplo, deberíamos ver los saldos reflejando el flujo del ejemplo:
GCIHA: 0
GDS5N: 500
GC2BK: 250
Ten en cuenta que GCIHA
(Cuenta A, el emisor) no posee ninguno de los activos a pesar de haber recuperado 250 de la Cuenta C. Esto debe dejar claro el hecho de que los activos recuperados son quemados, no transferidos.
(Puede parecer extraño que A nunca posea tokens de su activo personalizado, pero así es exactamente como funciona la emisión: creas valor donde antes no había ninguno. Enviar un activo a su cuenta emisora equivale a quemarlo, y auditar la cantidad total de un activo en existencia es una de las ventajas de distribuir adecuadamente un activo vía una cuenta de distribución, lo cual evitamos hacer aquí por brevedad del ejemplo.)
Ejemplo 2: Saldos reclamables
Los pagos directos no son la única manera de transferir activos entre cuentas: los saldos reclamables también lo hacen. Como son un mecanismo de pago separado, necesitan un mecanismo de recuperación separado.
Necesitamos algunos métodos auxiliares adicionales para empezar a trabajar eficientemente con saldos reclamables:
- JavaScript
function createClaimable(fromAccount, fromKey, toAccount, amount) {
return server.submitTransaction(
buildTx(fromAccount, fromKey, [
sdk.Operation.createClaimableBalance({
asset: ASSET,
amount: amount,
claimants: [new sdk.Claimant(toAccount.accountId())],
}),
]),
);
}
// https://developers.stellar.org/docs/encyclopedia/claimable-balance/#example
function getBalanceId(txResponse) {
const txResult = sdk.xdr.TransactionResult.fromXDR(
txResponse.result_xdr,
"base64",
);
const operationResult = txResult.result().results()[0];
let creationResult = operationResult.value().createClaimableBalanceResult();
return creationResult.balanceId().toXDR("hex");
}
function clawbackClaimable(issuerAccount, issuerKey, balanceId) {
return server.submitTransaction(
buildTx(issuerAccount, issuerKey, [
sdk.Operation.clawbackClaimableBalance({ balanceId }),
]),
);
}
Ahora, podemos completar el flujo: A paga a B, quien envía un saldo reclamable a C, quien lo recupera con la ayuda de A. (Ten en cuenta que confiamos en el helper makePayment
del ejemplo anterior.)
- JavaScript
function exampleClaimableBalanceClawback() {
return getAccounts()
.then(function (accounts) {
let [accountA, accountB, accountC] = accounts;
return makePayment(accountB, accountA, A, "1000")
.then(() => createClaimable(accountB, B, accountC, "500"))
.then((txResp) => clawbackClaimable(accountA, A, getBalanceId(txResp)));
})
.then(getAccounts)
.then(showBalances);
}
Después de ejecutar preamble().then(examplePaymentClawback)
, deberíamos ver los saldos reflejando nuestro flujo:
GCIHA: 0
GDS5N: 500
GC2BK: 0
Ejemplo 3: Habilitar recuperación selectivamente
Cuando habilitas la bandera AUTH_CLAWBACK_ENABLED_FLAG
en tu cuenta, hará que todas las trustlines futuras tengan la recuperación habilitada para cualquiera de tus activos emitidos. Esto puede no ser siempre deseable ya que podrías querer que ciertos activos se comporten como antes. Aunque podrías trabajar alrededor de esto reemitiendo activos desde una cuenta “dedicada a recuperaciones”, también puedes simplemente deshabilitar recuperaciones para ciertas trustlines eliminando la bandera TRUST_LINE_CLAWBACK_ENABLED_FLAG
en una trustline.
En el siguiente ejemplo, tendremos una cuenta (Cuenta A, como antes) que emitirá un nuevo activo y lo distribuirá a una segunda cuenta (Cuenta B). Luego, demostraremos cómo A recupera algunos de los activos de B, luego elimina la trustline y ya no puede recuperar el activo.
Primero, preparemos las cuentas (ten en cuenta que aquí confiamos en funciones auxiliares definidas en los ejemplos anteriores):
- JavaScript
function getAccounts() {
return Promise.all([
server.loadAccount(A.publicKey()),
server.loadAccount(B.publicKey()),
]);
}
function preambleRedux() {
return getAccounts().then((accounts) => {
return enableClawback(accounts[0], A).then(() =>
establishTrustline(accounts[1], B),
);
});
}
Ahora, distribuiremos parte de nuestro activo a la Cuenta B, solo para recuperarlo. Luego, eliminaremos la bandera de la trustline y mostraremos que no es posible realizar otra recuperación:
- JavaScript
function disableClawback(issuerAccount, issuerKeys, forTrustor) {
return server.submitTransaction(
buildTx(issuerAccount, issuerKeys, [
sdk.Operation.setTrustLineFlags({
trustor: forTrustor.accountId(),
asset: ASSET, // defined in the (original) preamble
flags: {
clawbackEnabled: false,
},
}),
]),
);
}
function exampleSelectiveClawback() {
return getAccounts()
.then((accounts) => {
let [accountA, accountB] = accounts;
return makePayment(accountB, accountA, A, "1000")
.then(getAccounts)
.then(showBalances)
.then(() => doClawback(accountA, A, accountB, "500"))
.then(getAccounts)
.then(showBalances)
.then(() => disableClawback(accountA, A, accountB))
.then(() => doClawback(accountA, A, accountB, "500"))
.catch((err) => {
if (err.response && err.response.data) {
// Note that this is a *very* specific way to check for an error, and
// you should probably never do it this way.
// We do this here to demonstrate that the clawback error *does*
// occur as expected.
const opErrors = err.response.data.extras.result_codes.operations;
if (
opErrors &&
opErrors.length > 0 &&
opErrors[0] === "op_not_clawback_enabled"
) {
console.info("Clawback failed, as expected!");
} else {
console.error(
"Uh-oh, some other failure occurred:",
err.response.data.extras,
);
}
} else {
console.error("Uh-oh, unknown failure:", err);
}
});
})
.then(getAccounts)
.then(showBalances);
}
Ejecuta el ejemplo (por ejemplo, via preambleRedux().then(exampleSelectiveClawback)
) y observa su resultado:
GCIHA: 0
GDS5N: 1000
GCIHA: 0
GDS5N: 500
Clawback failed, as expected!
GCIHA: 0
GDS5N: 500
Guías en esta categoría:
📄️ Crear una cuenta
Aprende sobre cómo crear cuentas Stellar, pares de llaves, financiamiento y conceptos básicos de las cuentas.
📄️ Enviar y recibir pagos
Aprende a enviar pagos y estar atento a los pagos recibidos en la red Stellar.
📄️ Cuentas canalizadas
Crea cuentas canalizadas para enviar transacciones a la red a una alta velocidad.
📄️ Saldos reclamables
Divide un pago en dos partes creando un saldo reclamable.
📄️ Recuperaciones
Usa las recuperaciones para quemar una cantidad específica de un activo habilitado para recuperación desde una trustline o un saldo reclamable.
📄️ Transacciones de suplemento de tarifa
Usa transacciones fee-bump para pagar las comisiones de transacción en nombre de otra cuenta sin volver a firmar la transacción.
📄️ Reservas patrocinadas
Utiliza las reservas patrocinadas para pagar las reservas base en nombre de otra cuenta.
📄️ Pagos con rutas
Enviar un pago donde el activo recibido sea diferente del activo enviado.
📄️ Cuentas agrupadas: cuentas muxed y memos
Usa cuentas muxed para diferenciar entre cuentas individuales dentro de una cuenta agrupada.
📄️ Instalar y desplegar un contrato inteligente con código
Instalar y desplegar un contrato inteligente con código.
📄️ Instalar WebAssembly (Wasm) bytecode usando código
Instala el Wasm del contrato usando js-stellar-sdk.
📄️ Invocar una función de contrato en una transacción Stellar utilizando SDKs
Usa el Stellar SDK para crear, simular y ensamblar una transacción.
📄️ guía del método RPC simulateTransaction
Guía de ejemplos y tutoriales de simulateTransaction.
📄️ Enviar una transacción a Stellar RPC utilizando el SDK de JavaScript
Utiliza un mecanismo de repetición para enviar una transacción al RPC.