Claimable Balances
Claimable balances were introduced in CAP-0023 and are used to split a payment into two parts:
- Sending account creates a payment, or ClaimableBalanceEntry, using the Create Claimable Balance operation.
- Destination account(s), or claimant(s), accepts the ClaimableBalanceEntry using the Claim Claimable Balance operation.
Claimable balances allow an account to send a payment to another account that is not necessarily prepared to receive the payment. They can be used when you send a non-native asset to an account that has not yet established a trustline, which is useful for anchors onboarding new users. A trustline must be established by the claimant to the asset before it can claim the claimable balance; otherwise, the claim will result in an op_no_trust error.
Unclaimed claimable balances sit on the ledger forever, taking up space and ultimately making the network less efficient. Thus, it is best practice to put one of your own accounts as a claimant (assuming no issuer clawbacks). Then you can accept your own claimable balance if needed, freeing up space and [account reserves].
Each ClaimableBalanceEntry is a ledger entry, and each claimant in that entry increases the source account’s minimum balance by one base reserve.
Once a ClaimableBalanceEntry has been claimed, it is deleted.
Operations
Create Claimable Balance
For basic parameters, see the Create Claimable Balance entry in our List of Operations section. (/_ ⚠ Should probably be together _/)
Other Parameters
-
Claim_Predicate_Claimant: An object that holds both the destination account that can claim theClaimableBalanceEntryand aClaimPredicatethat must evaluate to true for the claim to succeed. -
ClaimPredicate: A recursive data structure that can be used to construct complex conditionals using differentClaimPredicateTypes. Below are some examples with theClaim_Predicate_prefix removed for readability. Note that the SDKs expect the Unix timestamps to be expressed in seconds.UNCONDITIONAL: Can claim at any time.BEFORE_RELATIVE_TIME(X): Can claim if the close time of the ledger including the claim is before X seconds, plus the ledger close time in which theClaimableBalanceEntrywas created.NOT( BEFORE_RELATIVE_TIME(X) ): Can claim if the close time of the ledger including the claim is at or after X seconds, plus the ledger close time in which the ClaimableBalanceEntry was created.
BEFORE_ABSOLUTE_TIME(X): Can claim if the close time of the ledger including the claim is before X (Unix timestamp).NOT( BEFORE_ABSOLUTE_TIME(X) ): Can claim if the close time of the ledger including the claim is at or after X (Unix timestamp).
AND[ NOT( BEFORE_ABSOLUTE_TIME(X) ),BEFORE_ABSOLUTE_TIME(Y) ]: Can claim between X and Y Unix timestamps (given X < Y).OR[ BEFORE_ABSOLUTE_TIME(X),NOT( BEFORE_ABSOLUTE_TIME(Y) ) ]: Can claim outside X and Y Unix timestamps (given X < Y).
-
ClaimableBalanceID: ClaimableBalanceID is a union with one possible type (CLAIMABLE_BALANCE_ID_TYPE_V0). It contains an SHA-256 hash of the OperationID for claimable balances. -
ClientBalanceID: Hex ofClaimableBalanceIDreturned after a successfulCreateClaimableBalanceoperation. TheClaimClaimableBalanceoperation uses this (with zero-padding to 72 characters) to claim theClaimableBalanceEntry.
Claim Claimable Balance
For basic parameters, see the Claim Claimable Balance entry in our List of Operations section.
This operation will load the ClaimableBalanceEntry that corresponds to the ClientBalanceID and then search for the source account of this operation in the list of claimants on the entry. If a match on the claimant is found, and the ClaimPredicate evaluates to true, then the ClaimableBalanceEntry can be claimed. The balance on the entry will be moved to the source account if there are no limit or trustline issues (for non-native assets), meaning the claimant must establish a trustline to the asset before claiming it.
Clawback Claimable Balance
This operation claws back a claimable balance, returning the asset to the issuer account, burning it. You must claw back the entire claimable balance, not just part of it. Once a claimable balance has been claimed, use the regular clawback operation to claw it back.
You clawback a claimable balances with its ClientBalanceID.
Learn more about clawbacks in our Clawback Guide.
Example
The below code demonstrates how an account (Account ) creates a ClaimableBalanceEntry with two claimants: (itself) and Account (another recipient).
Setup
Each of these accounts can only claim the balance under unique conditions. has a full minute to claim the balance before can reclaim the balance back for itself.
The reclaim logic acts as a safety net if none of the predicates can be fulfilled.
Go Helper Functions
- Go
func fundAccount(rpcClient *client.Client, address string) error {
ctx := context.Background()
// Use GetNetwork method from client
networkResp, err := rpcClient.GetNetwork(ctx)
if err != nil {
return err
}
if networkResp.FriendbotURL != "" {
friendbotURL := networkResp.FriendbotURL + "?addr=" + url.QueryEscape(address)
resp, err := http.Post(friendbotURL, "application/x-www-form-urlencoded", nil)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return fmt.Errorf("friendbot failed with status: %d", resp.StatusCode)
}
return nil
}
return fmt.Errorf("friendbot not configured for network - %s", networkResp.Passphrase)
}
func panicIf(err error) {
if err != nil {
log.Fatal(err)
}
}
- Python
- JavaScript
- Java
- Go
import time
from stellar_sdk.xdr import TransactionResult, OperationType
from stellar_sdk.exceptions import NotFoundError, BadResponseError, BadRequestError
from stellar_sdk import (
Keypair,
Network,
Server,
TransactionBuilder,
Transaction,
Asset,
Operation,
Claimant,
ClaimPredicate,
CreateClaimableBalance,
ClaimClaimableBalance
)
var txResult xdr.TransactionResult
err := xdr.SafeUnmarshalBase64(resp.ResultXDR, &txResult)
if err != nil {
return "", err
}
if results, ok := txResult.OperationResults(); ok && len(results) > 0 {
operationResult := results[0].MustTr().CreateClaimableBalanceResult
return xdr.MarshalHex(operationResult.BalanceId)
}
try:
aAccount = server.load_account(A.public_key)
except NotFoundError:
raise Exception(f"Failed to load account")
# Create a claimable balance with our two above-described conditions.
bCanClaim = ClaimPredicate.predicate_before_relative_time(60)
soon = int(time.time() + 60)
aCanClaim = ClaimPredicate.predicate_not(
ClaimPredicate.predicate_before_absolute_time(
soon
)
)
# Create the operation and submit it in a transaction.
claimableBalanceEntry = CreateClaimableBalance(
asset = Asset.native(),
amount = "64",
claimants = [
Claimant(
destination = B.public_key,
predicate = bCanClaim
),
Claimant(
destination = A.public_key,
predicate = aCanClaim
)
]
)
transaction = (
TransactionBuilder (
source_account = aAccount,
network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE,
base_fee = Network.MIN_BASE_FEE
)
.append_operation(claimableBalanceEntry)
.set_timeout(180)
.build()
)
transaction.sign(A)
try:
txResponse = server.submit_transaction(transaction)
print("Claimable balance created!")
except (BadRequestError, BadResponseError) as err:
print(f"Tx submission failed: {err}")
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",
);
let aAccount;
try {
aAccount = await server.loadAccount(A.publicKey());
} catch (err) {
console.error(`Failed to load ${A.publicKey()}: ${err}`);
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()),
);
let claimableBalanceEntry = sdk.Operation.createClaimableBalance({
claimants: [
new sdk.Claimant(B.publicKey(), bCanClaim),
new sdk.Claimant(A.publicKey(), aCanReclaim),
],
asset: sdk.Asset.native(),
amount: "64",
});
let tx = new sdk.TransactionBuilder(aAccount, { fee: sdk.BASE_FEE })
.addOperation(claimableBalanceEntry)
.setNetworkPassphrase(sdk.Networks.TESTNET)
.setTimeout(180)
.build();
tx.sign(A);
try {
let txResponse = await server.submitTransaction(tx);
console.log("Claimable balance created!");
} catch (err) {
console.error(`Tx submission failed: ${err}`);
}
}
main();
import org.stellar.sdk.*;
import org.stellar.sdk.requests.RequestBuilder;
import org.stellar.sdk.responses.AccountResponse;
import org.stellar.sdk.responses.SubmitTransactionResponse;
import java.util.ArrayList;
import java.util.List;
public class StellarClaimableBalance {
public static void main(String[] args) {
Network.useTestNetwork();
Server server = new Server("https://horizon-testnet.stellar.org");
KeyPair aKeypair = KeyPair.fromSecretSeed(
"SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4"
);
String bPublicKey = "GA2C5RFPE6GCKMY3US5PAB6UZLKIGSPIUKSLRB6Q723BM2OARMDUYEJ5";
AccountResponse aAccount;
try {
aAccount = server.accounts().account(aKeypair.getAccountId());
} catch (Exception e) {
throw new RuntimeException("Failed to load account");
}
// Create a claimable balance with our two above-described conditions.
long soon = System.currentTimeMillis() / 1000L + 60;
ClaimPredicate bCanClaim = ClaimPredicate.BeforeRelativeTime(60L);
ClaimPredicate aCanReclaim = ClaimPredicate.Not(
ClaimPredicate.BeforeAbsoluteTime(soon)
);
List<Claimant> claimants = new ArrayList<>();
claimants.add(new Claimant(bPublicKey, bCanClaim));
claimants.add(new Claimant(aKeypair.getAccountId(), aCanReclaim));
CreateClaimableBalanceOperation entryCB = new CreateClaimableBalanceOperation.Builder(
AssetTypeNative.INSTANCE, "64", claimants
).build();
// Build, sign, and submit the transaction
Transaction transaction = new Transaction.Builder(aAccount, Network.TESTNET)
.addOperation(entryCB)
.setBaseFee(Transaction.MIN_BASE_FEE)
.setTimeout(180)
.build();
transaction.sign(aKeypair);
try {
SubmitTransactionResponse response = server.submitTransaction(transaction);
System.out.println(response);
System.out.println("Claimable balance created!");
} catch (Exception e) {
throw new RuntimeException("Failed to submit transaction");
}
}
}
package main
import (
"fmt"
"time"
"github.com/stellar/stellar-rpc/client"
"github.com/stellar/stellar-rpc/protocol"
"github.com/stellar/go-stellar-sdk/keypair"
"github.com/stellar/go-stellar-sdk/network"
"github.com/stellar/go-stellar-sdk/txnbuild"
"github.com/stellar/go-stellar-sdk/xdr"
)
func main() {
client := sdk.DefaultTestNetClient
aKeys := keypair.MustParseFull(
"SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4"
)
B := "GA2C5RFPE6GCKMY3US5PAB6UZLKIGSPIUKSLRB6Q723BM2OARMDUYEJ5"
aAccount, err := client.AccountDetail(
sdk.AccountRequest{
AccountID: aKeys.Address(),
}
)
if err != nil {
panic("Failed to load account A")
}
// Create a claimable balance with our two above-described conditions.
soon := time.Now().Add(time.Second * 60)
bCanClaim := txnbuild.BeforeRelativeTimePredicate(60)
aCanReclaim := txnbuild.NotPredicate(
txnbuild.BeforeAbsoluteTimePredicate(
soon.Unix()
)
)
claimants := []txnbuild.Claimant{
txnbuild.NewClaimant(B, bCanClaim),
txnbuild.NewClaimant(aKeys.Address(), aCanReclaim),
}
claimableBalanceEntry := txnbuild.CreateClaimableBalance{
Destinations: claimants,
Asset: txnbuild.NativeAsset{},
Amount: "64",
}
tx, err := txnbuild.NewTransaction(
txnbuild.TransactionParams{
SourceAccount: aAccount.AccountID,
IncrementSequenceNum: true,
BaseFee: txnbuild.MinBaseFee,
Timebounds: txnbuild.NewTimeout(180),
Operations: []txnbuild.Operation{&claimableBalanceEntry},
},
)
if err != nil {
panic("Failed to build transaction")
}
tx, err = tx.Sign(network.TestNetworkPassphrase, aKeys)
if err != nil {
panic("Failed to sign transaction")
}
txResponse, err := client.SubmitTransaction(tx)
if err != nil {
panic("Failed to submit transaction")
}
fmt.Println("Claimable balance created", txResponse)
}
Retrieval
At this point, the ClaimableBalanceEntry exists in the ledger, but we’ll need its client balance ID to claim it, which can be done in several ways:
- The submitter of the entry () can retrieve the client balance ID before submitting the transaction.
- The submitter parses the XDR of the transaction result’s operations.
- Someone queries the list of claimable balances.
Either party could also check the /effects of the transaction or query /claimable_balances with different filters in Horizon. Note that while (1) may be unavailable in some SDKs, as it’s just a helper, the other methods are universal.
- Python
- JavaScript
- Java
- Go
# Method 1: Suppose `tx` comes from the transaction built above.
# Notice that this can be done *before* submission.
# Use zero for `CreateClaimableBalance` first op.
clientBalanceID = tx.get_claimable_balance_id(0)
print(f"Balance ID (1): {clientBalanceID}")
# Method 2: Suppose `txResponse` comes from the transaction submission
# above.
txResult = TransactionResult.from_xdr(txResponse["result_xdr"])
results = txResult.result.results
# We look at the first result since our first (and only) operation
# in the transaction was the CreateClaimableBalanceOp.
operationResult = results[0].tr.create_claimable_balance_result
clientBalanceID = operationResult.balance_id.to_xdr_bytes().hex()
print(f"Balance ID (2): {clientBalanceID}")
# Method 3: Account B could alternatively do something like:
try:
balances = (
server
.claimable_balances()
.for_claimant(B.public_key)
.limit(1)
.order(desc = True)
.call()
)
except (BadRequestError, BadResponseError) as err:
print(f"Claimable balance retrieval failed: {err}")
clientBalanceID = balances["_embedded"]["records"][0]["id"]
print(f"Balance ID (3): {clientBalanceID}")
// Method 1: Suppose `tx` comes from the transaction built above.
// Notice that this can be done *before* submission.
// Use zero for `CreateClaimableBalance` first op.
let clientBalanceID = tx.getClaimableBalanceId(0);
console.log("Balance ID (1):", clientBalanceID);
// Replace with your actual Claimable Balance ID
// Format: 72 hex characters (includes ClaimableBalanceId type + hash)
const BALANCE_ID =
"00000000db1108ff108a807150d02b8672d9a8c0e808bff918cdbe5c7605e63a7f565df5";
// 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 clientBalanceID = operationResult.balanceId().toXDR("hex");
console.log("Balance ID (2):", clientBalanceID);
try {
console.log(`Looking up balance ID: ${balanceId}`);
// Parse the claimable balance ID from hex XDR
const claimableBalanceId = StellarSdk.xdr.ClaimableBalanceId.fromXDR(
balanceId,
"hex",
);
// Create ledger key for the claimable balance entry
const ledgerKey = StellarSdk.xdr.LedgerKey.claimableBalance(
new StellarSdk.xdr.LedgerKeyClaimableBalance({
balanceId: claimableBalanceId,
}),
);
console.log(`Fetching from RPC server...`);
// Use SDK's getLedgerEntries method with XDR object array
const response = await server.getLedgerEntries(ledgerKey);
if (response.entries && response.entries.length > 0) {
const claimableBalance = response.entries[0].val.claimableBalance();
const asset = StellarSdk.Asset.fromOperation(claimableBalance.asset());
console.log(`Found claimable balance`);
console.log(`Amount: ${claimableBalance.amount().toString()}`);
console.log(`Asset: ${asset.toString()} `);
// Show claimant details
console.log(`\nClaimants:`);
claimableBalance.claimants().forEach((claimant, index) => {
const destination = claimant.v0().destination().ed25519();
console.log(
` ${index + 1}. ${StellarSdk.StrKey.encodeEd25519PublicKey(
destination,
)}`,
);
});
} else {
console.log(`Claimable balance not found`);
}
} catch (error) {
console.error(`Error: ${error.message}`);
}
}
clientBalanceID = balances.records[0].id;
console.log("Balance ID (3):", clientBalanceID);
// Method 1: Suppose `tx` comes from the transaction built above.
// Notice that this can be done *before* submission.
// Use zero for `CreateClaimableBalance` first op.
String clientBalanceID = tx.getClaimableBalanceId(0)
System.out.println("Balance ID (1): " + clientBalanceID);
// Method 2: Suppose txResponse comes from the transaction submission above.
String txResponseResultXdr = txResponse.getResultXdr().get();
try {
TransactionResult txResult = TransactionResult.decode(
TransactionResult.class,
Util.fromBase64(
txResponseResultXdr
)
);
OperationResult operationResult = txResult.getResult().getResults()[0];
XdrDataInputStream xdrDataInputStream = new XdrDataInputStream(Util.fromBase64(txResponseResultXdr));
TransactionResult result = TransactionResult.decode(xdrDataInputStream);
CreateClaimableBalanceResult createClaimableBalanceResult = operationResult.getTr().getCreateClaimableBalanceResult();
String clientBalanceID = Util.bytesToHex(createClaimableBalanceResult.getBalanceId().toXdrByteArray());
System.out.println("Balance ID (2): " + clientBalanceID);
} catch (IOException e) {
e.printStackTrace();
}
// Method 3: Account B could alternatively do something like:
try {
Page<ClaimableBalanceResponse> balances = server.claimableBalances().forClaimant(
B.getAccountId()
).limit(1).order(RequestBuilder.Order.DESC).execute();
if (balances.getRecords().size() > 0) {
String clientBalanceID = balances.getRecords().get(0).getId();
System.out.println("Balance ID (3): " + clientBalanceID);
}
} catch (IOException e) {
System.out.println("Claimable balance retrieval failed: " + e.getMessage());
}
// Method 1: Suppose `tx` comes from the transaction built above.
// Notice that this can be done *before* submission.
// Use zero for `CreateClaimableBalance` first op.
clientBalanceID, err := tx.ClaimableBalanceID(0)
check(err)
fmt.Println("Balance ID (1):", clientBalanceID)
import (
"context"
"fmt"
"github.com/stellar/stellar-rpc/client"
"github.com/stellar/stellar-rpc/protocol"
"github.com/stellar/go-stellar-sdk/xdr"
)
// Replace with your claimable balance ID
const BALANCE_ID = "00000000a4c91c4561f2d8b30dad9cf6475221b3003a3b4e12fc0cf78a13251c0e7ff665"
func main() {
// Create RPC client
rpcClient := client.NewClient("https://soroban-testnet.stellar.org", nil)
defer rpcClient.Close()
fmt.Printf("Looking up balance ID: %s\n", BALANCE_ID)
// Parse claimable balance ID from hex
var claimableBalanceID xdr.ClaimableBalanceId
err := xdr.SafeUnmarshalHex(BALANCE_ID, &claimableBalanceID)
panicIf(err)
// Create ledger key for claimable balance
ledgerKey := xdr.LedgerKey{
Type: xdr.LedgerEntryTypeClaimableBalance,
ClaimableBalance: &xdr.LedgerKeyClaimableBalance{
BalanceId: claimableBalanceID,
},
}
// Convert ledger key to base64 for RPC call
ledgerKeyB64, err := xdr.MarshalBase64(ledgerKey)
panicIf(err)
fmt.Println("Fetching from RPC server...")
// Use GetLedgerEntries method from client
ctx := context.Background()
resp, err := rpcClient.GetLedgerEntries(ctx, protocol.GetLedgerEntriesRequest{
Keys: []string{ledgerKeyB64},
})
panicIf(err)
if len(resp.Entries) > 0 {
entry := resp.Entries[0]
fmt.Println("Found claimable balance")
// Parse the ledger entry XDR
var ledgerEntryData xdr.LedgerEntryData
err = xdr.SafeUnmarshalBase64(entry.DataXDR, &ledgerEntryData)
panicIf(err)
claimableBalance := ledgerEntryData.ClaimableBalance
// Display details
fmt.Printf("Amount: %d\n", int64(claimableBalance.Amount))
fmt.Printf("Asset: %s\n", claimableBalance.Asset.String())
// Show claimants
fmt.Println("\nClaimants:")
for i, claimant := range claimableBalance.Claimants {
address := claimant.V0.Destination.Address()
fmt.Printf(" %d. %s\n", i+1, address)
}
} else {
fmt.Println("Claimable balance not found")
}
}
Claiming
With the client balance ID acquired, either or can actually submit a claim, depending on which predicate is fulfilled. We’ll assume here that a minute has passed, so just reclaims the balance entry.
- Python
- JavaScript
- Java
- Go
tx = (
TransactionBuilder(
source_account = aAccount,
network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE,
base_fee = server.fetch_base_fee()
)
.append_operation(
ClaimClaimableBalance(
balance_id = clientBalanceID
)
)
.set_timeout(180)
.build()
)
tx.sign(A)
try:
txResponse = server.submit_transaction(tx)
print(f"{A.public_key} claimed {clientBalanceID}")
except (BadRequestError, BadResponseError) as err:
print(f"Tx submission failed: {err}")
let tx = new sdk.TransactionBuilder(aAccount, { fee: sdk.BASE_FEE })
.addOperation(
sdk.Operation.claimClaimableBalance({
balanceId: clientBalanceID,
});
)
.setNetworkPassphrase(sdk.Networks.TESTNET)
.setTimeout(180)
.build();
tx.sign(A);
await server.submitTransaction(tx).catch(function (err) {
console.error(`Tx submission failed: ${err}`);
});
console.log(A.publicKey(), "claimed", clientBalanceID);
Transaction tx = new Transaction.Builder(aAccount, Network.TESTNET)
.addOperation(
new ClaimClaimableBalanceOperation.Builder(clientBalanceID).build();
)
.setBaseFee(tx.MIN_BASE_FEE)
.setTimeout(180)
.build();
tx.sign(A);
try {
SubmitTransactionResponse response = server.submitTransaction(tx);
System.out.println(A.getAccountId() + " claimed " + clientBalanceID);
} catch (Exception e) {
System.err.println("Tx submission failed: " + e.getMessage());
}
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/stellar/stellar-rpc/client"
"github.com/stellar/stellar-rpc/protocol"
"github.com/stellar/go-stellar-sdk/keypair"
"github.com/stellar/go-stellar-sdk/network"
"github.com/stellar/go-stellar-sdk/txnbuild"
)
// Replace with your claimable balance ID and claimant secret
const BALANCE_ID = "00000000a4c91c4561f2d8b30dad9cf6475221b3003a3b4e12fc0cf78a13251c0e7ff665"
const CLAIMANT_SECRET = "SBMODTOLLH2LGF4AJU5XOGHRQCGKEVJUZSAUAGJL7KGKC7XLJ3SG3F7N"
func main() {
// Create RPC client
rpcClient := client.NewClient("https://soroban-testnet.stellar.org", nil)
defer rpcClient.Close()
// Create keypair from claimant secret
keypairAccA, err := keypair.ParseFull(CLAIMANT_SECRET)
panicIf(err)
fmt.Printf("Claiming account: %s\n", keypairAccA.Address())
fmt.Printf("Balance ID: %s\n", BALANCE_ID)
ctx := context.Background()
// Load the claimant account using client's LoadAccount method
accountA, err := rpcClient.LoadAccount(ctx, keypairAccA.Address())
panicIf(err)
// Create claim claimable balance operation
claimOp := txnbuild.ClaimClaimableBalance{
BalanceID: BALANCE_ID,
}
// Build transaction
fmt.Println("Building claim transaction...")
tx, err := txnbuild.NewTransaction(
txnbuild.TransactionParams{
SourceAccount: accountA,
IncrementSequenceNum: true,
BaseFee: txnbuild.MinBaseFee,
Preconditions: txnbuild.Preconditions{TimeBounds: txnbuild.NewInfiniteTimeout()},
Operations: []txnbuild.Operation{&claimOp},
},
)
panicIf(err)
// Sign transaction
tx, err = tx.Sign(network.TestNetworkPassphrase, keypairAccA)
panicIf(err)
// Get transaction XDR
txXDR, err := tx.Base64()
panicIf(err)
// Submit using RPC client's SendTransaction method
fmt.Println("Submitting claim transaction...")
sendResp, err := rpcClient.SendTransaction(ctx, protocol.SendTransactionRequest{
Transaction: txXDR,
})
panicIf(err)
if sendResp.Status != "PENDING" {
log.Fatalf("Transaction not pending: %s", sendResp.Status)
}
fmt.Printf("Transaction submitted: %s\n", sendResp.Hash)
// Poll for completion using RPC client's GetTransaction method
fmt.Println("Polling for result...")
for i := 0; i < 10; i++ {
resp, err := rpcClient.GetTransaction(ctx, protocol.GetTransactionRequest{
Hash: sendResp.Hash,
})
if err != nil {
log.Printf("Error getting transaction: %v", err)
time.Sleep(1 * time.Second)
continue
}
if resp.Status != protocol.TransactionStatusNotFound {
if resp.Status == protocol.TransactionStatusSuccess {
fmt.Println("\nSUCCESS: Claimable balance claimed")
fmt.Printf("Transaction hash: %s\n", sendResp.Hash)
fmt.Printf("Claimed by: %s\n", keypairAccA.Address())
} else {
fmt.Printf("Transaction failed: %s\n", resp.Status)
if resp.ResultXDR != "" {
fmt.Printf("Result XDR: %s\n", resp.ResultXDR)
}
}
return
}
time.Sleep(time.Duration(i+1) * time.Second)
}
fmt.Println("Transaction polling timeout")
}
And that’s it! Since we opted for the reclaim path, should have the same balance as what it started with (minus fees), and should be unchanged.
Guides in this category:
📄 Create an Account
Learn about creating Stellar accounts, keypairs, funding, and account basics.
📄 Send to and receive payments from Contract Accounts
Learn to send payments to and receive payments from Contract Accounts on the Stellar network.
📄 Send and receive payments
Learn to send payments and watch for received payments on the Stellar network.
📄 Channel Accounts
Create channel accounts to submit transactions to the network at a high rate.
📄 Signing Soroban contract invocations
Learn two methods to sign Soroban smart contract invocations: full transaction signing for G-accounts and auth-entry signing for G or C-accounts with sponsored fees.
📄 Claimable Balances
Split a payment into two parts by creating a claimable balance.
📄 Clawbacks
Use clawbacks to burn a specific amount of a clawback-enabled asset from a trustline or claimable balance.
📄 Fee-bump Transactions
Use fee-bump transactions to pay for transaction fees on behalf of another account without re-signing the transaction.
📄 Sponsored Reserves
Use sponsored reserves to pay for base reserves on behalf of another account.
📄 Path Payments
Send a payment where the asset received differs from the asset sent.
📄 Pooled accounts: muxed accounts and memos
Use muxed accounts to differentiate between individual accounts in a pooled account.
📄 Install and deploy a smart contract with code
Install and deploy a smart contract with code.
📄 Invoke a contract function in a transaction using SDKs
Use the Stellar SDK to create, simulate, and assemble a transaction.
📄 simulateTransaction RPC method guide
simulateTransaction examples and tutorials guide.
📄 Submit a transaction to Stellar RPC using the JavaScript SDK
Use a looping mechanism to submit a transaction to the RPC.
📄 Upload WebAssembly (Wasm) bytecode using code
Upload the Wasm of the contract using js-stellar-sdk.