LogoLogo
  • Welcome
  • Where to Start
    • Introduction
    • list of Operations
    • Bantu Stack
  • Tutorials
    • Create Account
    • Send and Receive Payments
    • Follow Received Payments
    • Securing Web-based Projects
  • Issue Assets
    • Overview
    • Anatomy of an Asset
    • Issue an Asset
    • Publish Information About an Asset
    • Control Access to an Asset
  • Building Apps
    • Overview
    • Project Setup
    • Key Management Basics
    • Create a Basic Wallet
    • Make XBN Payments
    • Handle Custom Assets
  • Run a Core Node
    • index
    • running-node
    • network-upgrades
    • tier-1-orgs
    • installation
    • prerequisites
    • configuring
    • publishing history archives
    • commands
    • monitoring
  • Run API Server
    • prerequisites
    • quickstart
    • index
    • installing
    • monitoring
    • Running
    • configuring
  • Software and SDKs
    • index
  • Glossary
    • scp
    • Claimable Balance
    • XDR
    • Assets
    • BUDS
    • Inflation
    • Miscellaneous Core Objects
    • Testnet
    • Accounts
    • Network Passphrase
    • Ledger
    • Versioning
    • Sponsored Reserves
    • Operations
    • Decentralized Exchange
    • Fees
    • XBN Supply
    • Fee Bumps
    • Channels
    • Transactions
    • Minimum Balance
    • Multisig
  • Docs
    • Index
  • API
    • Introduction
      • Index
      • Response Format
      • Streaming
      • Rate Limiting
      • XDR
      • Pagination
        • Index
        • Page Arguments
    • Resources
      • Untitled
      • Overview
      • Ledgers
        • index
        • Object
        • Single
        • Transactions
        • Operations
        • Payments
        • Effects
        • List
      • Transactions
        • Index
        • Object
        • Single
        • Operations
        • Effects
        • List
      • Operations
        • Index
        • Object
          • Index
          • Create Account
          • Payment
          • Path Payment Strict Send
          • Path Payment Strict Receive
          • Sell Offer
          • Buy Offer
          • Passive Sell Offer
          • Set Options
          • Change Trust
          • Allow Trust
          • Account Merge
          • Manage Data
          • Bump Sequence
          • Create Claimable Balance
          • Claim Claimable Balance
          • Begin Sponsoring Future Reserves
          • End Sponsoring Future Reserves
          • Revoke Sponsorship
        • Single
        • Effects
        • List
        • List Payments
      • Effects
        • Index
        • Types
        • List
      • Accounts
        • Index
        • Object
        • Transactions
        • List
        • Single
        • Operations
        • Payments
        • Effects
        • Offers
        • Trades
        • Data
      • Offers
        • Index
        • Object
        • Single
        • List
      • Trades
        • Index
        • Object
        • List
      • Assets
        • Index
        • Object
        • List
      • Claimable Balances
        • Index
        • Object
        • Single
        • List
    • Aggregations
      • Index
      • Order Books
        • Index
        • object
        • Single
      • Paths
        • Index
        • Object
        • Strict Receive
        • Strict Send
      • Trade Aggregations
        • Index
        • Object
        • List
      • Fee Stats
        • Index
        • Object
        • Single
    • Errors
      • Index
      • Response
      • HTTP Status Codes
        • Index
        • Standard
        • Expansion Specific
          • Index
          • Transaction Failed
          • Transaction Malformed
          • Before History
          • Stale History
          • Timeout
      • Result Codes
        • Index
        • Transactions
        • Operations
        • Operation Specific
          • Index
          • Create Account
          • Payment
          • Path Payment Strict Receive
          • Path Payment Strict Send
          • Manage Sell Offer
          • Manage Buy Offer
          • Create Passive Sell Offer
          • Set Options
          • Change Trust
          • Allow Trust
          • Account Merge
          • Manage Data
          • Bump Sequence
