# Claw Back First-Loss Capital This tutorial shows you how to claw back tokens from a [LoanBroker](/docs/references/protocol/ledger-data/ledger-entry-types/loanbroker) on the XRP Ledger. The clawback feature enables token issuers (Multi-Purpose or Trust Line) to meet regulatory standards and claw back funds even if those funds have been deposited as [first-loss capital](/docs/concepts/tokens/lending-protocol#risk-management). LendingProtocol ## Goals By the end of this tutorial, you will be able to: - Deposit an MPT as first-loss capital into a `LoanBroker` entry. - Verify the MPT issuer. - Claw back all first-loss capital from the `LoanBroker` entry. - Check the cover available for the `LoanBroker` entry. ## Prerequisites To complete this tutorial, you should: - Have a basic understanding of the XRP Ledger. - 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/javascript/build-apps/get-started) for setup steps. - **Python** with the [xrpl-py library](https://github.com/XRPLF/xrpl-py). See [Get Started Using Python](/docs/tutorials/python/build-apps/get-started) 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 code sample folder, use `npm` to install dependencies. ```bash npm install xrpl ``` Python From the code sample folder, set up a virtual environment and use `pip` to install dependencies. ```bash python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` ### 2. Set up client and accounts To get started, import the necessary libraries and instantiate a client to connect to the XRPL. This example imports: JavaScript - `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling. - `fs` and `child_process`: Used to run tutorial set up scripts. // IMPORTANT: This example deposits and claws back first-loss capital from a // preconfigured LoanBroker entry. The first-loss capital is an MPT // with clawback enabled. import fs from 'fs' import { execSync } from 'child_process' import xrpl from 'xrpl' // Connect to the network ---------------------- const client = new xrpl.Client('wss://s.devnet.rippletest.net:51233') await client.connect() Python - `xrpl`: Used for XRPL client connection, transaction submission, and wallet handling. - `json`: Used for loading and formatting JSON data. - `os`, `subprocess`, and `sys`: Used to run tutorial set up scripts. # IMPORTANT: This example deposits and claws back first-loss capital from a # preconfigured LoanBroker entry. The first-loss capital is an MPT # with clawback enabled. import json import os import subprocess import sys from xrpl.clients import JsonRpcClient from xrpl.models import LedgerEntry, LoanBrokerCoverClawback, LoanBrokerCoverDeposit, MPTAmount from xrpl.transaction import submit_and_wait from xrpl.wallet import Wallet # Set up client ---------------------- client = JsonRpcClient("https://s.devnet.rippletest.net:51234") Next, load the loan broker account, MPT issuer account, loan broker ID, and MPT ID. JavaScript // This step checks for the necessary setup data to run the lending protocol tutorials. // If missing, lendingSetup.js will generate the data. if (!fs.existsSync('lendingSetup.json')) { console.log(`\n=== Lending tutorial data doesn't exist. Running setup script... ===\n`) execSync('node lendingSetup.js', { stdio: 'inherit' }) } // Load preconfigured accounts and LoanBrokerID. const setupData = JSON.parse(fs.readFileSync('lendingSetup.json', 'utf8')) // You can replace these values with your own const loanBroker = xrpl.Wallet.fromSeed(setupData.loanBroker.seed) const mptIssuer = xrpl.Wallet.fromSeed(setupData.depositor.seed) const loanBrokerID = setupData.loanBrokerID const mptID = setupData.mptID console.log(`\nLoan broker address: ${loanBroker.address}`) console.log(`MPT issuer address: ${mptIssuer.address}`) console.log(`LoanBrokerID: ${loanBrokerID}`) console.log(`MPT ID: ${mptID}`) This example uses preconfigured accounts, MPTs, and loan broker data from the `lendingSetup.js` script, but you can replace `loanBroker`, `mptIssuer`, `loanBrokerID`, and `mptID` with your own values. Python # This step checks for the necessary setup data to run the lending protocol tutorials. # If missing, lending_setup.py will generate the data. if not os.path.exists("lending_setup.json"): print("\n=== Lending tutorial data doesn't exist. Running setup script... ===\n") subprocess.run([sys.executable, "lending_setup.py"], check=True) # Load preconfigured accounts, loan_broker_id, and mpt_id. with open("lending_setup.json") as f: setup_data = json.load(f) # You can replace these values with your own. loan_broker = Wallet.from_seed(setup_data["loan_broker"]["seed"]) mpt_issuer = Wallet.from_seed(setup_data["depositor"]["seed"]) loan_broker_id = setup_data["loan_broker_id"] mpt_id = setup_data["mpt_id"] print(f"\nLoan broker address: {loan_broker.address}") print(f"MPT issuer address: {mpt_issuer.address}") print(f"LoanBrokerID: {loan_broker_id}") print(f"MPT ID: {mpt_id}") This example uses preconfigured accounts, MPTs, and loan broker data from the `lending_setup.py` script, but you can replace `loan_broker`, `mpt_issuer`, `loan_broker_id`, and `mpt_id` with your own values. ### 3. Check initial cover available Check the initial cover (first-loss capital) available using the [ledger_entry method](/docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger_entry). JavaScript // Check cover available ---------------------- console.log(`\n=== Cover Available ===\n`) const coverInfo = await client.request({ command: 'ledger_entry', index: loanBrokerID, ledger_index: 'validated' }) let currentCoverAvailable = coverInfo.result.node.CoverAvailable || '0' console.log(`${currentCoverAvailable} TSTUSD`) Python # Check cover available ---------------------- print("\n=== Cover Available ===\n") cover_info = client.request(LedgerEntry( index=loan_broker_id, ledger_index="validated", )) current_cover_available = cover_info.result["node"].get("CoverAvailable", "0") print(f"{current_cover_available} TSTUSD") If the `CoverAvailable` field is missing, it means no first-loss capital has been deposited. ### 4. Prepare LoanBrokerCoverDeposit transaction Create the [LoanBrokerCoverDeposit transaction](/docs/references/protocol/transactions/types/loanbrokercoverdeposit) object. JavaScript // Prepare LoanBrokerCoverDeposit transaction ---------------------- console.log(`\n=== Preparing LoanBrokerCoverDeposit transaction ===\n`) const coverDepositTx = { TransactionType: 'LoanBrokerCoverDeposit', Account: loanBroker.address, LoanBrokerID: loanBrokerID, Amount: { mpt_issuance_id: mptID, value: '1000' } } // Validate the transaction structure before submitting xrpl.validate(coverDepositTx) console.log(JSON.stringify(coverDepositTx, null, 2)) The `Amount` field specifies the MPT and amount to deposit as first-loss capital. Python # Prepare LoanBrokerCoverDeposit transaction ---------------------- print("\n=== Preparing LoanBrokerCoverDeposit transaction ===\n") cover_deposit_tx = LoanBrokerCoverDeposit( account=loan_broker.address, loan_broker_id=loan_broker_id, amount=MPTAmount(mpt_issuance_id=mpt_id, value="1000"), ) print(json.dumps(cover_deposit_tx.to_xrpl(), indent=2)) The `amount` field specifies the MPT and amount to deposit as first-loss capital. If the transaction succeeds, the amount is deposited and held in the pseudo-account associated with the `LoanBroker` entry. ### 5. Submit LoanBrokerCoverDeposit transaction Sign and submit the `LoanBrokerCoverDeposit` transaction to the XRP Ledger. JavaScript // Sign, submit, and wait for deposit validation ---------------------- console.log(`\n=== Submitting LoanBrokerCoverDeposit transaction ===\n`) const depositResponse = await client.submitAndWait(coverDepositTx, { wallet: loanBroker, autofill: true }) if (depositResponse.result.meta.TransactionResult !== 'tesSUCCESS') { const resultCode = depositResponse.result.meta.TransactionResult console.error('Error: Unable to deposit cover:', resultCode) await client.disconnect() process.exit(1) } console.log('Cover deposit successful!') Python # Sign, submit, and wait for deposit validation ---------------------- print("\n=== Submitting LoanBrokerCoverDeposit transaction ===\n") deposit_response = submit_and_wait(cover_deposit_tx, client, loan_broker) if deposit_response.result["meta"]["TransactionResult"] != "tesSUCCESS": result_code = deposit_response.result["meta"]["TransactionResult"] print(f"Error: Unable to deposit cover: {result_code}") sys.exit(1) print("Cover deposit successful!") Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. ### 6. Check updated cover available Retrieve the cover available from the transaction result by checking the `LoanBroker` entry in the transaction metadata. JavaScript // Extract updated cover available after deposit ---------------------- console.log(`\n=== Cover Available After Deposit ===\n`) let loanBrokerNode = depositResponse.result.meta.AffectedNodes.find(node => node.ModifiedNode?.LedgerEntryType === 'LoanBroker' ) currentCoverAvailable = loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable console.log(`${currentCoverAvailable} TSTUSD`) Python # Extract updated cover available after deposit ---------------------- print("\n=== Cover Available After Deposit ===\n") loan_broker_node = next( node for node in deposit_response.result["meta"]["AffectedNodes"] if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker" ) current_cover_available = loan_broker_node["ModifiedNode"]["FinalFields"]["CoverAvailable"] print(f"{current_cover_available} TSTUSD") ### 7. Verify the asset issuer Before executing a clawback, verify that the account submitting the transaction is the same as the asset issuer. JavaScript // Verify issuer of cover asset matches ---------------------- // Only the issuer of the asset can submit clawback transactions. // The asset must also have clawback enabled. console.log(`\n=== Verifying Asset Issuer ===\n`) const assetIssuerInfo = await client.request({ command: 'ledger_entry', mpt_issuance: mptID, ledger_index: 'validated' }) if (assetIssuerInfo.result.node.Issuer !== mptIssuer.address) { console.error(`Error: ${assetIssuerInfo.result.node.Issuer} does not match account (${mptIssuer.address}) attempting clawback!`) await client.disconnect() process.exit(1) } console.log(`MPT issuer account verified: ${mptIssuer.address}. Proceeding to clawback.`) Python # Verify issuer of cover asset matches ---------------------- # Only the issuer of the asset can submit clawback transactions. # The asset must also have clawback enabled. print("\n=== Verifying Asset Issuer ===\n") asset_issuer_info = client.request(LedgerEntry( mpt_issuance=mpt_id, ledger_index="validated", )) if asset_issuer_info.result["node"]["Issuer"] != mpt_issuer.address: issuer = asset_issuer_info.result["node"]["Issuer"] print(f"Error: {issuer} does not match account ({mpt_issuer.address}) attempting clawback!") sys.exit(1) print(f"MPT issuer account verified: {mpt_issuer.address}. Proceeding to clawback.") Clawback functionality is disabled by default. In the case of MPTs, the `tfMPTCanClawback` flag must be enabled when the [MPTokenIssuanceCreate transaction](/docs/references/protocol/transactions/types/mptokenissuancecreate) is submitted. This tutorial uses an MPT that is already configured for clawback. ### 8. Prepare LoanBrokerCoverClawback transaction Create the [LoanBrokerCoverClawback transaction](/docs/references/protocol/transactions/types/loanbrokercoverclawback) object. JavaScript // Prepare LoanBrokerCoverClawback transaction ---------------------- console.log(`\n=== Preparing LoanBrokerCoverClawback transaction ===\n`) const coverClawbackTx = { TransactionType: 'LoanBrokerCoverClawback', Account: mptIssuer.address, LoanBrokerID: loanBrokerID, Amount: { mpt_issuance_id: mptID, value: currentCoverAvailable } } // Validate the transaction structure before submitting xrpl.validate(coverClawbackTx) console.log(JSON.stringify(coverClawbackTx, null, 2)) Python # Prepare LoanBrokerCoverClawback transaction ---------------------- print("\n=== Preparing LoanBrokerCoverClawback transaction ===\n") cover_clawback_tx = LoanBrokerCoverClawback( account=mpt_issuer.address, loan_broker_id=loan_broker_id, amount=MPTAmount(mpt_issuance_id=mpt_id, value=current_cover_available), ) print(json.dumps(cover_clawback_tx.to_xrpl(), indent=2)) In this example we claw back the entire amount, but you can specify any amount so long as it doesn't exceed the available cover or reduce the cover below the minimum required by the `LoanBroker`. ### 9. Submit LoanBrokerCoverClawback transaction Sign and submit the `LoanBrokerCoverClawback` transaction to the XRP Ledger. JavaScript // Sign, submit, and wait for clawback validation ---------------------- console.log(`\n=== Submitting LoanBrokerCoverClawback transaction ===\n`) const clawbackResponse = await client.submitAndWait(coverClawbackTx, { wallet: mptIssuer, autofill: true }) if (clawbackResponse.result.meta.TransactionResult !== 'tesSUCCESS') { const resultCode = clawbackResponse.result.meta.TransactionResult console.error('Error: Unable to clawback cover:', resultCode) await client.disconnect() process.exit(1) } console.log(`Successfully clawed back ${currentCoverAvailable} TSTUSD!`) Python # Sign, submit, and wait for clawback validation ---------------------- print("\n=== Submitting LoanBrokerCoverClawback transaction ===\n") clawback_response = submit_and_wait(cover_clawback_tx, client, mpt_issuer) if clawback_response.result["meta"]["TransactionResult"] != "tesSUCCESS": result_code = clawback_response.result["meta"]["TransactionResult"] print(f"Error: Unable to clawback cover: {result_code}") sys.exit(1) print(f"Successfully clawed back {current_cover_available} TSTUSD!") Verify that the transaction succeeded by checking for a `tesSUCCESS` result code. ### 10. Check final cover available Retrieve the final cover available from the transaction result. JavaScript // Extract final cover available ---------------------- console.log(`\n=== Final Cover Available After Clawback ===\n`) loanBrokerNode = clawbackResponse.result.meta.AffectedNodes.find(node => node.ModifiedNode?.LedgerEntryType === 'LoanBroker' ) console.log(`${loanBrokerNode.ModifiedNode.FinalFields.CoverAvailable || '0'} TSTUSD`) await client.disconnect() Python # Extract final cover available ---------------------- print("\n=== Final Cover Available After Clawback ===\n") loan_broker_node = next( node for node in clawback_response.result["meta"]["AffectedNodes"] if node.get("ModifiedNode", {}).get("LedgerEntryType") == "LoanBroker" ) print(f"{loan_broker_node['ModifiedNode']['FinalFields'].get('CoverAvailable', '0')} TSTUSD") ## See Also **Concepts**: - [Lending Protocol](/docs/concepts/tokens/lending-protocol) - [Clawing Back Tokens](/docs/concepts/tokens/fungible-tokens/clawing-back-tokens) **Tutorials**: - [Issue a Multi-Purpose Token (MPT)](/docs/tutorials/how-tos/use-tokens/issue-a-multi-purpose-token) **References**: - [LoanBrokerCoverDeposit transaction](/docs/references/protocol/transactions/types/loanbrokercoverdeposit) - [LoanBrokerCoverClawback transaction](/docs/references/protocol/transactions/types/loanbrokercoverclawback) - [MPTokenIssuanceCreate transaction](/docs/references/protocol/transactions/types/mptokenissuancecreate)