# Send Confidential MPT Payments

This tutorial shows you how to send a [confidential Multi-Purpose Token (MPT) payment](/es-es/docs/concepts/tokens/fungible-tokens/confidential-transfers), where the amount sent is encrypted instead of being publicly visible.

In the example, a *seller* sends a tokenized fund and a *buyer* pays for it with a different token (for example, a stablecoin). This is coordinated by an *orchestrator*, which represents a third party, such as an exchange. The orchestrator uses a [Batch transaction](/docs/references/protocol/transactions/types/batch) to settle both confidential payments atomically, without ever knowing the amounts involved.

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

- Prepare a confidential MPT payment, including the Zero-Knowledge Proof (ZKP) that the ledger validates it against.
- Settle two confidential payments atomically with a [Batch transaction](/docs/references/protocol/transactions/types/batch).
- Confirm each payment settled, and decrypt the resulting balances as a holder and as an 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.
- Have two accounts that each hold a confidential balance of a different MPT, with their encryption public keys registered on-ledger. See [Issue an MPT for Confidential Transfers](/es-es/docs/tutorials/tokens/mpts/issue-mpt-for-confidential-transfers#7-convert-the-public-balance-to-a-confidential-balance). 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 {
  BatchFlags,
  Client,
  GlobalFlags,
  Wallet,
  combineBatchSigners,
  deriveConfidentialKeypair,
  fetchMPToken,
  fetchMPTokenIssuance,
  getConfidentialBalance,
  hashes,
  loadMptCrypto,
  prepareConfidentialMergeInbox,
  prepareConfidentialSend,
  signMultiBatch,
  validate
} 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 key generation, confidential transaction builders, 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_merge_inbox,
    prepare_confidential_send,
)
from xrpl.models import Batch, LedgerEntry, Tx
from xrpl.models.requests.ledger_entry import MPToken
from xrpl.models.transactions import ConfidentialMPTSend, Transaction
from xrpl.models.transactions.batch import BatchFlag
from xrpl.models.transactions.transaction import TransactionFlag
from xrpl.transaction import (
    autofill,
    combine_batch_signers,
    sign_multiaccount_batch,
    submit_and_wait,
)
from xrpl.wallet import Wallet, generate_faucet_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 accounts, MPT issuance IDs, and encryption keys. This example uses pre-configured data from the setup script, including public and private keys, but you can replace these with your own values.

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 { wallet: orchestrator } = await client.fundWallet()
const seller = Wallet.fromSeed(setupData.seller.seed)
const buyer = Wallet.fromSeed(setupData.buyer.seed)
const auditor = Wallet.fromSeed(setupData.auditor.seed)

// deriveConfidentialKeypair rebuilds a confidential encryption keypair from an
// account seed. The same seed always gives the same keypair.
const sellerKeys = deriveConfidentialKeypair(seller.seed)
const buyerKeys = deriveConfidentialKeypair(buyer.seed)
const auditorKeys = deriveConfidentialKeypair(auditor.seed)

console.log(`Orchestrator address: ${orchestrator.address}`)
console.log(`Seller address: ${seller.address}`)
console.log(`Buyer address: ${buyer.address}`)
console.log(`Auditor address: ${auditor.address}`)
```

In `xrpl.js`, the `deriveConfidentialKeypair` function rebuilds a confidential encryption keypair from an account seed, so the same seed always gives you the same keypair.

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)

fund = setup_data["fund"]
stablecoin = setup_data["stablecoin"]

# Set up accounts
print("\n=== Getting accounts... ===")
orchestrator = generate_faucet_wallet(client)
seller = Wallet.from_seed(setup_data["seller"]["seed"])
buyer = Wallet.from_seed(setup_data["buyer"]["seed"])
auditor = Wallet.from_seed(setup_data["auditor"]["seed"])

# A confidential encryption keypair is generated at random and can't be rebuilt
# from an account seed, so the setup script saved each one it created.
seller_privkey = setup_data["seller"]["privateKey"]
seller_pubkey = setup_data["seller"]["publicKey"]
buyer_privkey = setup_data["buyer"]["privateKey"]
buyer_pubkey = setup_data["buyer"]["publicKey"]
issuer_pubkey = setup_data["issuer"]["publicKey"]
auditor_privkey = setup_data["auditor"]["privateKey"]
auditor_pubkey = setup_data["auditor"]["publicKey"]

print(f"Orchestrator address: {orchestrator.address}")
print(f"Seller address: {seller.address}")
print(f"Buyer address: {buyer.address}")
print(f"Auditor address: {auditor.address}")
```

