diff --git a/_code-samples/issue-mpt-with-metadata/js/README.md b/_code-samples/issue-mpt-with-metadata/js/README.md index 5e73cd1a0b..22f4c1a971 100644 --- a/_code-samples/issue-mpt-with-metadata/js/README.md +++ b/_code-samples/issue-mpt-with-metadata/js/README.md @@ -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' + } + ] +} ``` diff --git a/_code-samples/issue-mpt-with-metadata/js/issue-mpt-with-metadata.js b/_code-samples/issue-mpt-with-metadata/js/issue-mpt-with-metadata.js index 18d786de67..f4453b5d5e 100644 --- a/_code-samples/issue-mpt-with-metadata/js/issue-mpt-with-metadata.js +++ b/_code-samples/issue-mpt-with-metadata/js/issue-mpt-with-metadata.js @@ -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(); diff --git a/_code-samples/issue-mpt-with-metadata/js/package.json b/_code-samples/issue-mpt-with-metadata/js/package.json index 43c89bf1e0..fab7850610 100644 --- a/_code-samples/issue-mpt-with-metadata/js/package.json +++ b/_code-samples/issue-mpt-with-metadata/js/package.json @@ -1,6 +1,6 @@ { "dependencies": { - "xrpl": "^4.4.0" + "xrpl": "^4.4.3" }, "type": "module" } diff --git a/_code-samples/issue-mpt-with-metadata/py/README.md b/_code-samples/issue-mpt-with-metadata/py/README.md index 1e3f4e0f4f..b2fc018dee 100644 --- a/_code-samples/issue-mpt-with-metadata/py/README.md +++ b/_code-samples/issue-mpt-with-metadata/py/README.md @@ -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" + } + ] +} ``` diff --git a/_code-samples/issue-mpt-with-metadata/py/issue-mpt-with-metadata.py b/_code-samples/issue-mpt-with-metadata/py/issue-mpt-with-metadata.py index ec3663bfb8..890b8ef701 100644 --- a/_code-samples/issue-mpt-with-metadata/py/issue-mpt-with-metadata.py +++ b/_code-samples/issue-mpt-with-metadata/py/issue-mpt-with-metadata.py @@ -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)) diff --git a/_code-samples/issue-mpt-with-metadata/py/requirements.txt b/_code-samples/issue-mpt-with-metadata/py/requirements.txt index a566e000a8..31f58cc12c 100644 --- a/_code-samples/issue-mpt-with-metadata/py/requirements.txt +++ b/_code-samples/issue-mpt-with-metadata/py/requirements.txt @@ -1 +1 @@ -xrpl-py==4.3.0 +xrpl-py==4.3.1 diff --git a/docs/tutorials/how-tos/use-tokens/issue-a-multi-purpose-token.md b/docs/tutorials/how-tos/use-tokens/issue-a-multi-purpose-token.md new file mode 100644 index 0000000000..975105e941 --- /dev/null +++ b/docs/tutorials/how-tos/use-tokens/issue-a-multi-purpose-token.md @@ -0,0 +1,232 @@ +--- +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 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 and decode token metadata according to the XLS-89 standard. + +## 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 account + +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 %} + +{% admonition type="info" name="Note" %} +The ledger entry that defines an MPT issuance counts as one object towards the issuer's [owner reserve](../../../concepts/accounts/reserves.md#owner-reserves), so the issuer needs to set aside **{% $env.PUBLIC_OWNER_RESERVE %}** per MPT issuance. +{% /admonition %} + +### 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 raises an error if the input isn't a valid JSON object. +{% /admonition %} + +### 4. Prepare the MPTokenIssuanceCreate transaction + +To issue the MPT, create an `MPTokenIssuanceCreate` transaction object with the following fields: + +| Field | Value | +|:------------------- |:------ | +| `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 (for example, `4` means amounts are divided by `10,000`). | +| `MaximumAmount` | The maximum supply of the token to be issued. | +| `TransferFee` | The transfer fee (if any) to charge for token transfers. In this example it is set to `0`. | +| `Flags` | Flags to set token permissions. For this example, the following flags are configured: 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 + +Some important considerations about token metadata when you submit the transaction: + +- If you provide metadata that exceeds the 1024-byte limit, the transaction fails with an error. + +- If the metadata does not conform to the XLS-89 standards, the transaction still succeeds, but your token may not be compatible with wallets and applications that expect valid MPT metadata. The SDK libraries provide a warning to help you diagnose why your metadata may not be compliant. For example: + + ```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. + ``` + +Sign and submit the `MPTokenIssuanceCreate` transaction to the ledger. + +{% admonition type="warning" name="Warning" %} +Once created, the MPT cannot be modified. Review all settings carefully before submitting the transaction. Mutable token properties are planned for a future XRPL amendment ([XLS-94](https://xls.xrpl.org/xls/XLS-0094-dynamic-MPT.html)). +{% /admonition %} + +{% 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 %} + +### 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][] + - [MPTokenIssuanceDestroy transaction][] + - [MPTokenIssuanceSet transaction][] + + + +{% raw-partial file="/docs/_snippets/common-links.md" /%} diff --git a/sidebars.yaml b/sidebars.yaml index ee47bcbc2d..90ae56f1f7 100644 --- a/sidebars.yaml +++ b/sidebars.yaml @@ -322,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