Skip to content

Commit

Permalink
🦞 Clawbacks: apply name diction
Browse files Browse the repository at this point in the history
  • Loading branch information
JFWooten4 committed Aug 5, 2024
1 parent 86f3ca8 commit d1a9252
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 55 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,43 +26,44 @@ For basic parameters, see the Create Claimable Balance entry in our [List of Ope

#### Additional parameters

`Claim_Predicate_` Claimant — an object that holds both the destination account that can claim the ClaimableBalanceEntry and a ClaimPredicate that must evaluate to true for the claim to succeed.
- **`Claim_Predicate_` Claimant**: an object that holds both the destination account that can claim the `ClaimableBalanceEntry` and a `ClaimPredicate` that must evaluate to true for the claim to succeed.

A ClaimPredicate is a recursive data structure that can be used to construct complex conditionals using different ClaimPredicateTypes. Below are some examples with the `Claim_Predicate_` prefix removed for readability. Note that the SDKs expect the Unix timestamps to be expressed in seconds.
- **A `ClaimPredicate`**: a recursive data structure that can be used to construct complex conditionals using different `ClaimPredicateTypes`. Below are some examples with the `Claim_Predicate_` prefix removed for readability. Note that the SDKs expect the Unix timestamps to be expressed in seconds.

- Can claim at any time - `UNCONDITIONAL`
- Can claim if the close time of the ledger, including the claim is before X seconds + the ledger close time in which the ClaimableBalanceEntry was created - `BEFORE_RELATIVE_TIME(X)`
- Can claim if the close time of the ledger including the claim is before X (Unix timestamp) - `BEFORE_ABSOLUTE_TIME(X)`
- Can claim if the close time of the ledger, including the claim is at or after X seconds + the ledger close time in which the ClaimableBalanceEntry was created - `NOT(BEFORE_RELATIVE_TIME(X))`
- Can claim if the close time of the ledger, including the claim is at or after X (Unix timestamp) - `NOT(BEFORE_ABSOLUTE_TIME(X))`
- Can claim between X and Y Unix timestamps (given X < Y) - `AND(NOT(BEFORE_ABSOLUTE_TIME(X))`, `BEFORE_ABSOLUTE_TIME(Y))`
- Can claim outside X and Y Unix timestamps (given X < Y) - `OR(BEFORE_ABSOLUTE_TIME(X)`, `NOT(BEFORE_ABSOLUTE_TIME(Y))`
- `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 the `ClaimableBalanceEntry` was 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).
- Can claim between X and Y Unix timestamps (given X < Y) - `AND(NOT(BEFORE_ABSOLUTE_TIME(X))`, `BEFORE_ABSOLUTE_TIME(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.
- `OR( BEFORE_ABSOLUTE_TIME(X)`, `NOT( BEFORE_ABSOLUTE_TIME(Y) ) )`: Can claim outside X and Y Unix timestamps (given X < Y).

A successful Create Claimable Balance operation will return a Balance ID, which is required when claiming the ClaimableBalanceEntry with the Claim Claimable Balance operation.
- `ClaimableBalanceID` ClaimableBalanceID is a union with one possible type (`CLAIMABLE_BALANCE_ID_TYPE_V0`). It contains an SHA-256 hash of the [OperationID](https://github.com/stellar/stellar-protocol/blob/master/core/cap-0023.md#user-content-fn-id-arithmatic-d2727bb47f78787e54824bc772d9861c) for Claimable Balances.

- `ClientBalanceID`: Hex of `ClaimableBalanceID` returned after a successful `CreateClaimableBalance` operation. It is required when claiming the `ClaimableBalanceEntry` with the `ClaimClaimableBalance` operation.

### Claim Claimable Balance

For basic parameters, see the Claim Claimable Balance entry in our [List of Operations section](../../fundamentals/transactions/list-of-operations#claim-claimable-balance).
For basic parameters, see the Claim Claimable Balance entry in our [List of Operations section](../../fundamentals/transactions/list-of-operations.mdx#claim-claimable-balance).

This operation will load the ClaimableBalanceEntry that corresponds to the Balance ID 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.
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.

Clawback claimable balances require the claimable balance ID.
Clawback claimable balances require the client balance ID.

Learn more about clawbacks in our [Clawback Encyclopedia Entry](./clawbacks.mdx).

## Example

The below code demonstrates how an account (Account A) creates a ClaimableBalanceEntry with two claimants: Account A (itself) and Account B (another recipient).
The below code demonstrates how an account (Account $\mathcal{A}$) creates a ClaimableBalanceEntry with two claimants: $\mathcal{A}$ (itself) and Account $\mathcal{B}$ (another recipient).

### Setup

Each of these accounts can only claim the balance under unique conditions. Account B has a full minute to claim the balance before Account A can reclaim the balance back for itself.
Each of these accounts can only claim the balance under unique conditions. $\mathcal{B}$ has a full minute to claim the balance before $\mathcal{A}$ can reclaim the balance back for itself.

:::note Claimant Permanence

Expand Down Expand Up @@ -339,9 +340,9 @@ func main() {

### Retrieval

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:
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:

1. The submitter of the entry (Account A in this case) can retrieve the Balance ID before submitting the transaction;
1. The submitter of the entry ($\mathcal{A}$) can retrieve the client balance ID before submitting the transaction;
2. The submitter parses the XDR of the transaction result’s operations; or
3. Someone queries the list of claimable balances.

Expand All @@ -353,8 +354,8 @@ Either party could also check the /effects of the transaction, query /claimable_
# Method 1: Suppose `tx` comes from the transaction built above.
# Notice that this can be done *before* submission.
# Use zero for `CreateClaimableBalance` first op.
balanceId = tx.get_claimable_balance_id(0)
print(f"Balance ID (1): {balanceId}")
clientBalanceID = tx.get_claimable_balance_id(0)
print(f"Balance ID (1): {clientBalanceID}")

# Method 2: Suppose `txResponse` comes from the transaction submission
# above.
Expand All @@ -364,8 +365,8 @@ 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}")
clientBalanceID = operationResult.balance_id.to_xdr_bytes().hex()
print(f"Balance ID (2): {clientBalanceID}")

# Method 3: Account B could alternatively do something like:
try:
Expand All @@ -380,16 +381,16 @@ try:
except (BadRequestError, BadResponseError) as err:
print(f"Claimable balance retrieval failed: {err}")

balanceId = balances["_embedded"]["records"][0]["id"]
print(f"Balance ID (3): {balanceId}")
clientBalanceID = balances["_embedded"]["records"][0]["id"]
print(f"Balance ID (3): {clientBalanceID}")
```

```js
// 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 balanceId = tx.getClaimableBalanceId(0);
console.log("Balance ID (1):", balanceId);
let clientBalanceID = tx.getClaimableBalanceId(0);
console.log("Balance ID (1):", clientBalanceID);

// Method 2: Suppose `txResponse` comes from the transaction submission
// above.
Expand All @@ -402,8 +403,8 @@ 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);
let clientBalanceID = operationResult.balanceId().toXDR("hex");
console.log("Balance ID (2):", clientBalanceID);

// Method 3: Account B could alternatively do something like:
let balances = await server
Expand All @@ -419,16 +420,16 @@ if (!balances) {
return;
}

balanceId = balances.records[0].id;
console.log("Balance ID (3):", balanceId);
clientBalanceID = balances.records[0].id;
console.log("Balance ID (3):", clientBalanceID);
```

```java
// 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 balanceId = tx.getClaimableBalanceId(0)
System.out.println("Balance ID (1): " + balanceId);
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();
Expand All @@ -444,8 +445,8 @@ try {
TransactionResult result = TransactionResult.decode(xdrDataInputStream);

CreateClaimableBalanceResult createClaimableBalanceResult = operationResult.getTr().getCreateClaimableBalanceResult();
String balanceId = Util.bytesToHex(createClaimableBalanceResult.getBalanceId().toXdrByteArray());
System.out.println("Balance ID (2): " + balanceId);
String clientBalanceID = Util.bytesToHex(createClaimableBalanceResult.getBalanceId().toXdrByteArray());
System.out.println("Balance ID (2): " + clientBalanceID);
} catch (IOException e) {
e.printStackTrace();
}
Expand All @@ -456,8 +457,8 @@ try {
B.getAccountId()
).limit(1).order(RequestBuilder.Order.DESC).execute();
if (balances.getRecords().size() > 0) {
String balanceId = balances.getRecords().get(0).getId();
System.out.println("Balance ID (3): " + balanceId);
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());
Expand All @@ -468,9 +469,9 @@ try {
// Method 1: Suppose `tx` comes from the transaction built above.
// Notice that this can be done *before* submission.
// Use zero for `CreateClaimableBalance` first op.
balanceId, err := tx.ClaimableBalanceID(0)
clientBalanceID, err := tx.ClaimableBalanceID(0)
check(err)
fmt.Println("Balance ID (1):", balanceId)
fmt.Println("Balance ID (1):", clientBalanceID)

// Method 2: Suppose `txResp` comes from the transaction submission above.
var txResult xdr.TransactionResult
Expand All @@ -481,23 +482,23 @@ 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)
clientBalanceID, err := xdr.MarshalHex(operationResult.BalanceId)
check(err)
fmt.Println("Balance ID (2):", balanceId)
fmt.Println("Balance ID (2):", clientBalanceID)
}

// 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
fmt.Println("Balance ID (3):", balanceId)
clientBalanceID := balances.Embedded.Records[0].BalanceID
fmt.Println("Balance ID (3):", clientBalanceID)
```

</CodeExample>

### Claiming

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.
With the client balance ID acquired, either $\mathcal{B}$ or $\mathcal{A}$ can actually submit a claim, depending on which predicate is fulfilled. We’ll assume here that a minute has passed, so $\mathcal{A}$ just reclaims the balance entry.

<CodeExample>

Expand All @@ -510,7 +511,7 @@ tx = (
)
.append_operation(
ClaimClaimableBalance(
balance_id = balanceId
balance_id = clientBalanceID
)
)
.set_timeout(180)
Expand All @@ -520,7 +521,7 @@ tx = (
tx.sign(A)
try:
txResponse = server.submit_transaction(tx)
print(f"{A.public_key} claimed {balanceId}")
print(f"{A.public_key} claimed {clientBalanceID}")
except (BadRequestError, BadResponseError) as err:
print(f"Tx submission failed: {err}")
```
Expand All @@ -529,7 +530,7 @@ except (BadRequestError, BadResponseError) as err:
let tx = new sdk.TransactionBuilder(aAccount, { fee: sdk.BASE_FEE })
.addOperation(
sdk.Operation.claimClaimableBalance({
balanceId: balanceId,
balanceId: clientBalanceID,
});
)
.setNetworkPassphrase(sdk.Networks.TESTNET)
Expand All @@ -540,13 +541,13 @@ tx.sign(A);
await server.submitTransaction(tx).catch(function (err) {
console.error(`Tx submission failed: ${err}`);
});
console.log(A.publicKey(), "claimed", balanceId);
console.log(A.publicKey(), "claimed", clientBalanceID);
```

```java
Transaction transaction = new Transaction.Builder(aAccount, Network.TESTNET)
.addOperation(
new ClaimClaimableBalanceOperation.Builder(balanceId).build();
new ClaimClaimableBalanceOperation.Builder(clientBalanceID).build();
)
.setBaseFee(Transaction.MIN_BASE_FEE)
.setTimeout(180)
Expand All @@ -556,7 +557,7 @@ transaction.sign(A);

try {
SubmitTransactionResponse response = server.submitTransaction(transaction);
System.out.println(A.getAccountId() + " claimed " + balanceId);
System.out.println(A.getAccountId() + " claimed " + clientBalanceID);
} catch (Exception e) {
System.err.println("Tx submission failed: " + e.getMessage());
}
Expand All @@ -570,7 +571,7 @@ tx, err = txnbuild.NewTransaction(
BaseFee: txnbuild.MinBaseFee,
Timebounds: txnbuild.NewInfiniteTimeout(),
Operations: []txnbuild.Operation{
&txnbuild.ClaimClaimableBalance{BalanceID: balanceId}
&txnbuild.ClaimClaimableBalance{BalanceID: clientBalanceID}
},
},
)
Expand All @@ -579,9 +580,9 @@ tx, err = tx.Sign(network.TestNetworkPassphrase, aKeys)
check(err)
txResp, err = client.SubmitTransaction(tx)
check(err)
fmt.Printf("%s claimed %s\n", aKeys.Address(), balanceId)
fmt.Printf("%s claimed %s\n", aKeys.Address(), clientBalanceID)
```

</CodeExample>

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.
And that’s it! Since we opted for the reclaim path, $\mathcal{A}$ should have the same balance as what it started with (minus fees), and $\mathcal{B}$ should be unchanged.
10 changes: 7 additions & 3 deletions docs/learn/encyclopedia/transactions-specialized/clawbacks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ The issuing account uses this operation to claw back some or all of an asset. On

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.

Clawback claimable balances require the claimable balance ID.
Clawback claimable balances require the [claimable balance ID](../transactions-specialized/claimable-balances.mdx#additional-parameters).

### Set Trust Line Flag

Expand Down Expand Up @@ -292,7 +292,11 @@ C - GC2BK...CQVGF: 250

Notice that $\mathcal{A}$ (the issuer) holds none of the asset despite clawing back 250 from $\mathcal{C}$. This should drive home the fact that clawed-back assets are burned, not transferred.

(It may be strange that A never holds any `AstroToken`, but that’s exactly how issuing works: you create value where there used to be none. Sending an asset to its issuing account is equivalent to burning it, and auditing the total amount of an asset in existence is one of the benefits of properly distributing an asset via a distribution account, which we avoid doing here for example brevity.)
:::info

It may be strange that $\mathcal{A}$ never holds any `AstroToken`, but that’s exactly how issuing works: you create value where there used to be none. Sending an asset to its issuing account is equivalent to burning it, and auditing the total amount of an asset in existence is one of the benefits of properly distributing an asset via a distribution account, which we avoid doing here for example brevity.

:::

### Example 2: Claimable Balances

Expand Down Expand Up @@ -338,7 +342,7 @@ function clawbackClaimable(issuerAccount, issuerKey, balanceId) {

</CodeExample>

Now, we can fulfill the flow: A pays B, who sends a claimable balance to C, who gets it clawed back by A. (Note that we rely on the `makePayment` helper from the earlier example.)
Now, we can fulfill the flow: $\mathcal{A}$ pays $\mathcal{B}$, who sends a claimable balance to $\mathcal{C}$, who gets it clawed back by $\mathcal{A}$. (Note that we rely on the `makePayment` helper from the earlier example.)

<CodeExample>

Expand Down

0 comments on commit d1a9252

Please sign in to comment.