Powered by GitBook
On this page
  • Sponsorship effect on Minimum Balance
  • What can be sponsored?
  • Claimable Balances
  • Relevant operations
  • Begin and end sponsorships
  • Revoke Sponsorship
  • Examples
  • Preamble
  • Sponsoring Trustlines
  • Transferring Sponsorship
  • Sponsorship Revocation
  • Sponsorship Source Accounts
  • Other Examples
  • Footnote

Was this helpful?

  1. Glossary

Sponsored Reserves

PreviousVersioningNextOperations

Last updated 4 years ago

Was this helpful?

Protocol 15 introduces operations that allow an account to pay the base reserves for another account. This is done by using the and operations.

The sponsoring account establishes the is-sponsoring-future-reserves-for relationship, and the sponsored account terminates it. While this relationship exists, reserve requirements that would normally accumulate on the sponsored account will now accumulate on the sponsoring account. Both operations must appear in a single transaction, which guarantees that both the sponsoring and sponsored accounts agree to every sponsorship.

Sponsorship effect on Minimum Balance

The calculation once sponsorships are introduced becomes (2 + numSubEntries + numSponsoring - numSponsored) * baseReserve + liabilities.selling.

When account A is-sponsoring-future-reserves-for account B, any reserve requirements that would normally accumulate on B will instead accumulate on A as reflected in numSponsoring. The fact that these reserves are being provided by another account will be reflected on B in numSponsored, which will cancel out the increase in numSubEntries, keeping the minimum balance unchanged for B.

When a sponsored ledger entry or sub-entry is removed, numSponsoring is decreased on the sponsoring account and numSponsored is decreased on the sponsored account.

What can be sponsored?

Anything that increases the minimum balance can be sponsored (Accounts, Offers, Trustlines, AccountData, and Signers).

Claimable Balances

are unique in that they must be sponsored. They are not sub-entries of an account, so the sponsoring account uses the sponsorship mechanism to pay the base reserve by increasing numSponsoring. The sponsorship logic is handled through the Claimable Balance operations, so the use of sponsorships is transparent to the user.

Relevant operations

Begin and end sponsorships

will establish the is-sponsoring-future-reserves-for relationship where the sponsoring account is the source account of the operation, and the account specified in the operation is the sponsored account.

At the end of any transaction, there must be no ongoing is-sponsoring-future-reserves-for relationships. This is why these two operations must be used together in a single transaction.

Revoke Sponsorship

Operation logic

  • Entry/signer is sponsored

    • Source account is currently the beneficiary of a is-sponsoring-future-reserves-for relationship

      • Transfer sponsorship of entry/signer from source account to the account that is-sponsoring-future-reserves-for source account

    • Source account is not the beneficiary of a is-sponsoring-future-reserves-for relationship

      • Remove the sponsorship from the entry/signer

  • Entry/signer is not sponsored

    • Source account is currently the beneficiary of a is-sponsoring-future-reserves-for relationship

      • Establish sponsorship between entry/signer and the account that is-sponsoring-future-reserves-for source account

    • Source account is not the beneficiary of a is-sponsoring-future-reserves-for relationship

      • No-Op

Errors

Examples

Each example builds on itself, referencing variables from previous snippets. We'll demonstrate a few different things you can do with sponsoring:

Preamble

We'll start by including the boilerplate of account and asset creation.

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

let server = new sdk.Server("https://expansion-testnet.bantu.network");

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.bantu.network/?";
    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.bantu.network/?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()},
    }

    // ...

Sponsoring Trustlines

Now, let's sponsor trustlines for Account A. Notice how the CHANGE_TRUST operation is sandwiched between the begin and end sponsoring operations and that all relevant accounts need to sign the transaction.

  //
  // 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,
            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, []*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,
            SponsoredID:   addressA,
        },
        &txnbuild.ChangeTrust{
            Line:          &assets[1],
            Limit:         txnbuild.MaxTrustlineLimit,
        },
        &txnbuild.EndSponsoringFutureReserves{},

        &txnbuild.BeginSponsoringFutureReserves{
            SourceAccount: &s2Account,
            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, []*keypair.Full{S1, S2, A}, sponsorTrustline...)
    fmt.Println("Sponsored two trustlines of", A.Address())

