Compare commits

..

1 Commits

Author SHA1 Message Date
Maria Shodunke
84e08700b7 Add tutorial for issuing an MPT (Javascript and Python). 2025-11-21 11:31:18 +00:00
28 changed files with 634 additions and 1134 deletions

View File

@@ -1,5 +1,4 @@
# Batch
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.
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.

View File

@@ -1,301 +0,0 @@
# 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
```

View File

@@ -1,143 +0,0 @@
/**
* 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()

View File

@@ -1,6 +0,0 @@
{
"dependencies": {
"xrpl": "^4.4.3"
},
"type": "module"
}

View File

@@ -1,120 +0,0 @@
/**
* 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()

View File

@@ -9,8 +9,135 @@ npm i
node issue-mpt-with-metadata.js
```
The script should output a validated transaction and end with a line such as the following:
The script should output a validated transaction and decoded metadata, similar to the following:
```text
MPToken created successfully with issuance ID 005073C721E14A7613BAAF5E0B1A253459832FF8D0D81278.
```sh
=== Funding new wallet from faucet...===
Issuer address: r9fhoyac7uUM9XZFDJV9wXQ4pcJb6UDpJM
=== Encoding metadata...===
Encoded mpt_metadata_hex: 7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D
=== Sending MPTokenIssuanceCreate transaction...===
{
"TransactionType": "MPTokenIssuanceCreate",
"Account": "r9fhoyac7uUM9XZFDJV9wXQ4pcJb6UDpJM",
"AssetScale": 4,
"MaximumAmount": "50000000",
"TransferFee": 0,
"Flags": 48,
"MPTokenMetadata": "7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D"
}
=== Checking MPTokenIssuanceCreate results... ===
{
"close_time_iso": "2025-11-20T18:13:30Z",
"ctid": "C0148E8700000002",
"hash": "555FAFDB99B239567FDF30DDF22BA3B30F8E70D8D06833B1270AC600E1575948",
"ledger_hash": "A7010A2025989778420280F7F96B10F5D3C879E049BE5DA12500FFBB90D162C5",
"ledger_index": 1347207,
"meta": {
"AffectedNodes": [
{
"CreatedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "33468621DEF32177E84C1EBC2C457C908567E245622CBDE03185C4ABC83B7F9D",
"NewFields": {
"Owner": "r9fhoyac7uUM9XZFDJV9wXQ4pcJb6UDpJM",
"RootIndex": "33468621DEF32177E84C1EBC2C457C908567E245622CBDE03185C4ABC83B7F9D"
}
}
},
{
"CreatedNode": {
"LedgerEntryType": "MPTokenIssuance",
"LedgerIndex": "6567EE49937AADAB4FC4D5DDBD6A4A6E179E0E5A9DF2FC7ED8B41B807F0DDBF2",
"NewFields": {
"AssetScale": 4,
"Flags": 48,
"Issuer": "r9fhoyac7uUM9XZFDJV9wXQ4pcJb6UDpJM",
"MPTokenMetadata": "7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D",
"MaximumAmount": "50000000",
"Sequence": 1347205
}
}
},
{
"ModifiedNode": {
"FinalFields": {
"Account": "r9fhoyac7uUM9XZFDJV9wXQ4pcJb6UDpJM",
"Balance": "99999999",
"Flags": 0,
"OwnerCount": 1,
"Sequence": 1347206
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "AB5FC35110CED5BFD2CEA3E37B41E43CC4BBAF89AE66BA85942E04CBC38550FB",
"PreviousFields": {
"Balance": "100000000",
"OwnerCount": 0,
"Sequence": 1347205
},
"PreviousTxnID": "1CDF420134492607EC54838F91FA06A655E07DD296ED69CC7172C1AC356BF22B",
"PreviousTxnLgrSeq": 1347205
}
}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS",
"mpt_issuance_id": "00148E8558E6AEAA301085FBFD01D615F059A7CCE6E38296"
},
"tx_json": {
"Account": "r9fhoyac7uUM9XZFDJV9wXQ4pcJb6UDpJM",
"AssetScale": 4,
"Fee": "1",
"Flags": 48,
"LastLedgerSequence": 1347225,
"MPTokenMetadata": "7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D",
"MaximumAmount": "50000000",
"Sequence": 1347205,
"SigningPubKey": "ED1EC65DB85E686A55F8FD9BC6E405E8F2F8EA5E1712AED64E28C97350EB4EF6E7",
"TransactionType": "MPTokenIssuanceCreate",
"TransferFee": 0,
"TxnSignature": "3A671905D57342F051E3BF057CCF65B0D94114C04D255D4AE3CEE01C2D0B368118E94011CEB27EC9BB447D3498B24B750F2691B4D7AB71F82626BC6F49465806",
"ctid": "C0148E8700000002",
"date": 816977610,
"ledger_index": 1347207
},
"validated": true
}
- MPToken created successfully with issuance ID: 00148E8558E6AEAA301085FBFD01D615F059A7CCE6E38296
- Explorer URL: https://devnet.xrpl.org/mpt/00148E8558E6AEAA301085FBFD01D615F059A7CCE6E38296
=== Confirming MPT Issuance metadata in the validated ledger... ===
Decoded MPT metadata:
{
asset_class: 'rwa',
additional_info: {
cusip: '912796RX0',
interest_rate: '5.00%',
interest_type: 'variable',
maturity_date: '2045-06-30',
yield_source: 'U.S. Treasury Bills'
},
asset_subclass: 'treasury',
desc: 'A yield-bearing stablecoin backed by short-term U.S. Treasuries and money market instruments.',
icon: 'https://example.org/tbill-icon.png',
issuer_name: 'Example Yield Co.',
name: 'T-Bill Yield Token',
ticker: 'TBILL',
uris: [
{
category: 'website',
title: 'Product Page',
uri: 'https://exampleyield.co/tbill'
},
{
category: 'docs',
title: 'Yield Token Docs',
uri: 'https://exampleyield.co/docs'
}
]
}
```

View File

@@ -1,85 +1,108 @@
import { stringToHex, hexToString } from '@xrplf/isomorphic/dist/utils/index.js'
import { MPTokenIssuanceCreateFlags, Client } from 'xrpl'
import {
MPTokenIssuanceCreateFlags,
Client,
encodeMPTokenMetadata,
decodeMPTokenMetadata,
} from "xrpl";
// Connect to network and get a wallet
const client = new Client('wss://s.devnet.rippletest.net:51233')
await client.connect()
const client = new Client("wss://s.devnet.rippletest.net:51233");
await client.connect();
console.log('Funding new wallet from faucet...')
const { wallet } = await client.fundWallet()
console.log("=== Funding new wallet from faucet...===");
const { wallet: issuer } = await client.fundWallet();
console.log(`Issuer address: ${issuer.address}`);
// Define metadata as JSON
const mpt_metadata = {
t: 'TBILL',
n: 'T-Bill Yield Token',
d: 'A yield-bearing stablecoin backed by short-term U.S. Treasuries and money market instruments.',
i: 'https://example.org/tbill-icon.png',
ac: 'rwa',
as: 'treasury',
in: 'Example Yield Co.',
us: [
ticker: "TBILL",
name: "T-Bill Yield Token",
desc: "A yield-bearing stablecoin backed by short-term U.S. Treasuries and money market instruments.",
icon: "https://example.org/tbill-icon.png",
asset_class: "rwa",
asset_subclass: "treasury",
issuer_name: "Example Yield Co.",
uris: [
{
u: 'https://exampleyield.co/tbill',
c: 'website',
t: 'Product Page'
uri: "https://exampleyield.co/tbill",
category: "website",
title: "Product Page",
},
{
u: 'https://exampleyield.co/docs',
c: 'docs',
t: 'Yield Token Docs'
}
uri: "https://exampleyield.co/docs",
category: "docs",
title: "Yield Token Docs",
},
],
ai: {
interest_rate: '5.00%',
interest_type: 'variable',
yield_source: 'U.S. Treasury Bills',
maturity_date: '2045-06-30',
cusip: '912796RX0'
}
}
additional_info: {
interest_rate: "5.00%",
interest_type: "variable",
yield_source: "U.S. Treasury Bills",
maturity_date: "2045-06-30",
cusip: "912796RX0",
},
};
// Convert JSON to a string (without excess whitespace), then string to hex
const mpt_metadata_hex = stringToHex(JSON.stringify(mpt_metadata))
// Encode the metadata.
// The encodeMPTokenMetadata function converts the JSON metadata object into
// a compact, hex-encoded string, following the XLS-89 standard.
// https://xls.xrpl.org/xls/XLS-0089-multi-purpose-token-metadata-schema.html
console.log("\n=== Encoding metadata...===");
const mpt_metadata_hex = encodeMPTokenMetadata(mpt_metadata);
console.log("Encoded mpt_metadata_hex: ", mpt_metadata_hex);
// Define the transaction, including other MPT parameters
const mpt_issuance_create = {
TransactionType: 'MPTokenIssuanceCreate',
Account: wallet.address,
TransactionType: "MPTokenIssuanceCreate",
Account: issuer.address,
AssetScale: 4,
MaximumAmount: '50000000',
MaximumAmount: "50000000",
TransferFee: 0,
Flags: MPTokenIssuanceCreateFlags.tfMPTCanTransfer |
MPTokenIssuanceCreateFlags.tfMPTCanTrade,
MPTokenMetadata: mpt_metadata_hex
Flags:
MPTokenIssuanceCreateFlags.tfMPTCanTransfer |
MPTokenIssuanceCreateFlags.tfMPTCanTrade,
MPTokenMetadata: mpt_metadata_hex,
};
// Sign and submit the transaction
console.log("\n=== Sending MPTokenIssuanceCreate transaction...===");
console.log(JSON.stringify(mpt_issuance_create, null, 2));
const submit_response = await client.submitAndWait(mpt_issuance_create, {
wallet: issuer,
autofill: true,
});
// Check transaction results
console.log("\n=== Checking MPTokenIssuanceCreate results... ===");
console.log(JSON.stringify(submit_response.result, null, 2));
if (submit_response.result.meta.TransactionResult !== "tesSUCCESS") {
const result_code = submit_response.result.meta.TransactionResult;
console.warn(`Transaction failed with result code ${result_code}.`);
await client.disconnect();
process.exit(1);
}
// Prepare, sign, and submit the transaction
console.log('Sending MPTokenIssuanceCreate transaction...')
const submit_response = await client.submitAndWait(mpt_issuance_create, { wallet, autofill: true })
// Check transaction results and disconnect
console.log(JSON.stringify(submit_response, null, 2))
if (submit_response.result.meta.TransactionResult !== 'tesSUCCESS') {
const result_code = response.result.meta.TransactionResult
console.warn(`Transaction failed with result code ${result_code}.`)
process.exit(1)
}
const issuance_id = submit_response.result.meta.mpt_issuance_id
console.log(`MPToken created successfully with issuance ID ${issuance_id}.`)
const issuance_id = submit_response.result.meta.mpt_issuance_id;
console.log(
`\n- MPToken created successfully with issuance ID: ${issuance_id}`
);
// View the MPT issuance on the XRPL Explorer
console.log(`- Explorer URL: https://devnet.xrpl.org/mpt/${issuance_id}`);
// Look up MPT Issuance entry in the validated ledger
console.log('Confirming MPT Issuance metadata in the validated ledger.')
console.log("\n=== Confirming MPT Issuance metadata in the validated ledger... ===");
const ledger_entry_response = await client.request({
"command": "ledger_entry",
"mpt_issuance": issuance_id,
"ledger_index": "validated"
})
command: "ledger_entry",
mpt_issuance: issuance_id,
ledger_index: "validated",
});
// Decode the metadata
const metadata_blob = ledger_entry_response.result.node.MPTokenMetadata
const decoded_metadata = JSON.parse(hexToString(metadata_blob))
console.log('Decoded metadata:', decoded_metadata)
// Decode the metadata.
// The decodeMPTokenMetadata function takes a hex-encoded string representing MPT metadata,
// decodes it to a JSON object, and expands any compact field names to their full forms.
const metadata_blob = ledger_entry_response.result.node.MPTokenMetadata;
const decoded_metadata = decodeMPTokenMetadata(metadata_blob);
console.log("Decoded MPT metadata:\n", decoded_metadata);
client.disconnect()
// Disconnect from the client
await client.disconnect();

