Skip to content

Commit 890581a

Browse files
committed
Add tutorial for issuing an MPT (Javascript and Python).
1 parent 0039275 commit 890581a

File tree

8 files changed

+619
-101
lines changed

8 files changed

+619
-101
lines changed

_code-samples/issue-mpt-with-metadata/js/README.md

Lines changed: 130 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,135 @@ npm i
99
node issue-mpt-with-metadata.js
1010
```
1111

12-
The script should output a validated transaction and end with a line such as the following:
12+
The script should output a validated transaction and decoded metadata, similar to the following:
1313

14-
```text
15-
MPToken created successfully with issuance ID 005073C721E14A7613BAAF5E0B1A253459832FF8D0D81278.
14+
```sh
15+
=== Funding new wallet from faucet...===
16+
Issuer address: r9fhoyac7uUM9XZFDJV9wXQ4pcJb6UDpJM
17+
18+
=== Encoding metadata...===
19+
Encoded mpt_metadata_hex: 7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D
20+
21+
=== Sending MPTokenIssuanceCreate transaction...===
22+
{
23+
"TransactionType": "MPTokenIssuanceCreate",
24+
"Account": "r9fhoyac7uUM9XZFDJV9wXQ4pcJb6UDpJM",
25+
"AssetScale": 4,
26+
"MaximumAmount": "50000000",
27+
"TransferFee": 0,
28+
"Flags": 48,
29+
"MPTokenMetadata": "7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D"
30+
}
31+
32+
=== Checking MPTokenIssuanceCreate results... ===
33+
{
34+
"close_time_iso": "2025-11-20T18:13:30Z",
35+
"ctid": "C0148E8700000002",
36+
"hash": "555FAFDB99B239567FDF30DDF22BA3B30F8E70D8D06833B1270AC600E1575948",
37+
"ledger_hash": "A7010A2025989778420280F7F96B10F5D3C879E049BE5DA12500FFBB90D162C5",
38+
"ledger_index": 1347207,
39+
"meta": {
40+
"AffectedNodes": [
41+
{
42+
"CreatedNode": {
43+
"LedgerEntryType": "DirectoryNode",
44+
"LedgerIndex": "33468621DEF32177E84C1EBC2C457C908567E245622CBDE03185C4ABC83B7F9D",
45+
"NewFields": {
46+
"Owner": "r9fhoyac7uUM9XZFDJV9wXQ4pcJb6UDpJM",
47+
"RootIndex": "33468621DEF32177E84C1EBC2C457C908567E245622CBDE03185C4ABC83B7F9D"
48+
}
49+
}
50+
},
51+
{
52+
"CreatedNode": {
53+
"LedgerEntryType": "MPTokenIssuance",
54+
"LedgerIndex": "6567EE49937AADAB4FC4D5DDBD6A4A6E179E0E5A9DF2FC7ED8B41B807F0DDBF2",
55+
"NewFields": {
56+
"AssetScale": 4,
57+
"Flags": 48,
58+
"Issuer": "r9fhoyac7uUM9XZFDJV9wXQ4pcJb6UDpJM",
59+
"MPTokenMetadata": "7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D",
60+
"MaximumAmount": "50000000",
61+
"Sequence": 1347205
62+
}
63+
}
64+
},
65+
{
66+
"ModifiedNode": {
67+
"FinalFields": {
68+
"Account": "r9fhoyac7uUM9XZFDJV9wXQ4pcJb6UDpJM",
69+
"Balance": "99999999",
70+
"Flags": 0,
71+
"OwnerCount": 1,
72+
"Sequence": 1347206
73+
},
74+
"LedgerEntryType": "AccountRoot",
75+
"LedgerIndex": "AB5FC35110CED5BFD2CEA3E37B41E43CC4BBAF89AE66BA85942E04CBC38550FB",
76+
"PreviousFields": {
77+
"Balance": "100000000",
78+
"OwnerCount": 0,
79+
"Sequence": 1347205
80+
},
81+
"PreviousTxnID": "1CDF420134492607EC54838F91FA06A655E07DD296ED69CC7172C1AC356BF22B",
82+
"PreviousTxnLgrSeq": 1347205
83+
}
84+
}
85+
],
86+
"TransactionIndex": 0,
87+
"TransactionResult": "tesSUCCESS",
88+
"mpt_issuance_id": "00148E8558E6AEAA301085FBFD01D615F059A7CCE6E38296"
89+
},
90+
"tx_json": {
91+
"Account": "r9fhoyac7uUM9XZFDJV9wXQ4pcJb6UDpJM",
92+
"AssetScale": 4,
93+
"Fee": "1",
94+
"Flags": 48,
95+
"LastLedgerSequence": 1347225,
96+
"MPTokenMetadata": "7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D",
97+
"MaximumAmount": "50000000",
98+
"Sequence": 1347205,
99+
"SigningPubKey": "ED1EC65DB85E686A55F8FD9BC6E405E8F2F8EA5E1712AED64E28C97350EB4EF6E7",
100+
"TransactionType": "MPTokenIssuanceCreate",
101+
"TransferFee": 0,
102+
"TxnSignature": "3A671905D57342F051E3BF057CCF65B0D94114C04D255D4AE3CEE01C2D0B368118E94011CEB27EC9BB447D3498B24B750F2691B4D7AB71F82626BC6F49465806",
103+
"ctid": "C0148E8700000002",
104+
"date": 816977610,
105+
"ledger_index": 1347207
106+
},
107+
"validated": true
108+
}
109+
110+
- MPToken created successfully with issuance ID: 00148E8558E6AEAA301085FBFD01D615F059A7CCE6E38296
111+
- Explorer URL: https://devnet.xrpl.org/mpt/00148E8558E6AEAA301085FBFD01D615F059A7CCE6E38296
112+
113+
=== Confirming MPT Issuance metadata in the validated ledger... ===
114+
Decoded MPT metadata:
115+
{
116+
asset_class: 'rwa',
117+
additional_info: {
118+
cusip: '912796RX0',
119+
interest_rate: '5.00%',
120+
interest_type: 'variable',
121+
maturity_date: '2045-06-30',
122+
yield_source: 'U.S. Treasury Bills'
123+
},
124+
asset_subclass: 'treasury',
125+
desc: 'A yield-bearing stablecoin backed by short-term U.S. Treasuries and money market instruments.',
126+
icon: 'https://example.org/tbill-icon.png',
127+
issuer_name: 'Example Yield Co.',
128+
name: 'T-Bill Yield Token',
129+
ticker: 'TBILL',
130+
uris: [
131+
{
132+
category: 'website',
133+
title: 'Product Page',
134+
uri: 'https://exampleyield.co/tbill'
135+
},
136+
{
137+
category: 'docs',
138+
title: 'Yield Token Docs',
139+
uri: 'https://exampleyield.co/docs'
140+
}
141+
]
142+
}
16143
```
Lines changed: 83 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,108 @@
1-
import { stringToHex, hexToString } from '@xrplf/isomorphic/dist/utils/index.js'
2-
import { MPTokenIssuanceCreateFlags, Client } from 'xrpl'
1+
import {
2+
MPTokenIssuanceCreateFlags,
3+
Client,
4+
encodeMPTokenMetadata,
5+
decodeMPTokenMetadata,
6+
} from "xrpl";
37