`xrpl-py` generates a confidential encryption keypair at random and can't rebuild it from an account seed, so this example reads back the keys that the setup script saved.

Caution
For testing purposes, the example 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. Make each holder's balance spendable

The starting balances that the setup script distributed are in each holder's `ConfidentialBalanceInbox`, and a confidential payment can only spend from `ConfidentialBalanceSpending`.

Submit a [ConfidentialMPTMergeInbox transaction](/docs/references/protocol/transactions/types/confidentialmptmergeinbox) to move those funds into each holder's spending balance.

JavaScript
```js
// Make each holder's balance spendable ----------------------
console.log(`\n=== Merging inbox balance for seller and buyer... ===`)
const tokens = [setupData.fund, setupData.stablecoin]
const holders = [
  { name: 'Seller', holder: seller, keys: sellerKeys },
  { name: 'Buyer', holder: buyer, keys: buyerKeys }
]
const holdings = [
  { ...setupData.fund, holder: seller },
  { ...setupData.stablecoin, holder: buyer }
]

for (const { ticker, mptIssuanceID, holder } of holdings) {
  const mergeTx = await prepareConfidentialMergeInbox(client, {
    account: holder.address,
    mptIssuanceID
  })
  const mergeResponse = await client.submitAndWait(mergeTx, {
    wallet: holder,
    autofill: true
  })

  const mergeResult = mergeResponse.result.meta.TransactionResult
  if (mergeResult !== 'tesSUCCESS') {
    console.error(`Error: Unable to merge the ${ticker} inbox:`, mergeResult)
    await client.disconnect()
    process.exit(1)
  }
  console.log(`${holder.address} holds spendable confidential ${ticker}.`)
  console.log(`${EXPLORER}/transactions/${mergeResponse.result.hash}\n`)
}
```

Python
```py
# Make each holder's balance spendable ----------------------
print("\n=== Merging inbox balance for seller and buyer... ===")
tokens = (fund, stablecoin)
holders = (
    ("Seller", seller, seller_privkey),
    ("Buyer", buyer, buyer_privkey),
)
holdings = ((fund, seller), (stablecoin, buyer))

for token, holder in holdings:
    merge_tx = prepare_confidential_merge_inbox(client, holder, token["mptIssuanceID"])
    merge_response = submit_and_wait(merge_tx, client, holder, autofill=True)
    merge_result = merge_response.result["meta"]["TransactionResult"]
    if merge_result != "tesSUCCESS":
        print(f"Error: Unable to merge the {token['ticker']} inbox: {merge_result}")
        exit(1)
    print(f"{holder.address} holds spendable confidential {token['ticker']}.")
    print(f"{EXPLORER}/transactions/{merge_response.result['hash']}\n")
```

### 4. Prepare the ConfidentialMPTSend transactions

To make a confidential payment, you must submit a [ConfidentialMPTSend transaction](/docs/references/protocol/transactions/types/confidentialmptsend).

Create each transaction with the confidential send helper function: `prepareConfidentialSend` in `xrpl.js`, or `prepare_confidential_send` in `xrpl-py`. The helper encrypts the amount under the four public keys, and generates the Zero-Knowledge Proof in the transaction's `ZKProof` field that the ledger validates the transfer against. Without revealing the amount, the proof shows that:

- Every encrypted amount on the transaction encrypts the same value.
- The balance being spent is the one the ledger holds for the sender.
- The sender's remaining balance doesn't go negative.


