{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars.yaml","index-page-items":"/docs/tutorials/payments/send-confidential-payments_index-page-items"},"props":{"metadata":{"markdoc":{"tagList":["amendment-disclaimer","repo-link","tabs","tab","code-snippet","admonition"]},"redocly_category":"Documentation","type":"markdown","indexPage":true},"seo":{"title":"Send Confidential MPT Payments","siteUrl":"https://xrpl.org/","llmstxt":{"hide":false,"title":"XRPL Developer Portal & Documentation","description":"Explore XRP Ledger documentation, blogs, and other blockchain developer resources needed to start building and integrating with the ledger.","details":{"content":"XRP Ledger concepts, use cases, tutorials, references, and other blockchain developer resources. Also, stay up to date with release announcements and more through the XRPL Blog."},"sections":[{"title":"Introduction","description":"A high-level introduction to the XRP Ledger.","includeFiles":["docs/introduction/**/*.*","about/faq.md"],"excludeFiles":["docs/introduction/index.md"]},{"title":"Use Cases","description":"Real-world applications and business scenarios for the XRP Ledger.","includeFiles":["docs/use-cases/**/*.*"],"excludeFiles":["docs/use-cases/index.md","docs/use-cases/defi/index.md"]},{"title":"Agentic Transactions","description":"XRPL AI Starter Kit to help autonomous agents discover, set up, and execute agentic transactions on the XRP Ledger.","includeFiles":["docs/agents/**/*.*"],"excludeFiles":[]},{"title":"Concepts","description":"Core concepts including accounts, tokens, transactions, consensus, and more.","includeFiles":["docs/concepts/**/*.*"],"excludeFiles":["docs/concepts/index.md","docs/concepts/decentralized-storage/index.md","docs/concepts/payment-types/index.md"]},{"title":"Tutorials","description":"Step-by-step guides for building on the XRP Ledger in JavaScript, Python, Go, and more.","includeFiles":["docs/tutorials/**/*.*"],"excludeFiles":[]},{"title":"References","description":"Protocol specification, transaction types, ledger entries, and API methods.","includeFiles":["docs/references/**/*.*"],"excludeFiles":["docs/references/xrp-api.md","docs/references/data-api.md","docs/references/protocol/index.md","docs/references/protocol/ledger-data/ledger-entry-types/index.md","docs/references/protocol/transactions/index.md","docs/references/protocol/transactions/types/index.md","docs/references/http-websocket-apis/api-conventions/index.md","docs/references/http-websocket-apis/public-api-methods/*/index.md","docs/references/http-websocket-apis/admin-api-methods/*/index.md"]},{"title":"Infrastructure","description":"Install, configure, and troubleshoot xrpld and Clio servers.","includeFiles":["docs/infrastructure/**/*.*"],"excludeFiles":["docs/infrastructure/index.md","docs/infrastructure/*/index.md","docs/infrastructure/installation/build-run-xrpld-in-reporting-mode.md","docs/infrastructure/configuration/data-retention/index.md","docs/infrastructure/configuration/server-modes/index.md"]},{"title":"Blog (2023+)","description":"Recent XRPL Blog posts (showing 2023 and newer).","includeFiles":["blog/2023/**/*.*","blog/2024/**/*.*","blog/2025/**/*.*","blog/2026/**/*.*"],"excludeFiles":[]},{"title":"Resources","description":"Developer resources and contribution guidelines.","includeFiles":["resources/**/*.*"],"excludeFiles":["resources/index.md"]}],"excludeFiles":[]},"description":"Settle two confidential Multi-Purpose Token (MPT) payments atomically with a Batch transaction, then read the result as a holder and as an auditor."},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"send-confidential-mpt-payments","__idx":0},"children":["Send Confidential MPT Payments"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["This tutorial shows you how to send a ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/concepts/tokens/fungible-tokens/confidential-transfers"},"children":["confidential Multi-Purpose Token (MPT) payment"]},", where the amount sent is encrypted instead of being publicly visible."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In the example, a ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["seller"]}," sends a tokenized fund and a ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["buyer"]}," pays for it with a different token (for example, a stablecoin). This is coordinated by an ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["orchestrator"]},", which represents a third party, such as an exchange. The orchestrator uses a ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/references/protocol/transactions/types/batch"},"children":["Batch transaction"]}," to settle both confidential payments atomically, without ever knowing the amounts involved."]},{"$$mdtype":"Tag","name":"AmendmentDisclaimer","attributes":{"name":"ConfidentialTransfer","compact":false,"statusOnly":false,"mode":""},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"goals","__idx":1},"children":["Goals"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["By the end of this tutorial, you will be able to:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Prepare a confidential MPT payment, including the Zero-Knowledge Proof (ZKP) that the ledger validates it against."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Settle two confidential payments atomically with a ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/references/protocol/transactions/types/batch"},"children":["Batch transaction"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Confirm each payment settled, and decrypt the resulting balances as a holder and as an auditor."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"prerequisites","__idx":2},"children":["Prerequisites"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To complete this tutorial, you should:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Have a basic understanding of the XRP Ledger and the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/concepts/tokens/fungible-tokens/confidential-transfers"},"children":["Confidential Transfers"]}," concept."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Have two accounts that each hold a confidential balance of a different MPT, with their encryption public keys registered on-ledger. See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/tutorials/tokens/mpts/issue-mpt-for-confidential-transfers#7-convert-the-public-balance-to-a-confidential-balance"},"children":["Issue an MPT for Confidential Transfers"]},". The setup script for this tutorial creates them for you."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Have an XRP Ledger client library set up in your development environment. This page provides examples for the following:",{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["JavaScript"]}," with the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://github.com/XRPLF/xrpl.js"},"children":["xrpl.js library"]},". See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/tutorials/get-started/get-started-javascript"},"children":["Get Started Using JavaScript"]}," for setup steps."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Python"]}," with the ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"https://github.com/XRPLF/xrpl-py"},"children":["xrpl-py library"]},". See ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/tutorials/get-started/get-started-python"},"children":["Get Started Using Python"]}," for setup steps."]}]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"source-code","__idx":3},"children":["Source Code"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["You can find the complete source code for this tutorial's examples in the ",{"$$mdtype":"Tag","name":"RepoLink","attributes":{"path":"_code-samples/confidential-transfers/","github_fork":"https://github.com/XRPLF/xrpl-dev-portal","github_branch":"master"},"children":["code samples section of this website's repository"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"steps","__idx":4},"children":["Steps"]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"1-install-dependencies","__idx":5},"children":["1. Install dependencies"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["From the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["js/"]}," folder, use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["npm"]}," to install dependencies."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"sh","header":{"controls":{"copy":{}}},"source":"npm install\n","lang":"sh"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["From the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["py/"]}," folder, set up a virtual environment and use ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["pip"]}," to install dependencies."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"sh","header":{"controls":{"copy":{}}},"source":"python3 -m venv .venv\nsource .venv/bin/activate\npip install -r requirements.txt\n","lang":"sh"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"2-set-up-the-client-and-accounts","__idx":6},"children":["2. Set up the client and accounts"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To get started, import the necessary libraries and instantiate a client to connect to the XRP Ledger. This example imports:"]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["fs"]},": Used to check for and load the tutorial setup data."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["xrpl"]},": Used for XRPL client connection, transaction submission, and wallet handling."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["./confidentialTransfersSetup.js"]},": The tutorial setup script, imported and called directly."]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/js/sendConfidentialPayments.js","language":"js","header":{"controls":{"copy":{}}},"lang":"js","source":"import fs from 'fs'\nimport {\n  BatchFlags,\n  Client,\n  GlobalFlags,\n  Wallet,\n  combineBatchSigners,\n  deriveConfidentialKeypair,\n  fetchMPToken,\n  fetchMPTokenIssuance,\n  getConfidentialBalance,\n  hashes,\n  loadMptCrypto,\n  prepareConfidentialMergeInbox,\n  prepareConfidentialSend,\n  signMultiBatch,\n  validate\n} from 'xrpl'\n\nimport { setup } from './confidentialTransfersSetup.js'\n\n// Connect to the network ----------------------\nconst client = new Client('wss://s.devnet.rippletest.net:51233')\nawait client.connect()\n\nconst EXPLORER = 'https://devnet.xrpl.org'"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["asyncio"]},": Used to run the async tutorial setup function."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["json"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["os"]},": Used to check for and load the tutorial setup data."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["xrpl"]},": Used for XRPL client connection, transaction submission, and wallet handling."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["xrpl.ext.confidential"]},": Used for key generation, confidential transaction builders, and decryption."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["confidential_transfers_setup"]},": The tutorial setup script, imported and called directly."]}]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/py/send_confidential_payments.py","language":"py","header":{"controls":{"copy":{}}},"lang":"py","source":"import asyncio\nimport json\nimport os\n\nfrom xrpl.clients import JsonRpcClient\nfrom xrpl.ext.confidential import (\n    decrypt_confidential_balance,\n    prepare_confidential_merge_inbox,\n    prepare_confidential_send,\n)\nfrom xrpl.models import Batch, LedgerEntry, Tx\nfrom xrpl.models.requests.ledger_entry import MPToken\nfrom xrpl.models.transactions import ConfidentialMPTSend, Transaction\nfrom xrpl.models.transactions.batch import BatchFlag\nfrom xrpl.models.transactions.transaction import TransactionFlag\nfrom xrpl.transaction import (\n    autofill,\n    combine_batch_signers,\n    sign_multiaccount_batch,\n    submit_and_wait,\n)\nfrom xrpl.wallet import Wallet, generate_faucet_wallet\n\nfrom confidential_transfers_setup import main as run_setup\n\n# Connect to the network ----------------------\nclient = JsonRpcClient(\"https://s.devnet.rippletest.net:51234\")\n\nEXPLORER = \"https://devnet.xrpl.org\""},"children":[]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["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."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/js/sendConfidentialPayments.js","language":"js","header":{"controls":{"copy":{}}},"lang":"js","source":"// Load setup data ----------------------\n// This step checks for the necessary setup data to run the tutorial.\n// If missing, confidentialTransfersSetup.js will generate it.\nif (!fs.existsSync('confidentialTransfersSetup.json')) {\n  console.log(`\\n=== Setup data doesn't exist. Running setup script... ===\\n`)\n  await setup()\n}\n\nconst setupData = JSON.parse(\n  fs.readFileSync('confidentialTransfersSetup.json', 'utf8')\n)\n\n// Set up accounts\nconsole.log(`\\n=== Getting accounts... ===`)\nconst { wallet: orchestrator } = await client.fundWallet()\nconst seller = Wallet.fromSeed(setupData.seller.seed)\nconst buyer = Wallet.fromSeed(setupData.buyer.seed)\nconst auditor = Wallet.fromSeed(setupData.auditor.seed)\n\n// deriveConfidentialKeypair rebuilds a confidential encryption keypair from an\n// account seed. The same seed always gives the same keypair.\nconst sellerKeys = deriveConfidentialKeypair(seller.seed)\nconst buyerKeys = deriveConfidentialKeypair(buyer.seed)\nconst auditorKeys = deriveConfidentialKeypair(auditor.seed)\n\nconsole.log(`Orchestrator address: ${orchestrator.address}`)\nconsole.log(`Seller address: ${seller.address}`)\nconsole.log(`Buyer address: ${buyer.address}`)\nconsole.log(`Auditor address: ${auditor.address}`)"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["In ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["xrpl.js"]},", the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["deriveConfidentialKeypair"]}," function rebuilds a confidential encryption keypair from an account seed, so the same seed always gives you the same keypair."]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/py/send_confidential_payments.py","language":"py","header":{"controls":{"copy":{}}},"lang":"py","source":"# Load setup data ----------------------\n# This step checks for the necessary setup data to run the tutorial.\n# If missing, confidential_transfers_setup.py will generate it.\nif not os.path.exists(\"confidential_transfers_setup.json\"):\n    print(\"\\n=== Setup data doesn't exist. Running setup script... ===\\n\")\n    asyncio.run(run_setup())\n\nwith open(\"confidential_transfers_setup.json\") as setup_file:\n    setup_data = json.load(setup_file)\n\nfund = setup_data[\"fund\"]\nstablecoin = setup_data[\"stablecoin\"]\n\n# Set up accounts\nprint(\"\\n=== Getting accounts... ===\")\norchestrator = generate_faucet_wallet(client)\nseller = Wallet.from_seed(setup_data[\"seller\"][\"seed\"])\nbuyer = Wallet.from_seed(setup_data[\"buyer\"][\"seed\"])\nauditor = Wallet.from_seed(setup_data[\"auditor\"][\"seed\"])\n\n# A confidential encryption keypair is generated at random and can't be rebuilt\n# from an account seed, so the setup script saved each one it created.\nseller_privkey = setup_data[\"seller\"][\"privateKey\"]\nseller_pubkey = setup_data[\"seller\"][\"publicKey\"]\nbuyer_privkey = setup_data[\"buyer\"][\"privateKey\"]\nbuyer_pubkey = setup_data[\"buyer\"][\"publicKey\"]\nissuer_pubkey = setup_data[\"issuer\"][\"publicKey\"]\nauditor_privkey = setup_data[\"auditor\"][\"privateKey\"]\nauditor_pubkey = setup_data[\"auditor\"][\"publicKey\"]\n\nprint(f\"Orchestrator address: {orchestrator.address}\")\nprint(f\"Seller address: {seller.address}\")\nprint(f\"Buyer address: {buyer.address}\")\nprint(f\"Auditor address: {auditor.address}\")"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["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."]}]}]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"Caution"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["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."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"3-make-each-holders-balance-spendable","__idx":7},"children":["3. Make each holder's balance spendable"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The starting balances that the setup script distributed are in each holder's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConfidentialBalanceInbox"]},", and a confidential payment can only spend from ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConfidentialBalanceSpending"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Submit a ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/references/protocol/transactions/types/confidentialmptmergeinbox"},"children":["ConfidentialMPTMergeInbox transaction"]}," to move those funds into each holder's spending balance."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/js/sendConfidentialPayments.js","language":"js","header":{"controls":{"copy":{}}},"lang":"js","source":"// Make each holder's balance spendable ----------------------\nconsole.log(`\\n=== Merging inbox balance for seller and buyer... ===`)\nconst tokens = [setupData.fund, setupData.stablecoin]\nconst holders = [\n  { name: 'Seller', holder: seller, keys: sellerKeys },\n  { name: 'Buyer', holder: buyer, keys: buyerKeys }\n]\nconst holdings = [\n  { ...setupData.fund, holder: seller },\n  { ...setupData.stablecoin, holder: buyer }\n]\n\nfor (const { ticker, mptIssuanceID, holder } of holdings) {\n  const mergeTx = await prepareConfidentialMergeInbox(client, {\n    account: holder.address,\n    mptIssuanceID\n  })\n  const mergeResponse = await client.submitAndWait(mergeTx, {\n    wallet: holder,\n    autofill: true\n  })\n\n  const mergeResult = mergeResponse.result.meta.TransactionResult\n  if (mergeResult !== 'tesSUCCESS') {\n    console.error(`Error: Unable to merge the ${ticker} inbox:`, mergeResult)\n    await client.disconnect()\n    process.exit(1)\n  }\n  console.log(`${holder.address} holds spendable confidential ${ticker}.`)\n  console.log(`${EXPLORER}/transactions/${mergeResponse.result.hash}\\n`)\n}"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/py/send_confidential_payments.py","language":"py","header":{"controls":{"copy":{}}},"lang":"py","source":"# Make each holder's balance spendable ----------------------\nprint(\"\\n=== Merging inbox balance for seller and buyer... ===\")\ntokens = (fund, stablecoin)\nholders = (\n    (\"Seller\", seller, seller_privkey),\n    (\"Buyer\", buyer, buyer_privkey),\n)\nholdings = ((fund, seller), (stablecoin, buyer))\n\nfor token, holder in holdings:\n    merge_tx = prepare_confidential_merge_inbox(client, holder, token[\"mptIssuanceID\"])\n    merge_response = submit_and_wait(merge_tx, client, holder, autofill=True)\n    merge_result = merge_response.result[\"meta\"][\"TransactionResult\"]\n    if merge_result != \"tesSUCCESS\":\n        print(f\"Error: Unable to merge the {token['ticker']} inbox: {merge_result}\")\n        exit(1)\n    print(f\"{holder.address} holds spendable confidential {token['ticker']}.\")\n    print(f\"{EXPLORER}/transactions/{merge_response.result['hash']}\\n\")"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"4-prepare-the-confidentialmptsend-transactions","__idx":8},"children":["4. Prepare the ConfidentialMPTSend transactions"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To make a confidential payment, you must submit a ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/references/protocol/transactions/types/confidentialmptsend"},"children":["ConfidentialMPTSend transaction"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Create each transaction with the confidential send helper function: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["prepareConfidentialSend"]}," in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["xrpl.js"]},", or ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["prepare_confidential_send"]}," in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["xrpl-py"]},". The helper encrypts the amount under the four public keys, and generates the Zero-Knowledge Proof in the transaction's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ZKProof"]}," field that the ledger validates the transfer against. Without revealing the amount, the proof shows that:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Every encrypted amount on the transaction encrypts the same value."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The balance being spent is the one the ledger holds for the sender."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The sender's remaining balance doesn't go negative."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["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 ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Batch"]},"."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/js/sendConfidentialPayments.js","language":"js","header":{"controls":{"copy":{}}},"lang":"js","source":"// Build both confidential payments ----------------------\nconst fundAmount = BigInt(100)\nconst cashAmount = BigInt(500)\n\nconst [fundPayment, cashPayment] = await Promise.all([\n  prepareConfidentialSend(client, {\n    account: seller.address,\n    destination: buyer.address,\n    mptIssuanceID: setupData.fund.mptIssuanceID,\n    amount: fundAmount,\n    senderKeypair: sellerKeys\n  }),\n  prepareConfidentialSend(client, {\n    account: buyer.address,\n    destination: seller.address,\n    mptIssuanceID: setupData.stablecoin.mptIssuanceID,\n    amount: cashAmount,\n    senderKeypair: buyerKeys\n  })\n])\n\nconsole.log(`=== Prepared confidential payments ===`)\nconsole.log(`Payment1 (Fund): ${JSON.stringify(fundPayment, null, 2)}`)\nconsole.log(`\\nPayment2 (Cash): ${JSON.stringify(cashPayment, null, 2)}`)\n\n// Every inner batch transaction must have the tfInnerBatchTxn flag set.\nfundPayment.Flags = GlobalFlags.tfInnerBatchTxn\ncashPayment.Flags = GlobalFlags.tfInnerBatchTxn"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/py/send_confidential_payments.py","language":"py","header":{"controls":{"copy":{}}},"lang":"py","source":"# Build both confidential payments ----------------------\nfund_amount = 100\ncash_amount = 500\n\nfund_payment = prepare_confidential_send(\n    client,\n    seller,\n    buyer.address,\n    fund[\"mptIssuanceID\"],\n    fund_amount,\n    seller_privkey,\n    seller_pubkey,\n    buyer_pubkey,\n    issuer_pubkey,\n    auditor_pubkey,\n)\ncash_payment = prepare_confidential_send(\n    client,\n    buyer,\n    seller.address,\n    stablecoin[\"mptIssuanceID\"],\n    cash_amount,\n    buyer_privkey,\n    buyer_pubkey,\n    seller_pubkey,\n    issuer_pubkey,\n    auditor_pubkey,\n)\n\nprint(\"=== Prepared confidential payments ===\")\nprint(f\"Payment1 (Fund): {json.dumps(fund_payment.to_xrpl(), indent=2)}\")\nprint(f\"\\nPayment2 (Cash): {json.dumps(cash_payment.to_xrpl(), indent=2)}\")\n\n# Every inner Batch transaction needs the tfInnerBatchTxn flag and a Fee of 0.\nfund_payment, cash_payment = [\n    ConfidentialMPTSend.from_dict(\n        {**payment.to_dict(), \"fee\": \"0\", \"flags\": TransactionFlag.TF_INNER_BATCH_TXN}\n    )\n    for payment in (fund_payment, cash_payment)\n]"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"5-submit-confidential-payments","__idx":9},"children":["5. Submit confidential payments"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Use a ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/references/protocol/transactions/types/batch"},"children":["Batch transaction"]}," to submit both confidential payments atomically with the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tfAllOrNothing"]}," flag. This ensures that if there is a failure on either side, the whole transaction reverts."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Inner transactions must have a ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Fee"]}," of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["0"]},", so the outer ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Batch"]}," pays for everything inside it. That means 10 times the standard ",{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/concepts/transactions/transaction-cost"},"children":["transaction cost"]}," for each confidential payment, plus twice the standard cost for the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Batch"]}," itself, plus one standard cost for each signer."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["autofill"]}," covers each payment's full cost, so you only need to tell it how many inner signers to expect."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/js/sendConfidentialPayments.js","language":"js","header":{"controls":{"copy":{}}},"lang":"js","source":"// Settle both payments atomically ----------------------\nconsole.log(`=== Submit confidential payments in batch... ===`)\nconsole.log(`Seller sends ${setupData.fund.ticker} to Buyer.`)\nconsole.log(`Buyer sends ${setupData.stablecoin.ticker} to Seller.\\n`)\n\nconst batchTx = {\n  TransactionType: 'Batch',\n  Account: orchestrator.address,\n  Flags: BatchFlags.tfAllOrNothing,\n  RawTransactions: [\n    { RawTransaction: fundPayment },\n    { RawTransaction: cashPayment }\n  ]\n}\nvalidate(batchTx)\n\nconst autofilledBatchTx = await client.autofill(batchTx, 2)\n\nconst sellerBatch = { ...autofilledBatchTx }\nsignMultiBatch(seller, sellerBatch)\nconst buyerBatch = { ...autofilledBatchTx }\nsignMultiBatch(buyer, buyerBatch)\nconst combinedBatchTx = combineBatchSigners([sellerBatch, buyerBatch])\n\nconst batchResponse = await client.submitAndWait(combinedBatchTx, {\n  wallet: orchestrator\n})\nif (batchResponse.result.meta.TransactionResult !== 'tesSUCCESS') {\n  const resultCode = batchResponse.result.meta.TransactionResult\n  console.error('Error: Unable to submit the Batch:', resultCode)\n  await client.disconnect()\n  process.exit(1)\n}\nconsole.log(`Batch transaction hash: ${batchResponse.result.hash}`)\nconsole.log(`${EXPLORER}/transactions/${batchResponse.result.hash}`)"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/py/send_confidential_payments.py","language":"py","header":{"controls":{"copy":{}}},"lang":"py","source":"# Settle both payments atomically ----------------------\nprint(\"\\n=== Submit confidential payments in batch... ===\")\nprint(f\"Seller sends {fund['ticker']} to Buyer.\")\nprint(f\"Buyer sends {stablecoin['ticker']} to Seller.\\n\")\n\nbatch_tx = Batch(\n    account=orchestrator.address,\n    flags=BatchFlag.TF_ALL_OR_NOTHING,\n    raw_transactions=[fund_payment, cash_payment],\n)\n\nautofilled_batch_tx = autofill(batch_tx, client, 2)\n\nseller_batch = sign_multiaccount_batch(seller, autofilled_batch_tx)\nbuyer_batch = sign_multiaccount_batch(buyer, autofilled_batch_tx)\ncombined_batch_tx = combine_batch_signers([seller_batch, buyer_batch])\n\nbatch_response = submit_and_wait(combined_batch_tx, client, orchestrator)\nif batch_response.result[\"meta\"][\"TransactionResult\"] != \"tesSUCCESS\":\n    result_code = batch_response.result[\"meta\"][\"TransactionResult\"]\n    print(f\"Error: Unable to submit the Batch: {result_code}\")\n    exit(1)\nprint(f\"Batch transaction hash: {batch_response.result['hash']}\")\nprint(f\"{EXPLORER}/transactions/{batch_response.result['hash']}\")"},"children":[]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["seller"]}," and ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["buyer"]}," sign the Batch for their own inner payment, then the two sets of signers are combined and the ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["orchestrator"]}," submits it."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"6-verify-each-payment-individually","__idx":10},"children":["6. Verify each payment individually"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tesSUCCESS"]}," on the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Batch"]}," only means the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Batch"]}," itself was well-formed. Each inner payment has its own result, so you must verify the result of each inner transaction."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/js/sendConfidentialPayments.js","language":"js","header":{"controls":{"copy":{}}},"lang":"js","source":"// Verify each payment individually ----------------------\n// A tesSUCCESS on the Batch only means the Batch itself was well-formed. \n// Hash each inner transaction and look it up to confirm both payments applied.\nconsole.log(`\\n=== Verifying both payments... ===`)\nconst rawTransactions = batchResponse.result.tx_json.RawTransactions\n\nfor (const [index, { RawTransaction }] of rawTransactions.entries()) {\n  const innerHash = hashes.hashSignedTx(RawTransaction)\n  const innerTx = await client.request({ command: 'tx', transaction: innerHash })\n  const innerResult = innerTx.result.meta.TransactionResult\n  console.log(`Payment ${index + 1}: ${innerResult}`)\n  console.log(`${EXPLORER}/transactions/${innerHash}`)\n\n  if (innerResult !== 'tesSUCCESS') {\n    console.error('Error: An inner payment failed:', innerResult)\n    await client.disconnect()\n    process.exit(1)\n  }\n}\nconsole.log(`\\nPayments both successful!`)"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/py/send_confidential_payments.py","language":"py","header":{"controls":{"copy":{}}},"lang":"py","source":"# Verify each payment individually ----------------------\n# A tesSUCCESS on the Batch only means the Batch itself was well-formed.\n# Hash each inner transaction and look it up to confirm both payments applied.\nprint(\"\\n=== Verifying both payments... ===\")\nraw_transactions = batch_response.result[\"tx_json\"][\"RawTransactions\"]\nfor index, raw_transaction in enumerate(raw_transactions):\n    inner = Transaction.from_xrpl(raw_transaction[\"RawTransaction\"])\n    inner_hash = inner.get_hash()\n    inner_result = client.request(Tx(transaction=inner_hash)).result[\"meta\"][\n        \"TransactionResult\"\n    ]\n    print(f\"Payment {index + 1}: {inner_result}\")\n    print(f\"{EXPLORER}/transactions/{inner_hash}\")\n\n    if inner_result != \"tesSUCCESS\":\n        print(f\"Error: An inner payment failed: {inner_result}\")\n        exit(1)\nprint(\"\\nPayments both successful!\")"},"children":[]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["With ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tfAllOrNothing"]},", both inner results are either ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["tesSUCCESS"]}," or reverted together, so this check confirms the settlement applied as intended."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"7-merge-the-received-confidential-amounts","__idx":11},"children":["7. Merge the received confidential amounts"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Now that each confidential payment has settled, each amount is in its recipient's ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConfidentialBalanceInbox"]},". A holder can decrypt an inbox amount, but can't spend it."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Merge the confidential balances so both holders can spend them later."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/js/sendConfidentialPayments.js","language":"js","header":{"controls":{"copy":{}}},"lang":"js","source":"// Merge the received amounts into each spending balance ----------------------\nconsole.log(`\\n=== Merging settled amounts into spending balance... ===`)\nconst settlements = [\n  { ...setupData.fund, recipient: buyer },\n  { ...setupData.stablecoin, recipient: seller }\n]\n\nfor (const { ticker, mptIssuanceID, recipient } of settlements) {\n  const mergeTx = await prepareConfidentialMergeInbox(client, {\n    account: recipient.address,\n    mptIssuanceID\n  })\n  const mergeResponse = await client.submitAndWait(mergeTx, {\n    wallet: recipient,\n    autofill: true\n  })\n  const mergeResult = mergeResponse.result.meta.TransactionResult\n  if (mergeResult !== 'tesSUCCESS') {\n    console.error(`Error: Unable to merge the ${ticker} inbox:`, mergeResult)\n    await client.disconnect()\n    process.exit(1)\n  }\n  console.log(`${recipient.address} can spend the ${ticker} it received.`)\n  console.log(`${EXPLORER}/transactions/${mergeResponse.result.hash}\\n`)\n}"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/py/send_confidential_payments.py","language":"py","header":{"controls":{"copy":{}}},"lang":"py","source":"# Merge the received amounts into each spending balance ----------------------\nprint(\"\\n=== Merging settled amounts into spending balance... ===\")\nsettlements = ((fund, buyer), (stablecoin, seller))\n\nfor token, recipient in settlements:\n    merge_tx = prepare_confidential_merge_inbox(\n        client, recipient, token[\"mptIssuanceID\"]\n    )\n    merge_response = submit_and_wait(merge_tx, client, recipient, autofill=True)\n    merge_result = merge_response.result[\"meta\"][\"TransactionResult\"]\n    if merge_result != \"tesSUCCESS\":\n        print(f\"Error: Unable to merge the {token['ticker']} inbox: {merge_result}\")\n        exit(1)\n    print(f\"{recipient.address} can spend the {token['ticker']} it received.\")\n    print(f\"{EXPLORER}/transactions/{merge_response.result['hash']}\\n\")"},"children":[]}]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"8-decrypt-the-balances-and-settled-amounts","__idx":12},"children":["8. Decrypt the balances and settled amounts"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Reading a confidential amount requires an encryption private key, so only the two holders and the auditor can decrypt anything here."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["First, decrypt each holder's balance on both issuances."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/js/sendConfidentialPayments.js","language":"js","header":{"controls":{"copy":{}}},"lang":"js","source":"// Decrypt balances as each holder ----------------------\nconsole.log(`=== Decrypting balances as each holder... ===`)\nfor (const { name, holder, keys } of holders) {\n  console.log(`${name} reads its own balance as:`)\n  for (const token of tokens) {\n    const balance = await getConfidentialBalance(\n      client,\n      holder.address,\n      token.mptIssuanceID,\n      keys.privateKey\n    )\n    console.log(`     - ${balance} ${token.ticker}`)\n  }\n}"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/py/send_confidential_payments.py","language":"py","header":{"controls":{"copy":{}}},"lang":"py","source":"# Decrypt balances as each holder ----------------------\nconfidential_supplies = {}\nfor token in tokens:\n    issuance = client.request(\n        LedgerEntry(mpt_issuance=token[\"mptIssuanceID\"]),\n    ).result[\"node\"]\n    confidential_supplies[token[\"mptIssuanceID\"]] = int(\n        issuance[\"ConfidentialOutstandingAmount\"]\n    )\n\nprint(\"=== Decrypting balances as each holder... ===\")\nfor name, holder, holder_privkey in holders:\n    print(f\"{name} reads its own balance as:\")\n    for token in tokens:\n        mptoken = client.request(\n            LedgerEntry(\n                mptoken=MPToken(\n                    mpt_issuance_id=token[\"mptIssuanceID\"], account=holder.address\n                ),\n            ),\n        ).result[\"node\"]\n        balance = decrypt_confidential_balance(\n            mptoken[\"ConfidentialBalanceSpending\"],\n            holder_privkey,\n            range_high=confidential_supplies[token[\"mptIssuanceID\"]],\n        )\n        print(f\"     - {balance} {token['ticker']}\")"},"children":[]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each holder reads ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ConfidentialBalanceSpending"]},", and only with its own encryption private key, so neither can read the other's balance."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Then, as the auditor, decrypt both balances along with the amount each payment moved."]},{"$$mdtype":"Tag","name":"Tabs","attributes":{"size":"medium"},"children":[{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"JavaScript","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/js/sendConfidentialPayments.js","language":"js","header":{"controls":{"copy":{}}},"lang":"js","source":"// Decrypt the balances and amounts as the auditor ----------------------\nconsole.log(`\\n=== Decrypting balances and amounts as the auditor... ===`)\nconst crypto = await loadMptCrypto()\n\nconst confidentialSupplies = {}\nfor (const token of tokens) {\n  const issuance = await fetchMPTokenIssuance(client, token.mptIssuanceID)\n  confidentialSupplies[token.mptIssuanceID] = BigInt(\n    issuance.ConfidentialOutstandingAmount\n  )\n}\n\nfor (const { name, holder } of holders) {\n  console.log(`Auditor reads the ${name.toLowerCase()}'s balance as:`)\n  for (const token of tokens) {\n    const mptoken = await fetchMPToken(\n      client,\n      holder.address,\n      token.mptIssuanceID\n    )\n    const auditorView = await crypto.decryptAmount(\n      mptoken.AuditorEncryptedBalance,\n      auditorKeys.privateKey,\n      confidentialSupplies[token.mptIssuanceID]\n    )\n    console.log(`     - ${auditorView} ${token.ticker}`)\n  }\n}\n\nconsole.log(`\\nAuditor reads the settled amounts as:`)\nconst settled = [\n  { token: setupData.fund, payment: fundPayment },\n  { token: setupData.stablecoin, payment: cashPayment }\n]\nfor (const { token, payment } of settled) {\n  const settledAmount = await crypto.decryptAmount(\n    payment.AuditorEncryptedAmount,\n    auditorKeys.privateKey,\n    confidentialSupplies[token.mptIssuanceID]\n  )\n  console.log(`     - ${settledAmount} ${token.ticker}`)\n}\n\nawait client.disconnect()\n"},"children":[]}]},{"$$mdtype":"Tag","name":"TabItemFragment","attributes":{"label":"Python","disable":false},"children":[{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"file":"/_code-samples/confidential-transfers/py/send_confidential_payments.py","language":"py","header":{"controls":{"copy":{}}},"lang":"py","source":"# Decrypt the balances and amounts as the auditor ----------------------\nprint(\"\\n=== Decrypting balances and amounts as the auditor... ===\")\nfor name, holder, _ in holders:\n    print(f\"Auditor reads the {name.lower()}'s balance as:\")\n    for token in tokens:\n        mptoken = client.request(\n            LedgerEntry(\n                mptoken=MPToken(\n                    mpt_issuance_id=token[\"mptIssuanceID\"], account=holder.address\n                ),\n            ),\n        ).result[\"node\"]\n        auditor_view = decrypt_confidential_balance(\n            mptoken[\"AuditorEncryptedBalance\"],\n            auditor_privkey,\n            range_high=confidential_supplies[token[\"mptIssuanceID\"]],\n        )\n        print(f\"     - {auditor_view} {token['ticker']}\")\n\nprint(\"\\nAuditor reads the settled amounts as:\")\nsettled = ((fund, fund_payment), (stablecoin, cash_payment))\n\nfor token, payment in settled:\n    settled_amount = decrypt_confidential_balance(\n        payment.auditor_encrypted_amount,\n        auditor_privkey,\n        range_high=confidential_supplies[token[\"mptIssuanceID\"]],\n    )\n    print(f\"     - {settled_amount} {token['ticker']}\")\n"},"children":[]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The auditor decrypts ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["AuditorEncryptedBalance"]}," on the same entries with its own encryption private key, so one key reads both sides of the settlement. The same key decrypts ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["AuditorEncryptedAmount"]}," on each payment, which is the amount that moved rather than the balance it landed in."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Everyone else (for example, the ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["orchestrator"]},") only sees the encrypted amounts, on the balances and on the payments that moved them."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"see-also","__idx":13},"children":["See Also"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Concepts"]},":",{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/concepts/tokens/fungible-tokens/confidential-transfers"},"children":["Confidential Transfers"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/concepts/transactions/batch-transactions"},"children":["Batch Transactions"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/concepts/tokens/fungible-tokens/multi-purpose-tokens"},"children":["Multi-Purpose Tokens (MPT)"]}]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["Tutorials"]},":",{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/tutorials/tokens/mpts/issue-mpt-for-confidential-transfers"},"children":["Issue an MPT for Confidential Transfers"]}]}]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["References"]},":",{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/references/protocol/transactions/types/batch"},"children":["Batch transaction"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/references/protocol/transactions/types/confidentialmptmergeinbox"},"children":["ConfidentialMPTMergeInbox transaction"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/references/protocol/transactions/types/confidentialmptsend"},"children":["ConfidentialMPTSend transaction"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"MarkdownLink","attributes":{"href":"/docs/references/protocol/ledger-data/ledger-entry-types/mptoken"},"children":["MPToken entry"]}]}]}]}]}]},"headings":[{"value":"Send Confidential MPT Payments","id":"send-confidential-mpt-payments","depth":1},{"value":"Goals","id":"goals","depth":2},{"value":"Prerequisites","id":"prerequisites","depth":2},{"value":"Source Code","id":"source-code","depth":2},{"value":"Steps","id":"steps","depth":2},{"value":"1. Install dependencies","id":"1-install-dependencies","depth":3},{"value":"2. Set up the client and accounts","id":"2-set-up-the-client-and-accounts","depth":3},{"value":"3. Make each holder's balance spendable","id":"3-make-each-holders-balance-spendable","depth":3},{"value":"4. Prepare the ConfidentialMPTSend transactions","id":"4-prepare-the-confidentialmptsend-transactions","depth":3},{"value":"5. Submit confidential payments","id":"5-submit-confidential-payments","depth":3},{"value":"6. Verify each payment individually","id":"6-verify-each-payment-individually","depth":3},{"value":"7. Merge the received confidential amounts","id":"7-merge-the-received-confidential-amounts","depth":3},{"value":"8. Decrypt the balances and settled amounts","id":"8-decrypt-the-balances-and-settled-amounts","depth":3},{"value":"See Also","id":"see-also","depth":2}],"frontmatter":{"seo":{"description":"Settle two confidential Multi-Purpose Token (MPT) payments atomically with a Batch transaction, then read the result as a holder and as an auditor.","title":"Send Confidential MPT Payments"},"metadata":{"indexPage":true},"labels":["Multi-Purpose Token","MPT","Confidential Transfers","Payments"],"status":"not_enabled"},"editPage":{"to":"https://github.com/XRPLF/xrpl-dev-portal/tree/master/docs/tutorials/payments/send-confidential-payments.md"},"lastModified":"2026-08-26T10:26:57.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/tutorials/payments/send-confidential-payments","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}