Skip to content

Commit 020eb78

Browse files
authored
Add encode_mptoken_metadata and decode_mptoken_metadata utility functions (#880)
* WIP commit * WIP commit * WIP commit * WIP commit * fix tests * some refactors * fix docstrings * update changelog * use dict over Dict * add encode decode in IT * fix IT * fix test
1 parent 4a5e54c commit 020eb78

14 files changed

+1204
-285
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [[Unreleased]]
99

10+
### Added
11+
12+
- Add `encode_mptoken_metadata` and `decode_mptoken_metadata` helper functions to encode and decode MPTokenMetadata as per XLS-89 standard.
13+
1014
### Fixed
1115

1216
- Removed snippets files from the xrpl-py code repository. Updated the README file to point to the correct location on XRPL.org.
17+
- [Breaking change] Fix `MPTokenMetadata` type to adhere to the XLS-89 standard. Since XLS-89 is still in a forming state and undergoing changes, this breaking change is being released as a bug fix via patch version bump. If you are using `MPTokenMetadata` in your code, please verify that it adheres to the updated type definition.
18+
- [Breaking change] Fix `validate_mptoken_metadata` to correctly validate MPTokenMetadata as per XLS-89 standard. Since XLS-89 is still in a forming state and undergoing changes, this breaking change is being released as a bug fix via patch version bump. If you are using `validateMPTokenMetadata` in your code, expect it to change as per the XLS-89 standard.
1319

1420
## [[4.3.0]] - 2025-07-29
1521

tests/integration/transactions/test_mptoken_issuance_create.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,38 @@
44
test_async_and_sync,
55
)
66
from tests.integration.reusable_values import WALLET
7+
from xrpl.models.mptoken_metadata import MPTokenMetadata, MPTokenMetadataUri
78
from xrpl.models.requests.account_objects import AccountObjects, AccountObjectType
9+
from xrpl.models.requests.tx import Tx
810
from xrpl.models.transactions import MPTokenIssuanceCreate
11+
from xrpl.utils.mptoken_metadata import decode_mptoken_metadata, encode_mptoken_metadata
912

1013

1114
class TestMPTokenIssuanceCreate(IntegrationTestCase):
1215
@test_async_and_sync(globals())
1316
async def test_basic_functionality(self, client):
17+
metadata = MPTokenMetadata(
18+
ticker="TBILL",
19+
name="T-Bill Yield Token",
20+
icon="https://example.org/tbill-icon.png",
21+
asset_class="rwa",
22+
asset_subclass="treasury",
23+
issuer_name="Example Yield Co.",
24+
uris=[
25+
MPTokenMetadataUri(
26+
uri="https://exampleyield.co/tbill",
27+
category="website",
28+
title="Product Page",
29+
),
30+
],
31+
)
32+
33+
encoded_metadata = encode_mptoken_metadata(metadata)
1434
tx = MPTokenIssuanceCreate(
1535
account=WALLET.classic_address,
1636
maximum_amount="9223372036854775807", # "7fffffffffffffff"
1737
asset_scale=2,
38+
mptoken_metadata=encoded_metadata,
1839
)
1940

