diff --git a/_code-samples/batch/README.md b/_code-samples/batch/README.md index c0852466c27..7900423e5fa 100644 --- a/_code-samples/batch/README.md +++ b/_code-samples/batch/README.md @@ -1,4 +1,5 @@ # Batch -Code samples showing how to create and submit a [Batch transaction](../../docs/concepts/transactions/batch-transactions.md). -Both for simple and multi account batch transactions. +Code samples showing how to create and submit a [Batch transaction](https://xrpl.org/docs/concepts/transactions/batch-transactions). + +Both for single and multi-account batch transactions. diff --git a/_code-samples/batch/js/README.md b/_code-samples/batch/js/README.md new file mode 100644 index 00000000000..b7a25fc6e99 --- /dev/null +++ b/_code-samples/batch/js/README.md @@ -0,0 +1,301 @@ +# Send a Batch Transaction + +Code samples showing how to create and submit a [Batch transaction](https://xrpl.org/docs/concepts/transactions/batch-transactions) with Javascript. + +Both for single and multi-account batch transactions. + +## Single Account Batch Transaction + +Quick setup and usage: + +```sh +npm install xrpl +node singleAccountBatch.js +``` + +The script should output the following: + +```sh +=== Funding new wallets from faucet... === +Sender: rP9EsVosrmx2HyrmLgWJpJacX5ZrVVQsim, Balance: 100 XRP +Wallet1: rGx6SACvYEvX8SRrvTPD91UhBmJ16pxL94, Balance: 100 XRP +Wallet2: r3qetgSfAtyCpGc4rvKNz4LX3F3urMSJJy, Balance: 100 XRP + +=== Creating Batch transaction... === +{ + "TransactionType": "Batch", + "Account": "rP9EsVosrmx2HyrmLgWJpJacX5ZrVVQsim", + "Flags": 65536, + "RawTransactions": [ + { + "RawTransaction": { + "TransactionType": "Payment", + "Account": "rP9EsVosrmx2HyrmLgWJpJacX5ZrVVQsim", + "Destination": "rGx6SACvYEvX8SRrvTPD91UhBmJ16pxL94", + "Amount": "2000000", + "Flags": 1073741824 + } + }, + { + "RawTransaction": { + "TransactionType": "Payment", + "Account": "rP9EsVosrmx2HyrmLgWJpJacX5ZrVVQsim", + "Destination": "r3qetgSfAtyCpGc4rvKNz4LX3F3urMSJJy", + "Amount": "5000000", + "Flags": 1073741824 + } + } + ] +} + +=== Submitting Batch transaction... === + +Batch transaction submitted successfully! +Result: + { + "close_time_iso": "2025-11-17T12:04:50Z", + "ctid": "C013313800030002", + "hash": "AE118213B0A183528418ABC5F14E3BFD6524020C5DB1C060157A0D3FDE15B900", + "ledger_hash": "621183809B68A794371C5EC6522105FF04E502C48EBDC8171B80224991E33394", + "ledger_index": 1257784, + "meta": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rP9EsVosrmx2HyrmLgWJpJacX5ZrVVQsim", + "Balance": "99999996", + "Flags": 0, + "OwnerCount": 0, + "Sequence": 1257779 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "42CC98AF0A28EDDDC7E359B5622CC5748BDE2A93E124AF5C32647ECA8F68D480", + "PreviousFields": { + "Balance": "100000000", + "Sequence": 1257778 + }, + "PreviousTxnID": "081C42DAE12001735AC4E9A7F027636DF612DB17B4BFA2333F4DB8EA0C9D1E9F", + "PreviousTxnLgrSeq": 1257778 + } + } + ], + "TransactionIndex": 3, + "TransactionResult": "tesSUCCESS" + }, + "tx_json": { + "Account": "rP9EsVosrmx2HyrmLgWJpJacX5ZrVVQsim", + "Fee": "4", + "Flags": 65536, + "LastLedgerSequence": 1257802, + "RawTransactions": [ + { + "RawTransaction": { + "Account": "rP9EsVosrmx2HyrmLgWJpJacX5ZrVVQsim", + "Amount": "2000000", + "Destination": "rGx6SACvYEvX8SRrvTPD91UhBmJ16pxL94", + "Fee": "0", + "Flags": 1073741824, + "Sequence": 1257779, + "SigningPubKey": "", + "TransactionType": "Payment" + } + }, + { + "RawTransaction": { + "Account": "rP9EsVosrmx2HyrmLgWJpJacX5ZrVVQsim", + "Amount": "5000000", + "Destination": "r3qetgSfAtyCpGc4rvKNz4LX3F3urMSJJy", + "Fee": "0", + "Flags": 1073741824, + "Sequence": 1257780, + "SigningPubKey": "", + "TransactionType": "Payment" + } + } + ], + "Sequence": 1257778, + "SigningPubKey": "ED7031CA5BA4EC745610AB495F5053F318C119E87567BE485A494773AD8ED4FBCE", + "TransactionType": "Batch", + "TxnSignature": "0610A277086943BC462C1A5F85BEB667B62B4BDA59525138B6014101C08297897A73D3D2D247CB37A06E1EA36267C53A51C0FDF32F3D8E974029BEDC41105B07", + "ctid": "C013313800030002", + "date": 816696290, + "ledger_index": 1257784 + }, + "validated": true +} + +Batch transaction URL: +https://devnet.xrpl.org/transactions/AE118213B0A183528418ABC5F14E3BFD6524020C5DB1C060157A0D3FDE15B900 + +=== Verifying inner transactions... === + +Transaction 1 hash: D18EA54D5653BBB5C87F116978822EAB7A26EDFB1D6C41910F36D7484D4890E3 + - Status: tesSUCCESS (Ledger 1257784) + - Transaction URL: https://devnet.xrpl.org/transactions/D18EA54D5653BBB5C87F116978822EAB7A26EDFB1D6C41910F36D7484D4890E3 + +Transaction 2 hash: 5660DB400F08EE5543C54D4D65824A2142F9D5AC17294A4ABF654260F129B44E + - Status: tesSUCCESS (Ledger 1257784) + - Transaction URL: https://devnet.xrpl.org/transactions/5660DB400F08EE5543C54D4D65824A2142F9D5AC17294A4ABF654260F129B44E + +=== Final balances === +Sender: rP9EsVosrmx2HyrmLgWJpJacX5ZrVVQsim, Balance: 92.999996 XRP +Wallet1: rGx6SACvYEvX8SRrvTPD91UhBmJ16pxL94, Balance: 102 XRP +Wallet2: r3qetgSfAtyCpGc4rvKNz4LX3F3urMSJJy, Balance: 105 XRP +``` + +## Multi-Account Batch Transaction + +```sh +npm install xrpl +node multiAccountBatch.js +``` + +The script should output the following: + +```sh +=== Funding new wallets from faucet... === +Alice: rHpve1GL2ZXUs3NB5iU91BrXBSwb5PbBrG, Balance: 100 XRP +Bob: r3ruQ92bqXwWxcR2w4cC1tW35og9h3UbBq, Balance: 100 XRP +Charlie: rsi5D9bkczpbGykPxoGNBVVmFFFXGwm3QA, Balance: 100 XRP +Third-party wallet: rfUpGXTzU3siTr4UovV6Wt86Vw3gQU4ttA, Balance: 100 XRP + +=== Creating Batch transaction... === +{ + "TransactionType": "Batch", + "Account": "rfUpGXTzU3siTr4UovV6Wt86Vw3gQU4ttA", + "Flags": 65536, + "RawTransactions": [ + { + "RawTransaction": { + "TransactionType": "Payment", + "Account": "rsi5D9bkczpbGykPxoGNBVVmFFFXGwm3QA", + "Destination": "rHpve1GL2ZXUs3NB5iU91BrXBSwb5PbBrG", + "Amount": "50000000", + "Flags": 1073741824 + } + }, + { + "RawTransaction": { + "TransactionType": "Payment", + "Account": "r3ruQ92bqXwWxcR2w4cC1tW35og9h3UbBq", + "Destination": "rHpve1GL2ZXUs3NB5iU91BrXBSwb5PbBrG", + "Amount": "50000000", + "Flags": 1073741824 + } + } + ] +} + +=== Submitting Batch transaction... === + +Batch transaction submitted successfully! +Result: + { + "close_time_iso": "2025-11-17T12:08:31Z", + "ctid": "C013317600000002", + "hash": "1299D20C6B489DA5C632AE4DBE49475DBF42D9444C7E9C109CC9B8DD0FD55FEC", + "ledger_hash": "E45ECF69057084CD02BA49A17E4D0C9154D33A98BB3C95A11B2EB9BE18F32C9B", + "ledger_index": 1257846, + "meta": { + "AffectedNodes": [ + { + "ModifiedNode": { + "FinalFields": { + "Account": "rfUpGXTzU3siTr4UovV6Wt86Vw3gQU4ttA", + "Balance": "99999994", + "Flags": 0, + "OwnerCount": 0, + "Sequence": 1257845 + }, + "LedgerEntryType": "AccountRoot", + "LedgerIndex": "2D9E0A02007241C38A8DF679E7E62AA0B273E8B12A5430B7B9D99300424F0E1F", + "PreviousFields": { + "Balance": "100000000", + "Sequence": 1257844 + }, + "PreviousTxnID": "3153DE8DE922538A6BE54AA8F783CAD4B848A321AFF028D3E6DD0E80C4B9C237", + "PreviousTxnLgrSeq": 1257844 + } + } + ], + "TransactionIndex": 0, + "TransactionResult": "tesSUCCESS" + }, + "tx_json": { + "Account": "rfUpGXTzU3siTr4UovV6Wt86Vw3gQU4ttA", + "BatchSigners": [ + { + "BatchSigner": { + "Account": "rsi5D9bkczpbGykPxoGNBVVmFFFXGwm3QA", + "SigningPubKey": "EDEB88C2868BD25BF03DB26050E16579FA6F8F9E3FF3172E0DC3DCBDA5408572EB", + "TxnSignature": "9508568084596147CFDCFC18A62DC298A78AD1148BA4B0EB99BEE1CD37E5555FE3930810790D5708F9739B0E3F79772012C154CA33C2280BDD5B72473C17A607" + } + }, + { + "BatchSigner": { + "Account": "r3ruQ92bqXwWxcR2w4cC1tW35og9h3UbBq", + "SigningPubKey": "ED82F98DA6A3FC3E88D2EE3A5469D92C7070513BEF4DEE75CAB0BDAA81E8AE378D", + "TxnSignature": "A482C8747F79857530474F1677599766C0BE283CB7E2A05AACF76E61BECCA16DCE3802D2D8244FBF4546A1C0E5EB70691255E3EFD2F8AC80B55357BDAB9ACD05" + } + } + ], + "Fee": "6", + "Flags": 65536, + "LastLedgerSequence": 1257864, + "RawTransactions": [ + { + "RawTransaction": { + "Account": "rsi5D9bkczpbGykPxoGNBVVmFFFXGwm3QA", + "Amount": "50000000", + "Destination": "rHpve1GL2ZXUs3NB5iU91BrXBSwb5PbBrG", + "Fee": "0", + "Flags": 1073741824, + "Sequence": 1257842, + "SigningPubKey": "", + "TransactionType": "Payment" + } + }, + { + "RawTransaction": { + "Account": "r3ruQ92bqXwWxcR2w4cC1tW35og9h3UbBq", + "Amount": "50000000", + "Destination": "rHpve1GL2ZXUs3NB5iU91BrXBSwb5PbBrG", + "Fee": "0", + "Flags": 1073741824, + "Sequence": 1257841, + "SigningPubKey": "", + "TransactionType": "Payment" + } + } + ], + "Sequence": 1257844, + "SigningPubKey": "ED22A32B61EDF083315515831723BC18F8311F03886BBA375DFF46335BB7A75F0B", + "TransactionType": "Batch", + "TxnSignature": "156791D2DBFAEFC9B0AC29F2D8D0CDB25E13F92E70E6D5414FE31BD8573CA23D3F62F8B34FC1F117BD556B25E4F748095A24C4342108AB32F1B2BAFBF1443501", + "ctid": "C013317600000002", + "date": 816696511, + "ledger_index": 1257846 + }, + "validated": true +} + +Batch transaction URL: +https://devnet.xrpl.org/transactions/1299D20C6B489DA5C632AE4DBE49475DBF42D9444C7E9C109CC9B8DD0FD55FEC + +=== Verifying inner transactions === + +Transaction 1 hash: 0F71979E3F641C980929F926640DCA886C30236ED0CD7C94B6CB36F0D42948AC + - Status: tesSUCCESS (Ledger 1257846) + - Transaction URL: https://devnet.xrpl.org/transactions/0F71979E3F641C980929F926640DCA886C30236ED0CD7C94B6CB36F0D42948AC + +Transaction 2 hash: BC124CB29334AA1079139A9BE186B69A0AC467797F147754E2406714854D2A50 + - Status: tesSUCCESS (Ledger 1257846) + - Transaction URL: https://devnet.xrpl.org/transactions/BC124CB29334AA1079139A9BE186B69A0AC467797F147754E2406714854D2A50 + +=== Final balances === +Alice: rHpve1GL2ZXUs3NB5iU91BrXBSwb5PbBrG, Balance: 200 XRP +Bob: r3ruQ92bqXwWxcR2w4cC1tW35og9h3UbBq, Balance: 50 XRP +Charlie: rsi5D9bkczpbGykPxoGNBVVmFFFXGwm3QA, Balance: 50 XRP +Third-party wallet: rfUpGXTzU3siTr4UovV6Wt86Vw3gQU4ttA, Balance: 99.999994 XRP +``` diff --git a/_code-samples/batch/js/multiAccountBatch.js b/_code-samples/batch/js/multiAccountBatch.js new file mode 100644 index 00000000000..25a8307f091 --- /dev/null +++ b/_code-samples/batch/js/multiAccountBatch.js @@ -0,0 +1,143 @@ +/** + * XRP Ledger Batch Transactions Tutorial + * + * This tutorial demonstrates how to use the Batch transaction feature (XLS-56) + * to perform a multi-account batch transaction. + * Concept doc: https://xrpl.org/docs/concepts/transactions/batch-transactions + * Reference doc: https://xrpl.org/docs/references/protocol/transactions/types/batch +*/ + +import xrpl from "xrpl" + +const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233/") +await client.connect() + +// Create and fund wallets +console.log("=== Funding new wallets from faucet... ==="); +const [ + { wallet: alice }, + { wallet: bob }, + { wallet: charlie }, + { wallet: thirdPartyWallet }, +] = await Promise.all([ + client.fundWallet(), + client.fundWallet(), + client.fundWallet(), + client.fundWallet(), +]); + +console.log(`Alice: ${alice.address}, Balance: ${await client.getXrpBalance(alice.address)} XRP`) +console.log(`Bob: ${bob.address}, Balance: ${await client.getXrpBalance(bob.address)} XRP`) +console.log(`Charlie: ${charlie.address}, Balance: ${await client.getXrpBalance(charlie.address)} XRP`) +console.log(`Third-party wallet: ${thirdPartyWallet.address}, Balance: ${await client.getXrpBalance(thirdPartyWallet.address)} XRP`) + +// Create inner transactions -------------------------------------------- +// REQUIRED: Inner transactions MUST have the tfInnerBatchTxn flag (0x40000000). +// This marks them as part of a batch (requires Fee: 0 and empty SigningPubKey). + +// Transaction 1: Charlie pays Alice +const charliePayment = { + TransactionType: "Payment", + Account: charlie.address, + Destination: alice.address, + Amount: xrpl.xrpToDrops(50), + Flags: xrpl.GlobalFlags.tfInnerBatchTxn // THIS IS REQUIRED +} + +// Transaction 2: Bob pays Alice +const bobPayment = { + TransactionType: "Payment", + Account: bob.address, + Destination: alice.address, + Amount: xrpl.xrpToDrops(50), + Flags: xrpl.GlobalFlags.tfInnerBatchTxn // THIS IS REQUIRED +} + +// Send Batch transaction -------------------------------------------- +console.log("\n=== Creating Batch transaction... ===") +const batchTx = { + TransactionType: "Batch", + Account: thirdPartyWallet.address, + Flags: xrpl.BatchFlags.tfAllOrNothing, // tfAllOrNothing: All inner transactions must succeed + // Must include a minimum of 2 transactions and a maximum of 8 transactions. + RawTransactions: [ + { RawTransaction: charliePayment }, + { RawTransaction: bobPayment }, + ] +} +console.log(JSON.stringify(batchTx, null, 2)) + +// Validate the transaction structure +xrpl.validate(batchTx) + +// Set the expected number of signers, which is 2 (Bob and Charlie) in this case, for this transaction. +// "autofill" will automatically add Fee: "0" and SigningPubKey: "" to inner transactions. +const autofilledBatchTx = await client.autofill(batchTx, 2) + +// Gather batch signatures -------------------------------- +// Each signer needs their own tx copy because signMultiBatch modifies the object. +// Charlie signs the Batch transaction +const charlieBatch = { ...autofilledBatchTx } +xrpl.signMultiBatch(charlie, charlieBatch) + +// Bob signs the Batch transaction +const bobBatch = { ...autofilledBatchTx } +xrpl.signMultiBatch(bob, bobBatch) + +// Combine inner transaction signatures. +// This returns a signed transaction blob (hex string) ready for submission. +const combinedSignedTx = xrpl.combineBatchSigners([charlieBatch, bobBatch]) + +// Submit the signed blob with the third-party's wallet +console.log("\n=== Submitting Batch transaction... ===") +const submitResponse = await client.submitAndWait(combinedSignedTx, + { wallet: thirdPartyWallet } +) + +// Check Batch transaction result -------------------------------- +if (submitResponse.result.meta.TransactionResult !== "tesSUCCESS") { + const resultCode = submitResponse.result.meta.TransactionResult + console.warn(`\nTransaction failed with result code ${resultCode}`) + await client.disconnect() + process.exit(1) +} + +console.log("\nBatch transaction submitted successfully!") +console.log("Result:\n", JSON.stringify(submitResponse.result, null, 2)) +// View the transaction on the XRPL Explorer +console.log(`\nBatch transaction URL:\nhttps://devnet.xrpl.org/transactions/${submitResponse.result.hash}`) + +// Calculate and verify inner transaction hashes -------------------------------------------- +console.log("\n=== Verifying inner transactions ===") +const rawTransactions = submitResponse.result.tx_json.RawTransactions +let hasFailure = false + +for (let i = 0; i < rawTransactions.length; i++) { + const innerTx = rawTransactions[i].RawTransaction + const hash = xrpl.hashes.hashSignedTx(innerTx) + console.log(`\nTransaction ${i + 1} hash: ${hash}`) + + try { + const tx = await client.request({ command: 'tx', transaction: hash }) + const status = tx.result.meta?.TransactionResult + console.log(` - Status: ${status} (Ledger ${tx.result.ledger_index})`) + console.log(` - Transaction URL: https://devnet.xrpl.org/transactions/${hash}`) + } catch (error) { + hasFailure = true + console.log(` - Transaction not found: ${error}`) + } +} +if (hasFailure) { + console.error("\n--- Error: One or more inner transactions failed. ---") + await client.disconnect() + process.exit(1) +} + +// Verify balances after transaction +console.log("\n=== Final balances ===") +console.log(`Alice: ${alice.address}, Balance: ${await client.getXrpBalance(alice.address)} XRP`) +console.log(`Bob: ${bob.address}, Balance: ${await client.getXrpBalance(bob.address)} XRP`) +console.log(`Charlie: ${charlie.address}, Balance: ${await client.getXrpBalance(charlie.address)} XRP`) +console.log(`Third-party wallet: ${thirdPartyWallet.address}, Balance: ${await client.getXrpBalance(thirdPartyWallet.address)} XRP`) + +await client.disconnect() diff --git a/_code-samples/batch/js/package.json b/_code-samples/batch/js/package.json new file mode 100644 index 00000000000..fab7850610c --- /dev/null +++ b/_code-samples/batch/js/package.json @@ -0,0 +1,6 @@ +{ + "dependencies": { + "xrpl": "^4.4.3" + }, + "type": "module" +} diff --git a/_code-samples/batch/js/singleAccountBatch.js b/_code-samples/batch/js/singleAccountBatch.js new file mode 100644 index 00000000000..6e8c9a7ebdc --- /dev/null +++ b/_code-samples/batch/js/singleAccountBatch.js @@ -0,0 +1,120 @@ +/** + * Single Account Batch Transaction Example + * + * This example demonstrates how to use the Batch transactions feature (XLS-56) + * to create a single-account batch transaction that sends payments + * to multiple destinations in one atomic operation. + * Concept doc: https://xrpl.org/docs/concepts/transactions/batch-transactions + * Reference doc: https://xrpl.org/docs/references/protocol/transactions/types/batch +*/ + +import xrpl from "xrpl" + +const client = new xrpl.Client("wss://s.devnet.rippletest.net:51233/") +await client.connect() + +// Create and fund wallets +console.log("=== Funding new wallets from faucet... ==="); +const [{ wallet: sender }, { wallet: wallet1 }, { wallet: wallet2 }] = + await Promise.all([ + client.fundWallet(), + client.fundWallet(), + client.fundWallet(), + ]); + +console.log(`Sender: ${sender.address}, Balance: ${await client.getXrpBalance(sender.address)} XRP`) +console.log(`Wallet1: ${wallet1.address}, Balance: ${await client.getXrpBalance(wallet1.address)} XRP`) +console.log(`Wallet2: ${wallet2.address}, Balance: ${await client.getXrpBalance(wallet2.address)} XRP`) + +// Create inner transactions -------------------------------------------- +// REQUIRED: Inner transactions MUST have the tfInnerBatchTxn flag (0x40000000). +// This marks them as part of a batch (requires Fee: 0 and empty SigningPubKey). + +// Transaction 1 +const payment1 = { + TransactionType: "Payment", + Account: sender.address, + Destination: wallet1.address, + Amount: xrpl.xrpToDrops(2), + Flags: xrpl.GlobalFlags.tfInnerBatchTxn // THIS IS REQUIRED +} + +// Transaction 2 +const payment2 = { + TransactionType: "Payment", + Account: sender.address, + Destination: wallet2.address, + Amount: xrpl.xrpToDrops(5), + Flags: xrpl.GlobalFlags.tfInnerBatchTxn // THIS IS REQUIRED +} + +// Send Batch transaction -------------------------------------------- +console.log("\n=== Creating Batch transaction... ===") +const batchTx = { + TransactionType: "Batch", + Account: sender.address, + Flags: xrpl.BatchFlags.tfAllOrNothing, // tfAllOrNothing: All inner transactions must succeed + // Must include a minimum of 2 transactions and a maximum of 8 transactions. + RawTransactions: [ + { RawTransaction: payment1 }, + { RawTransaction: payment2 } + ] +} +console.log(JSON.stringify(batchTx, null, 2)) + +// Validate the transaction structure before submitting +xrpl.validate(batchTx) + +// Submit and wait for validation +console.log("\n=== Submitting Batch transaction... ===") +const submitResponse = await client.submitAndWait(batchTx, { + wallet: sender, + // "autofill" will automatically add Fee: "0" and SigningPubKey: "" to inner transactions. + autofill: true +}) + +// Check Batch transaction result -------------------------------- +if (submitResponse.result.meta.TransactionResult !== "tesSUCCESS") { + const resultCode = submitResponse.result.meta.TransactionResult + console.warn(`\nTransaction failed with result code ${resultCode}`) + await client.disconnect() + process.exit(1) +} +console.log("\nBatch transaction submitted successfully!") +console.log("Result:\n", JSON.stringify(submitResponse.result, null, 2)) +// View the batch transaction on the XRPL Explorer +console.log(`\nBatch transaction URL:\nhttps://devnet.xrpl.org/transactions/${submitResponse.result.hash}`) + +// Calculate and verify inner transaction hashes -------------------------------------------- +console.log("\n=== Verifying inner transactions... ===") +const rawTransactions = submitResponse.result.tx_json.RawTransactions +let hasFailure = false + +for (let i = 0; i < rawTransactions.length; i++) { + const innerTx = rawTransactions[i].RawTransaction + const hash = xrpl.hashes.hashSignedTx(innerTx) + console.log(`\nTransaction ${i + 1} hash: ${hash}`) + + try { + const tx = await client.request({ command: 'tx', transaction: hash }) + const status = tx.result.meta?.TransactionResult + console.log(` - Status: ${status} (Ledger ${tx.result.ledger_index})`) + console.log(` - Transaction URL: https://devnet.xrpl.org/transactions/${hash}`) + } catch (error) { + hasFailure = true + console.log(` - Transaction not found: ${error}`) + } +} +if (hasFailure) { + console.error("\n--- Error: One or more inner transactions failed. ---") + await client.disconnect() + process.exit(1) +} + +// Verify balances after transaction +console.log("\n=== Final balances ===") +console.log(`Sender: ${sender.address}, Balance: ${await client.getXrpBalance(sender.address)} XRP`) +console.log(`Wallet1: ${wallet1.address}, Balance: ${await client.getXrpBalance(wallet1.address)} XRP`) +console.log(`Wallet2: ${wallet2.address}, Balance: ${await client.getXrpBalance(wallet2.address)} XRP`) + +await client.disconnect() diff --git a/docs/references/protocol/transactions/types/batch.md b/docs/references/protocol/transactions/types/batch.md index d7d0df02746..518bf3e3cdc 100644 --- a/docs/references/protocol/transactions/types/batch.md +++ b/docs/references/protocol/transactions/types/batch.md @@ -121,12 +121,12 @@ In this example, two users are atomically swapping their tokens: XRP for GKO. | Field | JSON Type | [Internal Type][] | Required? | Description | |:------------------|:----------|:------------------|:----------|:------------| | `Flags` | Number | UInt32 | Yes | A bit-flag for this transaction. Exactly one must be specified to represent the batch mode of the transaction. See: [Batch Flags](#batch-flags). | -| `RawTransactions` | Array | Array | Yes | The list of transactions to apply. | +| `RawTransactions` | Array | Array | Yes | The list of transactions to apply. See [RawTransactions](#rawtransactions). | | `BatchSigners` | Array | Array | No | The signatures authorizing a multi-account `Batch` transaction. | ### RawTransactions -`RawTransactions` contains the list of inner transactions to be applied. There can be up to 8 transactions included. These transactions can come from one account or multiple accounts. +`RawTransactions` contains the list of inner transactions to be applied. There must be a minimum of **2** transactions and a maximum of **8** transactions. These transactions can come from one account or multiple accounts. Each inner transaction: @@ -169,6 +169,7 @@ A transaction is considered successful if it receives a `tesSUCCESS` result. | Error Code | Description | |:--------------------------|:--------------------------------------------------| +| `temARRAY_EMPTY` | The batch transaction contains zero or one inner transaction. You must submit at least two inner transactions. | | `temINVALID_INNER_BATCH` | An inner transaction is malformed. | | `temSEQ_AND_TICKET` | The transaction contains both a `TicketSequence` field and a non-zero `Sequence` value. A transaction can't include both fields, but must have at least one. | diff --git a/docs/tutorials/how-tos/use-batch-transactions/index.md b/docs/tutorials/how-tos/use-batch-transactions/index.md new file mode 100644 index 00000000000..d64f91fc482 --- /dev/null +++ b/docs/tutorials/how-tos/use-batch-transactions/index.md @@ -0,0 +1,14 @@ +--- +seo: + description: Batch multiple transactions together and execute them as a single unit. +metadata: + indexPage: true +labels: + - Batch + - Transactions +--- +# Use Batch Transactions + +Batch multiple transactions together and execute them as a single unit. + +{% child-pages /%} diff --git a/docs/tutorials/how-tos/use-batch-transactions/send-a-multi-account-batch-transaction.md b/docs/tutorials/how-tos/use-batch-transactions/send-a-multi-account-batch-transaction.md new file mode 100644 index 00000000000..e0f1513e592 --- /dev/null +++ b/docs/tutorials/how-tos/use-batch-transactions/send-a-multi-account-batch-transaction.md @@ -0,0 +1,172 @@ +--- +seo: + description: Send a Batch transaction containing transactions from multiple accounts. +metadata: + indexPage: true +labels: + - Batch + - Transactions +--- +# Send a Multi-Account Batch Transaction + +This tutorial shows you how to create a [Batch transaction][] containing transactions from multiple accounts, where each account must sign the `Batch` transaction. Any account, even one not involved in the inner transactions, can submit the batch. + +## Goals + +By the end of this tutorial, you will be able to: + +- Create a `Batch` transaction with multiple inner transactions, signed by multiple accounts, and submitted by a third party account. +- Configure the `Batch` transaction to ensure atomicity, so that either all inner transactions succeed or they all fail. + +## 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](../../javascript/build-apps/get-started.md) 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](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/batch/). + +## Steps + +The example in this tutorial demonstrates a scenario where Bob and Charlie both owe Alice 50 XRP each, and a third-party (such as a payment processor) submits the `Batch` transaction atomically to ensure Alice is paid by both parties. + +### 1. Install dependencies + +{% tabs %} +{% tab label="Javascript" %} +From the code sample folder, use npm to install dependencies: + +```bash +npm install xrpl +``` + +{% /tab %} +{% /tabs %} + +### 2. Set up client and accounts + +To get started, import the client library and instantiate a client to connect to the XRPL. Then, create the accounts for Alice, Bob, Charlie, and the third-party. + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/multiAccountBatch.js" language="js" from="import xrpl" before="// Create inner transactions" /%} +{% /tab %} +{% /tabs %} + +### 3. Prepare inner transactions + +Next, prepare the inner transactions that will be included in the batch. + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/multiAccountBatch.js" language="js" from="// Create inner transactions" to="// Send Batch transaction" /%} +{% /tab %} +{% /tabs %} + +The first transaction sends a payment of 50 XRP from Charlie to Alice, and the second sends a payment of 50 XRP from Bob to Alice. Both transactions must include the `tfInnerBatchTxn` (0x40000000) flag to indicate that they are inner transactions of a batch. + +Inner transactions must have a Fee of **0** and an empty string for the `SigningPubKey`. The outer `Batch` transaction handles the overall fee and signing for all inner transactions. + +{% admonition type="info" name="Note" %} +The `Fee` and `SigningPubKey` fields are omitted as the client library's _autofill_ functionality automatically populates these when submitting the `Batch` transaction. + +You typically don't need to set these manually, but if you do, ensure `Fee` is set to 0 and `SigningPubKey` is an empty string. +{% /admonition %} + +### 4. Prepare Batch transaction + +Create the `Batch` transaction and provide the inner transactions. The key fields to note are: + +| Field | Value | +|:---------------- |:---------- | +| TransactionType | The type of transaction, in this case `Batch`.| +| Account | The wallet address of the account that is sending the `Batch` transaction. | +| Flags | The flags for the `Batch` transaction. For this example the transaction is configured with the `tfAllOrNothing` (0x00010000) flag to ensure that either all inner transactions succeed or they all fail atomically. See [Batch Flags](../../../references/protocol/transactions/types/batch.md#batch-flags) for other options. | +| RawTransactions | Contains the list of inner transactions to be applied. Must include a minimum of **2** transactions and a maximum of **8** transactions. These transactions can come from one account or multiple accounts. | +| BatchSigners | The list of signatures required for the `Batch` transaction. This is required because there are multiple accounts' transactions included in the batch. | + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/multiAccountBatch.js" language="js" from="// Send Batch transaction" before="// Gather batch signatures" /%} +{% /tab %} +{% /tabs %} + +Because we used `autofill`, the client library automatically fills in any missing fields, like `Fee` and `SigningPubKey`. Additionally, we specify the expected number of signers (2 in this case). + +### 5. Gather batch signatures + +To add the `BatchSigners` field, you need to collect signatures from each account that's sending a transaction within the batch. In this case we need two signatures, one from Charlie and one from Bob. Each sender must sign the `Batch` transaction to authorize their payment. + +{% tabs %} +{% tab label="Javascript" %} +The **xrpl.js** library provides a helper function, `signMultiBatch()`, to sign the `Batch` transaction for each account. + +Then, to combine the signatures into a single signed `Batch` transaction, use the `combineBatchSigners()` utility function. +{% code-snippet file="/_code-samples/batch/js/multiAccountBatch.js" language="js" from="// Gather batch signatures" to="// Submit" /%} +{% /tab %} +{% /tabs %} + +### 6. Submit Batch transaction + +With all the required signatures gathered, the third-party wallet can now submit the `Batch` transaction. + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/multiAccountBatch.js" language="js" from="// Submit" before="// Check Batch transaction" /%} +{% /tab %} +{% /tabs %} + +### 7. Check Batch transaction result + +To check the result of the `Batch` transaction submission: + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/multiAccountBatch.js" language="js" from="// Check Batch transaction" before="// Calculate and verify" /%} +{% /tab %} +{% /tabs %} + +The code checks for a `tesSUCCESS` result and displays the response details. + +{% admonition type="warning" name="Warning" %} +A `tesSUCCESS` result indicates that the `Batch` transaction was processed successfully, but does not guarantee the inner transactions succeeded. For example, see the [following transaction on the XRPL Explorer](https://devnet.xrpl.org/transactions/20CFCE5CF75E93E6D1E9C1E42F8E8C8C4CB1786A65BE23D2EA77EAAB65A455C5/simple). +{% /admonition %} + +Because the `Batch` transaction is configured with a `tfAllOrNothing` flag, if any inner transaction fails, **all** inner transactions wil fail, and only the `Batch` transaction fee is deducted from the **third-party wallet**. + +### 8. Verify inner transactions + +Since there is no way to check the status of inner transactions in the `Batch` transaction result, you need to calculate the inner transaction hashes and look them up on the ledger: + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/multiAccountBatch.js" language="js" from="// Calculate and verify" before="// Verify balances after transaction" /%} +{% /tab %} +{% /tabs %} + +The code extracts the actual inner transactions from the batch response, calculates the hash of each inner transaction and looks up each transaction on the ledger using its hash. + +### 9. Verify balances + +You can also verify that the inner transactions executed successfully by checking the account balances to confirm the expected changes. + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/multiAccountBatch.js" language="js" from="// Verify balances after transaction" /%} +{% /tab %} +{% /tabs %} + +## See Also + +- **Concepts**: + - [Batch Transactions](../../../concepts/transactions/batch-transactions.md) +- **Tutorials**: + - [Send a Single Account Batch Transaction](send-a-single-account-batch-transaction.md) +- **References**: + - [Batch Transaction][] + +{% raw-partial file="/docs/_snippets/common-links.md" /%} diff --git a/docs/tutorials/how-tos/use-batch-transactions/send-a-single-account-batch-transaction.md b/docs/tutorials/how-tos/use-batch-transactions/send-a-single-account-batch-transaction.md new file mode 100644 index 00000000000..69e08208575 --- /dev/null +++ b/docs/tutorials/how-tos/use-batch-transactions/send-a-single-account-batch-transaction.md @@ -0,0 +1,162 @@ +--- +seo: + description: Send a Batch transaction from a single account. +metadata: + indexPage: true +labels: + - Batch + - Transactions +--- +# Send a Single Account Batch Transaction + +A [Batch transaction][] allows you to group multiple transactions together and execute them as a single atomic operation. + +This tutorial shows you how to create a `Batch` transaction where a single account submits multiple transactions that either all succeed together or all fail together. + +## Goals + +By the end of this tutorial, you will be able to: + +- Create a `Batch` transaction with multiple inner transactions, signed and submitted by a single account. +- Configure the `Batch` transaction to ensure atomicity, so that either all inner transactions succeed or they all fail. + +## 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](../../javascript/build-apps/get-started.md) 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](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/batch/). + +## Steps + +The example in this tutorial demonstrates a scenario where an account sends multiple payments that must be processed atomically in one `Batch` transaction. + +### 1. Install dependencies + +{% tabs %} +{% tab label="Javascript" %} +From the code sample folder, use npm to install dependencies: + +```bash +npm install xrpl +``` + +{% /tab %} +{% /tabs %} + +### 2. Set up client and accounts + +To get started, import the client library and instantiate a client to connect to the XRPL. For this tutorial you need a funded account for the `Batch` transaction **sender**, and two other accounts to **receive** the payments. + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/singleAccountBatch.js" language="js" from="import xrpl" before="// Create inner transactions" /%} +{% /tab %} +{% /tabs %} + +### 3. Prepare inner transactions + +Next, prepare the inner transactions that will be included in the batch. + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/singleAccountBatch.js" language="js" from="// Create inner transactions" to="// Send Batch transaction" /%} +{% /tab %} +{% /tabs %} + +The first transaction sends a payment of 2 XRP from the sender to `wallet1`, and the second transaction sends 5 XRP from the sender to `wallet2`. Both transactions must include the `tfInnerBatchTxn` (0x40000000) flag to indicate that they are inner transactions of a batch. + +Inner transactions must have a Fee of **0** and an empty string for the `SigningPubKey`. The outer `Batch` transaction handles the overall fee and signing for all inner transactions. + +{% admonition type="info" name="Note" %} +The `Fee` and `SigningPubKey` fields are omitted as the client library's _autofill_ functionality automatically populates these when submitting the `Batch` transaction. + +You typically don't need to set these manually, but if you do, ensure `Fee` is set to 0 and `SigningPubKey` is an empty string. +{% /admonition %} + +### 4. Prepare Batch transaction + +Create the `Batch` transaction and provide the inner transactions. The key fields to note are: + +| Field | Value | +|:---------------- |:---------- | +| TransactionType | The type of transaction, in this case `Batch`.| +| Account | The wallet address of the account that is sending the `Batch` transaction. | +| Flags | The flags for the `Batch` transaction. For this example the transaction is configured with the `tfAllOrNothing` (0x00010000) flag to ensure that either all inner transactions succeed or they all fail atomically. See [Batch Flags](../../../references/protocol/transactions/types/batch.md#batch-flags) for other options. | +| RawTransactions | Contains the list of inner transactions to be applied. Must include a minimum of **2** transactions and a maximum of **8** transactions. These transactions can come from one account or multiple accounts. | + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/singleAccountBatch.js" language="js" from="// Send Batch transaction" before="// Submit" /%} +{% /tab %} +{% /tabs %} + +### 5. Submit Batch transaction + +Now the sender can submit the `Batch` transaction: + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/singleAccountBatch.js" language="js" from="// Submit" before="// Check Batch transaction" /%} +{% /tab %} +{% /tabs %} + +Because `autofill` is set to `true`, the client library automatically fills in any missing fields, like the `Fee` and `SigningPubKey`, before submitting the batch. + +### 6. Check Batch transaction result + +To check the result of the `Batch` transaction submission: + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/singleAccountBatch.js" language="js" from="// Check Batch transaction" before="// Calculate and verify" /%} +{% /tab %} +{% /tabs %} + +The code checks for a `tesSUCCESS` result and displays the response details. + +{% admonition type="warning" name="Warning" %} +A `tesSUCCESS` result indicates that the `Batch` transaction was processed successfully, but does not guarantee the inner transactions succeeded. + +For example, see the [following transaction on the XRPL Explorer](https://devnet.xrpl.org/transactions/20CFCE5CF75E93E6D1E9C1E42F8E8C8C4CB1786A65BE23D2EA77EAAB65A455C5/simple). +{% /admonition %} + +Because the `Batch` transaction is configured with a `tfAllOrNothing` flag, if any inner transaction fails, **all** inner transactions wil fail, and only the `Batch` transaction fee is deducted from the **third-party wallet**. + +### 7. Verify inner transactions + +Since there is no way to check the status of inner transactions in the `Batch` transaction result, you need to calculate the inner transaction hashes and look them up on the ledger: + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/singleAccountBatch.js" language="js" from="// Calculate and verify" before="// Verify balances after transaction" /%} +{% /tab %} +{% /tabs %} + +The code extracts the actual inner transactions from the batch response, calculates the hash of each inner transaction and looks up each transaction on the ledger using its hash. + +### 8. Verify balances + +You can also verify that the inner transactions executed successfully by checking the account balances to confirm the expected changes. + +{% tabs %} +{% tab label="Javascript" %} +{% code-snippet file="/_code-samples/batch/js/singleAccountBatch.js" language="js" from="// Verify balances after transaction" /%} +{% /tab %} +{% /tabs %} + +## See Also + +- **Concepts**: + - [Batch Transactions](../../../concepts/transactions/batch-transactions.md) +- **Tutorials**: + - [Send a Multi-Account Batch Transaction](send-a-multi-account-batch-transaction.md) +- **References**: + - [Batch Transaction][] + +{% raw-partial file="/docs/_snippets/common-links.md" /%} diff --git a/sidebars.yaml b/sidebars.yaml index ee47bcbc2d4..18fc89bf593 100644 --- a/sidebars.yaml +++ b/sidebars.yaml @@ -295,6 +295,11 @@ - page: docs/tutorials/how-tos/manage-account-settings/require-destination-tags.md - page: docs/tutorials/how-tos/manage-account-settings/offline-account-setup.md - page: docs/tutorials/how-tos/manage-account-settings/use-tickets.md + - page: docs/tutorials/how-tos/use-batch-transactions/index.md + expanded: false + items: + - page: docs/tutorials/how-tos/use-batch-transactions/send-a-single-account-batch-transaction.md + - page: docs/tutorials/how-tos/use-batch-transactions/send-a-multi-account-batch-transaction.md - page: docs/tutorials/how-tos/use-specialized-payment-types/index.md expanded: false items: