mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-21 04:05:49 +00:00
Compare commits
1 Commits
remove-rba
...
issue-mpto
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b67931baa |
@@ -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.
|
||||
|
||||
@@ -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
|
||||
```
|
||||
@@ -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()
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"xrpl": "^4.4.3"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
@@ -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()
|
||||
@@ -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'
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"xrpl": "^4.4.0"
|
||||
"xrpl": "^4.4.3"
|
||||
},
|
||||
"type": "module"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -1 +1 @@
|
||||
xrpl-py==4.3.0
|
||||
xrpl-py==4.3.1
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
|
||||
@@ -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" /%} |
|
||||
|
||||
@@ -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. |
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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. |
|
||||
|
||||
@@ -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. |
|
||||
|
||||
@@ -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.) |
|
||||
|
||||
@@ -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. |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 /%}
|
||||
@@ -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" /%}
|
||||
@@ -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" /%}
|
||||
221
docs/tutorials/how-tos/use-tokens/issue-a-multi-purpose-token.md
Normal file
221
docs/tutorials/how-tos/use-tokens/issue-a-multi-purpose-token.md
Normal 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, it’s 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 stills 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" /%}
|
||||
@@ -42,6 +42,9 @@ metadataGlobs:
|
||||
redocly_category: Community
|
||||
seo:
|
||||
siteUrl: https://xrpl.org/
|
||||
rbac:
|
||||
reunite:
|
||||
xrpl-org-editors: maintain
|
||||
analytics:
|
||||
gtm:
|
||||
includeInDevelopment: true
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user