48
// Connect to network and get a wallet
5-
const client = new Client('wss://s.devnet.rippletest.net:51233')
6-
await client.connect()
9+
const client = new Client("wss://s.devnet.rippletest.net:51233");
10+
await client.connect();
711

8-
console.log('Funding new wallet from faucet...')
9-
const { wallet } = await client.fundWallet()
12+
console.log("=== Funding new wallet from faucet...===");
13+
const { wallet: issuer } = await client.fundWallet();
14+
console.log(`Issuer address: ${issuer.address}`);
1015

1116
// Define metadata as JSON
1217
const mpt_metadata = {
13-
t: 'TBILL',
14-
n: 'T-Bill Yield Token',
15-
d: 'A yield-bearing stablecoin backed by short-term U.S. Treasuries and money market instruments.',
16-
i: 'https://example.org/tbill-icon.png',
17-
ac: 'rwa',
18-
as: 'treasury',
19-
in: 'Example Yield Co.',
20-
us: [
18+
ticker: "TBILL",
19+
name: "T-Bill Yield Token",
20+
desc: "A yield-bearing stablecoin backed by short-term U.S. Treasuries and money market instruments.",
21+
icon: "https://example.org/tbill-icon.png",
22+
asset_class: "rwa",
23+
asset_subclass: "treasury",
24+
issuer_name: "Example Yield Co.",
25+
uris: [
2126
{
22-
u: 'https://exampleyield.co/tbill',
23-
c: 'website',
24-
t: 'Product Page'
27+
uri: "https://exampleyield.co/tbill",
28+
category: "website",
29+
title: "Product Page",
2530
},
2631
{
27-
u: 'https://exampleyield.co/docs',
28-
c: 'docs',
29-
t: 'Yield Token Docs'
30-
}
32+
uri: "https://exampleyield.co/docs",
33+
category: "docs",
34+
title: "Yield Token Docs",
35+
},
3136
],
32-
ai: {
33-
interest_rate: '5.00%',
34-
interest_type: 'variable',
35-
yield_source: 'U.S. Treasury Bills',
36-
maturity_date: '2045-06-30',
37-
cusip: '912796RX0'
38-
}
39-
}
37+
additional_info: {
38+
interest_rate: "5.00%",
39+
interest_type: "variable",
40+
yield_source: "U.S. Treasury Bills",
41+
maturity_date: "2045-06-30",
42+
cusip: "912796RX0",
43+
},
44+
};
4045