Transferring Sponsorship

Suppose that now Signer 1 wants to transfer responsibility of sponsoring reserves for the trustline to Sponsor 2. This is accomplished by sandwiching the transfer between the BEGIN/END_SPONSORING_FUTURE_RESERVES operations. Both of the participants must sign the transaction, though either can submit it.

An intuitive way to think of a sponsorship transfer is that the very act of sponsorship is being sponsored by a new account. That is, the new sponsor takes over the responsibilities of the old sponsor by sponsoring a revocation.

  //
  // 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,
            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, []*keypair.Full{S1, S2}, transferOps...)
    fmt.Println("Transferred sponsorship for", A.Address())

Sponsorship Revocation

Finally, we can demonstrate complete revocation of sponsorships. Below, Signer 2 removes themselves from all responsibility over the two asset trustlines. Notice that Account A is not involved at all, since revocation should be performable purely at the sponsor's discretion.

  //
  // 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, []*keypair.Full{S2}, revokeOps...)
    fmt.Println("Revoked sponsorship for", A.Address())
} // ends main()

Sponsorship Source Accounts

This relation is initiated by BeginSponsoringFutureReservesOp, where the sponsoring account is the source account, and is terminated by EndSponsoringFutureReserveOp, where the sponsored account is the source account.

Since the source account defaults to the transaction submitter when omitted, this field needs always needs to be set for either the Begin or the End.

    sponsorTrustline := []txnbuild.Operation{
        &txnbuild.BeginSponsoringFutureReserves{
            SponsoredID: addressA,
        },
        &txnbuild.ChangeTrust{
            SourceAccount: &aAccount,
            Line:          &assets[0],
            Limit:         txnbuild.MaxTrustlineLimit,
        },
        &txnbuild.EndSponsoringFutureReserves{
            SourceAccount: &aAccount,
        },
    }

    // Again, both participants must still sign the transaction: the sponsored
    // account must consent to the sponsorship.
    SignAndSend(client, &s1Account, []*keypair.Full{S1, A}, sponsorTrustline...)

Other Examples

Footnote

For the above examples, an implementation of SignAndSend (Golang) and some (very) rudimentary error checking code (all languages) might look something like this:

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

will end the current is-sponsoring-future-reserves-for relationship for the source account of the operation.

is the third and final operation relevant to sponsorships. It allows the sponsoring account to remove/transfer sponsorships of existing ledgerEntries and signers. If the ledgerEntry/signer is not sponsored, the owner of the ledgerEntry/signer can establish a sponsorship if it is the beneficiary of a is-sponsoring-future-reserves-for relationship.

See for more information about the structure of this operation object.

The logic above does not detail any of the error cases, which are specified .

of a trustline for another account.

for an account via two different sponsors.

responsibility from one account to another.

by an account entirely.

(For brevity in the Golang examples, we'll assume the existence of a SignAndSend(...) method (defined ) which creates and submits a transaction with the proper parameters and error-checking.

At this point, Signer 1 is only sponsoring the first asset (arbitrarily coded as ABCD), while Signer 2 is sponsoring the other two assets. (Recall that Signer 1 was also sponsoring EFGH.)

When it comes to the SourceAccount fields of the sponsorship sandwich, it's important to refer to the wisdom of :

For example, the following is an identical expression of the of sponsoring a trustline, just submitted by the sponsor (Sponsor 1) rather than the sponsored account (Account A). Notice the differences in where SourceAccount is set:

If you'd like other examples, or want to view a more-generic pseudocode breakdown of these sponsorship scenarios, you can refer to directly.

CAP-33
CAP-33
Sponsor creation
Sponsor two trustlines
Transfer sponsorship
Revoke sponsorship
below
initially
earlier Golang example
Minimum Balance
Claimable Balances
Begin Sponsoring Future Reserves
End Sponsoring Future Reserves
Begin Sponsoring Future Reserves
End Sponsoring Future Reserves
Revoke Sponsorship
Revoke Sponsorship
here