The proof is also bound to the sender's sequence number, so don't submit anything else from either account between preparing the payments and submitting the `Batch`.

JavaScript
```js
// Build both confidential payments ----------------------
const fundAmount = BigInt(100)
const cashAmount = BigInt(500)

const [fundPayment, cashPayment] = await Promise.all([
  prepareConfidentialSend(client, {
    account: seller.address,
    destination: buyer.address,
    mptIssuanceID: setupData.fund.mptIssuanceID,
    amount: fundAmount,
    senderKeypair: sellerKeys
  }),
  prepareConfidentialSend(client, {
    account: buyer.address,
    destination: seller.address,
    mptIssuanceID: setupData.stablecoin.mptIssuanceID,
    amount: cashAmount,
    senderKeypair: buyerKeys
  })
])

console.log(`=== Prepared confidential payments ===`)
console.log(`Payment1 (Fund): ${JSON.stringify(fundPayment, null, 2)}`)
console.log(`\nPayment2 (Cash): ${JSON.stringify(cashPayment, null, 2)}`)

// Every inner batch transaction must have the tfInnerBatchTxn flag set.
fundPayment.Flags = GlobalFlags.tfInnerBatchTxn
cashPayment.Flags = GlobalFlags.tfInnerBatchTxn
```

Python
```py
# Build both confidential payments ----------------------
fund_amount = 100
cash_amount = 500

fund_payment = prepare_confidential_send(
    client,
    seller,
    buyer.address,
    fund["mptIssuanceID"],
    fund_amount,
    seller_privkey,
    seller_pubkey,
    buyer_pubkey,
    issuer_pubkey,
    auditor_pubkey,
)
cash_payment = prepare_confidential_send(
    client,
    buyer,
    seller.address,
    stablecoin["mptIssuanceID"],
    cash_amount,
    buyer_privkey,
    buyer_pubkey,
    seller_pubkey,
    issuer_pubkey,
    auditor_pubkey,
)

print("=== Prepared confidential payments ===")
print(f"Payment1 (Fund): {json.dumps(fund_payment.to_xrpl(), indent=2)}")
print(f"\nPayment2 (Cash): {json.dumps(cash_payment.to_xrpl(), indent=2)}")

# Every inner Batch transaction needs the tfInnerBatchTxn flag and a Fee of 0.
fund_payment, cash_payment = [
    ConfidentialMPTSend.from_dict(
        {**payment.to_dict(), "fee": "0", "flags": TransactionFlag.TF_INNER_BATCH_TXN}
    )
    for payment in (fund_payment, cash_payment)
]
```

### 5. Submit confidential payments

Use a [Batch transaction](/docs/references/protocol/transactions/types/batch) to submit both confidential payments atomically with the `tfAllOrNothing` flag. This ensures that if there is a failure on either side, the whole transaction reverts.

Inner transactions must have a `Fee` of `0`, so the outer `Batch` pays for everything inside it. That means 10 times the standard [transaction cost](/es-es/docs/concepts/transactions/transaction-cost) for each confidential payment, plus twice the standard cost for the `Batch` itself, plus one standard cost for each signer.

`autofill` covers each payment's full cost, so you only need to tell it how many inner signers to expect.

JavaScript
```js
// Settle both payments atomically ----------------------
console.log(`=== Submit confidential payments in batch... ===`)
console.log(`Seller sends ${setupData.fund.ticker} to Buyer.`)
console.log(`Buyer sends ${setupData.stablecoin.ticker} to Seller.\n`)

const batchTx = {
  TransactionType: 'Batch',
  Account: orchestrator.address,
  Flags: BatchFlags.tfAllOrNothing,
  RawTransactions: [
    { RawTransaction: fundPayment },
    { RawTransaction: cashPayment }
  ]
}
validate(batchTx)

const autofilledBatchTx = await client.autofill(batchTx, 2)

const sellerBatch = { ...autofilledBatchTx }
signMultiBatch(seller, sellerBatch)
const buyerBatch = { ...autofilledBatchTx }
signMultiBatch(buyer, buyerBatch)
const combinedBatchTx = combineBatchSigners([sellerBatch, buyerBatch])

const batchResponse = await client.submitAndWait(combinedBatchTx, {
  wallet: orchestrator
})
if (batchResponse.result.meta.TransactionResult !== 'tesSUCCESS') {
  const resultCode = batchResponse.result.meta.TransactionResult
  console.error('Error: Unable to submit the Batch:', resultCode)
  await client.disconnect()
  process.exit(1)
}
console.log(`Batch transaction hash: ${batchResponse.result.hash}`)
console.log(`${EXPLORER}/transactions/${batchResponse.result.hash}`)
```