View File

@@ -1,6 +1,6 @@
{
"dependencies": {
"xrpl": "^4.4.0"
"xrpl": "^4.4.3"
},
"type": "module"
}

View File

@@ -11,8 +11,137 @@ pip install -r requirements.txt
python issue-mpt-with-metadata.py
```
The script should output a validated transaction and end with a line such as the following:
The script should output a validated transaction and decoded metadata, similar to the following:
```text
MPToken created successfully with issuance ID 0050773D6B8DF8C6BEA497016C8679728A217DE1C4D50AC5.
```sh
=== Funding new wallet from faucet... ===
Attempting to fund address rN1vQBHqgbfXjeAfYVUVpQXMyyZYjAnQkS
Faucet fund successful.
=== Encoding metadata...===
Encoded mpt_metadata_hex: 7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D
=== Sending MPTokenIssuanceCreate transaction...===
{
"Account": "rN1vQBHqgbfXjeAfYVUVpQXMyyZYjAnQkS",
"TransactionType": "MPTokenIssuanceCreate",
"Flags": 48,
"SigningPubKey": "",
"AssetScale": 4,
"MaximumAmount": "50000000",
"TransferFee": 0,
"MPTokenMetadata": "7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D"
}
=== Checking MPTokenIssuanceCreate results... ===
{
"close_time_iso": "2025-11-20T18:21:12Z",
"ctid": "C0148F2200000002",
"hash": "47D87C3C93C80F2158CE5A688C63386E939BC77CFF4F5B62F84775A97EF991AE",
"ledger_hash": "663C9D10B10586009F5C17B4A9A98220ECB00AF64A248A71ECF970D3E7D206F4",
"ledger_index": 1347362,
"meta": {
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"Account": "rN1vQBHqgbfXjeAfYVUVpQXMyyZYjAnQkS",
"Balance": "99999999",
"Flags": 0,
"OwnerCount": 1,
"Sequence": 1347360
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "0B10E7C08910B27DE817A935972FBD91B57E6177627FDA78C9C75CD83D32D973",
"PreviousFields": {
"Balance": "100000000",
"OwnerCount": 0,
"Sequence": 1347359
},
"PreviousTxnID": "2166929BBF80BEAA631AB4FBE6864E03CD669D4AFEE6559BA6AB850602A9151A",
"PreviousTxnLgrSeq": 1347359
}
},
{
"CreatedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "5D2D7A2717A4ECF4C865A6F80E0C2C228409B27CE948307F3ED01213C9906AC4",
"NewFields": {
"Owner": "rN1vQBHqgbfXjeAfYVUVpQXMyyZYjAnQkS",
"RootIndex": "5D2D7A2717A4ECF4C865A6F80E0C2C228409B27CE948307F3ED01213C9906AC4"
}
}
},
{
"CreatedNode": {
"LedgerEntryType": "MPTokenIssuance",
"LedgerIndex": "886355A55396B5511A96BCA43E73E3DEDC2875776EC307252157142B1D36B852",
"NewFields": {
"AssetScale": 4,
"Flags": 48,
"Issuer": "rN1vQBHqgbfXjeAfYVUVpQXMyyZYjAnQkS",
"MPTokenMetadata": "7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D",
"MaximumAmount": "50000000",
"Sequence": 1347359
}
}
}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS",
"mpt_issuance_id": "00148F1F983B024FB54CE16CBC7F788C2F71AC9728355EFC"
},
"tx_json": {
"Account": "rN1vQBHqgbfXjeAfYVUVpQXMyyZYjAnQkS",
"AssetScale": 4,
"Fee": "1",
"Flags": 48,
"LastLedgerSequence": 1347380,
"MPTokenMetadata": "7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D",
"MaximumAmount": "50000000",
"Sequence": 1347359,
"SigningPubKey": "ED0BFB56FB91211F7DCB245C3863958B8FF5A5BAC4B7293E598C7B4D34265EF0A9",
"TransactionType": "MPTokenIssuanceCreate",
"TransferFee": 0,
"TxnSignature": "4710CCD303902101E6A009E8D459774D1FA9C59E20816588B9248883FF6A37DD8670C1C6EEED1DE5B363A15C88FCA40C1E74319886F3DB8278A63CF0B88CDC0A",
"ctid": "C0148F2200000002",
"date": 816978072,
"ledger_index": 1347362
},
"validated": true
}
- MPToken created successfully with issuance ID: 00148F1F983B024FB54CE16CBC7F788C2F71AC9728355EFC
- Explorer URL: https://devnet.xrpl.org/mpt/00148F1F983B024FB54CE16CBC7F788C2F71AC9728355EFC
=== Confirming MPT Issuance metadata in the validated ledger... ===
Decoded MPT metadata:
{
"asset_class": "rwa",
"additional_info": {
"cusip": "912796RX0",
"interest_rate": "5.00%",
"interest_type": "variable",
"maturity_date": "2045-06-30",
"yield_source": "U.S. Treasury Bills"
},
"asset_subclass": "treasury",
"desc": "A yield-bearing stablecoin backed by short-term U.S. Treasuries and money market instruments.",
"icon": "https://example.org/tbill-icon.png",
"issuer_name": "Example Yield Co.",
"name": "T-Bill Yield Token",
"ticker": "TBILL",
"uris": [
{
"category": "website",
"title": "Product Page",
"uri": "https://exampleyield.co/tbill"
},
{
"category": "docs",
"title": "Yield Token Docs",
"uri": "https://exampleyield.co/docs"
}
]
}
```

