Reservas patrocinadas
Las reservas patrocinadas fueron introducidas en CAP-0033 y permiten que una cuenta (cuenta patrocinadora) pague las reservas base para otra cuenta (cuenta patrocinada). Mientras exista esta relación, los requerimientos de reservas base que normalmente se acumularían en la cuenta patrocinada ahora se acumulan en la cuenta patrocinadora.
Las operaciones Begin Sponsoring Future Reserves y End Sponsoring Future Reserves deben aparecer en la transacción de patrocinio, garantizando que ambas cuentas estén de acuerdo con el patrocinio.
Se puede patrocinar cualquier cosa que aumente el saldo mínimo (creación de cuenta, ofertas, trustlines, entradas de datos, signatarios, saldos reclamables).
Para obtener información sobre reservas base, consulta nuestra sección sobre Lumens.
Utiliza el Calculador de Patrocinio de Billeteras Stellar para estimar los requerimientos de XLM para billeteras que busquen usar reservas patrocinadas y transacciones con fee-bump para cubrir la creación de cuentas, tarifas de transacción, trustlines, y más.
Operaciones de reservas patrocinadas
Iniciar y finalizar patrocinio
Para crear una reserva patrocinada, debes usar una transacción sándwich que incluya tres operaciones.
- La primera operación: Begin Sponsoring Future Reserves inicia el patrocinio y requiere la firma de la cuenta patrocinadora.
- La segunda operación: especifica qué se está patrocinando.
- La tercera operación: End Sponsoring Future Reserves, permite que la cuenta patrocinada acepte el patrocinio y requiere la firma de ésta.
Begin Sponsoring Future Reserves establece la relación is-sponsoring-future-reserves-for donde la cuenta patrocinadora es la cuenta fuente de la operación. La cuenta especificada en la operación es la cuenta patrocinada.
End Sponsoring Future Reserves termina la relación actual de is-sponsoring-future-reserves-for para la cuenta fuente de la operación.
Al final de cualquier transacción, no debe haber relaciones is-sponsoring-future-reserves-for activas, razón por la cual estas dos operaciones deben usarse juntas en una única transacción.
Consulta los detalles de la operación en nuestra sección Lista de Operaciones.
Revocar patrocinio
Permite a la cuenta patrocinadora eliminar o transferir patrocinios de ledgerEntries y signatarios existentes. Si el ledgerEntry o signatario no está patrocinado, el propietario del ledgerEntry o signatario puede establecer un patrocinio si es el beneficiario de una relación is-sponsoring-future-reserves-for.
Lógica de la operación
- Entrada/signatario está patrocinado
- La cuenta fuente es actualmente el beneficiario de una relación is-sponsoring-future-reserves-for
- Transfiere el patrocinio de la entrada/signatario desde la cuenta fuente a la cuenta que es el origen de is-sponsoring-future-reserves-for
- La cuenta fuente no es el beneficiario de una relación is-sponsoring-future-reserves-for
- Elimina el patrocinio de la entrada/signatario
- La cuenta fuente es actualmente el beneficiario de una relación is-sponsoring-future-reserves-for
- Entrada/signatario no está patrocinado
- La cuenta fuente es actualmente el beneficiario de una relación is-sponsoring-future-reserves-for
- Establece un patrocinio entre la entrada/signatario y la cuenta que es el origen de is-sponsoring-future-reserves-for
- La cuenta fuente no es el beneficiario de una relación is-sponsoring-future-reserves-for
- No-Op
- La cuenta fuente es actualmente el beneficiario de una relación is-sponsoring-future-reserves-for
Consulta los detalles de la operación en nuestra sección Lista de Operaciones.
Efecto en el saldo mínimo
Una vez que se introducen los patrocinios, el cálculo del saldo mínimo es: (2 reservas base + numSubEntries
+ numSponsoring
- numSponsored
) * baseReserve
+ liabilities.selling
.
Cuando la cuenta A está patrocinando reservas futuras para la cuenta B, cualquier requisito de reserva que normalmente se acumularía en B se acumula en A, mostrado en numSponsoring
. El hecho de que estas reservas sean proporcionadas por otra cuenta se reflejará en B en numSponsored
, lo que cancelará el aumento de numSubEntries
, manteniendo el saldo mínimo sin cambios para B.
Cuando una entrada patrocinada o subentrada es eliminada, numSponsoring
disminuye en la cuenta patrocinadora y numSponsored
disminuye en la cuenta patrocinada.
Para saber más sobre los requisitos de saldo mínimo, consulta nuestra sección sobre Lumens.
Efecto en los saldos reclamables
Todos los saldos reclamables están patrocinados mediante lógica incorporada en las operaciones de saldo reclamable. La cuenta que crea el saldo reclamable paga la reserva base para obtener el saldo en el ledger. Cuando el saldo reclamable es reclamado por el(los) reclamante(s), el saldo reclamable se elimina del ledger, y la cuenta que lo creó recupera la reserva base.
Lee más sobre saldos reclamables en nuestra Entrada en la Enciclopedia de Saldos Reclamables.
Ejemplos
Cada uno de los siguientes ejemplos se basa en el anterior, haciendo referencia a variables de fragmentos previos. Los siguientes ejemplos demostrarán:
- Patrocinar la creación de un trustline para otra cuenta
- Patrocinar dos trustlines para una cuenta a través de dos patrocinadores diferentes
- Transferir la responsabilidad del patrocinio de una cuenta a otra
- Revocar el patrocinio por completo por parte de una cuenta
Para brevedad en los ejemplos de Golang, asumiremos la existencia de un método SignAndSend
(...). Un método (definido más abajo) que crea y envía una transacción con los parámetros correctos y una verificación básica de errores.
Preámbulo
Comenzaremos incluyendo el código básico para la creación de cuenta y activos.
- JavaScript
- Go
const sdk = require("stellar-sdk");
const http = require("got");
let server = new sdk.Server("https://horizon-testnet.stellar.org");
async function main() {
// Create & fund the new accounts.
let keypairs = [
sdk.Keypair.random(),
sdk.Keypair.random(),
sdk.Keypair.random(),
];
for (const keypair of keypairs) {
const base = "https://friendbot.stellar.org/?";
const path = base + "addr=" + encodeURIComponent(keypair.publicKey());
console.log(`Funding:\n ${keypair.secret()}\n ${keypair.publicKey()}`);
// We use the "got" library here to do the HTTP request synchronously, but
// you can obviously use any method you'd like for this.
const response = await http(path).catch(function (error) {
console.error(" failed:", error.response.body);
});
}
// Arbitrary assets to sponsor trustlines for. Let's assume they make sense.
let S1 = keypairs[0], A = keypairs[1], S2 = keypairs[2];
let assets = [
new sdk.Asset("ABCD", S1.publicKey()),
new sdk.Asset("EFGH", S1.publicKey()),
new sdk.Asset("IJKL", S2.publicKey()),
];
// ...
package main
import (
"fmt"
"net/http"
sdk "github.com/stellar/go/clients/horizonclient"
"github.com/stellar/go/keypair"
"github.com/stellar/go/network"
protocol "github.com/stellar/go/protocols/horizon"
"github.com/stellar/go/txnbuild"
)
func main() {
client := sdk.DefaultTestNetClient
// Both S1 and S2 will be sponsors for A at various points in time.
S1, A, S2 := keypair.MustRandom(), keypair.MustRandom(), keypair.MustRandom()
addressA := A.Address()
for _, pair := range []*keypair.Full{S1, A, S2} {
resp, err := http.Get("https://friendbot.stellar.org/?addr=" + pair.Address())
check(err)
resp.Body.Close()
fmt.Println("Funded", pair.Address())
}
// Load the corresponding account for both A and C.
s1Account, err := client.AccountDetail(sdk.AccountRequest{AccountID: S1.Address()})
check(err)
aAccount, err := client.AccountDetail(sdk.AccountRequest{AccountID: addressA})
check(err)
s2Account, err := client.AccountDetail(sdk.AccountRequest{AccountID: S2.Address()})
check(err)
// Arbitrary assets to sponsor trustlines for. Let's assume they make sense.
assets := []txnbuild.CreditAsset{
txnbuild.CreditAsset{Code: "ABCD", Issuer: S1.Address()},
txnbuild.CreditAsset{Code: "EFGH", Issuer: S1.Address()},
txnbuild.CreditAsset{Code: "IJKL", Issuer: S2.Address()},
}
// ...
1. Patrocinando trustlines
Ahora, patrocinemos trustlines para la Cuenta A. Observa cómo la operación CHANGE_TRUST
está entre las operaciones de inicio y fin del patrocinio, y que todas las cuentas relevantes deben firmar la transacción.
- JavaScript
- Go
//
// 1. S1 will sponsor a trustline for Account A.
//
let s1Account = await server.loadAccount(S1.publicKey()).catch(accountFail);
let tx = new sdk.TransactionBuilder(s1Account, { fee: sdk.BASE_FEE })
.addOperation(
sdk.Operation.beginSponsoringFutureReserves({
sponsoredId: A.publicKey(),
}),
)
.addOperation(
sdk.Operation.changeTrust({
source: A.publicKey(),
asset: assets[0],
limit: "1000", // This limit can vary according with your application;
// if left empty, it defaults to the max limit.
}),
)
.addOperation(
sdk.Operation.endSponsoringFutureReserves({
source: A.publicKey(),
}),
)
.setNetworkPassphrase(sdk.Networks.TESTNET)
.setTimeout(180)
.build();
// Note that while either can submit this transaction, both must sign it.
tx.sign(S1, A);
let txResponse = await server.submitTransaction(tx).catch(txCheck);
if (!txResponse) {
return;
}
console.log("Sponsored a trustline of", A.publicKey());
//
// 2. Both S1 and S2 sponsor trustlines for Account A for different assets.
//
let aAccount = await server.loadAccount(A.publicKey()).catch(accountFail);
let tx = new sdk.TransactionBuilder(aAccount, { fee: sdk.BASE_FEE })
.addOperation(
sdk.Operation.beginSponsoringFutureReserves({
source: S1.publicKey(),
sponsoredId: A.publicKey(),
}),
)
.addOperation(
sdk.Operation.changeTrust({
asset: assets[1],
limit: "5000",
}),
)
.addOperation(sdk.Operation.endSponsoringFutureReserves())
.addOperation(
sdk.Operation.beginSponsoringFutureReserves({
source: S2.publicKey(),
sponsoredId: A.publicKey(),
}),
)
.addOperation(
sdk.Operation.changeTrust({
asset: assets[2],
limit: "2500",
}),
)
.addOperation(sdk.Operation.endSponsoringFutureReserves())
.setNetworkPassphrase(sdk.Networks.TESTNET)
.setTimeout(180)
.build();
// Note that all 3 accounts must approve/sign this transaction.
tx.sign(S1, S2, A);
let txResponse = await server.submitTransaction(tx).catch(txCheck);
if (!txResponse) {
return;
}
console.log("Sponsored two trustlines of", A.publicKey());
//
// 1. S1 will sponsor a trustline for Account A.
//
sponsorTrustline := []txnbuild.Operation{
&txnbuild.BeginSponsoringFutureReserves{
SourceAccount: s1Account.AccountID,
SponsoredID: addressA,
},
&txnbuild.ChangeTrust{
Line: &assets[0],
Limit: txnbuild.MaxTrustlineLimit,
},
&txnbuild.EndSponsoringFutureReserves{},
}
// Note that while A can submit this transaction, both sign it.
SignAndSend(client, aAccount.AccountID, []*keypair.Full{S1, A}, sponsorTrustline...)
fmt.Println("Sponsored a trustline of", A.Address())
//
// 2. Both S1 and S2 sponsor trustlines for Account A for different assets.
//
sponsorTrustline = []txnbuild.Operation{
&txnbuild.BeginSponsoringFutureReserves{
SourceAccount: s1Account.AccountID,
SponsoredID: addressA,
},
&txnbuild.ChangeTrust{
Line: &assets[1],
Limit: txnbuild.MaxTrustlineLimit,
},
&txnbuild.EndSponsoringFutureReserves{},
&txnbuild.BeginSponsoringFutureReserves{
SourceAccount: s2Account.AccountID,
SponsoredID: addressA,
},
&txnbuild.ChangeTrust{
Line: &assets[2],
Limit: txnbuild.MaxTrustlineLimit,
},
&txnbuild.EndSponsoringFutureReserves{},
}
// Note that all 3 accounts must approve/sign this transaction.
SignAndSend(client, aAccount.AccountID, []*keypair.Full{S1, S2, A}, sponsorTrustline...)
fmt.Println("Sponsored two trustlines of", A.Address())
2. Transferir patrocinio
Supongamos que ahora el Firmante 1 quiere transferir la responsabilidad de patrocinar las reservas para el trustline al Patrocinador 2. Esto se logra intercalando la transferencia entre las operaciones BEGIN/END_SPONSORING_FUTURE_RESERVES
. Ambos participantes deben firmar la transacción, aunque cualquiera de ellos puede enviarla.
Una forma intuitiva de pensar en una transferencia de patrocinio es que el acto mismo del patrocinio está siendo patrocinado por una nueva cuenta. Es decir, el nuevo patrocinador asume las responsabilidades del antiguo patrocinador patrocinando una revocación.
- JavaScript
- Go
//
// 3. Transfer sponsorship of B's second trustline from S1 to S2.
//
let tx = new sdk.TransactionBuilder(s1Account, { fee: sdk.BASE_FEE })
.addOperation(
sdk.Operation.beginSponsoringFutureReserves({
source: S2.publicKey(),
sponsoredId: S1.publicKey(),
}),
)
.addOperation(
sdk.Operation.revokeTrustlineSponsorship({
account: A.publicKey(),
asset: assets[1],
}),
)
.addOperation(sdk.Operation.endSponsoringFutureReserves())
.setNetworkPassphrase(sdk.Networks.TESTNET)
.setTimeout(180)
.build();
// Notice that while the old sponsor *sends* the transaction, both sponsors
// must *approve* the transfer.
tx.sign(S1, S2);
let txResponse = await server.submitTransaction(tx).catch(txCheck);
if (!txResponse) {
return;
}
console.log("Transferred sponsorship for", A.publicKey());
//
// 3. Transfer sponsorship of B's second trustline from S1 to S2.
//
transferOps := []txnbuild.Operation{
&txnbuild.BeginSponsoringFutureReserves{
SourceAccount: s2Account.AccountID,
SponsoredID: S1.Address(),
},
&txnbuild.RevokeSponsorship{
SponsorshipType: txnbuild.RevokeSponsorshipTypeTrustLine,
Account: &addressA,
TrustLine: &txnbuild.TrustLineID{
Account: addressA,
Asset: assets[1],
},
},
&txnbuild.EndSponsoringFutureReserves{},
}
// Notice that while the old sponsor *sends* the transaction (in this case),
// both sponsors must *approve* the transfer.
SignAndSend(client, s1Account.AccountID, []*keypair.Full{S1, S2}, transferOps...)
fmt.Println("Transferred sponsorship for", A.Address())
En este punto, el Firmante 1 solo está patrocinando el primer activo (codificado arbitrariamente como ABCD), mientras que el Firmante 2 está patrocinando los otros dos activos. (Recuerda que inicialmente el Firmante 1 también patrocinaba EFGH).
3. Revocación de patrocinio
Finalmente, podemos demostrar la revocación completa de patrocinios. A continuación, el Firmante 2 se elimina a sí mismo de toda responsabilidad sobre los dos trustlines de activos. Observa que la Cuenta A no está involucrada en absoluto, ya que la revocación debería poder realizarse únicamente a discreción del patrocinador.
- JavaScript
- Go
//
// 4. S2 revokes sponsorship of B's trustlines entirely.
//
let s2Account = await server.loadAccount(S2.publicKey()).catch(accountFail);
let tx = new sdk.TransactionBuilder(s2Account, {fee: sdk.BASE_FEE})
.addOperation(sdk.Operation.revokeTrustlineSponsorship({
account: A.publicKey(),
asset: assets[1],
}))
.addOperation(sdk.Operation.revokeTrustlineSponsorship({
account: A.publicKey(),
asset: assets[2],
}))
.setNetworkPassphrase(sdk.Networks.TESTNET)
.setTimeout(180)
.build();
tx.sign(S2);
let txResponse = await server.submitTransaction(tx).catch(txCheck);
if (!txResponse) { return; }
console.log("Revoked sponsorship for", A.publicKey());
} // ends main()
//
// 4. S2 revokes sponsorship of B's trustlines entirely.
//
revokeOps := []txnbuild.Operation{
&txnbuild.RevokeSponsorship{
SponsorshipType: txnbuild.RevokeSponsorshipTypeTrustLine,
Account: &addressA,
TrustLine: &txnbuild.TrustLineID{
Account: addressA,
Asset: assets[1],
},
},
&txnbuild.RevokeSponsorship{
SponsorshipType: txnbuild.RevokeSponsorshipTypeTrustLine,
Account: &addressA,
TrustLine: &txnbuild.TrustLineID{
Account: addressA,
Asset: assets[2],
},
},
}
SignAndSend(client, s2Account.AccountID, []*keypair.Full{S2}, revokeOps...)
fmt.Println("Revoked sponsorship for", A.Address())
} // ends main()
Cuentas fuente de patrocinio
En cuanto a los campos SourceAccount del sándwich de patrocinio, es importante referirse a la sabiduría de CAP-33:
Esta relación es iniciada por
BeginSponsoringFutureReservesOp
, donde la cuenta patrocinadora es la cuenta fuente, y termina conEndSponsoringFutureReserveOp
, donde la cuenta patrocinada es la cuenta fuente.
Como la cuenta fuente se establece por defecto como el remitente de la transacción si se omite, este campo siempre debe establecerse para el Begin
o el End
.
Por ejemplo, la siguiente es una expresión idéntica al ejemplo previo de Golang de patrocinar un trustline, solo que enviada por el patrocinador (Patrocinador 1) en lugar de por la cuenta patrocinada (Cuenta A). Observa las diferencias en dónde se establece SourceAccount
:
- Go
sponsorTrustline := []txnbuild.Operation{
&txnbuild.BeginSponsoringFutureReserves{
SponsoredID: addressA,
},
&txnbuild.ChangeTrust{
SourceAccount: aAccount.AccountID,
Line: &assets[0],
Limit: txnbuild.MaxTrustlineLimit,
},
&txnbuild.EndSponsoringFutureReserves{
SourceAccount: aAccount.AccountID,
},
}
// Again, both participants must still sign the transaction: the sponsored
// account must consent to the sponsorship.
SignAndSend(client, s1Account.AccountID, []*keypair.Full{S1, A}, sponsorTrustline...)
Otros ejemplos
Si deseas otros ejemplos o quieres ver un desglose más genérico en pseudo-código de estos escenarios de patrocinio, puedes referirte directamente a CAP-0033.
Nota al pie
Para los ejemplos anteriores, una implementación de SignAndSend (Golang) y un código de verificación de errores rudimentario (en todos los lenguajes) podría ser algo así:
- JavaScript
- Go
function txCheck(err) {
console.error("Transaction submission failed:", err);
if (err.response != null && err.response.data != null) {
console.error("More details:", err.response.data.extras);
} else {
console.error("Unknown reason:", err);
}
}
function accountFail(err) {
console.error(" Failed to load account:", err.response.body);
}
// Builds a transaction containing `operations...`, signed (by `signers`), and
// submitted using the given `client` on behalf of `account`.
func SignAndSend(
client *sdk.Client,
account txnbuild.Account,
signers []*keypair.Full,
operations ...txnbuild.Operation,
) protocol.Transaction {
// Build, sign, and submit the transaction
tx, err := txnbuild.NewTransaction(
txnbuild.TransactionParams{
SourceAccount: account,
IncrementSequenceNum: true,
BaseFee: txnbuild.MinBaseFee,
Timebounds: txnbuild.NewInfiniteTimeout(),
Operations: operations,
},
)
check(err)
for _, signer := range signers {
tx, err = tx.Sign(network.TestNetworkPassphrase, signer)
check(err)
}
txResp, err := client.SubmitTransaction(tx)
if err != nil {
if prob := sdk.GetError(err); prob != nil {
fmt.Printf(" problem: %s\n", prob.Problem.Detail)
fmt.Printf(" extras: %s\n", prob.Problem.Extras["result_codes"])
}
check(err)
}
return txResp
}
func check(err error) {
if err != nil {
panic(err)
}
}
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.