Python
```py
# Settle both payments atomically ----------------------
print("\n=== Submit confidential payments in batch... ===")
print(f"Seller sends {fund['ticker']} to Buyer.")
print(f"Buyer sends {stablecoin['ticker']} to Seller.\n")

batch_tx = Batch(
    account=orchestrator.address,
    flags=BatchFlag.TF_ALL_OR_NOTHING,
    raw_transactions=[fund_payment, cash_payment],
)

autofilled_batch_tx = autofill(batch_tx, client, 2)

seller_batch = sign_multiaccount_batch(seller, autofilled_batch_tx)
buyer_batch = sign_multiaccount_batch(buyer, autofilled_batch_tx)
combined_batch_tx = combine_batch_signers([seller_batch, buyer_batch])

batch_response = submit_and_wait(combined_batch_tx, client, orchestrator)
if batch_response.result["meta"]["TransactionResult"] != "tesSUCCESS":
    result_code = batch_response.result["meta"]["TransactionResult"]
    print(f"Error: Unable to submit the Batch: {result_code}")
    exit(1)
print(f"Batch transaction hash: {batch_response.result['hash']}")
print(f"{EXPLORER}/transactions/{batch_response.result['hash']}")
```

The *seller* and *buyer* sign the Batch for their own inner payment, then the two sets of signers are combined and the *orchestrator* submits it.

### 6. Verify each payment individually

A `tesSUCCESS` on the `Batch` only means the `Batch` itself was well-formed. Each inner payment has its own result, so you must verify the result of each inner transaction.

JavaScript
```js
// Verify each payment individually ----------------------
// A tesSUCCESS on the Batch only means the Batch itself was well-formed. 
// Hash each inner transaction and look it up to confirm both payments applied.
console.log(`\n=== Verifying both payments... ===`)
const rawTransactions = batchResponse.result.tx_json.RawTransactions

for (const [index, { RawTransaction }] of rawTransactions.entries()) {
  const innerHash = hashes.hashSignedTx(RawTransaction)
  const innerTx = await client.request({ command: 'tx', transaction: innerHash })
  const innerResult = innerTx.result.meta.TransactionResult
  console.log(`Payment ${index + 1}: ${innerResult}`)
  console.log(`${EXPLORER}/transactions/${innerHash}`)

  if (innerResult !== 'tesSUCCESS') {
    console.error('Error: An inner payment failed:', innerResult)
    await client.disconnect()
    process.exit(1)
  }
}
console.log(`\nPayments both successful!`)
```

Python
```py
# Verify each payment individually ----------------------
# A tesSUCCESS on the Batch only means the Batch itself was well-formed.
# Hash each inner transaction and look it up to confirm both payments applied.
print("\n=== Verifying both payments... ===")
raw_transactions = batch_response.result["tx_json"]["RawTransactions"]
for index, raw_transaction in enumerate(raw_transactions):
    inner = Transaction.from_xrpl(raw_transaction["RawTransaction"])
    inner_hash = inner.get_hash()
    inner_result = client.request(Tx(transaction=inner_hash)).result["meta"][
        "TransactionResult"
    ]
    print(f"Payment {index + 1}: {inner_result}")
    print(f"{EXPLORER}/transactions/{inner_hash}")

    if inner_result != "tesSUCCESS":
        print(f"Error: An inner payment failed: {inner_result}")
        exit(1)
print("\nPayments both successful!")
```