View File

@@ -1,5 +1,5 @@
import json
from xrpl.utils import str_to_hex, hex_to_str
from xrpl.utils import encode_mptoken_metadata, decode_mptoken_metadata
from xrpl.clients import JsonRpcClient
from xrpl.wallet import generate_faucet_wallet
from xrpl.transaction import submit_and_wait
@@ -7,31 +7,31 @@ from xrpl.models import LedgerEntry, MPTokenIssuanceCreate, MPTokenIssuanceCreat
# Set up client and get a wallet
client = JsonRpcClient("https://s.devnet.rippletest.net:51234")
print("Funding new wallet from faucet...")
wallet = generate_faucet_wallet(client, debug=True)
print("=== Funding new wallet from faucet... ===")
issuer = generate_faucet_wallet(client, debug=True)
# Define metadata as JSON
# Define metadata as JSON
mpt_metadata = {
"t": "TBILL",
"n": "T-Bill Yield Token",
"d": "A yield-bearing stablecoin backed by short-term U.S. Treasuries and money market instruments.",
"i": "example.org/tbill-icon.png",
"ac": "rwa",
"as": "treasury",
"in": "Example Yield Co.",
"us": [
"ticker": "TBILL",
"name": "T-Bill Yield Token",
"desc": "A yield-bearing stablecoin backed by short-term U.S. Treasuries and money market instruments.",
"icon": "https://example.org/tbill-icon.png",
"asset_class": "rwa",
"asset_subclass": "treasury",
"issuer_name": "Example Yield Co.",
"uris": [
{
"u": "exampleyield.co/tbill",
"c": "website",
"t": "Product Page"
"uri": "https://exampleyield.co/tbill",
"category": "website",
"title": "Product Page"
},
{
"u": "exampleyield.co/docs",
"c": "docs",
"t": "Yield Token Docs"
"uri": "https://exampleyield.co/docs",
"category": "docs",
"title": "Yield Token Docs"
}
],
"ai": {
"additional_info": {
"interest_rate": "5.00%",
"interest_type": "variable",
"yield_source": "U.S. Treasury Bills",
@@ -40,13 +40,17 @@ mpt_metadata = {
}
}
# Convert JSON to a string (without excess whitespace), then string to hex
mpt_metadata_string = json.dumps(mpt_metadata, separators=(',', ':'))
mpt_metadata_hex = str_to_hex(mpt_metadata_string)
# Encode the metadata.
# The encode_mptoken_metadata function converts the JSON metadata object into
# a compact, hex-encoded string, following the XLS-89 standard.
# https://xls.xrpl.org/xls/XLS-0089-multi-purpose-token-metadata-schema.html
print("\n=== Encoding metadata...===")
mpt_metadata_hex = encode_mptoken_metadata(mpt_metadata)
print("Encoded mpt_metadata_hex:", mpt_metadata_hex)
# Define the transaction, including other MPT parameters
mpt_issuance_create = MPTokenIssuanceCreate(
account=wallet.address,
account=issuer.address,
asset_scale=4,
maximum_amount="50000000",
transfer_fee=0,
@@ -55,28 +59,33 @@ mpt_issuance_create = MPTokenIssuanceCreate(
mptoken_metadata=mpt_metadata_hex
)
# Prepare, sign, and submit the transaction
print("Sending MPTokenIssuanceCreate transaction...")
response = submit_and_wait(mpt_issuance_create, client, wallet, autofill=True)
print(json.dumps(response.result, indent=2))
# Sign and submit the transaction
print("\n=== Sending MPTokenIssuanceCreate transaction...===")
print(json.dumps(mpt_issuance_create.to_xrpl(), indent=2))
response = submit_and_wait(mpt_issuance_create, client, issuer, autofill=True)
# Check transaction results
print("\n=== Checking MPTokenIssuanceCreate results... ===")
print(json.dumps(response.result, indent=2))
result_code = response.result["meta"]["TransactionResult"]
if result_code != "tesSUCCESS":
print(f"Transaction failed with result code {result_code}")
print(f"Transaction failed with result code {result_code}.")
exit(1)
issuance_id = response.result["meta"]["mpt_issuance_id"]
print(f"MPToken successfully created with issuance ID {issuance_id}")
print(f"\n- MPToken created successfully with issuance ID: {issuance_id}")
print(f"- Explorer URL: https://devnet.xrpl.org/mpt/{issuance_id}")
# Look up MPT Issuance entry in the validated ledger
print("Confirming MPT Issuance metadata in the validated ledger.")
print("\n=== Confirming MPT Issuance metadata in the validated ledger... ===")
ledger_entry_response = client.request(LedgerEntry(
mpt_issuance=issuance_id,
ledger_index="validated"
))
# Decode the metadata
# Decode the metadata.
# The decode_mptoken_metadata function takes a hex-encoded string representing MPT metadata,
# decodes it to a JSON object, and expands any compact field names to their full forms.
metadata_blob = ledger_entry_response.result["node"]["MPTokenMetadata"]
decoded_metadata = json.loads(hex_to_str(metadata_blob))
print("Decoded metadata:", decoded_metadata)
decoded_metadata = decode_mptoken_metadata(metadata_blob)
print("Decoded MPT metadata:\n", json.dumps(decoded_metadata, indent=2))

View File

@@ -1 +1 @@
xrpl-py==4.3.0
xrpl-py==4.3.1

View File

@@ -1,70 +0,0 @@
---
category: 2025
date: "2025-11-19"
template: '../../@theme/templates/blogpost'
seo:
title: Introducing XRP Ledger version 2.6.2
description: rippled version 2.6.2 is now available. This version contains a new amendment and a critical bug fix.
labels:
- rippled Release Notes
markdown:
editPage:
hide: true
---
# Introducing XRP Ledger version 2.6.2
Version 2.6.2 of `rippled`, the reference server implementation of the XRP Ledger protocol, is now available. This release adds a new `fixDirectoryLimit` amendment and a critical bug fix.
## Action Required
If you run an XRP Ledger server, upgrade to version 2.6.2 as soon as possible to ensure service continuity.
## Install / Upgrade
On supported platforms, see the [instructions on installing or updating `rippled`](../../docs/infrastructure/installation/index.md).
| Package | SHA-256 |
|:--------|:--------|
| [RPM for Red Hat / CentOS (x86-64)](https://repos.ripple.com/repos/rippled-rpm/stable/rippled-2.6.2-1.el9.x86_64.rpm) | `e3b041906a75c3c52cc6423219d7ba9c199a5d736d2e3978a5ce0ac5ef693fdf` |
| [DEB for Ubuntu / Debian (x86-64)](https://repos.ripple.com/repos/rippled-deb/pool/stable/rippled_2.6.2-1_amd64.deb) | `0887b5a77c43c362ea7680b83df40b955a5748b712924acf2212b2de29e3373b` |
For other platforms, please [build from source](https://github.com/XRPLF/rippled/blob/master/BUILD.md). The most recent commit in the git log should be the change setting the version:
```text
commit df24ee077438e03673a9c6661c41e8f070b90cd9
Author: Vladislav Vysokikh <vvysokikh@gmail.com>
Date: Tue Nov 18 09:28:59 2025 +0000
Version 2.6.2
```
## Full Changelog
### Amendments
The following amendment is open for voting with this release:
- **fixDirectoryLimit** - Removes directory page limits. Object reserve requirements provide enough incentive to avoid creating unnecessary objects on the XRP Ledger. ([#5935](https://github.com/XRPLF/rippled/pull/5935))
### Bug Fixes
- Fixed an assertion failure when all the inner transactions of a `Batch` transaction were invalid. ([#5670](https://github.com/XRPLF/rippled/pull/5670))
## Credits
The following GitHub users contributed to this release:
- RippleX Engineering
- RippleX Docs
- RippleX Product
## Bug Bounties and Responsible Disclosures
We welcome reviews of the `rippled` code and urge researchers to responsibly disclose any issues they may find.
To report a bug, please send a detailed report to: <bugs@xrpl.org>

View File

@@ -10,7 +10,6 @@
- group: '2025'
expanded: false
items:
- page: 2025/rippled-2.6.2.md
- page: 2025/rippled-2.6.1.md
- page: 2025/vulnerabilitydisclosurereport-bug-sep2025.md
- page: 2025/devnet-reset-oct.md

View File

@@ -27,7 +27,7 @@ A transaction that fails with a `tec` code destroys the XRP paid as a [transacti
| `tecCANT_ACCEPT_OWN_NFTOKEN_OFFER` | 157 | The transaction tried to accept an offer that was placed by the same account to buy or sell a [non-fungible token](../../../../concepts/tokens/nfts/index.md). {% amendment-disclaimer name="NonFungibleTokensV1_1" /%} |
| `tecCLAIM` | 100 | Unspecified failure, with transaction cost destroyed. |
| `tecCRYPTOCONDITION_ERROR` | 146 | This [EscrowCreate][] or [EscrowFinish][] transaction contained a malformed or mismatched crypto-condition. |
| `tecDIR_FULL` | 121 | The transaction tried to add an object (such as a trust line, Check, Escrow, or Payment Channel) to an account's owner directory, but that account cannot own any more objects in the ledger.<br>This error is effectively impossible to receive if {% amendment-disclaimer name="fixDirectoryLimit" compact=true /%} is enabled. |
| `tecDIR_FULL` | 121 | The transaction tried to add an object (such as a trust line, Check, Escrow, or Payment Channel) to an account's owner directory, but that account cannot own any more objects in the ledger. |
| `tecDUPLICATE` | 149 | The transaction tried to create an object (such as a [DepositPreauth][] authorization) that already exists. |
| `tecDST_TAG_NEEDED` | 143 | The [Payment transaction][] omitted a [destination tag](../../../../concepts/transactions/source-and-destination-tags.md), but the destination account has the `lsfRequireDestTag` flag enabled. |
| `tecEMPTY_DID` | 187 | The transaction tried to create a [DID entry][] with no contents. A DID must not be empty. {% amendment-disclaimer name="DID" /%} |

View File

@@ -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. See [RawTransactions](#rawtransactions). |
| `RawTransactions` | Array | Array | Yes | The list of transactions to apply. |
| `BatchSigners` | Array | Array | No | The signatures authorizing a multi-account `Batch` transaction. |
### RawTransactions
`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.
`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.
Each inner transaction:
@@ -169,7 +169,6 @@ 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. |

View File

@@ -40,18 +40,14 @@ Create an on-ledger [check](../../../../concepts/payment-types/checks.md), which
## Error Cases
Besides errors that can occur for all transactions, {% $frontmatter.seo.title %} transactions can result in the following [transaction result codes](../transaction-results/index.md):
| Error Code | Description |
|:-----------|:------------|
| `tecNO_PERMISSION` | The `Destination` account is blocking incoming Checks. {% amendment-disclaimer name="DisallowIncoming" /%} |
| `temREDUNDANT` | The `Destination` is the sender of the transaction. |
| `tecNO_DST` | The `Destination` [account](../../../../concepts/accounts/index.md) does not exist in the ledger. |
| `tecDST_TAG_NEEDED` | The `Destination` account has the `RequireDest` flag enabled but the transaction does not include a `DestinationTag` field. |
| `tecFROZEN` | `SendMax` specifies a token which is [frozen](../../../../concepts/tokens/fungible-tokens/freezes.md). |
| `tecEXPIRED` | The `Expiration` of the transaction is in the past. |
| `tecINSUFFICIENT_RESERVE` | The sender does not have enough XRP to meet the [owner reserve](../../../../concepts/accounts/reserves.md#owner-reserves) after adding the Check. |
| `tecDIR_FULL` | Either the sender or the destination of the Check cannot own more objects in the ledger.<br>This error is effectively impossible to receive if {% amendment-disclaimer name="fixDirectoryLimit" compact=true /%} is enabled. |
- If the `Destination` account is blocking incoming Checks, the transaction fails with the result code `tecNO_PERMISSION`. {% amendment-disclaimer name="DisallowIncoming" /%}
- If the `Destination` is the sender of the transaction, the transaction fails with the result code `temREDUNDANT`.
- If the `Destination` [account](../../../../concepts/accounts/index.md) does not exist in the ledger, the transaction fails with the result code `tecNO_DST`.
- If the `Destination` account has the `RequireDest` flag enabled but the transaction does not include a `DestinationTag` field, the transaction fails with the result code `tecDST_TAG_NEEDED`.
- If `SendMax` specifies a token which is [frozen](../../../../concepts/tokens/fungible-tokens/freezes.md), the transaction fails with the result `tecFROZEN`.
- If the `Expiration` of the transaction is in the past, the transaction fails with the result `tecEXPIRED`.
- If the sender does not have enough XRP to meet the [owner reserve](../../../../concepts/accounts/reserves.md#owner-reserves) after adding the Check, the transaction fails with the result `tecINSUFFICIENT_RESERVE`.
- If either the sender or the destination of the Check cannot own more objects in the ledger, the transaction fails with the result `tecDIR_FULL`.
## See Also

View File

@@ -65,7 +65,7 @@ Besides errors that can occur for all transactions, {% $frontmatter.seo.title %}
| Error Code | Description |
|:--------------------------|:------------|
| `tecDIR_FULL` | The sender owns too many items in the ledger already.<br>This error is effectively impossible to receive if {% amendment-disclaimer name="fixDirectoryLimit" compact=true /%} is enabled. |
| `tecDIR_FULL` | The sender owns too many items in the ledger already. |
| `tecINSUFFICIENT_RESERVE` | The sender does not have enough XRP to meet the [reserve requirement](/docs/concepts/accounts/reserves.md) of creating a new Delegate ledger entry. |
| `tecNO_PERMISSION` | At least one permission in the `Permissions` list is not delegatable. See [Permission Values](../../data-types/permission-values.md) for which permissions are not delegatable. |
| `tecNO_TARGET` | The account specified in the `Authorize` field does not exist in the ledger. |

View File

@@ -56,7 +56,7 @@ Besides errors that can occur for all transactions, {% $frontmatter.seo.title %}
| `temDISABLED` | The [NonFungibleTokensV1 amendment][] is not enabled. |
| `temBAD_AMOUNT` | The `Amount` field is not valid. For example, the amount was zero for a buy offer, or the amount is denominated in fungible tokens but the `NFToken` has the [`lsfOnlyXRP` flag](../../data-types/nftoken.md#nftoken-flags) enabled. |
| `temBAD_EXPIRATION` | The specified `Expiration` time is invalid (for example, `0`). |
| `tecDIR_FULL` | The sender already owns too many objects in the ledger, or there are already too many offers to buy or sell this token.<br>This error is effectively impossible to receive if {% amendment-disclaimer name="fixDirectoryLimit" compact=true /%} is enabled. |
| `tecDIR_FULL` | The sender already owns too many objects in the ledger, or there are already too many offers to buy or sell this token. |
| `tecEXPIRED` | The specified `Expiration` time has already passed. |
| `tecFROZEN` | The `Amount` is denominated in fungible tokens, but one of the trust lines that would receive tokens from this offer is [frozen](../../../../concepts/tokens/fungible-tokens/freezes.md). This could be the seller's trust line or the `NFToken`'s issuer's trust line (if the `NFToken` has a transfer fee). |
| `tecINSUFFICIENT_RESERVE` | The sender does not have enough XRP to meet the [reserve requirement](../../../../concepts/accounts/reserves.md) after placing this offer. |

View File

@@ -58,7 +58,7 @@ Transactions of the OfferCreate type support additional values in the [`Flags` f
| Error Code | Description |
|:-------------------------|:--------------------------------------------------|
| `tecDIR_FULL` | The owner owns too many items in the ledger, or the order book contains too many Offers at the same exchange rate already.<br>This error is effectively impossible to receive if {% amendment-disclaimer name="fixDirectoryLimit" compact=true /%} is enabled. |
| `tecDIR_FULL` | The owner owns too many items in the ledger, or the order book contains too many Offers at the same exchange rate already. |
| `tecEXPIRED` | The transaction specifies an `Expiration` time that has already passed. |
| `tecFROZEN` | The transaction involves a token on a [frozen](../../../../concepts/tokens/fungible-tokens/freezes.md) trust line (including local and global freezes). The `TakerPays` (buy amount) token has been deep-frozen by the issuer. |
| `tecINSUF_RESERVE_OFFER` | The owner does not have enough XRP to meet the reserve requirement of adding a new offer ledger entry, and the transaction did not convert any currency. (If the transaction successfully traded any amount, the transaction succeeds with the result code `tesSUCCESS`, but does not create an offer ledger entry for the remainder.) |

View File

@@ -55,7 +55,7 @@ Besides errors that can occur for all transactions, {% $frontmatter.seo.title %}
| Error Code | Description |
|:--------------------------|:------------|
| `tecDIR_FULL` | The transaction would create a new PermissionedDomain, but the sender's owner directory is full.<br>This error is effectively impossible to receive if {% amendment-disclaimer name="fixDirectoryLimit" compact=true /%} is enabled. |
| `tecDIR_FULL` | The transaction would create a new PermissionedDomain, but the sender's owner directory is full. |
| `tecINSUFFICIENT_RESERVE` | The transaction would create a new PermissionedDomain, but the sender does not have enough XRP to meet the increased owner reserve. |
| `tecNO_ENTRY` | The transaction attempted to modify a Domain that does not exist. Check the `DomainID` field of the transaction. |
| `tecNO_ISSUER` | At least one of the issuers specified in the `AcceptedCredentials` field is does not exist in the XRP Ledger. Check the `Issuer` field of each member of the array. |

View File

@@ -43,7 +43,7 @@ Besides errors that can occur for all transactions, {% $frontmatter.seo.title %}
| Error Code | Description |
|:--------------------------|:-------------------------------------------------|
| `temINVALID_COUNT` | The `TicketCount` field is invalid. It must be an integer from 1 to 250. |
| `tecDIR_FULL` | This transaction would cause the account to own more than the limit of 250 Tickets at a time, or more than the maximum number of ledger objects in general.<br>The maximum ledger objects error is effectively impossible to receive if {% amendment-disclaimer name="fixDirectoryLimit" compact=true /%} is enabled. |
| `tecDIR_FULL` | This transaction would cause the account to own more than the limit of 250 Tickets at a time, or more than the maximum number of ledger objects in general. |
| `tecINSUFFICIENT_RESERVE` | The sending account does not have enough XRP to meet the [owner reserve](../../../../concepts/accounts/reserves.md) of all the requested Tickets. |
## See Also

View File

@@ -1,14 +0,0 @@
---
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 /%}

View File

@@ -1,172 +0,0 @@
---
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" /%}

View File

@@ -1,162 +0,0 @@
---
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" /%}

View File

@@ -0,0 +1,221 @@
---
seo:
description: Issue a Multi-Purpose Token (MPT) with arbitrary metadata on the XRP Ledger.
metadata:
indexPage: true
labels:
- Multi-Purpose Token
- MPT
- Token Issuance
---
# Issue a Multi-Purpose Token (MPT)
A [Multi-Purpose Token (MPT)](../../../concepts/tokens/fungible-tokens/multi-purpose-tokens.md) lets you to quickly access powerful, built-in tokenization features on the XRP Ledger with minimal code.
This tutorial shows you how to issue an MPT with on-chain metadata such as the token's ticker, name, or description, encoded according to the MPT [metadata schema](../../../concepts/tokens/fungible-tokens/multi-purpose-tokens.md#metadata-schema) defined in [XLS-89](https://xls.xrpl.org/xls/XLS-0089-multi-purpose-token-metadata-schema.html).
## Goals
By the end of this tutorial, you will be able to:
- Issue a new MPToken on the XRP Ledger.
- Encode or decode token metadata following MPT standards best practices.
## 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.
- **Python** with the [xrpl-py library](https://github.com/XRPLF/xrpl-py). See [Get Started Using Python](../../python/build-apps/get-started.md) for setup steps.
## Source Code
You can find the complete source code for this tutorial's example in the [code samples section of this website's repository](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/issue-mpt-with-metadata).
## Steps
The example in this tutorial demonstrates how to issue a sample [US Treasury bill (T-bill)](https://www.treasurydirect.gov/research-center/history-of-marketable-securities/bills/t-bills-indepth/) as an MPT on the XRP Ledger.
### 1. Install dependencies
{% tabs %}
{% tab label="Javascript" %}
From the code sample folder, use npm to install dependencies:
```bash
npm install xrpl
```
{% /tab %}
{% tab label="Python" %}
From the code sample folder, install dependencies using pip:
```bash
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
{% /tab %}
{% /tabs %}
### 2. Set up client and fund issuer wallet
Import the client library, instantiate a client to connect to the XRPL, and fund a new wallet to act as the token issuer.
{% tabs %}
{% tab label="Javascript" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/js/issue-mpt-with-metadata.js" language="js" before="// Define metadata as JSON" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/py/issue-mpt-with-metadata.py" language="py" before="# Define metadata as JSON" /%}
{% /tab %}
{% /tabs %}
### 3. Define and encode MPT metadata
The metadata you provide is what distinguishes your token from other MPTs. Define the JSON metadata as shown in the following code snippet:
{% tabs %}
{% tab label="Javascript" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/js/issue-mpt-with-metadata.js" language="js" from="// Define metadata as JSON" before="// Encode the metadata" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/py/issue-mpt-with-metadata.py" language="py" from="# Define metadata as JSON" before="# Encode the metadata" /%}
{% /tab %}
{% /tabs %}
The metadata schema defined in XLS-89 supports both long field names (`ticker`, `name`, `desc`) and compact short keys (`t`, `n`, `d`). To save space on the ledger, its recommended to use short key names. The MPT metadata field has a 1024-byte limit, so using compact keys allows you to include more information.
The SDK libraries provide utility functions to encode or decode the metadata for you, so you don't have to. If long field names are provided in the JSON, the **encoding utility function** automatically shortens them to their compact key equivalents before encoding. Similarly, when decoding, the **decoding utility function** converts the short keys back to their respective long names.
To encode the metadata:
{% tabs %}
{% tab label="Javascript" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/js/issue-mpt-with-metadata.js" language="js" from="// Encode the metadata" before="// Define the transaction" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/py/issue-mpt-with-metadata.py" language="py" from="# Encode the metadata" before="# Define the transaction" /%}
{% /tab %}
{% /tabs %}
{% admonition type="warning" name="Warning" %}
The encoding function will raise an error if the input is not a valid JSON object.
{% /admonition %}
### 4. Prepare the MPTokenIssuanceCreate transaction
To issue the MPT, create an `MPTokenIssuanceCreate` transaction object. Specify the issuer, asset scale, maximum amount, flags, and the encoded metadata.
| Field | Description |
|:------------------- |:------ |
| `TransactionType` | The type of transaction, in this case `MPTokenIssuanceCreate`. |
| `Account` | The wallet address of the account that is issuing the MPT, in this case the `issuer`. |
| `AssetScale` | The number of decimal places for the token. |
| `MaximumAmount` | The maximum supply of the token to be issued. |
| `TransferFee` | The transfer fee (if any) to charge for token transfers. |
| `Flags` | Flags to set token permissions. For this example the **Can Transfer** and **Can Trade** flags are enabled. See [MPTokenIssuanceCreate Flags](../../../references/protocol/transactions/types/mptokenissuancecreate.md#mptokenissuancecreate-flags) for all available flags. |
| `MPTokenMetadata` | The hex-encoded metadata for the token. |
{% tabs %}
{% tab label="Javascript" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/js/issue-mpt-with-metadata.js" language="js" from="// Define the transaction" before="// Sign and submit the transaction" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/py/issue-mpt-with-metadata.py" language="py" from="# Define the transaction" before="# Sign and submit the transaction" /%}
{% /tab %}
{% /tabs %}
### 5. Submit MPTokenIssuanceCreate transaction
Sign and submit the `MPTokenIssuanceCreate` transaction to the ledger.
{% tabs %}
{% tab label="Javascript" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/js/issue-mpt-with-metadata.js" language="js" from="// Sign and submit the transaction" before="// Check transaction results" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/py/issue-mpt-with-metadata.py" language="py" from="# Sign and submit the transaction" before="# Check transaction results" /%}
{% /tab %}
{% /tabs %}
Some important considerations about token metadata when you submit the transaction:
- If you provide metadata that exceeds the 1024-byte limit, the transaction will fail with an error.
- If the metadata does not conform to the XLS-89 standards, the transaction still succeeds, but your token may not be compatible with wallets and applications that expect valid MPT metadata.
The SDK libraries provide a warning to help you diagnose why your metadata is not compliant. For example, you might see a warning like the following:
```sh
MPTokenMetadata is not properly formatted as JSON as per the XLS-89d standard.
While adherence to this standard is not mandatory, such non-compliant MPToken's
might not be discoverable by Explorers and Indexers in the XRPL ecosystem.
- ticker/t: should have uppercase letters (A-Z) and digits (0-9) only. Max 6 characters recommended.
- name/n: should be a non-empty string.
- icon/i: should be a non-empty string.
- asset_class/ac: should be one of rwa, memes, wrapped, gaming, defi, other.
```
### 6. Check transaction result
Verify that the transaction succeeded and retrieve the MPT issuance ID.
{% tabs %}
{% tab label="Javascript" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/js/issue-mpt-with-metadata.js" language="js" from="// Check transaction results" before="// Look up MPT Issuance entry" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/py/issue-mpt-with-metadata.py" language="py" from="# Check transaction results" before="# Look up MPT Issuance entry" /%}
{% /tab %}
{% /tabs %}
A `tesSUCCESS` result indicates that the transaction is successful and the token has been created.
### 7. Confirm MPT issuance and decode metadata
Look up the MPT issuance entry in the validated ledger and decode the metadata to verify it matches your original input.
{% tabs %}
{% tab label="Javascript" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/js/issue-mpt-with-metadata.js" language="js" from="// Look up MPT Issuance entry" /%}
{% /tab %}
{% tab label="Python" %}
{% code-snippet file="/_code-samples/issue-mpt-with-metadata/py/issue-mpt-with-metadata.py" language="py" from="# Look up MPT Issuance entry" /%}
{% /tab %}
{% /tabs %}
The decoding utility function converts the metadata back to a JSON object and expands the compact key names back to their respective long names.
## See Also
- **Concepts**:
- [Multi-Purpose Tokens (MPT)](../../../concepts/tokens/fungible-tokens/multi-purpose-tokens.md)
- **References**:
- [MPTokenIssuance entry][]
- [MPTokenIssuanceCreate transaction][]
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -42,6 +42,9 @@ metadataGlobs:
redocly_category: Community
seo:
siteUrl: https://xrpl.org/
rbac:
reunite:
xrpl-org-editors: maintain
analytics:
gtm:
includeInDevelopment: true

View File

@@ -742,19 +742,6 @@ Changes the way Checks transactions affect account metadata, so that Checks are
Without this amendment, Checks transactions ([CheckCreate][], [CheckCash][], and [CheckCancel][]) only update the account history of the sender. With this amendment, those transactions affect both the sending and receiving accounts. This amendment has no effect unless the [Checks amendment](#checks) is also enabled.
### fixDirectoryLimit
[fixDirectoryLimit]: #fixdirectorylimit
| Amendment | fixdirectorylimit |
|:-------------|:----------------------|
| Amendment ID | 41765F664A8D67FF03DDB1C1A893DE6273690BA340A6C2B07C8D29D0DD013D3A |
| Status | Open for Voting |
| Default Vote (Latest stable release) | No |
| Pre-amendment functionality retired? | No |
This amendment removes the directory page limit. Object reserve requirements provide enough incentive to avoid creating unnecessary objects on the XRP Ledger.
### fixDisallowIncomingV1
[fixDisallowIncomingV1]: #fixdisallowincomingv1

View File

@@ -295,11 +295,6 @@
- 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:
@@ -327,6 +322,7 @@
expanded: false
items:
- page: docs/tutorials/how-tos/use-tokens/issue-a-fungible-token.md
- page: docs/tutorials/how-tos/use-tokens/issue-a-multi-purpose-token.md
- page: docs/tutorials/how-tos/use-tokens/trade-in-the-decentralized-exchange.md
- page: docs/tutorials/how-tos/use-tokens/enable-no-freeze.md
- page: docs/tutorials/how-tos/use-tokens/enact-global-freeze.md