2041
response = await sign_and_reliable_submission_async(
@@ -26,11 +47,25 @@ async def test_basic_functionality(self, client):
2647
self.assertTrue(response.is_successful())
2748
self.assertEqual(response.result["engine_result"], "tesSUCCESS")
2849

50+
tx_hash = response.result["tx_json"]["hash"]
51+
tx_res = await client.request(Tx(transaction=tx_hash))
52+
mpt_issuance_id = tx_res.result["meta"]["mpt_issuance_id"]
53+
2954
# confirm MPTokenIssuance ledger object was created
3055
account_objects_response = await client.request(
3156
AccountObjects(account=WALLET.address, type=AccountObjectType.MPT_ISSUANCE)
3257
)
58+
mptoken_issuance_obj = next(
59+
(
60+
obj
61+
for obj in account_objects_response.result["account_objects"]
62+
if obj["mpt_issuance_id"] == mpt_issuance_id
63+
),
64+
None,
65+
)
3366

34-
# subsequent integration tests (sync/async + json/websocket) add one
35-
# MPTokenIssuance object to the account
36-
self.assertTrue(len(account_objects_response.result["account_objects"]) > 0)
67+
self.assertIsNotNone(mptoken_issuance_obj)
68+
self.assertEqual(
69+
decode_mptoken_metadata(mptoken_issuance_obj["MPTokenMetadata"]),
70+
metadata,
71+
)
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
[
2+
{
3+
"testName": "valid long MPTokenMetadata",
4+
"mptMetadata": {
5+
"ticker": "TBILL",
6+
"name": "T-Bill Yield Token",
7+
"desc": "A yield-bearing stablecoin backed by short-term U.S. Treasuries and money market instruments.",
8+
"icon": "https://example.org/tbill-icon.png",
9+
"asset_class": "rwa",
10+
"asset_subclass": "treasury",
11+
"issuer_name": "Example Yield Co.",
12+
"uris": [
13+
{
14+
"uri": "https://exampleyield.co/tbill",
15+
"category": "website",
16+
"title": "Product Page"
17+
},
18+
{
19+
"uri": "https://exampleyield.co/docs",
20+
"category": "docs",
21+
"title": "Yield Token Docs"
22+
}
23+
],
24+
"additional_info": {
25+
"interest_rate": "5.00%",
26+
"interest_type": "variable",
27+
"yield_source": "U.S. Treasury Bills",
28+
"maturity_date": "2045-06-30",
29+
"cusip": "912796RX0"
30+
}
31+
},
32+
"expectedLongForm": {
33+
"ticker": "TBILL",
34+
"name": "T-Bill Yield Token",
35+
"desc": "A yield-bearing stablecoin backed by short-term U.S. Treasuries and money market instruments.",
36+
"icon": "https://example.org/tbill-icon.png",
37+
"asset_class": "rwa",
38+
"asset_subclass": "treasury",
39+
"issuer_name": "Example Yield Co.",
40+
"uris": [
41+
{
42+
"uri": "https://exampleyield.co/tbill",
43+
"category": "website",
44+
"title": "Product Page"
45+
},
46+
{
47+
"uri": "https://exampleyield.co/docs",
48+
"category": "docs",
49+
"title": "Yield Token Docs"
50+
}
51+
],
52+
"additional_info": {
53+
"interest_rate": "5.00%",
54+
"interest_type": "variable",
55+
"yield_source": "U.S. Treasury Bills",
56+
"maturity_date": "2045-06-30",
57+
"cusip": "912796RX0"
58+
}
59+
},
60+
"hex": "7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D"
61+
},
62+
{
63+
"testName": "valid MPTokenMetadata with all short field names",
64+
"mptMetadata": {
65+
"t": "TBILL",
66+
"n": "T-Bill Yield Token",
67+
"d": "A yield-bearing stablecoin backed by short-term U.S. Treasuries and money market instruments.",
68+
"i": "https://example.org/tbill-icon.png",
69+
"ac": "rwa",
70+
"as": "treasury",
71+
"in": "Example Yield Co.",
72+
"us": [
73+
{
74+
"u": "https://exampleyield.co/tbill",
75+
"c": "website",
76+
"t": "Product Page"
77+
},
78+
{
79+
"u": "https://exampleyield.co/docs",
80+
"c": "docs",
81+
"t": "Yield Token Docs"
82+
}
83+
],
84+
"ai": {
85+
"interest_rate": "5.00%",
86+
"interest_type": "variable",
87+
"yield_source": "U.S. Treasury Bills",
88+
"maturity_date": "2045-06-30",
89+
"cusip": "912796RX0"
90+
}
91+
},
92+
"expectedLongForm": {
93+
"ticker": "TBILL",
94+
"name": "T-Bill Yield Token",
95+
"desc": "A yield-bearing stablecoin backed by short-term U.S. Treasuries and money market instruments.",
96+
"icon": "https://example.org/tbill-icon.png",
97+
"asset_class": "rwa",
98+
"asset_subclass": "treasury",
99+
"issuer_name": "Example Yield Co.",
100+
"uris": [
101+
{
102+
"uri": "https://exampleyield.co/tbill",
103+
"category": "website",
104+
"title": "Product Page"
105+
},
106+
{
107+
"uri": "https://exampleyield.co/docs",
108+
"category": "docs",
109+
"title": "Yield Token Docs"
110+
}
111+
],
112+
"additional_info": {
113+
"interest_rate": "5.00%",
114+
"interest_type": "variable",
115+
"yield_source": "U.S. Treasury Bills",
116+
"maturity_date": "2045-06-30",
117+
"cusip": "912796RX0"
118+
}
119+
},
120+
"hex": "7B226163223A22727761222C226169223A7B226375736970223A22393132373936525830222C22696E7465726573745F72617465223A22352E303025222C22696E7465726573745F74797065223A227661726961626C65222C226D617475726974795F64617465223A22323034352D30362D3330222C227969656C645F736F75726365223A22552E532E2054726561737572792042696C6C73227D2C226173223A227472656173757279222C2264223A2241207969656C642D62656172696E6720737461626C65636F696E206261636B65642062792073686F72742D7465726D20552E532E205472656173757269657320616E64206D6F6E6579206D61726B657420696E737472756D656E74732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F7462696C6C2D69636F6E2E706E67222C22696E223A224578616D706C65205969656C6420436F2E222C226E223A22542D42696C6C205969656C6420546F6B656E222C2274223A225442494C4C222C227573223A5B7B2263223A2277656273697465222C2274223A2250726F647563742050616765222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F7462696C6C227D2C7B2263223A22646F6373222C2274223A225969656C6420546F6B656E20446F6373222C2275223A2268747470733A2F2F6578616D706C657969656C642E636F2F646F6373227D5D7D"
121+
},
122+
{
123+
"testName": "valid MPTokenMetadata with mixed short and long field names",
124+
"mptMetadata": {
125+
"ticker": "CRYPTO",
126+
"n": "Crypto Token",
127+
"icon": "https://example.org/crypto-icon.png",
128+
"asset_class": "gaming",
129+
"d": "A gaming token for virtual worlds.",
130+
"issuer_name": "Gaming Studios Inc.",
131+
"as": "equity",
132+
"uris": [
133+
{
134+
"uri": "https://gamingstudios.com",
135+
"c": "website",
136+
"title": "Main Website"
137+
},
138+
{
139+
"uri": "https://gamingstudios.com",
140+
"category": "website",
141+
"t": "Main Website"
142+
}
143+
],
144+
"ai": "Gaming ecosystem token"
145+
},
146+
"expectedLongForm": {
147+
"ticker": "CRYPTO",
148+
"name": "Crypto Token",
149+
"icon": "https://example.org/crypto-icon.png",
150+
"asset_class": "gaming",
151+
"desc": "A gaming token for virtual worlds.",
152+
"issuer_name": "Gaming Studios Inc.",
153+
"asset_subclass": "equity",
154+
"uris": [
155+
{
156+
"uri": "https://gamingstudios.com",
157+
"category": "website",
158+
"title": "Main Website"
159+
},
160+
{
161+
"uri": "https://gamingstudios.com",
162+
"category": "website",
163+
"title": "Main Website"
164+
}
165+
],
166+
"additional_info": "Gaming ecosystem token"
167+
},
168+
"hex": "7B226163223A2267616D696E67222C226169223A2247616D696E672065636F73797374656D20746F6B656E222C226173223A22657175697479222C2264223A22412067616D696E6720746F6B656E20666F72207669727475616C20776F726C64732E222C2269223A2268747470733A2F2F6578616D706C652E6F72672F63727970746F2D69636F6E2E706E67222C22696E223A2247616D696E672053747564696F7320496E632E222C226E223A2243727970746F20546F6B656E222C2274223A2243525950544F222C227573223A5B7B2263223A2277656273697465222C2274223A224D61696E2057656273697465222C2275223A2268747470733A2F2F67616D696E6773747564696F732E636F6D227D2C7B2263223A2277656273697465222C2274223A224D61696E2057656273697465222C2275223A2268747470733A2F2F67616D696E6773747564696F732E636F6D227D5D7D"
169+
},
170+
{
171+
"testName": "with extra fields",
172+
"mptMetadata": {
173+
"ticker": "CRYPTO",
174+
"n": "Crypto Token",
175+
"icon": "https://example.org/crypto-icon.png",
176+
"asset_class": "gaming",
177+
"d": "A gaming token for virtual worlds.",
178+
"issuer_name": "Gaming Studios Inc.",
179+
"as": "equity",
180+
"uris": [
181+
{
182+
"uri": "https://gamingstudios.com",
183+
"c": "website",
184+
"title": "Main Website"
185+
},
186+
{
187+
"uri": "https://gamingstudios.com",
188+
"category": "website",
189+
"t": "Main Website"
190+
}
191+
],
192+
"ai": "Gaming ecosystem token",
193+
"extra": {
194+
"extra": "extra"
195+
}
196+
},
197+
"expectedLongForm": {
198+
"ticker": "CRYPTO",
199+
"name": "Crypto Token",
200+
"icon": "https://example.org/crypto-icon.png",
201+
"asset_class": "gaming",
202+
"desc": "A gaming token for virtual worlds.",
203+
"issuer_name": "Gaming Studios Inc.",
204+
"asset_subclass": "equity",
205+
"uris": [
206+
{
207+
"uri": "https://gamingstudios.com",
208+
"category": "website",
209+
"title": "Main Website"
210+
},
211+
{
212+
"uri": "https://gamingstudios.com",
213+
"category": "website",
214+
"title": "Main Website"
215+
}
216+
],
217+
"additional_info": "Gaming ecosystem token",
218+
"extra": {
219+
"extra": "extra"
220+
}
221+
},
222+
"hex": "7B226163223A2267616D696E67222C226169223A2247616D696E672065636F73797374656D20746F6B656E222C226173223A22657175697479222C2264223A22412067616D696E6720746F6B656E20666F72207669727475616C20776F726C64732E222C226578747261223A7B226578747261223A226578747261227D2C2269223A2268747470733A2F2F6578616D706C652E6F72672F63727970746F2D69636F6E2E706E67222C22696E223A2247616D696E672053747564696F7320496E632E222C226E223A2243727970746F20546F6B656E222C2274223A2243525950544F222C227573223A5B7B2263223A2277656273697465222C2274223A224D61696E2057656273697465222C2275223A2268747470733A2F2F67616D696E6773747564696F732E636F6D227D2C7B2263223A2277656273697465222C2274223A224D61696E2057656273697465222C2275223A2268747470733A2F2F67616D696E6773747564696F732E636F6D227D5D7D"
223+
},
224+
{
225+
"testName": "with unkown null fields",
226+
"mptMetadata": {
227+
"t": "CRYPTO",
228+
"extra": null
229+
},
230+
"expectedLongForm": {
231+
"ticker": "CRYPTO",
232+
"extra": null
233+
},
234+
"hex": "7B226578747261223A6E756C6C2C2274223A2243525950544F227D"
235+
},
236+
{
237+
"testName": "multiple uris and us",
238+
"mptMetadata": {
239+
"t": "CRYPTO",
240+
"uris": [
241+
{
242+
"u": "https://gamingstudios.com",
243+
"c": "website",
244+
"t": "Main Website"
245+
}
246+
],
247+
"us": [
248+
{
249+
"uri": "https://gamingstudios.com",
250+
"category": "website",
251+
"title": "Main Website"
252+
}
253+
]
254+
},
255+
"expectedLongForm": {
256+
"ticker": "CRYPTO",
257+
"uris": [
258+
{
259+
"uri": "https://gamingstudios.com",
260+
"category": "website",
261+
"title": "Main Website"
262+
}
263+
],
264+
"us": [
265+
{
266+
"uri": "https://gamingstudios.com",
267+
"category": "website",
268+
"title": "Main Website"
269+
}
270+
]
271+
},
272+
"hex": "7B2274223A2243525950544F222C2275726973223A5B7B2263223A2277656273697465222C2274223A224D61696E2057656273697465222C2275223A2268747470733A2F2F67616D696E6773747564696F732E636F6D227D5D2C227573223A5B7B2263223A2277656273697465222C2274223A224D61696E2057656273697465222C2275223A2268747470733A2F2F67616D696E6773747564696F732E636F6D227D5D7D"
273+
},
274+
{
275+
"testName": "multiple keys in uri",
276+
"mptMetadata": {
277+
"us": [
278+
{
279+
"uri": "https://gamingstudios.com",
280+
"u": "website",
281+
"category": "Main Website",
282+
"c": "Main Website"
283+
}
284+
]
285+
},
286+
"expectedLongForm": {
287+
"uris": [
288+
{
289+
"uri": "https://gamingstudios.com",
290+
"u": "website",
291+
"category": "Main Website",
292+
"c": "Main Website"
293+
}
294+
]
295+
},
296+
"hex": "7B227573223A5B7B2263223A224D61696E2057656273697465222C2263617465676F7279223A224D61696E2057656273697465222C2275223A2277656273697465222C22757269223A2268747470733A2F2F67616D696E6773747564696F732E636F6D227D5D7D"
297+
}
298+
]

0 commit comments

Comments
 (0)