With `tfAllOrNothing`, both inner results are either `tesSUCCESS` or reverted together, so this check confirms the settlement applied as intended.

### 7. Merge the received confidential amounts

Now that each confidential payment has settled, each amount is in its recipient's `ConfidentialBalanceInbox`. A holder can decrypt an inbox amount, but can't spend it.

Merge the confidential balances so both holders can spend them later.

JavaScript
```js
// Merge the received amounts into each spending balance ----------------------
console.log(`\n=== Merging settled amounts into spending balance... ===`)
const settlements = [
  { ...setupData.fund, recipient: buyer },
  { ...setupData.stablecoin, recipient: seller }
]

for (const { ticker, mptIssuanceID, recipient } of settlements) {
  const mergeTx = await prepareConfidentialMergeInbox(client, {
    account: recipient.address,
    mptIssuanceID
  })
  const mergeResponse = await client.submitAndWait(mergeTx, {
    wallet: recipient,
    autofill: true
  })
  const mergeResult = mergeResponse.result.meta.TransactionResult
  if (mergeResult !== 'tesSUCCESS') {
    console.error(`Error: Unable to merge the ${ticker} inbox:`, mergeResult)
    await client.disconnect()
    process.exit(1)
  }
  console.log(`${recipient.address} can spend the ${ticker} it received.`)
  console.log(`${EXPLORER}/transactions/${mergeResponse.result.hash}\n`)
}
```

Python
```py
# Merge the received amounts into each spending balance ----------------------
print("\n=== Merging settled amounts into spending balance... ===")
settlements = ((fund, buyer), (stablecoin, seller))

for token, recipient in settlements:
    merge_tx = prepare_confidential_merge_inbox(
        client, recipient, token["mptIssuanceID"]
    )
    merge_response = submit_and_wait(merge_tx, client, recipient, autofill=True)
    merge_result = merge_response.result["meta"]["TransactionResult"]
    if merge_result != "tesSUCCESS":
        print(f"Error: Unable to merge the {token['ticker']} inbox: {merge_result}")
        exit(1)
    print(f"{recipient.address} can spend the {token['ticker']} it received.")
    print(f"{EXPLORER}/transactions/{merge_response.result['hash']}\n")
```

### 8. Decrypt the balances and settled amounts

Reading a confidential amount requires an encryption private key, so only the two holders and the auditor can decrypt anything here.

First, decrypt each holder's balance on both issuances.

JavaScript
```js
// Decrypt balances as each holder ----------------------
console.log(`=== Decrypting balances as each holder... ===`)
for (const { name, holder, keys } of holders) {
  console.log(`${name} reads its own balance as:`)
  for (const token of tokens) {
    const balance = await getConfidentialBalance(
      client,
      holder.address,
      token.mptIssuanceID,
      keys.privateKey
    )
    console.log(`     - ${balance} ${token.ticker}`)
  }
}
```

Python
```py
# Decrypt balances as each holder ----------------------
confidential_supplies = {}
for token in tokens:
    issuance = client.request(
        LedgerEntry(mpt_issuance=token["mptIssuanceID"]),
    ).result["node"]
    confidential_supplies[token["mptIssuanceID"]] = int(
        issuance["ConfidentialOutstandingAmount"]
    )

print("=== Decrypting balances as each holder... ===")
for name, holder, holder_privkey in holders:
    print(f"{name} reads its own balance as:")
    for token in tokens:
        mptoken = client.request(
            LedgerEntry(
                mptoken=MPToken(
                    mpt_issuance_id=token["mptIssuanceID"], account=holder.address
                ),
            ),
        ).result["node"]
        balance = decrypt_confidential_balance(
            mptoken["ConfidentialBalanceSpending"],
            holder_privkey,
            range_high=confidential_supplies[token["mptIssuanceID"]],
        )
        print(f"     - {balance} {token['ticker']}")
```

Each holder reads `ConfidentialBalanceSpending`, and only with its own encryption private key, so neither can read the other's balance.

Then, as the auditor, decrypt both balances along with the amount each payment moved.

