From ae1d3bf05b27df25189494e7565f05c62bedf438 Mon Sep 17 00:00:00 2001
From: Bri <92327786+briwylde08@users.noreply.github.com>
Date: Tue, 30 Aug 2022 10:13:26 -0600
Subject: [PATCH] Added code blocks
---
docs/encyclopedia/claimable-balances.mdx | 362 ++++++++++++++++++++++-
1 file changed, 358 insertions(+), 4 deletions(-)
diff --git a/docs/encyclopedia/claimable-balances.mdx b/docs/encyclopedia/claimable-balances.mdx
index 95df2a552..ae4eb2517 100644
--- a/docs/encyclopedia/claimable-balances.mdx
+++ b/docs/encyclopedia/claimable-balances.mdx
@@ -2,6 +2,8 @@
title: Claimable Balances
---
+import { CodeExample } from "@site/src/components/CodeExample"
+
Claimable balances were introduced in [CAP-0023](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0023.md) and are used to split a payment into two parts.
- Part 1: sending account creates a payment, or ClaimableBalanceEntry, using the Create Claimable Balance operation
@@ -57,7 +59,202 @@ Each of these accounts can only claim the balance under unique conditions. Accou
**Note:** there is no recovery mechanism for a claimable balance in general — if none of the predicates can be fulfilled, the balance cannot be recovered. The reclaim example below acts as a safety net for this situation.
-[insert code]
+
+
+```js
+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}`);
+ });
+}
+```
+
+```go
+package main
+
+import (
+ "fmt"
+ "time"
+
+ sdk "github.com/stellar/go/clients/horizonclient"
+ "github.com/stellar/go/keypair"
+ "github.com/stellar/go/network"
+ "github.com/stellar/go/txnbuild"
+)
+
+
+func main() {
+ client := sdk.DefaultTestNetClient
+
+ // Suppose that these accounts exist and are funded accordingly:
+ A := "SCZANGBA5YHTNYVVV4C3U252E2B6P6F5T3U6MM63WBSBZATAQI3EBTQ4"
+ B := "GA2C5RFPE6GCKMY3US5PAB6UZLKIGSPIUKSLRB6Q723BM2OARMDUYEJ5"
+
+ // Load the corresponding account for A.
+ aKeys := keypair.MustParseFull(A)
+ aAccount, err := client.AccountDetail(sdk.AccountRequest{
+ AccountID: aKeys.Address(),
+ })
+ check(err)
+
+ // 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),
+ }
+
+ // Create the operation and submit it in a transaction.
+ claimableBalanceEntry := txnbuild.CreateClaimableBalance{
+ Destinations: claimants,
+ Asset: txnbuild.NativeAsset{},
+ Amount: "420",
+ }
+
+ // Build, sign, and submit the transaction
+ tx, err := txnbuild.NewTransaction(
+ txnbuild.TransactionParams{
+ SourceAccount: aAccount.AccountID,
+ IncrementSequenceNum: true,
+ BaseFee: txnbuild.MinBaseFee,
+ // Use a real timeout in production!
+ Timebounds: txnbuild.NewInfiniteTimeout(),
+ Operations: []txnbuild.Operation{&claimableBalanceEntry},
+ },
+ )
+ check(err)
+ tx, err = tx.Sign(network.TestNetworkPassphrase, aKeys)
+ check(err)
+ txResp, err := client.SubmitTransaction(tx)
+ check(err)
+
+ fmt.Println(txResp)
+ fmt.Println("Claimable balance created!")
+}
+```
+
+```python
+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
+)
+
+server = Server("https://horizon-testnet.stellar.org")
+
+A = Keypair.from_secret("SANRGB5VXZ52E7XDGH2BHVBFZR4S25AUQ4BR7SFXIQYT5J6W2OES2OP7")
+B = Keypair.from_public_key("GAAPSRMYNFAO3TDQTLNLKN76IQ3E6IQAKU23PSQX3BIV7RTEBXHQIWU6")
+
+# NOTE: Proper error checks are omitted for brevity; always validate things!
+
+try:
+ aAccount = server.load_account(A.public_key)
+except NotFoundError:
+ raise Exception(f"Failed to load {A.public_key}")
+
+# Create a claimable balance with our two above-described conditions.
+soon = int(time.time() + 60)
+bCanClaim = ClaimPredicate.predicate_before_relative_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 = "420",
+ claimants = [
+ Claimant(destination = B.public_key, predicate = bCanClaim),
+ Claimant(destination = A.public_key, predicate = aCanClaim)
+ ]
+)
+
+tx = (
+ TransactionBuilder (
+ source_account = aAccount,
+ network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE,
+ base_fee = server.fetch_base_fee()
+ )
+ .append_operation(claimableBalanceEntry)
+ .set_timeout(180)
+ .build()
+)
+
+tx.sign(A)
+try:
+ txResponse = server.submit_transaction(tx)
+ print("Claimable balance created!")
+except (BadRequestError, BadResponseError) as err:
+ print(f"Tx submission failed: {err}")
+```
+
+
+
At this point, the `ClaimableBalanceEntry` exists in the ledger, but we’ll need its Balance ID to claim it, which can be done in several ways:
@@ -67,10 +264,167 @@ At this point, the `ClaimableBalanceEntry` exists in the ledger, but we’ll nee
Either party could also check the /effects of the transaction, query /claimable_balances with different filters, etc. Note that while (1) may be unavailable in some SDKs as it's just a helper, the other methods are universal.
-[insert code]
+
+
+```js
+// Method 1: Not available in the JavaScript SDK yet.
+
+// Method 2: Suppose `txResponse` comes from the transaction submission
+// above.
+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);
+```
+
+```go
+// Method 1: Suppose `tx` comes from the transaction built above.
+// Notice that this can be done *before* submission.
+balanceId, err := tx.ClaimableBalanceID(0)
+check(err)
+
+// Method 2: Suppose `txResp` comes from the transaction submission above.
+var txResult xdr.TransactionResult
+err = xdr.SafeUnmarshalBase64(txResp.ResultXdr, &txResult)
+check(err)
+
+if results, ok := txResult.OperationResults(); ok {
+ // We look at the first result since our first (and only) operation in the
+ // transaction was the CreateClaimableBalanceOp.
+ operationResult := results[0].MustTr().CreateClaimableBalanceResult
+ balanceId, err := xdr.MarshalHex(operationResult.BalanceId)
+ check(err)
+ fmt.Println("Balance ID:", balanceId)
+}
+
+// Method 3: Account B could alternatively do something like:
+balances, err := client.ClaimableBalances(sdk.ClaimableBalanceRequest{Claimant: B})
+check(err)
+balanceId := balances.Embedded.Records[0].BalanceID
+```
+
+```python
+# Method 1: Not available in the Python SDK yet.
+
+# 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
+balanceId = operationResult.balance_id.to_xdr_bytes().hex()
+print(f"Balance ID (2): {balanceId}")
+
+# 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}")
+
+balanceId = balances["_embedded"]["records"][0]["id"]
+print(f"Balance ID (3): {balanceId}")
+```
+
+
+
With the Claimable Balance ID acquired, either Account B or A can actually submit a claim, depending on which predicate is fulfilled. We’ll assume here that a minute has passed, so Account A just reclaims the balance entry.
-[insert code]
+
+
+```js
+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}`);
+});
+```
+
+
+```go
+claimBalance := txnbuild.ClaimClaimableBalance{BalanceID: balanceId}
+tx, err = txnbuild.NewTransaction(
+ txnbuild.TransactionParams{
+ SourceAccount: aAccount.AccountID, // or Account B, depending on the condition!
+ IncrementSequenceNum: true,
+ BaseFee: txnbuild.MinBaseFee,
+ Timebounds: txnbuild.NewInfiniteTimeout(),
+ Operations: []txnbuild.Operation{&claimBalance},
+ },
+)
+check(err)
+tx, err = tx.Sign(network.TestNetworkPassphrase, aKeys)
+check(err)
+txResp, err = client.SubmitTransaction(tx)
+check(err)
+```
+
+```python
+claimBalance = ClaimClaimableBalance(balance_id = balanceId)
+print(f"{A.public_key} claiming {balanceId}")
+
+tx = (
+ TransactionBuilder (
+ source_account = aAccount,
+ network_passphrase = Network.TESTNET_NETWORK_PASSPHRASE,
+ base_fee = server.fetch_base_fee()
+ )
+ .append_operation(claimBalance)
+ .set_timeout(180)
+ .build()
+)
+
+tx.sign(A)
+try:
+ txResponse = server.submit_transaction(tx)
+except (BadRequestError, BadResponseError) as err:
+ print(f"Tx submission failed: {err}")
+```
+
+
+
-And that’s it! Since we opted for the reclaim path, Account A should have the same balance as what it started with (minus fees), and Account B should be unchanged.
\ No newline at end of file
+And that’s it! Since we opted for the reclaim path, Account A should have the same balance as what it started with (minus fees), and Account B should be unchanged.