41-
// Convert JSON to a string (without excess whitespace), then string to hex
42-
const mpt_metadata_hex = stringToHex(JSON.stringify(mpt_metadata))
46+
// Encode the metadata.
47+
// The encodeMPTokenMetadata function converts the JSON metadata object into
48+
// a compact, hex-encoded string, following the XLS-89 standard.
49+
// https://xls.xrpl.org/xls/XLS-0089-multi-purpose-token-metadata-schema.html
50+
console.log("\n=== Encoding metadata...===");
51+
const mpt_metadata_hex = encodeMPTokenMetadata(mpt_metadata);
52+
console.log("Encoded mpt_metadata_hex: ", mpt_metadata_hex);
4353

4454
// Define the transaction, including other MPT parameters
4555
const mpt_issuance_create = {
46-
TransactionType: 'MPTokenIssuanceCreate',
47-
Account: wallet.address,
56+
TransactionType: "MPTokenIssuanceCreate",
57+
Account: issuer.address,
4858
AssetScale: 4,
49-
MaximumAmount: '50000000',
59+
MaximumAmount: "50000000",
5060
TransferFee: 0,
51-
Flags: MPTokenIssuanceCreateFlags.tfMPTCanTransfer |
52-
MPTokenIssuanceCreateFlags.tfMPTCanTrade,
53-
MPTokenMetadata: mpt_metadata_hex
54-
}
61+
Flags:
62+
MPTokenIssuanceCreateFlags.tfMPTCanTransfer |
63+
MPTokenIssuanceCreateFlags.tfMPTCanTrade,
64+
MPTokenMetadata: mpt_metadata_hex,
65+
};
5566

56-
// Prepare, sign, and submit the transaction
57-
console.log('Sending MPTokenIssuanceCreate transaction...')
58-
const submit_response = await client.submitAndWait(mpt_issuance_create, { wallet, autofill: true })
67+
// Sign and submit the transaction
68+
console.log("\n=== Sending MPTokenIssuanceCreate transaction...===");
69+
console.log(JSON.stringify(mpt_issuance_create, null, 2));
70+
const submit_response = await client.submitAndWait(mpt_issuance_create, {
71+
wallet: issuer,
72+
autofill: true,
73+
});
5974

60-
// Check transaction results and disconnect
61-
console.log(JSON.stringify(submit_response, null, 2))
62-
if (submit_response.result.meta.TransactionResult !== 'tesSUCCESS') {
63-
const result_code = response.result.meta.TransactionResult
64-
console.warn(`Transaction failed with result code ${result_code}.`)
65-
process.exit(1)
75+
// Check transaction results
76+
console.log("\n=== Checking MPTokenIssuanceCreate results... ===");
77+
console.log(JSON.stringify(submit_response.result, null, 2));
78+
if (submit_response.result.meta.TransactionResult !== "tesSUCCESS") {
79+
const result_code = submit_response.result.meta.TransactionResult;
80+
console.warn(`Transaction failed with result code ${result_code}.`);
81+
await client.disconnect();
82+
process.exit(1);
6683
}
6784

68-
const issuance_id = submit_response.result.meta.mpt_issuance_id
69-
console.log(`MPToken created successfully with issuance ID ${issuance_id}.`)
85+
const issuance_id = submit_response.result.meta.mpt_issuance_id;
86+
console.log(
87+
`\n- MPToken created successfully with issuance ID: ${issuance_id}`
88+
);
89+
// View the MPT issuance on the XRPL Explorer
90+
console.log(`- Explorer URL: https://devnet.xrpl.org/mpt/${issuance_id}`);
7091

7192
// Look up MPT Issuance entry in the validated ledger
72-
console.log('Confirming MPT Issuance metadata in the validated ledger.')
93+
console.log("\n=== Confirming MPT Issuance metadata in the validated ledger... ===");
7394
const ledger_entry_response = await client.request({
74-
"command": "ledger_entry",
75-
"mpt_issuance": issuance_id,
76-
"ledger_index": "validated"
77-
})
78-
79-
// Decode the metadata
80-
const metadata_blob = ledger_entry_response.result.node.MPTokenMetadata
81-
const decoded_metadata = JSON.parse(hexToString(metadata_blob))
82-
console.log('Decoded metadata:', decoded_metadata)
95+
command: "ledger_entry",
96+
mpt_issuance: issuance_id,
97+
ledger_index: "validated",
98+
});
8399

100+
// Decode the metadata.
101+
// The decodeMPTokenMetadata function takes a hex-encoded string representing MPT metadata,
102+
// decodes it to a JSON object, and expands any compact field names to their full forms.
103+
const metadata_blob = ledger_entry_response.result.node.MPTokenMetadata;
104+
const decoded_metadata = decodeMPTokenMetadata(metadata_blob);
105+
console.log("Decoded MPT metadata:\n", decoded_metadata);
84106

85-
client.disconnect()
107+
// Disconnect from the client
108+
await client.disconnect();
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"dependencies": {
3-
"xrpl": "^4.4.0"
3+
"xrpl": "^4.4.3"
44
},
55
"type": "module"
66
}

0 commit comments

Comments
 (0)