JavaScript
```js
// Decrypt the balances and amounts as the auditor ----------------------
console.log(`\n=== Decrypting balances and amounts as the auditor... ===`)
const crypto = await loadMptCrypto()

const confidentialSupplies = {}
for (const token of tokens) {
  const issuance = await fetchMPTokenIssuance(client, token.mptIssuanceID)
  confidentialSupplies[token.mptIssuanceID] = BigInt(
    issuance.ConfidentialOutstandingAmount
  )
}

for (const { name, holder } of holders) {
  console.log(`Auditor reads the ${name.toLowerCase()}'s balance as:`)
  for (const token of tokens) {
    const mptoken = await fetchMPToken(
      client,
      holder.address,
      token.mptIssuanceID
    )
    const auditorView = await crypto.decryptAmount(
      mptoken.AuditorEncryptedBalance,
      auditorKeys.privateKey,
      confidentialSupplies[token.mptIssuanceID]
    )
    console.log(`     - ${auditorView} ${token.ticker}`)
  }
}

console.log(`\nAuditor reads the settled amounts as:`)
const settled = [
  { token: setupData.fund, payment: fundPayment },
  { token: setupData.stablecoin, payment: cashPayment }
]
for (const { token, payment } of settled) {
  const settledAmount = await crypto.decryptAmount(
    payment.AuditorEncryptedAmount,
    auditorKeys.privateKey,
    confidentialSupplies[token.mptIssuanceID]
  )
  console.log(`     - ${settledAmount} ${token.ticker}`)
}

await client.disconnect()
```

Python
```py
# Decrypt the balances and amounts as the auditor ----------------------
print("\n=== Decrypting balances and amounts as the auditor... ===")
for name, holder, _ in holders:
    print(f"Auditor reads the {name.lower()}'s balance as:")
    for token in tokens:
        mptoken = client.request(
            LedgerEntry(
                mptoken=MPToken(
                    mpt_issuance_id=token["mptIssuanceID"], account=holder.address
                ),
            ),
        ).result["node"]
        auditor_view = decrypt_confidential_balance(
            mptoken["AuditorEncryptedBalance"],
            auditor_privkey,
            range_high=confidential_supplies[token["mptIssuanceID"]],
        )
        print(f"     - {auditor_view} {token['ticker']}")

print("\nAuditor reads the settled amounts as:")
settled = ((fund, fund_payment), (stablecoin, cash_payment))

for token, payment in settled:
    settled_amount = decrypt_confidential_balance(
        payment.auditor_encrypted_amount,
        auditor_privkey,
        range_high=confidential_supplies[token["mptIssuanceID"]],
    )
    print(f"     - {settled_amount} {token['ticker']}")
```

The auditor decrypts `AuditorEncryptedBalance` on the same entries with its own encryption private key, so one key reads both sides of the settlement. The same key decrypts `AuditorEncryptedAmount` on each payment, which is the amount that moved rather than the balance it landed in.

Everyone else (for example, the *orchestrator*) only sees the encrypted amounts, on the balances and on the payments that moved them.

## See Also

- **Concepts**:
  - [Confidential Transfers](/es-es/docs/concepts/tokens/fungible-tokens/confidential-transfers)
  - [Batch Transactions](/es-es/docs/concepts/transactions/batch-transactions)
  - [Multi-Purpose Tokens (MPT)](/es-es/docs/concepts/tokens/fungible-tokens/multi-purpose-tokens)
- **Tutorials**:
  - [Issue an MPT for Confidential Transfers](/es-es/docs/tutorials/tokens/mpts/issue-mpt-for-confidential-transfers)
- **References**:
  - [Batch transaction](/docs/references/protocol/transactions/types/batch)
  - [ConfidentialMPTMergeInbox transaction](/docs/references/protocol/transactions/types/confidentialmptmergeinbox)
  - [ConfidentialMPTSend transaction](/docs/references/protocol/transactions/types/confidentialmptsend)
  - [MPToken entry](/docs/references/protocol/ledger-data/ledger-entry-types/mptoken)