# Claw Back Confidential Balances

This tutorial shows you how to claw back a holder's [confidential Multi-Purpose Token (MPT)](/es-es/docs/concepts/tokens/fungible-tokens/confidential-transfers) balance. A confidential clawback takes the holder's total confidential balance (spending + inbox balance), so there is no partial clawback. The clawback amount is publicly visible, so the holder's total confidential balance at that moment can be seen by everyone.

_Requires the [ConfidentialTransfer amendment](/resources/known-amendments#confidentialtransfer). (Open for Voting: 5.71%)_

## Goals

By the end of this tutorial, you will be able to:

- Lock an MPT issuance for a holder so that a clawback proof stays valid.
- Claw back the holder's entire confidential balance, and verify the result as the holder, the issuer, and the auditor.


## Prerequisites

To complete this tutorial, you should:

- Have a basic understanding of the XRP Ledger and the [Confidential Transfers](/es-es/docs/concepts/tokens/fungible-tokens/confidential-transfers) concept, in particular [Confidential Clawback](/es-es/docs/concepts/tokens/fungible-tokens/confidential-transfers#confidential-clawback).
- Have an MPT issued with both the **Can Clawback** and **Can Lock** flags, the issuer and auditor encryption keys registered on it, and a holder with a confidential balance. See [Issue an MPT for Confidential Transfers](/es-es/docs/tutorials/tokens/mpts/issue-mpt-for-confidential-transfers). The setup script for this tutorial creates them for you.
- Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:
  - **JavaScript** with the [xrpl.js library](https://github.com/XRPLF/xrpl.js). See [Get Started Using JavaScript](/docs/tutorials/get-started/get-started-javascript) for setup steps.
  - **Python** with the [xrpl-py library](https://github.com/XRPLF/xrpl-py). See [Get Started Using Python](/docs/tutorials/get-started/get-started-python) for setup steps.


## Source Code

You can find the complete source code for this tutorial's examples in the code samples section of this website's repository.

## Steps

### 1. Install dependencies

JavaScript
From the `js/` folder, use `npm` to install dependencies.

```sh
npm install
```

Python
From the `py/` folder, set up a virtual environment and use `pip` to install dependencies.

```sh
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```

### 2. Set up the client and accounts

To get started, import the necessary libraries and instantiate a client to connect to the XRP Ledger. This example imports:

JavaScript
- `fs`: Used to check for and load the tutorial setup data.
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `./confidentialTransfersSetup.js`: The tutorial setup script, imported and called directly.


```js
import fs from 'fs'
import {
  Client,
  MPTokenIssuanceSetFlags,
  Wallet,
  deriveConfidentialKeypair,
  fetchMPToken,
  fetchMPTokenIssuance,
  loadMptCrypto,
  prepareConfidentialClawback
} from 'xrpl'

import { setup } from './confidentialTransfersSetup.js'

// Connect to the network ----------------------
const client = new Client('wss://s.devnet.rippletest.net:51233')
await client.connect()

const EXPLORER = 'https://devnet.xrpl.org'
```

Python
- `asyncio`: Used to run the async tutorial setup function.
- `json`, `os`: Used to check for and load the tutorial setup data.
- `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling.
- `xrpl.ext.confidential`: Used for the confidential clawback builder and decryption.
- `confidential_transfers_setup`: The tutorial setup script, imported and called directly.


```py
import asyncio
import json
import os

from xrpl.clients import JsonRpcClient
from xrpl.ext.confidential import (
    decrypt_confidential_balance,
    prepare_confidential_clawback,
)
from xrpl.models import LedgerEntry, MPTokenIssuanceSet
from xrpl.models.requests.ledger_entry import MPToken
from xrpl.models.transactions import MPTokenIssuanceSetFlag
from xrpl.transaction import submit_and_wait
from xrpl.wallet import Wallet

from confidential_transfers_setup import main as run_setup

# Connect to the network ----------------------
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")

EXPLORER = "https://devnet.xrpl.org"
```

Load the issuer, the holder, the auditor, and the issuance the clawback applies to. Only the issuer can claw back, and it needs its own encryption keys to read the holder's balance and build the proof.

JavaScript
```js
// Load setup data ----------------------
// This step checks for the necessary setup data to run the tutorial.
// If missing, confidentialTransfersSetup.js will generate it.
if (!fs.existsSync('confidentialTransfersSetup.json')) {
  console.log(`\n=== Setup data doesn't exist. Running setup script... ===\n`)
  await setup()
}

const setupData = JSON.parse(
  fs.readFileSync('confidentialTransfersSetup.json', 'utf8')
)

// Set up accounts ----------------------
console.log(`\n=== Getting accounts... ===`)
const issuer = Wallet.fromSeed(setupData.issuer.seed)
const auditor = Wallet.fromSeed(setupData.auditor.seed)
const holder = Wallet.fromSeed(setupData.flaggedHolder.seed)

const issuerKeys = deriveConfidentialKeypair(issuer.seed)
const auditorKeys = deriveConfidentialKeypair(auditor.seed)
const holderKeys = deriveConfidentialKeypair(holder.seed)

const { ticker, mptIssuanceID } = setupData.stablecoin

console.log(`Issuer address: ${issuer.address}`)
console.log(`Holder address: ${holder.address}`)
console.log(`Auditor address: ${auditor.address}`)
```

Python
```py
# Load setup data ----------------------
# This step checks for the necessary setup data to run the tutorial.
# If missing, confidential_transfers_setup.py will generate it.
if not os.path.exists("confidential_transfers_setup.json"):
    print("\n=== Setup data doesn't exist. Running setup script... ===\n")
    asyncio.run(run_setup())

with open("confidential_transfers_setup.json") as setup_file:
    setup_data = json.load(setup_file)

# Set up accounts ----------------------
print("\n=== Getting accounts... ===")
issuer = Wallet.from_seed(setup_data["issuer"]["seed"])
auditor = Wallet.from_seed(setup_data["auditor"]["seed"])
holder = Wallet.from_seed(setup_data["flaggedHolder"]["seed"])

issuer_privkey = setup_data["issuer"]["privateKey"]
issuer_pubkey = setup_data["issuer"]["publicKey"]
auditor_privkey = setup_data["auditor"]["privateKey"]
holder_privkey = setup_data["flaggedHolder"]["privateKey"]

ticker = setup_data["stablecoin"]["ticker"]
mpt_issuance_id = setup_data["stablecoin"]["mptIssuanceID"]

print(f"Issuer address: {issuer.address}")
print(f"Holder address: {holder.address}")
print(f"Auditor address: {auditor.address}")
```

This example uses pre-configured data from a setup script, including public and private keys, but you can replace these with your own values.

Caution
For testing purposes, the example's private keys are stored in a JSON file by the setup script. In production, private keys should be stored in a secure, encrypted key store.

### 3. Lock the issuance for the holder

A clawback proof is built against the balance the issuer reads. If the holder spends or receives anything before the clawback, the balance changes, the proof goes stale, and the clawback fails.

Submit an [MPTokenIssuanceSet transaction](/docs/references/protocol/transactions/types/mptokenissuanceset) with the `tfMPTLock` flag to [lock](/es-es/docs/concepts/tokens/fungible-tokens/deep-freeze#how-does-mpt-freezelock-behavior-differ-from-iou) the MPT issuance for the holder. This requires the issuance to have the **Can Lock** flag enabled.

JavaScript
```js
// Lock the issuance for the holder ----------------------
console.log(`\n=== Locking ${ticker} for the holder... ===`)
const lockTx = {
  TransactionType: 'MPTokenIssuanceSet',
  Account: issuer.address,
  MPTokenIssuanceID: mptIssuanceID,
  Holder: holder.address,
  Flags: MPTokenIssuanceSetFlags.tfMPTLock
}

const lockResponse = await client.submitAndWait(lockTx, {
  wallet: issuer,
  autofill: true
})
if (lockResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
  const resultCode = lockResponse.result.meta.TransactionResult
  console.error('Error: Unable to lock the issuance:', resultCode)
  await client.disconnect()
  process.exit(1)
}
console.log(`${ticker} is locked for ${holder.address}.`)
console.log(`${EXPLORER}/transactions/${lockResponse.result.hash}`)
```

Python
```py
# Lock the issuance for the holder ----------------------
print(f"\n=== Locking {ticker} for the holder... ===")
lock_tx = MPTokenIssuanceSet(
    account=issuer.address,
    mptoken_issuance_id=mpt_issuance_id,
    holder=holder.address,
    flags=MPTokenIssuanceSetFlag.TF_MPT_LOCK,
)

lock_response = submit_and_wait(lock_tx, client, issuer, autofill=True)
if lock_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
    result_code = lock_response.result["meta"]["TransactionResult"]
    print(f"Error: Unable to lock the issuance: {result_code}")
    exit(1)
print(f"{ticker} is locked for {holder.address}.")
print(f"{EXPLORER}/transactions/{lock_response.result['hash']}")
```

### 4. Read the confidential supply

The issuance's `ConfidentialOutstandingAmount` is the total confidential supply and is public, so the issuer can watch the clawback take effect without decrypting anything.

JavaScript
```js
// Read the confidential supply before the clawback ----------------------
console.log(`\n=== Reading the confidential supply... ===`)
const mptIssuance = await fetchMPTokenIssuance(client, mptIssuanceID)
const confidentialSupply = BigInt(mptIssuance.ConfidentialOutstandingAmount)
console.log(`Confidential supply before clawback: ${confidentialSupply}`)
```

Python
```py
# Read the confidential supply before the clawback ----------------------
print("\n=== Reading the confidential supply... ===")
mpt_issuance = client.request(
    LedgerEntry(mpt_issuance=mpt_issuance_id),
).result["node"]
confidential_supply = int(mpt_issuance["ConfidentialOutstandingAmount"])
print(f"Confidential supply before clawback: {confidential_supply}")
```

### 5. Claw back the confidential balance

The holder's [MPToken entry](/docs/references/protocol/ledger-data/ledger-entry-types/mptoken) also carries an `IssuerEncryptedBalance`, which holds the holder's spending and inbox balances added together, encrypted with the issuer's public key. That single total is what makes an unassisted clawback possible, because the issuer can read the exact amount without any cooperation from the holder.

Submit a [ConfidentialMPTClawback transaction](/docs/references/protocol/transactions/types/confidentialmptclawback) to claw back the holder's total confidential balance. The transaction carries the amount in the clear, along with a Zero-Knowledge Proof (ZKP) that the amount matches the `IssuerEncryptedBalance` it was read from. The two libraries split that work differently.

JavaScript
`prepareConfidentialClawback` needs only the issuer's keypair. It fetches the holder's `MPToken` entry, decrypts `IssuerEncryptedBalance`, and sets `MPTAmount` on the prepared transaction, which is where this example reads the amount from.

```js
// Claw back the confidential balance ----------------------
console.log(`\n=== Clawing back the holder's confidential balance... ===`)
// prepareConfidentialClawback attaches the amount the issuer read and a
// Zero-Knowledge Proof (ZKP) that the amount matches the encrypted balance.
const clawbackTx = await prepareConfidentialClawback(client, {
  account: issuer.address,
  holder: holder.address,
  mptIssuanceID,
  issuerKeypair: issuerKeys
})

// A clawback takes the whole balance, so there is nothing left to reclaim if
// this sample has already run against this setup data. The protocol rejects a
// clawback of zero, so stop before submitting.
if (clawbackTx.MPTAmount === '0') {
  console.error(
    `Error: The confidential balance of ${holder.address} is already zero.`
  )
  console.error(
    'Delete confidentialTransfersSetup.json and run the setup script again.'
  )
  await client.disconnect()
  process.exit(1)
}
console.log(JSON.stringify(clawbackTx, null, 2))

const clawbackResponse = await client.submitAndWait(clawbackTx, {
  wallet: issuer,
  autofill: true
})
if (clawbackResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
  const resultCode = clawbackResponse.result.meta.TransactionResult
  console.error('Error: Unable to claw back the balance:', resultCode)
  await client.disconnect()
  process.exit(1)
}
console.log(`Clawed back ${clawbackTx.MPTAmount} ${ticker} from ${holder.address}.`)
console.log(`${EXPLORER}/transactions/${clawbackResponse.result.hash}`)
```

Python
`prepare_confidential_clawback` needs the amount and the ciphertext it was read from, so this example fetches the holder's `MPToken` entry and decrypts `IssuerEncryptedBalance` itself before calling it.

```py
# Claw back the confidential balance ----------------------
print("\n=== Clawing back the holder's confidential balance... ===")
holder_mptoken = client.request(
    LedgerEntry(
        mptoken=MPToken(mpt_issuance_id=mpt_issuance_id, account=holder.address),
    ),
).result["node"]

issuer_encrypted_balance = holder_mptoken["IssuerEncryptedBalance"]
clawback_amount = decrypt_confidential_balance(
    issuer_encrypted_balance,
    issuer_privkey,
    range_high=confidential_supply,
)

# A clawback takes the whole balance, so there is nothing left to reclaim if
# this sample has already run against this setup data. The protocol rejects a
# clawback of zero, so stop before submitting.
if clawback_amount == 0:
    print(f"Error: The confidential balance of {holder.address} is already zero.")
    print("Delete confidential_transfers_setup.json and run the setup script again.")
    exit(1)

# prepare_confidential_clawback attaches the amount the issuer read and a
# Zero-Knowledge Proof (ZKP) that the amount matches the encrypted balance.
clawback_tx = prepare_confidential_clawback(
    client,
    issuer,
    holder.address,
    mpt_issuance_id,
    clawback_amount,
    issuer_privkey,
    issuer_pubkey,
    issuer_encrypted_balance,
)
print(json.dumps(clawback_tx.to_dict(), indent=2))

clawback_response = submit_and_wait(clawback_tx, client, issuer, autofill=True)
if clawback_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
    result_code = clawback_response.result["meta"]["TransactionResult"]
    print(f"Error: Unable to claw back the balance: {result_code}")
    exit(1)
print(f"Clawed back {clawback_amount} {ticker} from {holder.address}.")
print(f"{EXPLORER}/transactions/{clawback_response.result['hash']}")
```

### 6. Verify the clawback

The clawback sets both the spending balance and the inbox to encrypted zero, so every key that could read the holder's balance now reads zero. Decrypt all four copies to confirm this, one per key that has access.

JavaScript
```js
// Verify the clawback ----------------------
console.log(`\n=== Verifying the clawback... ===`)
const mptoken = await fetchMPToken(client, holder.address, mptIssuanceID)
console.log(`MPToken entry:`)
console.log(JSON.stringify(mptoken, null, 2))

const crypto = await loadMptCrypto()
const [spendingBalance, inboxBalance, issuerView, auditorView] =
  await Promise.all([
    crypto.decryptAmount(
      mptoken.ConfidentialBalanceSpending,
      holderKeys.privateKey,
      confidentialSupply
    ),
    crypto.decryptAmount(
      mptoken.ConfidentialBalanceInbox,
      holderKeys.privateKey,
      confidentialSupply
    ),
    crypto.decryptAmount(
      mptoken.IssuerEncryptedBalance,
      issuerKeys.privateKey,
      confidentialSupply
    ),
    crypto.decryptAmount(
      mptoken.AuditorEncryptedBalance,
      auditorKeys.privateKey,
      confidentialSupply
    )
  ])
console.log(`\nHolder reads its spending balance as ${spendingBalance} ${ticker}.`)
console.log(`Holder reads its inbox balance as ${inboxBalance} ${ticker}.\n`)
console.log(`Issuer reads the holder's balance as ${issuerView} ${ticker}.`)
console.log(`Auditor reads the holder's balance as ${auditorView} ${ticker}.\n`)

const issuanceAfter = await fetchMPTokenIssuance(client, mptIssuanceID)
const supplyAfter = issuanceAfter.ConfidentialOutstandingAmount
console.log(`Confidential supply after the clawback: ${supplyAfter}`)
console.log(`Total supply in circulation (public + confidential): ${issuanceAfter.OutstandingAmount}`)

await client.disconnect()
```

Python
```py
# Verify the clawback ----------------------
print("\n=== Verifying the clawback... ===")
holder_mptoken_after = client.request(
    LedgerEntry(
        mptoken=MPToken(mpt_issuance_id=mpt_issuance_id, account=holder.address),
    ),
).result["node"]
print("MPToken entry:")
print(json.dumps(holder_mptoken_after, indent=2))

spending = holder_mptoken_after["ConfidentialBalanceSpending"]
inbox = holder_mptoken_after["ConfidentialBalanceInbox"]
issuer_balance = holder_mptoken_after["IssuerEncryptedBalance"]
auditor_balance = holder_mptoken_after["AuditorEncryptedBalance"]

spending_balance = decrypt_confidential_balance(
    spending, holder_privkey, range_high=confidential_supply
)
inbox_balance = decrypt_confidential_balance(
    inbox, holder_privkey, range_high=confidential_supply
)
issuer_view = decrypt_confidential_balance(
    issuer_balance, issuer_privkey, range_high=confidential_supply
)
auditor_view = decrypt_confidential_balance(
    auditor_balance, auditor_privkey, range_high=confidential_supply
)
print(f"\nHolder reads its spending balance as {spending_balance} {ticker}.")
print(f"Holder reads its inbox balance as {inbox_balance} {ticker}.\n")
print(f"Issuer reads the holder's balance as {issuer_view} {ticker}.")
print(f"Auditor reads the holder's balance as {auditor_view} {ticker}.\n")

issuance_after = client.request(
    LedgerEntry(mpt_issuance=mpt_issuance_id),
).result["node"]
supply_after = issuance_after["ConfidentialOutstandingAmount"]
print(f"Confidential supply after the clawback: {supply_after}")
print(
    f"Total supply in circulation (public + confidential): {issuance_after['OutstandingAmount']}"
)
```

The clawed back tokens leave circulation entirely, which the issuance shows in public. `ConfidentialOutstandingAmount` drops by the amount clawed back, and `OutstandingAmount` drops with it.

## See Also

- **Concepts**:
  - [Confidential Transfers](/es-es/docs/concepts/tokens/fungible-tokens/confidential-transfers)
  - [Multi-Purpose Tokens (MPT)](/es-es/docs/concepts/tokens/fungible-tokens/multi-purpose-tokens)
  - [Clawing Back Tokens](/es-es/docs/concepts/tokens/fungible-tokens/clawing-back-tokens)
- **Tutorials**:
  - [Issue an MPT for Confidential Transfers](/es-es/docs/tutorials/tokens/mpts/issue-mpt-for-confidential-transfers)
  - [Send Confidential MPT Payments](/es-es/docs/tutorials/payments/send-confidential-payments)
- **References**:
  - [ConfidentialMPTClawback transaction](/docs/references/protocol/transactions/types/confidentialmptclawback)
  - [MPTokenIssuanceSet transaction](/docs/references/protocol/transactions/types/mptokenissuanceset)
  - [MPToken entry](/docs/references/protocol/ledger-data/ledger-entry-types/mptoken)
  - [MPTokenIssuance entry](/docs/references/protocol/ledger-data/ledger-entry-types/mptokenissuance)