From 0681e0a40c4582bb83896bffadd59530f91b94a4 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Mon, 15 Sep 2025 15:40:31 -0700 Subject: [PATCH 01/17] models and integration tests for Dynamic MPTs (XLS-94D) --- .ci-config/rippled.cfg | 1 + .../src/enums/definitions.json | 10 ++ packages/xrpl/HISTORY.md | 3 + .../xrpl/src/models/ledger/LedgerEntry.ts | 2 + .../xrpl/src/models/ledger/MPTokenIssuance.ts | 88 +++++++++++++++ .../transactions/MPTokenIssuanceCreate.ts | 19 ++++ .../models/transactions/MPTokenIssuanceSet.ts | 102 ++++++++++++++++++ .../transactions/mptokenIssuanceSet.test.ts | 61 +++++++++++ .../test/models/MPTokenIssuanceCreate.test.ts | 1 + .../test/models/MPTokenIssuanceSet.test.ts | 7 ++ 10 files changed, 294 insertions(+) diff --git a/.ci-config/rippled.cfg b/.ci-config/rippled.cfg index 05d5753eea..361782cffa 100644 --- a/.ci-config/rippled.cfg +++ b/.ci-config/rippled.cfg @@ -193,6 +193,7 @@ Batch PermissionedDEX TokenEscrow SingleAssetVault +DynamicMPT # This section can be used to simulate various FeeSettings scenarios for rippled node in standalone mode [voting] diff --git a/packages/ripple-binary-codec/src/enums/definitions.json b/packages/ripple-binary-codec/src/enums/definitions.json index 4899a4df01..f7e5604bfc 100644 --- a/packages/ripple-binary-codec/src/enums/definitions.json +++ b/packages/ripple-binary-codec/src/enums/definitions.json @@ -680,6 +680,16 @@ "type": "UInt32" } ], + [ + "MutableFlags", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 53, + "type": "UInt32" + } + ], [ "IndexNext", { diff --git a/packages/xrpl/HISTORY.md b/packages/xrpl/HISTORY.md index 177270d1e5..620fb86fe6 100644 --- a/packages/xrpl/HISTORY.md +++ b/packages/xrpl/HISTORY.md @@ -4,6 +4,9 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr ## Unreleased +### Added +* Support for `Dynamic MPT` (XLS-94D) + ## 4.4.1 (2025-08-29) ### Fixed diff --git a/packages/xrpl/src/models/ledger/LedgerEntry.ts b/packages/xrpl/src/models/ledger/LedgerEntry.ts index d106090fa3..44d0bb98f0 100644 --- a/packages/xrpl/src/models/ledger/LedgerEntry.ts +++ b/packages/xrpl/src/models/ledger/LedgerEntry.ts @@ -10,6 +10,7 @@ import DirectoryNode from './DirectoryNode' import Escrow from './Escrow' import FeeSettings from './FeeSettings' import LedgerHashes from './LedgerHashes' +import { MPTokenIssuance } from './MPTokenIssuance' import NegativeUNL from './NegativeUNL' import Offer from './Offer' import Oracle from './Oracle' @@ -46,6 +47,7 @@ type LedgerEntry = | Vault | XChainOwnedClaimID | XChainOwnedCreateAccountClaimID + | MPTokenIssuance type LedgerEntryFilter = | 'account' diff --git a/packages/xrpl/src/models/ledger/MPTokenIssuance.ts b/packages/xrpl/src/models/ledger/MPTokenIssuance.ts index b590071478..1bc3e05e1e 100644 --- a/packages/xrpl/src/models/ledger/MPTokenIssuance.ts +++ b/packages/xrpl/src/models/ledger/MPTokenIssuance.ts @@ -11,4 +11,92 @@ export interface MPTokenIssuance extends BaseLedgerEntry, HasPreviousTxnID { MPTokenMetadata?: string OwnerNode?: string LockedAmount?: string + DomainID?: string + MutableFlags: number +} + +export interface MPTokenIssuanceFlagsInterface { + lsfMPTLocked?: boolean + lsfMPTCanLock?: boolean + lsfMPTRequireAuth?: boolean + lsfMPTCanEscrow?: boolean + lsfMPTCanTrade?: boolean + lsfMPTCanTransfer?: boolean + lsfMPTCanClawback?: boolean + + /** + * Indicates flag lsfMPTCanLock can be changed + */ + lsfMPTCanMutateCanLock?: boolean + /** + * Indicates flag lsfMPTRequireAuth can be changed + */ + lsfMPTCanMutateRequireAuth?: boolean + /** + * Indicates flag lsfMPTCanEscrow can be changed + */ + lsfMPTCanMutateCanEscrow?: boolean + /** + * Indicates flag lsfMPTCanTrade can be changed + */ + lsfMPTCanMutateCanTrade?: boolean + /** + * Indicates flag lsfMPTCanTransfer can be changed + */ + lsfMPTCanMutateCanTransfer?: boolean + /** + * Indicates flag lsfMPTCanClawback can be changed + */ + lsfMPTCanMutateCanClawback?: boolean + /** + * Allows field MPTokenMetadata to be modified + */ + lsfMPTCanMutateMetadata?: boolean + /** + * Allows field TransferFee to be modified + */ + lsfMPTCanMutateTransferFee?: boolean +} + +export enum MPTokenIssuanceFlags { + lsfMPTLocked = 0x00000001, + lsfMPTCanLock = 0x00000002, + lsfMPTRequireAuth = 0x00000004, + lsfMPTCanEscrow = 0x00000008, + lsfMPTCanTrade = 0x00000010, + lsfMPTCanTransfer = 0x00000020, + lsfMPTCanClawback = 0x00000040, + + /** + * Indicates flag lsfMPTCanLock can be changed + */ + lsfMPTCanMutateCanLock = 0x00000002, + /** + * Indicates flag lsfMPTRequireAuth can be changed + */ + lsfMPTCanMutateRequireAuth = 0x00000004, + /** + * Indicates flag lsfMPTCanEscrow can be changed + */ + lsfMPTCanMutateCanEscrow = 0x00000008, + /** + * Indicates flag lsfMPTCanTrade can be changed + */ + lsfMPTCanMutateCanTrade = 0x00000010, + /** + * Indicates flag lsfMPTCanTransfer can be changed + */ + lsfMPTCanMutateCanTransfer = 0x00000020, + /** + * Indicates flag lsfMPTCanClawback can be changed + */ + lsfMPTCanMutateCanClawback = 0x00000040, + /** + * Allows field MPTokenMetadata to be modified + */ + lsfMPTCanMutateMetadata = 0x00010000, + /** + * Allows field TransferFee to be modified + */ + lsfMPTCanMutateTransferFee = 0x00020000, } diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index b9b753cc08..dfa561b185 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -53,6 +53,15 @@ export enum MPTokenIssuanceCreateFlags { * to clawback value from individual holders. */ tfMPTCanClawback = 0x00000040, + + tfMPTCanMutateCanLock = 0x00000002, + tfMPTCanMutateRequireAuth = 0x00000004, + tfMPTCanMutateCanEscrow = 0x00000008, + tfMPTCanMutateCanTrade = 0x00000010, + tfMPTCanMutateCanTransfer = 0x00000020, + tfMPTCanMutateCanClawback = 0x00000040, + tfMPTCanMutateMetadata = 0x00010000, + tfMPTCanMutateTransferFee = 0x00020000, } /** @@ -69,6 +78,15 @@ export interface MPTokenIssuanceCreateFlagsInterface tfMPTCanTrade?: boolean tfMPTCanTransfer?: boolean tfMPTCanClawback?: boolean + + tfMPTCanMutateCanLock?: boolean + tfMPTCanMutateRequireAuth?: boolean + tfMPTCanMutateCanEscrow?: boolean + tfMPTCanMutateCanTrade?: boolean + tfMPTCanMutateCanTransfer?: boolean + tfMPTCanMutateCanClawback?: boolean + tfMPTCanMutateMetadata?: boolean + tfMPTCanMutateTransferFee?: boolean } /** @@ -120,6 +138,7 @@ export interface MPTokenIssuanceCreate extends BaseTransaction { MPTokenMetadata?: string Flags?: number | MPTokenIssuanceCreateFlagsInterface + MutableFlags?: number } export interface MPTokenIssuanceCreateMetadata extends TransactionMetadataBase { diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts index 12d15260ff..3624241c73 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -26,6 +26,55 @@ export enum MPTokenIssuanceSetFlags { * If set, indicates that issuer unlocks the MPT */ tfMPTUnlock = 0x00000002, + // The below flags are used in the MutableFlags field + /* + ** Sets the lsfMPTCanLock flag. Enables the token to be locked both individually and globally. + */ + tfMPTSetCanLock = 0x00000001, + /* + ** Clears the lsfMPTCanLock flag. Disables both individual and global locking of the token. + */ + tfMPTClearCanLock = 0x00000002, + /* + ** Sets the lsfMPTRequireAuth flag. Requires individual holders to be authorized. + */ + tfMPTSetRequireAuth = 0x00000004, + /* + ** Clears the lsfMPTRequireAuth flag. Holders are not required to be authorized. + */ + tfMPTClearRequireAuth = 0x00000008, + /* + ** Sets the lsfMPTCanEscrow flag. Allows holders to place balances into escrow. + */ + tfMPTSetCanEscrow = 0x00000010, + /* + ** Clears the lsfMPTCanEscrow flag. Disallows holders from placing balances into escrow. + */ + tfMPTClearCanEscrow = 0x00000020, + /* + ** Sets the lsfMPTCanTrade flag. Allows holders to trade balances on the XRPL DEX. + */ + tfMPTSetCanTrade = 0x00000040, + /* + ** Clears the lsfMPTCanTrade flag. Disallows holders from trading balances on the XRPL DEX. + */ + tfMPTClearCanTrade = 0x00000080, + /* + ** Sets the lsfMPTCanTransfer flag. Allows tokens to be transferred to non-issuer accounts. + */ + tfMPTSetCanTransfer = 0x00000100, + /* + ** Clears the lsfMPTCanTransfer flag. Disallows transfers to non-issuer accounts. + */ + tfMPTClearCanTransfer = 0x00000200, + /* + ** Sets the lsfMPTCanClawback flag. Enables the issuer to claw back tokens via Clawback or AMMClawback transactions. + */ + tfMPTSetCanClawback = 0x00000400, + /* + ** Clears the lsfMPTCanClawback flag. The token can not be clawed back. + */ + tfMPTClearCanClawback = 0x00000800, } /** @@ -37,6 +86,55 @@ export enum MPTokenIssuanceSetFlags { export interface MPTokenIssuanceSetFlagsInterface extends GlobalFlagsInterface { tfMPTLock?: boolean tfMPTUnlock?: boolean + // The below flags are used in the MutableFlags field + /* + ** Sets the lsfMPTCanLock flag. Enables the token to be locked both individually and globally. + */ + tfMPTSetCanLock?: boolean + /* + ** Clears the lsfMPTCanLock flag. Disables both individual and global locking of the token. + */ + tfMPTClearCanLock?: boolean + /* + ** Sets the lsfMPTRequireAuth flag. Requires individual holders to be authorized. + */ + tfMPTSetRequireAuth?: boolean + /* + ** Clears the lsfMPTRequireAuth flag. Holders are not required to be authorized. + */ + tfMPTClearRequireAuth?: boolean + /* + ** Sets the lsfMPTCanEscrow flag. Allows holders to place balances into escrow. + */ + tfMPTSetCanEscrow?: boolean + /* + ** Clears the lsfMPTCanEscrow flag. Disallows holders from placing balances into escrow. + */ + tfMPTClearCanEscrow?: boolean + /* + ** Sets the lsfMPTCanTrade flag. Allows holders to trade balances on the XRPL DEX. + */ + tfMPTSetCanTrade?: boolean + /* + ** Clears the lsfMPTCanTrade flag. Disallows holders from trading balances on the XRPL DEX. + */ + tfMPTClearCanTrade?: boolean + /* + ** Sets the lsfMPTCanTransfer flag. Allows tokens to be transferred to non-issuer accounts. + */ + tfMPTSetCanTransfer?: boolean + /* + ** Clears the lsfMPTCanTransfer flag. Disallows transfers to non-issuer accounts. + */ + tfMPTClearCanTransfer?: boolean + /* + ** Sets the lsfMPTCanClawback flag. Enables the issuer to claw back tokens via Clawback or AMMClawback transactions. + */ + tfMPTSetCanClawback?: boolean + /* + ** Clears the lsfMPTCanClawback flag. The token can not be clawed back. + */ + tfMPTClearCanClawback?: boolean } /** @@ -55,6 +153,10 @@ export interface MPTokenIssuanceSet extends BaseTransaction { */ Holder?: Account Flags?: number | MPTokenIssuanceSetFlagsInterface + + MPTokenMetadata?: string + TransferFee?: number + MutableFlags?: number } /** diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts index 195fbe5949..4318e5d5d2 100644 --- a/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts @@ -75,4 +75,65 @@ describe('MPTokenIssuanceDestroy', function () { }, TIMEOUT, ) + + it( + 'Test Mutability of Flags as per Dynamic MPT (XLS-94D) amendment', + async () => { + const createTx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + Flags: MPTokenIssuanceCreateFlags.tfMPTCanTransfer, + MutableFlags: + MPTokenIssuanceCreateFlags.tfMPTCanMutateTransferFee + + MPTokenIssuanceCreateFlags.tfMPTCanMutateCanTransfer, + } + + const mptCreateRes = await testTransaction( + testContext.client, + createTx, + testContext.wallet, + ) + + const txHash = mptCreateRes.result.tx_json.hash + + const txResponse = await testContext.client.request({ + command: 'tx', + transaction: txHash, + }) + + const meta = txResponse.result + .meta as TransactionMetadata + + const mptID = meta.mpt_issuance_id + + const setTransferFeeTx: MPTokenIssuanceSet = { + TransactionType: 'MPTokenIssuanceSet', + Account: testContext.wallet.classicAddress, + MPTokenIssuanceID: mptID!, + // set the transfer fee to a non-zero value + TransferFee: 200, + } + + await testTransaction( + testContext.client, + setTransferFeeTx, + testContext.wallet, + ) + + // remove the ability to transfer the MPT + const clearTransferFlagTx: MPTokenIssuanceSet = { + TransactionType: 'MPTokenIssuanceSet', + Account: testContext.wallet.classicAddress, + MPTokenIssuanceID: mptID!, + MutableFlags: MPTokenIssuanceSetFlags.tfMPTClearCanTransfer, + } + + await testTransaction( + testContext.client, + clearTransferFlagTx, + testContext.wallet, + ) + }, + TIMEOUT, + ) }) diff --git a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts index 26a4d0496a..32f0ceebc6 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts @@ -28,6 +28,7 @@ describe('MPTokenIssuanceCreate', function () { AssetScale: 2, TransferFee: 1, Flags: MPTokenIssuanceCreateFlags.tfMPTCanTransfer, + MutableFlags: MPTokenIssuanceCreateFlags.tfMPTCanMutateTransferFee, MPTokenMetadata: stringToHex(`{ "ticker": "TBILL", "name": "T-Bill Yield Token", diff --git a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts index 2a05f2a71a..9d87c88dfa 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts @@ -44,6 +44,13 @@ describe('MPTokenIssuanceSet', function () { } as any assertValid(validMPTokenIssuanceSet) + + assertValid({ + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MutableFlags: MPTokenIssuanceSetFlags.tfMPTClearCanTransfer, + } as any) }) it(`throws w/ missing MPTokenIssuanceID`, function () { From ad316b7369c243b59deeb61ae09cde1465591ef8 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Tue, 16 Sep 2025 12:28:38 -0700 Subject: [PATCH 02/17] unit tests suggested by rabbit --- .../transactions/MPTokenIssuanceCreate.ts | 3 +- .../models/transactions/MPTokenIssuanceSet.ts | 28 ++++++- .../test/models/MPTokenIssuanceSet.test.ts | 80 +++++++++++++++++++ 3 files changed, 109 insertions(+), 2 deletions(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index dfa561b185..6a7f0fb50a 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -16,7 +16,7 @@ import type { TransactionMetadataBase } from './metadata' // 2^63 - 1 const MAX_AMT = '9223372036854775807' -const MAX_TRANSFER_FEE = 50000 +export const MAX_TRANSFER_FEE = 50000 /** * Transaction Flags for an MPTokenIssuanceCreate Transaction. @@ -160,6 +160,7 @@ export function validateMPTokenIssuanceCreate( validateOptionalField(tx, 'MPTokenMetadata', isString) validateOptionalField(tx, 'TransferFee', isNumber) validateOptionalField(tx, 'AssetScale', isNumber) + validateOptionalField(tx, 'MutableFlags', isNumber) if ( typeof tx.MPTokenMetadata === 'string' && diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts index 3624241c73..fc1e9546a5 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -1,5 +1,5 @@ import { ValidationError } from '../../errors' -import { isFlagEnabled } from '../utils' +import { isFlagEnabled, isHex } from '../utils' import { BaseTransaction, @@ -10,8 +10,12 @@ import { validateOptionalField, isAccount, GlobalFlagsInterface, + isNumber, + MAX_MPT_META_BYTE_LENGTH, } from './common' +import { MAX_TRANSFER_FEE } from './MPTokenIssuanceCreate' + /** * Transaction Flags for an MPTokenIssuanceSet Transaction. * @@ -169,6 +173,10 @@ export function validateMPTokenIssuanceSet(tx: Record): void { validateBaseTransaction(tx) validateRequiredField(tx, 'MPTokenIssuanceID', isString) validateOptionalField(tx, 'Holder', isAccount) + validateOptionalField(tx, 'MPTokenMetadata', isString) + validateOptionalField(tx, 'TransferFee', isNumber) + validateOptionalField(tx, 'MutableFlags', isNumber) + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Not necessary const flags = (tx.Flags ?? 0) as number | MPTokenIssuanceSetFlagsInterface @@ -185,4 +193,22 @@ export function validateMPTokenIssuanceSet(tx: Record): void { if (isTfMPTLock && isTfMPTUnlock) { throw new ValidationError('MPTokenIssuanceSet: flag conflict') } + + if (typeof tx.TransferFee === 'number') { + if (tx.TransferFee < 0 || tx.TransferFee > MAX_TRANSFER_FEE) { + throw new ValidationError( + `MPTokenIssuanceSet: TransferFee must be between 0 and ${MAX_TRANSFER_FEE}`, + ) + } + } + + if ( + typeof tx.MPTokenMetadata === 'string' && + (!isHex(tx.MPTokenMetadata) || + tx.MPTokenMetadata.length / 2 > MAX_MPT_META_BYTE_LENGTH) + ) { + throw new ValidationError( + `MPTokenIssuanceSet: MPTokenMetadata (hex format) must be non-empty and no more than ${MAX_MPT_META_BYTE_LENGTH} bytes.`, + ) + } } diff --git a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts index 9d87c88dfa..8c4f740b53 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts @@ -1,4 +1,7 @@ +import { stringToHex } from '@xrplf/isomorphic/dist/utils' import { MPTokenIssuanceSetFlags } from '../../src' +import { MAX_MPT_META_BYTE_LENGTH } from '../../src/models/transactions/common' +import { MAX_TRANSFER_FEE } from '../../src/models/transactions/MPTokenIssuanceCreate' import { validateMPTokenIssuanceSet } from '../../src/models/transactions/MPTokenIssuanceSet' import { assertTxIsValid, assertTxValidationError } from '../testUtils' @@ -82,4 +85,81 @@ describe('MPTokenIssuanceSet', function () { assertInvalid(invalid, 'MPTokenIssuanceSet: flag conflict') }) + + it(`Throws w/ invalid type of TransferFee`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + TransferFee: '100', + } as any + + assertInvalid(invalid, 'MPTokenIssuanceSet: invalid field TransferFee') + }) + + it(`Throws w/ invalid (too low) value of TransferFee`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + TransferFee: -1, + } as any + + assertInvalid(invalid, `MPTokenIssuanceSet: TransferFee must be between 0 and ${MAX_TRANSFER_FEE}`) + }) + + it(`Throws w/ invalid (too high) value of TransferFee`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + TransferFee: MAX_TRANSFER_FEE + 1, + } as any + + assertInvalid(invalid, `MPTokenIssuanceSet: TransferFee must be between 0 and ${MAX_TRANSFER_FEE}`) + }) + + it(`Throws w/ invalid type of MutableFlags`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MutableFlags: '100', + } as any + + assertInvalid(invalid, 'MPTokenIssuanceSet: invalid field MutableFlags') + }) + + it(`Throws w/ invalid type of MPTokenMetadata`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MPTokenMetadata: 1234, + } as any + + assertInvalid(invalid, 'MPTokenIssuanceSet: invalid field MPTokenMetadata') + }) + + it(`Throws w/ invalid (non-hex characters) MPTokenMetadata`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MPTokenMetadata: 'zznothex', + } as any + + assertInvalid(invalid, `MPTokenIssuanceSet: MPTokenMetadata (hex format) must be non-empty and no more than ${MAX_MPT_META_BYTE_LENGTH} bytes.`,) + }) + + it(`Throws w/ invalid (too large) MPTokenMetadata`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MPTokenMetadata: stringToHex('a'.repeat(MAX_MPT_META_BYTE_LENGTH + 1)), + } as any + + assertInvalid(invalid, `MPTokenIssuanceSet: MPTokenMetadata (hex format) must be non-empty and no more than ${MAX_MPT_META_BYTE_LENGTH} bytes.`,) + }) }) From fd1513d4b620eaf572175a1d07d07f5466760a8e Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Tue, 16 Sep 2025 13:44:29 -0700 Subject: [PATCH 03/17] fix linter errors --- .../models/transactions/MPTokenIssuanceSet.ts | 98 +++++-------------- .../test/models/MPTokenIssuanceSet.test.ts | 21 +++- 2 files changed, 41 insertions(+), 78 deletions(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts index fc1e9546a5..e493a06b82 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -13,7 +13,6 @@ import { isNumber, MAX_MPT_META_BYTE_LENGTH, } from './common' - import { MAX_TRANSFER_FEE } from './MPTokenIssuanceCreate' /** @@ -31,53 +30,29 @@ export enum MPTokenIssuanceSetFlags { */ tfMPTUnlock = 0x00000002, // The below flags are used in the MutableFlags field - /* - ** Sets the lsfMPTCanLock flag. Enables the token to be locked both individually and globally. - */ + /* Sets the lsfMPTCanLock flag. Enables the token to be locked both individually and globally. */ tfMPTSetCanLock = 0x00000001, - /* - ** Clears the lsfMPTCanLock flag. Disables both individual and global locking of the token. - */ + /* Clears the lsfMPTCanLock flag. Disables both individual and global locking of the token. */ tfMPTClearCanLock = 0x00000002, - /* - ** Sets the lsfMPTRequireAuth flag. Requires individual holders to be authorized. - */ + /* Sets the lsfMPTRequireAuth flag. Requires individual holders to be authorized. */ tfMPTSetRequireAuth = 0x00000004, - /* - ** Clears the lsfMPTRequireAuth flag. Holders are not required to be authorized. - */ + /* Clears the lsfMPTRequireAuth flag. Holders are not required to be authorized. */ tfMPTClearRequireAuth = 0x00000008, - /* - ** Sets the lsfMPTCanEscrow flag. Allows holders to place balances into escrow. - */ + /* Sets the lsfMPTCanEscrow flag. Allows holders to place balances into escrow. */ tfMPTSetCanEscrow = 0x00000010, - /* - ** Clears the lsfMPTCanEscrow flag. Disallows holders from placing balances into escrow. - */ + /* Clears the lsfMPTCanEscrow flag. Disallows holders from placing balances into escrow. */ tfMPTClearCanEscrow = 0x00000020, - /* - ** Sets the lsfMPTCanTrade flag. Allows holders to trade balances on the XRPL DEX. - */ + /* Sets the lsfMPTCanTrade flag. Allows holders to trade balances on the XRPL DEX. */ tfMPTSetCanTrade = 0x00000040, - /* - ** Clears the lsfMPTCanTrade flag. Disallows holders from trading balances on the XRPL DEX. - */ + /* Clears the lsfMPTCanTrade flag. Disallows holders from trading balances on the XRPL DEX. */ tfMPTClearCanTrade = 0x00000080, - /* - ** Sets the lsfMPTCanTransfer flag. Allows tokens to be transferred to non-issuer accounts. - */ + /* Sets the lsfMPTCanTransfer flag. Allows tokens to be transferred to non-issuer accounts. */ tfMPTSetCanTransfer = 0x00000100, - /* - ** Clears the lsfMPTCanTransfer flag. Disallows transfers to non-issuer accounts. - */ + /* Clears the lsfMPTCanTransfer flag. Disallows transfers to non-issuer accounts. */ tfMPTClearCanTransfer = 0x00000200, - /* - ** Sets the lsfMPTCanClawback flag. Enables the issuer to claw back tokens via Clawback or AMMClawback transactions. - */ + /* Sets the lsfMPTCanClawback flag. Enables the issuer to claw back tokens via Clawback or AMMClawback transactions. */ tfMPTSetCanClawback = 0x00000400, - /* - ** Clears the lsfMPTCanClawback flag. The token can not be clawed back. - */ + /* Clears the lsfMPTCanClawback flag. The token can not be clawed back. */ tfMPTClearCanClawback = 0x00000800, } @@ -91,53 +66,29 @@ export interface MPTokenIssuanceSetFlagsInterface extends GlobalFlagsInterface { tfMPTLock?: boolean tfMPTUnlock?: boolean // The below flags are used in the MutableFlags field - /* - ** Sets the lsfMPTCanLock flag. Enables the token to be locked both individually and globally. - */ + /* Sets the lsfMPTCanLock flag. Enables the token to be locked both individually and globally. */ tfMPTSetCanLock?: boolean - /* - ** Clears the lsfMPTCanLock flag. Disables both individual and global locking of the token. - */ + /* Clears the lsfMPTCanLock flag. Disables both individual and global locking of the token. */ tfMPTClearCanLock?: boolean - /* - ** Sets the lsfMPTRequireAuth flag. Requires individual holders to be authorized. - */ + /* Sets the lsfMPTRequireAuth flag. Requires individual holders to be authorized. */ tfMPTSetRequireAuth?: boolean - /* - ** Clears the lsfMPTRequireAuth flag. Holders are not required to be authorized. - */ + /* Clears the lsfMPTRequireAuth flag. Holders are not required to be authorized. */ tfMPTClearRequireAuth?: boolean - /* - ** Sets the lsfMPTCanEscrow flag. Allows holders to place balances into escrow. - */ + /* Sets the lsfMPTCanEscrow flag. Allows holders to place balances into escrow. */ tfMPTSetCanEscrow?: boolean - /* - ** Clears the lsfMPTCanEscrow flag. Disallows holders from placing balances into escrow. - */ + /* Clears the lsfMPTCanEscrow flag. Disallows holders from placing balances into escrow. */ tfMPTClearCanEscrow?: boolean - /* - ** Sets the lsfMPTCanTrade flag. Allows holders to trade balances on the XRPL DEX. - */ + /* Sets the lsfMPTCanTrade flag. Allows holders to trade balances on the XRPL DEX. */ tfMPTSetCanTrade?: boolean - /* - ** Clears the lsfMPTCanTrade flag. Disallows holders from trading balances on the XRPL DEX. - */ + /* Clears the lsfMPTCanTrade flag. Disallows holders from trading balances on the XRPL DEX. */ tfMPTClearCanTrade?: boolean - /* - ** Sets the lsfMPTCanTransfer flag. Allows tokens to be transferred to non-issuer accounts. - */ + /* Sets the lsfMPTCanTransfer flag. Allows tokens to be transferred to non-issuer accounts. */ tfMPTSetCanTransfer?: boolean - /* - ** Clears the lsfMPTCanTransfer flag. Disallows transfers to non-issuer accounts. - */ + /* Clears the lsfMPTCanTransfer flag. Disallows transfers to non-issuer accounts. */ tfMPTClearCanTransfer?: boolean - /* - ** Sets the lsfMPTCanClawback flag. Enables the issuer to claw back tokens via Clawback or AMMClawback transactions. - */ + /* Sets the lsfMPTCanClawback flag. Enables the issuer to claw back tokens via Clawback or AMMClawback transactions. */ tfMPTSetCanClawback?: boolean - /* - ** Clears the lsfMPTCanClawback flag. The token can not be clawed back. - */ + /* Clears the lsfMPTCanClawback flag. The token can not be clawed back. */ tfMPTClearCanClawback?: boolean } @@ -177,7 +128,6 @@ export function validateMPTokenIssuanceSet(tx: Record): void { validateOptionalField(tx, 'TransferFee', isNumber) validateOptionalField(tx, 'MutableFlags', isNumber) - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Not necessary const flags = (tx.Flags ?? 0) as number | MPTokenIssuanceSetFlagsInterface const isTfMPTLock = diff --git a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts index 8c4f740b53..7556870cbd 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts @@ -1,4 +1,5 @@ import { stringToHex } from '@xrplf/isomorphic/dist/utils' + import { MPTokenIssuanceSetFlags } from '../../src' import { MAX_MPT_META_BYTE_LENGTH } from '../../src/models/transactions/common' import { MAX_TRANSFER_FEE } from '../../src/models/transactions/MPTokenIssuanceCreate' @@ -105,7 +106,10 @@ describe('MPTokenIssuanceSet', function () { TransferFee: -1, } as any - assertInvalid(invalid, `MPTokenIssuanceSet: TransferFee must be between 0 and ${MAX_TRANSFER_FEE}`) + assertInvalid( + invalid, + `MPTokenIssuanceSet: TransferFee must be between 0 and ${MAX_TRANSFER_FEE}`, + ) }) it(`Throws w/ invalid (too high) value of TransferFee`, function () { @@ -116,7 +120,10 @@ describe('MPTokenIssuanceSet', function () { TransferFee: MAX_TRANSFER_FEE + 1, } as any - assertInvalid(invalid, `MPTokenIssuanceSet: TransferFee must be between 0 and ${MAX_TRANSFER_FEE}`) + assertInvalid( + invalid, + `MPTokenIssuanceSet: TransferFee must be between 0 and ${MAX_TRANSFER_FEE}`, + ) }) it(`Throws w/ invalid type of MutableFlags`, function () { @@ -149,7 +156,10 @@ describe('MPTokenIssuanceSet', function () { MPTokenMetadata: 'zznothex', } as any - assertInvalid(invalid, `MPTokenIssuanceSet: MPTokenMetadata (hex format) must be non-empty and no more than ${MAX_MPT_META_BYTE_LENGTH} bytes.`,) + assertInvalid( + invalid, + `MPTokenIssuanceSet: MPTokenMetadata (hex format) must be non-empty and no more than ${MAX_MPT_META_BYTE_LENGTH} bytes.`, + ) }) it(`Throws w/ invalid (too large) MPTokenMetadata`, function () { @@ -160,6 +170,9 @@ describe('MPTokenIssuanceSet', function () { MPTokenMetadata: stringToHex('a'.repeat(MAX_MPT_META_BYTE_LENGTH + 1)), } as any - assertInvalid(invalid, `MPTokenIssuanceSet: MPTokenMetadata (hex format) must be non-empty and no more than ${MAX_MPT_META_BYTE_LENGTH} bytes.`,) + assertInvalid( + invalid, + `MPTokenIssuanceSet: MPTokenMetadata (hex format) must be non-empty and no more than ${MAX_MPT_META_BYTE_LENGTH} bytes.`, + ) }) }) From 058ad5b367e9d3a6f8c00a00879f3d0d8d86f4c1 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Mon, 22 Sep 2025 09:44:14 -0700 Subject: [PATCH 04/17] updated flages in Dynamic MPT amendment to use tmf and lmf prefixes --- .../xrpl/src/models/ledger/MPTokenIssuance.ts | 32 ++++++------- .../transactions/MPTokenIssuanceCreate.ts | 32 ++++++------- .../models/transactions/MPTokenIssuanceSet.ts | 48 +++++++++---------- .../transactions/mptokenIssuanceSet.test.ts | 6 +-- .../test/models/MPTokenIssuanceCreate.test.ts | 2 +- .../test/models/MPTokenIssuanceSet.test.ts | 2 +- 6 files changed, 61 insertions(+), 61 deletions(-) diff --git a/packages/xrpl/src/models/ledger/MPTokenIssuance.ts b/packages/xrpl/src/models/ledger/MPTokenIssuance.ts index 1bc3e05e1e..3054bcd7ca 100644 --- a/packages/xrpl/src/models/ledger/MPTokenIssuance.ts +++ b/packages/xrpl/src/models/ledger/MPTokenIssuance.ts @@ -27,35 +27,35 @@ export interface MPTokenIssuanceFlagsInterface { /** * Indicates flag lsfMPTCanLock can be changed */ - lsfMPTCanMutateCanLock?: boolean + lmfMPTCanMutateCanLock?: boolean /** * Indicates flag lsfMPTRequireAuth can be changed */ - lsfMPTCanMutateRequireAuth?: boolean + lmfMPTCanMutateRequireAuth?: boolean /** * Indicates flag lsfMPTCanEscrow can be changed */ - lsfMPTCanMutateCanEscrow?: boolean + lmfMPTCanMutateCanEscrow?: boolean /** * Indicates flag lsfMPTCanTrade can be changed */ - lsfMPTCanMutateCanTrade?: boolean + lmfMPTCanMutateCanTrade?: boolean /** * Indicates flag lsfMPTCanTransfer can be changed */ - lsfMPTCanMutateCanTransfer?: boolean + lmfMPTCanMutateCanTransfer?: boolean /** * Indicates flag lsfMPTCanClawback can be changed */ - lsfMPTCanMutateCanClawback?: boolean + lmfMPTCanMutateCanClawback?: boolean /** * Allows field MPTokenMetadata to be modified */ - lsfMPTCanMutateMetadata?: boolean + lmfMPTCanMutateMetadata?: boolean /** * Allows field TransferFee to be modified */ - lsfMPTCanMutateTransferFee?: boolean + lmfMPTCanMutateTransferFee?: boolean } export enum MPTokenIssuanceFlags { @@ -70,33 +70,33 @@ export enum MPTokenIssuanceFlags { /** * Indicates flag lsfMPTCanLock can be changed */ - lsfMPTCanMutateCanLock = 0x00000002, + lmfMPTCanMutateCanLock = 0x00000002, /** * Indicates flag lsfMPTRequireAuth can be changed */ - lsfMPTCanMutateRequireAuth = 0x00000004, + lmfMPTCanMutateRequireAuth = 0x00000004, /** * Indicates flag lsfMPTCanEscrow can be changed */ - lsfMPTCanMutateCanEscrow = 0x00000008, + lmfMPTCanMutateCanEscrow = 0x00000008, /** * Indicates flag lsfMPTCanTrade can be changed */ - lsfMPTCanMutateCanTrade = 0x00000010, + lmfMPTCanMutateCanTrade = 0x00000010, /** * Indicates flag lsfMPTCanTransfer can be changed */ - lsfMPTCanMutateCanTransfer = 0x00000020, + lmfMPTCanMutateCanTransfer = 0x00000020, /** * Indicates flag lsfMPTCanClawback can be changed */ - lsfMPTCanMutateCanClawback = 0x00000040, + lmfMPTCanMutateCanClawback = 0x00000040, /** * Allows field MPTokenMetadata to be modified */ - lsfMPTCanMutateMetadata = 0x00010000, + lmfMPTCanMutateMetadata = 0x00010000, /** * Allows field TransferFee to be modified */ - lsfMPTCanMutateTransferFee = 0x00020000, + lmfMPTCanMutateTransferFee = 0x00020000, } diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index 6a7f0fb50a..126072600a 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -54,14 +54,14 @@ export enum MPTokenIssuanceCreateFlags { */ tfMPTCanClawback = 0x00000040, - tfMPTCanMutateCanLock = 0x00000002, - tfMPTCanMutateRequireAuth = 0x00000004, - tfMPTCanMutateCanEscrow = 0x00000008, - tfMPTCanMutateCanTrade = 0x00000010, - tfMPTCanMutateCanTransfer = 0x00000020, - tfMPTCanMutateCanClawback = 0x00000040, - tfMPTCanMutateMetadata = 0x00010000, - tfMPTCanMutateTransferFee = 0x00020000, + tmfMPTCanMutateCanLock = 0x00000002, + tmfMPTCanMutateRequireAuth = 0x00000004, + tmfMPTCanMutateCanEscrow = 0x00000008, + tmfMPTCanMutateCanTrade = 0x00000010, + tmfMPTCanMutateCanTransfer = 0x00000020, + tmfMPTCanMutateCanClawback = 0x00000040, + tmfMPTCanMutateMetadata = 0x00010000, + tmfMPTCanMutateTransferFee = 0x00020000, } /** @@ -79,14 +79,14 @@ export interface MPTokenIssuanceCreateFlagsInterface tfMPTCanTransfer?: boolean tfMPTCanClawback?: boolean - tfMPTCanMutateCanLock?: boolean - tfMPTCanMutateRequireAuth?: boolean - tfMPTCanMutateCanEscrow?: boolean - tfMPTCanMutateCanTrade?: boolean - tfMPTCanMutateCanTransfer?: boolean - tfMPTCanMutateCanClawback?: boolean - tfMPTCanMutateMetadata?: boolean - tfMPTCanMutateTransferFee?: boolean + tmfMPTCanMutateCanLock?: boolean + tmfMPTCanMutateRequireAuth?: boolean + tmfMPTCanMutateCanEscrow?: boolean + tmfMPTCanMutateCanTrade?: boolean + tmfMPTCanMutateCanTransfer?: boolean + tmfMPTCanMutateCanClawback?: boolean + tmfMPTCanMutateMetadata?: boolean + tmfMPTCanMutateTransferFee?: boolean } /** diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts index e493a06b82..e9d5e10b6e 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -31,29 +31,29 @@ export enum MPTokenIssuanceSetFlags { tfMPTUnlock = 0x00000002, // The below flags are used in the MutableFlags field /* Sets the lsfMPTCanLock flag. Enables the token to be locked both individually and globally. */ - tfMPTSetCanLock = 0x00000001, + tmfMPTSetCanLock = 0x00000001, /* Clears the lsfMPTCanLock flag. Disables both individual and global locking of the token. */ - tfMPTClearCanLock = 0x00000002, + tmfMPTClearCanLock = 0x00000002, /* Sets the lsfMPTRequireAuth flag. Requires individual holders to be authorized. */ - tfMPTSetRequireAuth = 0x00000004, + tmfMPTSetRequireAuth = 0x00000004, /* Clears the lsfMPTRequireAuth flag. Holders are not required to be authorized. */ - tfMPTClearRequireAuth = 0x00000008, + tmfMPTClearRequireAuth = 0x00000008, /* Sets the lsfMPTCanEscrow flag. Allows holders to place balances into escrow. */ - tfMPTSetCanEscrow = 0x00000010, + tmfMPTSetCanEscrow = 0x00000010, /* Clears the lsfMPTCanEscrow flag. Disallows holders from placing balances into escrow. */ - tfMPTClearCanEscrow = 0x00000020, + tmfMPTClearCanEscrow = 0x00000020, /* Sets the lsfMPTCanTrade flag. Allows holders to trade balances on the XRPL DEX. */ - tfMPTSetCanTrade = 0x00000040, + tmfMPTSetCanTrade = 0x00000040, /* Clears the lsfMPTCanTrade flag. Disallows holders from trading balances on the XRPL DEX. */ - tfMPTClearCanTrade = 0x00000080, + tmfMPTClearCanTrade = 0x00000080, /* Sets the lsfMPTCanTransfer flag. Allows tokens to be transferred to non-issuer accounts. */ - tfMPTSetCanTransfer = 0x00000100, + tmfMPTSetCanTransfer = 0x00000100, /* Clears the lsfMPTCanTransfer flag. Disallows transfers to non-issuer accounts. */ - tfMPTClearCanTransfer = 0x00000200, + tmfMPTClearCanTransfer = 0x00000200, /* Sets the lsfMPTCanClawback flag. Enables the issuer to claw back tokens via Clawback or AMMClawback transactions. */ - tfMPTSetCanClawback = 0x00000400, + tmfMPTSetCanClawback = 0x00000400, /* Clears the lsfMPTCanClawback flag. The token can not be clawed back. */ - tfMPTClearCanClawback = 0x00000800, + tmfMPTClearCanClawback = 0x00000800, } /** @@ -67,29 +67,29 @@ export interface MPTokenIssuanceSetFlagsInterface extends GlobalFlagsInterface { tfMPTUnlock?: boolean // The below flags are used in the MutableFlags field /* Sets the lsfMPTCanLock flag. Enables the token to be locked both individually and globally. */ - tfMPTSetCanLock?: boolean + tmfMPTSetCanLock?: boolean /* Clears the lsfMPTCanLock flag. Disables both individual and global locking of the token. */ - tfMPTClearCanLock?: boolean + tmfMPTClearCanLock?: boolean /* Sets the lsfMPTRequireAuth flag. Requires individual holders to be authorized. */ - tfMPTSetRequireAuth?: boolean + tmfMPTSetRequireAuth?: boolean /* Clears the lsfMPTRequireAuth flag. Holders are not required to be authorized. */ - tfMPTClearRequireAuth?: boolean + tmfMPTClearRequireAuth?: boolean /* Sets the lsfMPTCanEscrow flag. Allows holders to place balances into escrow. */ - tfMPTSetCanEscrow?: boolean + tmfMPTSetCanEscrow?: boolean /* Clears the lsfMPTCanEscrow flag. Disallows holders from placing balances into escrow. */ - tfMPTClearCanEscrow?: boolean + tmfMPTClearCanEscrow?: boolean /* Sets the lsfMPTCanTrade flag. Allows holders to trade balances on the XRPL DEX. */ - tfMPTSetCanTrade?: boolean + tmfMPTSetCanTrade?: boolean /* Clears the lsfMPTCanTrade flag. Disallows holders from trading balances on the XRPL DEX. */ - tfMPTClearCanTrade?: boolean + tmfMPTClearCanTrade?: boolean /* Sets the lsfMPTCanTransfer flag. Allows tokens to be transferred to non-issuer accounts. */ - tfMPTSetCanTransfer?: boolean + tmfMPTSetCanTransfer?: boolean /* Clears the lsfMPTCanTransfer flag. Disallows transfers to non-issuer accounts. */ - tfMPTClearCanTransfer?: boolean + tmfMPTClearCanTransfer?: boolean /* Sets the lsfMPTCanClawback flag. Enables the issuer to claw back tokens via Clawback or AMMClawback transactions. */ - tfMPTSetCanClawback?: boolean + tmfMPTSetCanClawback?: boolean /* Clears the lsfMPTCanClawback flag. The token can not be clawed back. */ - tfMPTClearCanClawback?: boolean + tmfMPTClearCanClawback?: boolean } /** diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts index 4318e5d5d2..dd7d9986d0 100644 --- a/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts @@ -84,8 +84,8 @@ describe('MPTokenIssuanceDestroy', function () { Account: testContext.wallet.classicAddress, Flags: MPTokenIssuanceCreateFlags.tfMPTCanTransfer, MutableFlags: - MPTokenIssuanceCreateFlags.tfMPTCanMutateTransferFee + - MPTokenIssuanceCreateFlags.tfMPTCanMutateCanTransfer, + MPTokenIssuanceCreateFlags.tmfMPTCanMutateTransferFee + + MPTokenIssuanceCreateFlags.tmfMPTCanMutateCanTransfer, } const mptCreateRes = await testTransaction( @@ -125,7 +125,7 @@ describe('MPTokenIssuanceDestroy', function () { TransactionType: 'MPTokenIssuanceSet', Account: testContext.wallet.classicAddress, MPTokenIssuanceID: mptID!, - MutableFlags: MPTokenIssuanceSetFlags.tfMPTClearCanTransfer, + MutableFlags: MPTokenIssuanceSetFlags.tmfMPTClearCanTransfer, } await testTransaction( diff --git a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts index 32f0ceebc6..742757e67a 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts @@ -28,7 +28,7 @@ describe('MPTokenIssuanceCreate', function () { AssetScale: 2, TransferFee: 1, Flags: MPTokenIssuanceCreateFlags.tfMPTCanTransfer, - MutableFlags: MPTokenIssuanceCreateFlags.tfMPTCanMutateTransferFee, + MutableFlags: MPTokenIssuanceCreateFlags.tmfMPTCanMutateTransferFee, MPTokenMetadata: stringToHex(`{ "ticker": "TBILL", "name": "T-Bill Yield Token", diff --git a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts index 7556870cbd..6bd70453f1 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts @@ -53,7 +53,7 @@ describe('MPTokenIssuanceSet', function () { TransactionType: 'MPTokenIssuanceSet', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', MPTokenIssuanceID: TOKEN_ID, - MutableFlags: MPTokenIssuanceSetFlags.tfMPTClearCanTransfer, + MutableFlags: MPTokenIssuanceSetFlags.tmfMPTClearCanTransfer, } as any) }) From 1e0aaa0076aab11ba6fee0ef1a5f3731ef0d1d67 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Mon, 22 Sep 2025 13:17:54 -0700 Subject: [PATCH 05/17] add Github Actions step to inspect Docker containter --- .github/workflows/nodejs.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 8a2f528c1d..00fe9730d2 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -154,6 +154,16 @@ jobs: - run: npm run build + - name: Check if Docker container is running + run: | + docker ps + docker ps -a + docker inspect rippled-service + if ! docker ps | grep -q rippled-service; then + echo "rippled-service Docker container is not running" + exit 1 + fi + - name: Run integration test run: npm run test:integration @@ -208,6 +218,16 @@ jobs: - run: npm run build + - name: Check if Docker container is running + run: | + docker ps + docker ps -a + docker inspect rippled-service + if ! docker ps | grep -q rippled-service; then + echo "rippled-service Docker container is not running" + exit 1 + fi + - name: Run integration test run: npm run test:browser From fc51d7e0c27f9dabcc8e525cb9becd017dc98757 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Mon, 22 Sep 2025 13:28:36 -0700 Subject: [PATCH 06/17] helpful message in Github Actions output --- .github/workflows/nodejs.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 00fe9730d2..951c2b9b8c 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -155,20 +155,23 @@ jobs: - run: npm run build - name: Check if Docker container is running + id: check-docker-container run: | docker ps - docker ps -a docker inspect rippled-service if ! docker ps | grep -q rippled-service; then echo "rippled-service Docker container is not running" - exit 1 + echo "docker-container-running=false" >> $GITHUB_OUTPUT + else + echo "docker-container-running=true" >> $GITHUB_OUTPUT fi - name: Run integration test + if: steps.check-docker-container.outputs.docker-container-running == 'true' run: npm run test:integration - name: Stop docker container - if: always() + if: steps.check-docker-container.outputs.docker-container-running == 'true' run: docker stop rippled-service browser: From b792695462cbc314f1c2e1c53522b6798bcff355 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Mon, 22 Sep 2025 13:32:33 -0700 Subject: [PATCH 07/17] reorder the conditions --- .github/workflows/nodejs.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 951c2b9b8c..5992fe5450 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -157,14 +157,14 @@ jobs: - name: Check if Docker container is running id: check-docker-container run: | - docker ps - docker inspect rippled-service if ! docker ps | grep -q rippled-service; then echo "rippled-service Docker container is not running" echo "docker-container-running=false" >> $GITHUB_OUTPUT else echo "docker-container-running=true" >> $GITHUB_OUTPUT fi + docker ps + docker inspect rippled-service - name: Run integration test if: steps.check-docker-container.outputs.docker-container-running == 'true' @@ -223,13 +223,13 @@ jobs: - name: Check if Docker container is running run: | - docker ps - docker ps -a - docker inspect rippled-service if ! docker ps | grep -q rippled-service; then echo "rippled-service Docker container is not running" exit 1 fi + docker ps + docker ps -a + docker inspect rippled-service - name: Run integration test run: npm run test:browser From 7e2e712947c71dfb1ec1d89b1bce09ae91b5c3b8 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Mon, 22 Sep 2025 13:45:12 -0700 Subject: [PATCH 08/17] Github Actions: include docker-inspect command to investigate the container --- .github/workflows/nodejs.yml | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 5992fe5450..72d1c7b215 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -122,8 +122,11 @@ jobs: fetch-depth: 0 - name: Run docker in background + id: run-docker run: | docker run --detach --rm -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/etc/opt/ripple/" --name rippled-service --health-cmd="rippled server_info || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env GITHUB_ACTIONS=true --env CI=true --entrypoint bash ${{ env.RIPPLED_DOCKER_IMAGE }} -c "rippled -a" + CONTAINER_ID=$(docker ps -aqf "name=rippled-service") + echo "docker-container-id=$CONTAINER_ID" >> $GITHUB_OUTPUT - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 @@ -163,8 +166,7 @@ jobs: else echo "docker-container-running=true" >> $GITHUB_OUTPUT fi - docker ps - docker inspect rippled-service + docker inspect ${{ steps.run-docker.outputs.docker-container-id }} - name: Run integration test if: steps.check-docker-container.outputs.docker-container-running == 'true' @@ -194,8 +196,11 @@ jobs: node-version: ${{ matrix.node-version }} - name: Run docker in background + id: run-docker run: | docker run --detach --rm -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/etc/opt/ripple/" --name rippled-service --health-cmd="rippled server_info || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env GITHUB_ACTIONS=true --env CI=true --entrypoint bash ${{ env.RIPPLED_DOCKER_IMAGE }} -c "rippled -a" + CONTAINER_ID=$(docker ps -aqf "name=rippled-service") + echo "docker-container-id=$CONTAINER_ID" >> $GITHUB_OUTPUT - name: Setup npm version 10 run: | @@ -222,20 +227,22 @@ jobs: - run: npm run build - name: Check if Docker container is running + id: check-docker-container run: | if ! docker ps | grep -q rippled-service; then echo "rippled-service Docker container is not running" - exit 1 + echo "docker-container-running=false" >> $GITHUB_OUTPUT + else + echo "docker-container-running=true" >> $GITHUB_OUTPUT fi - docker ps - docker ps -a - docker inspect rippled-service + docker inspect ${{ steps.run-docker.outputs.docker-container-id }} - name: Run integration test + if: steps.check-docker-container.outputs.docker-container-running == 'true' run: npm run test:browser - name: Stop docker container - if: always() + if: steps.check-docker-container.outputs.docker-container-running == 'true' run: docker stop rippled-service generate-documentation: From 02d4a966e906a18673f837894c7e79e9e8a8a5d0 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Mon, 29 Sep 2025 11:17:54 -0700 Subject: [PATCH 09/17] simplify Github Actions steps --- .github/workflows/nodejs.yml | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 72d1c7b215..1287388fba 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -161,19 +161,17 @@ jobs: id: check-docker-container run: | if ! docker ps | grep -q rippled-service; then - echo "rippled-service Docker container is not running" - echo "docker-container-running=false" >> $GITHUB_OUTPUT - else - echo "docker-container-running=true" >> $GITHUB_OUTPUT - fi + echo "INFO: Currently running docker containers:" + docker ps + echo "ERROR: rippled-service Docker container is not running" + exit 1 docker inspect ${{ steps.run-docker.outputs.docker-container-id }} - name: Run integration test - if: steps.check-docker-container.outputs.docker-container-running == 'true' run: npm run test:integration - name: Stop docker container - if: steps.check-docker-container.outputs.docker-container-running == 'true' + if: always() run: docker stop rippled-service browser: @@ -230,19 +228,16 @@ jobs: id: check-docker-container run: | if ! docker ps | grep -q rippled-service; then - echo "rippled-service Docker container is not running" - echo "docker-container-running=false" >> $GITHUB_OUTPUT - else - echo "docker-container-running=true" >> $GITHUB_OUTPUT - fi + echo "INFO: Currently running docker containers:" + docker ps + echo "ERROR: rippled-service Docker container is not running" docker inspect ${{ steps.run-docker.outputs.docker-container-id }} - name: Run integration test - if: steps.check-docker-container.outputs.docker-container-running == 'true' run: npm run test:browser - name: Stop docker container - if: steps.check-docker-container.outputs.docker-container-running == 'true' + if: always() run: docker stop rippled-service generate-documentation: From d6ec04a89cb204484faa1bdfdb41e8c8ff3ae6d2 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Mon, 29 Sep 2025 11:25:43 -0700 Subject: [PATCH 10/17] fix: rectify the syntax error in bash script --- .github/workflows/nodejs.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 1287388fba..a6100b565b 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -165,6 +165,7 @@ jobs: docker ps echo "ERROR: rippled-service Docker container is not running" exit 1 + fi docker inspect ${{ steps.run-docker.outputs.docker-container-id }} - name: Run integration test @@ -231,6 +232,8 @@ jobs: echo "INFO: Currently running docker containers:" docker ps echo "ERROR: rippled-service Docker container is not running" + exit 1 + fi docker inspect ${{ steps.run-docker.outputs.docker-container-id }} - name: Run integration test From bda0c2e71eae4fc771edc6b57d43dbefdbc3c417 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Tue, 30 Sep 2025 12:22:42 -0700 Subject: [PATCH 11/17] additional comments for MPToken transaction flags --- .../transactions/MPTokenIssuanceCreate.ts | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index 126072600a..f1ec3ee08c 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -54,13 +54,37 @@ export enum MPTokenIssuanceCreateFlags { */ tfMPTCanClawback = 0x00000040, + /** + * If set, Indicates flag lsfMPTCanLock can be changed. + */ tmfMPTCanMutateCanLock = 0x00000002, + /** + * If set, Indicates flag lsfMPTRequireAuth can be changed + */ tmfMPTCanMutateRequireAuth = 0x00000004, + /** + * If set, Indicates flag lsfMPTCanEscrow can be changed. + */ tmfMPTCanMutateCanEscrow = 0x00000008, + /** + * If set, Indicates flag lsfMPTCanTrade can be changed. + */ tmfMPTCanMutateCanTrade = 0x00000010, + /** + * If set, Indicates flag lsfMPTCanTransfer can be changed. + */ tmfMPTCanMutateCanTransfer = 0x00000020, + /** + * If set, Indicates flag lsfMPTCanClawback can be changed. + */ tmfMPTCanMutateCanClawback = 0x00000040, + /** + * If set, Allows field MPTokenMetadata to be modified. + */ tmfMPTCanMutateMetadata = 0x00010000, + /** + * If set, Allows field TransferFee to be modified. + */ tmfMPTCanMutateTransferFee = 0x00020000, } @@ -72,20 +96,67 @@ export enum MPTokenIssuanceCreateFlags { */ export interface MPTokenIssuanceCreateFlagsInterface extends GlobalFlagsInterface { + /** + * If set, indicates that the MPT can be locked both individually and globally. + * If not set, the MPT cannot be locked in any way. + */ tfMPTCanLock?: boolean + /** + * If set, indicates that individual holders must be authorized. + * This enables issuers to limit who can hold their assets. + */ tfMPTRequireAuth?: boolean + /** + * If set, indicates that individual holders can place their balances into an escrow. + */ tfMPTCanEscrow?: boolean + /** + * If set, indicates that individual holders can trade their balances + * using the XRP Ledger DEX or AMM. + */ tfMPTCanTrade?: boolean + /** + * If set, indicates that tokens may be transferred to other accounts + * that are not the issuer. + */ tfMPTCanTransfer?: boolean + /** + * If set, indicates that the issuer may use the Clawback transaction + * to clawback value from individual holders. + */ tfMPTCanClawback?: boolean + /** + * If set, Indicates flag lsfMPTCanLock can be changed. + */ tmfMPTCanMutateCanLock?: boolean + /** + * If set, Indicates flag lsfMPTRequireAuth can be changed. + */ tmfMPTCanMutateRequireAuth?: boolean + /** + * If set, Indicates flag lsfMPTCanEscrow can be changed. + */ tmfMPTCanMutateCanEscrow?: boolean + /** + * If set, Indicates flag lsfMPTCanTrade can be changed. + */ tmfMPTCanMutateCanTrade?: boolean + /** + * If set, Indicates flag lsfMPTCanTransfer can be changed. + */ tmfMPTCanMutateCanTransfer?: boolean + /** + * If set, Indicates flag lsfMPTCanClawback can be changed. + */ tmfMPTCanMutateCanClawback?: boolean + /** + * If set, Allows field MPTokenMetadata to be modified. + */ tmfMPTCanMutateMetadata?: boolean + /** + * If set, Allows field TransferFee to be modified. + */ tmfMPTCanMutateTransferFee?: boolean } From cbdcbc3f5087862d0384fa7bc834e9957957bf7a Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Tue, 30 Sep 2025 13:01:01 -0700 Subject: [PATCH 12/17] Additional validation on sfMutableFlags field for MPTokenIssuance(Create/Set) transaction --- .../transactions/MPTokenIssuanceCreate.ts | 27 +++++++++++++++++-- .../models/transactions/MPTokenIssuanceSet.ts | 27 +++++++++++++++++++ .../test/models/MPTokenIssuanceCreate.test.ts | 15 ++++++++++- .../test/models/MPTokenIssuanceSet.test.ts | 16 ++++++++++- 4 files changed, 81 insertions(+), 4 deletions(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index f1ec3ee08c..04d6c2e98c 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -88,6 +88,19 @@ export enum MPTokenIssuanceCreateFlags { tmfMPTCanMutateTransferFee = 0x00020000, } +/* eslint-disable no-bitwise -- Need bitwise operations to replicate rippled behavior */ +export const tmfMPTokenIssuanceCreateMutableMask = ~( + MPTokenIssuanceCreateFlags.tmfMPTCanMutateCanLock | + MPTokenIssuanceCreateFlags.tmfMPTCanMutateRequireAuth | + MPTokenIssuanceCreateFlags.tmfMPTCanMutateCanEscrow | + MPTokenIssuanceCreateFlags.tmfMPTCanMutateCanTrade | + MPTokenIssuanceCreateFlags.tmfMPTCanMutateCanTransfer | + MPTokenIssuanceCreateFlags.tmfMPTCanMutateCanClawback | + MPTokenIssuanceCreateFlags.tmfMPTCanMutateMetadata | + MPTokenIssuanceCreateFlags.tmfMPTCanMutateTransferFee +) +/* eslint-enable no-bitwise */ + /** * Map of flags to boolean values representing {@link MPTokenIssuanceCreate} transaction * flags. @@ -216,7 +229,7 @@ export interface MPTokenIssuanceCreateMetadata extends TransactionMetadataBase { mpt_issuance_id?: string } -/* eslint-disable max-lines-per-function -- Not needed to reduce function */ +/* eslint-disable max-lines-per-function, max-statements -- Not needed to reduce function */ /** * Verify the form and type of an MPTokenIssuanceCreate at runtime. * @@ -233,6 +246,16 @@ export function validateMPTokenIssuanceCreate( validateOptionalField(tx, 'AssetScale', isNumber) validateOptionalField(tx, 'MutableFlags', isNumber) + if ( + tx.MutableFlags != null && + // eslint-disable-next-line no-bitwise -- Need bitwise operations to replicate rippled behavior + tx.MutableFlags & tmfMPTokenIssuanceCreateMutableMask + ) { + throw new ValidationError( + 'MPTokenIssuanceCreate: Invalid MutableFlags value', + ) + } + if ( typeof tx.MPTokenMetadata === 'string' && (!isHex(tx.MPTokenMetadata) || @@ -293,4 +316,4 @@ export function validateMPTokenIssuanceCreate( } } } -/* eslint-enable max-lines-per-function */ +/* eslint-enable max-lines-per-function, max-statements */ diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts index e9d5e10b6e..8555e6d423 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -56,6 +56,23 @@ export enum MPTokenIssuanceSetFlags { tmfMPTClearCanClawback = 0x00000800, } +/* eslint-disable no-bitwise -- Need bitwise operations to replicate rippled behavior */ +export const tmfMPTokenIssuanceSetMutableMask = ~( + MPTokenIssuanceSetFlags.tmfMPTSetCanLock | + MPTokenIssuanceSetFlags.tmfMPTClearCanLock | + MPTokenIssuanceSetFlags.tmfMPTSetRequireAuth | + MPTokenIssuanceSetFlags.tmfMPTClearRequireAuth | + MPTokenIssuanceSetFlags.tmfMPTSetCanEscrow | + MPTokenIssuanceSetFlags.tmfMPTClearCanEscrow | + MPTokenIssuanceSetFlags.tmfMPTSetCanTrade | + MPTokenIssuanceSetFlags.tmfMPTClearCanTrade | + MPTokenIssuanceSetFlags.tmfMPTSetCanTransfer | + MPTokenIssuanceSetFlags.tmfMPTClearCanTransfer | + MPTokenIssuanceSetFlags.tmfMPTSetCanClawback | + MPTokenIssuanceSetFlags.tmfMPTClearCanClawback +) +/* eslint-enable no-bitwise */ + /** * Map of flags to boolean values representing {@link MPTokenIssuanceSet} transaction * flags. @@ -114,6 +131,7 @@ export interface MPTokenIssuanceSet extends BaseTransaction { MutableFlags?: number } +/* eslint-disable max-lines-per-function -- All validation rules are needed */ /** * Verify the form and type of an MPTokenIssuanceSet at runtime. * @@ -128,6 +146,14 @@ export function validateMPTokenIssuanceSet(tx: Record): void { validateOptionalField(tx, 'TransferFee', isNumber) validateOptionalField(tx, 'MutableFlags', isNumber) + if ( + tx.MutableFlags != null && + // eslint-disable-next-line no-bitwise -- Need bitwise operations to replicate rippled behavior + tx.MutableFlags & tmfMPTokenIssuanceSetMutableMask + ) { + throw new ValidationError('MPTokenIssuanceSet: Invalid MutableFlags value') + } + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Not necessary const flags = (tx.Flags ?? 0) as number | MPTokenIssuanceSetFlagsInterface const isTfMPTLock = @@ -162,3 +188,4 @@ export function validateMPTokenIssuanceSet(tx: Record): void { ) } } +/* eslint-enable max-lines-per-function */ diff --git a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts index 742757e67a..bbc7f973d1 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts @@ -5,7 +5,10 @@ import { MAX_MPT_META_BYTE_LENGTH, MPT_META_WARNING_HEADER, } from '../../src/models/transactions/common' -import { validateMPTokenIssuanceCreate } from '../../src/models/transactions/MPTokenIssuanceCreate' +import { + tmfMPTokenIssuanceCreateMutableMask, + validateMPTokenIssuanceCreate, +} from '../../src/models/transactions/MPTokenIssuanceCreate' import { assertTxIsValid, assertTxValidationError } from '../testUtils' const assertValid = (tx: any): void => @@ -142,6 +145,16 @@ describe('MPTokenIssuanceCreate', function () { 'MPTokenIssuanceCreate: TransferFee cannot be provided without enabling tfMPTCanTransfer flag', ) }) + + it(`throws w/ invalid MutableFlags value`, async () => { + const invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MutableFlags: tmfMPTokenIssuanceCreateMutableMask, + } as any + + assertInvalid(invalid, 'MPTokenIssuanceCreate: Invalid MutableFlags value') + }) }) /** diff --git a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts index 6bd70453f1..226a776496 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts @@ -3,7 +3,10 @@ import { stringToHex } from '@xrplf/isomorphic/dist/utils' import { MPTokenIssuanceSetFlags } from '../../src' import { MAX_MPT_META_BYTE_LENGTH } from '../../src/models/transactions/common' import { MAX_TRANSFER_FEE } from '../../src/models/transactions/MPTokenIssuanceCreate' -import { validateMPTokenIssuanceSet } from '../../src/models/transactions/MPTokenIssuanceSet' +import { + validateMPTokenIssuanceSet, + tmfMPTokenIssuanceSetMutableMask, +} from '../../src/models/transactions/MPTokenIssuanceSet' import { assertTxIsValid, assertTxValidationError } from '../testUtils' const assertValid = (tx: any): void => @@ -137,6 +140,17 @@ describe('MPTokenIssuanceSet', function () { assertInvalid(invalid, 'MPTokenIssuanceSet: invalid field MutableFlags') }) + it(`Throws w/ invalid MutableFlags value`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MutableFlags: tmfMPTokenIssuanceSetMutableMask, + } as any + + assertInvalid(invalid, 'MPTokenIssuanceSet: Invalid MutableFlags value') + }) + it(`Throws w/ invalid type of MPTokenMetadata`, function () { const invalid = { TransactionType: 'MPTokenIssuanceSet', From 565402bda5c80f281e3fc213503e28be3c41ef14 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Tue, 30 Sep 2025 13:05:06 -0700 Subject: [PATCH 13/17] Rename ledger flags corresponding to rippled commit: 8e4fda160d9fb66d736b16c909da7d19fdc7d19b --- .../xrpl/src/models/ledger/MPTokenIssuance.ts | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/packages/xrpl/src/models/ledger/MPTokenIssuance.ts b/packages/xrpl/src/models/ledger/MPTokenIssuance.ts index 3054bcd7ca..47a07d1192 100644 --- a/packages/xrpl/src/models/ledger/MPTokenIssuance.ts +++ b/packages/xrpl/src/models/ledger/MPTokenIssuance.ts @@ -27,35 +27,35 @@ export interface MPTokenIssuanceFlagsInterface { /** * Indicates flag lsfMPTCanLock can be changed */ - lmfMPTCanMutateCanLock?: boolean + lsmfMPTCanMutateCanLock?: boolean /** * Indicates flag lsfMPTRequireAuth can be changed */ - lmfMPTCanMutateRequireAuth?: boolean + lsmfMPTCanMutateRequireAuth?: boolean /** * Indicates flag lsfMPTCanEscrow can be changed */ - lmfMPTCanMutateCanEscrow?: boolean + lsmfMPTCanMutateCanEscrow?: boolean /** * Indicates flag lsfMPTCanTrade can be changed */ - lmfMPTCanMutateCanTrade?: boolean + lsmfMPTCanMutateCanTrade?: boolean /** * Indicates flag lsfMPTCanTransfer can be changed */ - lmfMPTCanMutateCanTransfer?: boolean + lsmfMPTCanMutateCanTransfer?: boolean /** * Indicates flag lsfMPTCanClawback can be changed */ - lmfMPTCanMutateCanClawback?: boolean + lsmfMPTCanMutateCanClawback?: boolean /** * Allows field MPTokenMetadata to be modified */ - lmfMPTCanMutateMetadata?: boolean + lsmfMPTCanMutateMetadata?: boolean /** * Allows field TransferFee to be modified */ - lmfMPTCanMutateTransferFee?: boolean + lsmfMPTCanMutateTransferFee?: boolean } export enum MPTokenIssuanceFlags { @@ -70,33 +70,33 @@ export enum MPTokenIssuanceFlags { /** * Indicates flag lsfMPTCanLock can be changed */ - lmfMPTCanMutateCanLock = 0x00000002, + lsmfMPTCanMutateCanLock = 0x00000002, /** * Indicates flag lsfMPTRequireAuth can be changed */ - lmfMPTCanMutateRequireAuth = 0x00000004, + lsmfMPTCanMutateRequireAuth = 0x00000004, /** * Indicates flag lsfMPTCanEscrow can be changed */ - lmfMPTCanMutateCanEscrow = 0x00000008, + lsmfMPTCanMutateCanEscrow = 0x00000008, /** * Indicates flag lsfMPTCanTrade can be changed */ - lmfMPTCanMutateCanTrade = 0x00000010, + lsmfMPTCanMutateCanTrade = 0x00000010, /** * Indicates flag lsfMPTCanTransfer can be changed */ - lmfMPTCanMutateCanTransfer = 0x00000020, + lsmfMPTCanMutateCanTransfer = 0x00000020, /** * Indicates flag lsfMPTCanClawback can be changed */ - lmfMPTCanMutateCanClawback = 0x00000040, + lsmfMPTCanMutateCanClawback = 0x00000040, /** * Allows field MPTokenMetadata to be modified */ - lmfMPTCanMutateMetadata = 0x00010000, + lsmfMPTCanMutateMetadata = 0x00010000, /** * Allows field TransferFee to be modified */ - lmfMPTCanMutateTransferFee = 0x00020000, + lsmfMPTCanMutateTransferFee = 0x00020000, } From ce166161d0b9fe9d60ddd246e7997f0ef5c0bd48 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Mon, 6 Oct 2025 15:14:52 -0700 Subject: [PATCH 14/17] address Phu comment: Introduce distinct MutableFlags interface --- .../transactions/MPTokenIssuanceCreate.ts | 20 +++++++++++-------- .../test/models/MPTokenIssuanceCreate.test.ts | 5 ++++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index 04d6c2e98c..ffdbe03575 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -53,7 +53,9 @@ export enum MPTokenIssuanceCreateFlags { * to clawback value from individual holders. */ tfMPTCanClawback = 0x00000040, +} +export enum MPTokenIssuanceCreateMutableFlags { /** * If set, Indicates flag lsfMPTCanLock can be changed. */ @@ -90,14 +92,14 @@ export enum MPTokenIssuanceCreateFlags { /* eslint-disable no-bitwise -- Need bitwise operations to replicate rippled behavior */ export const tmfMPTokenIssuanceCreateMutableMask = ~( - MPTokenIssuanceCreateFlags.tmfMPTCanMutateCanLock | - MPTokenIssuanceCreateFlags.tmfMPTCanMutateRequireAuth | - MPTokenIssuanceCreateFlags.tmfMPTCanMutateCanEscrow | - MPTokenIssuanceCreateFlags.tmfMPTCanMutateCanTrade | - MPTokenIssuanceCreateFlags.tmfMPTCanMutateCanTransfer | - MPTokenIssuanceCreateFlags.tmfMPTCanMutateCanClawback | - MPTokenIssuanceCreateFlags.tmfMPTCanMutateMetadata | - MPTokenIssuanceCreateFlags.tmfMPTCanMutateTransferFee + MPTokenIssuanceCreateMutableFlags.tmfMPTCanMutateCanLock | + MPTokenIssuanceCreateMutableFlags.tmfMPTCanMutateRequireAuth | + MPTokenIssuanceCreateMutableFlags.tmfMPTCanMutateCanEscrow | + MPTokenIssuanceCreateMutableFlags.tmfMPTCanMutateCanTrade | + MPTokenIssuanceCreateMutableFlags.tmfMPTCanMutateCanTransfer | + MPTokenIssuanceCreateMutableFlags.tmfMPTCanMutateCanClawback | + MPTokenIssuanceCreateMutableFlags.tmfMPTCanMutateMetadata | + MPTokenIssuanceCreateMutableFlags.tmfMPTCanMutateTransferFee ) /* eslint-enable no-bitwise */ @@ -138,7 +140,9 @@ export interface MPTokenIssuanceCreateFlagsInterface * to clawback value from individual holders. */ tfMPTCanClawback?: boolean +} +export interface MPTokenIssuanceCreateMutableFlagsInterface { /** * If set, Indicates flag lsfMPTCanLock can be changed. */ diff --git a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts index bbc7f973d1..0f6a0591eb 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts @@ -1,11 +1,13 @@ import { stringToHex } from '@xrplf/isomorphic/src/utils' import { MPTokenIssuanceCreateFlags, MPTokenMetadata } from '../../src' +// import { MPTokenIssuanceCreateMutableFlags } from '../../src/models/transactions/MPTokenIssuanceCreate' import { MAX_MPT_META_BYTE_LENGTH, MPT_META_WARNING_HEADER, } from '../../src/models/transactions/common' import { + MPTokenIssuanceCreateMutableFlags, tmfMPTokenIssuanceCreateMutableMask, validateMPTokenIssuanceCreate, } from '../../src/models/transactions/MPTokenIssuanceCreate' @@ -31,7 +33,8 @@ describe('MPTokenIssuanceCreate', function () { AssetScale: 2, TransferFee: 1, Flags: MPTokenIssuanceCreateFlags.tfMPTCanTransfer, - MutableFlags: MPTokenIssuanceCreateFlags.tmfMPTCanMutateTransferFee, + MutableFlags: + MPTokenIssuanceCreateMutableFlags.tmfMPTCanMutateTransferFee, MPTokenMetadata: stringToHex(`{ "ticker": "TBILL", "name": "T-Bill Yield Token", From 42049ac99187fad5819f5d2c69d883d179ab428e Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Mon, 6 Oct 2025 16:05:38 -0700 Subject: [PATCH 15/17] address Phu comment: refactor MutableFlags in MPTokenIssuanceSet transaction --- .../models/transactions/MPTokenIssuanceSet.ts | 32 +++++++++++-------- .../xrpl/src/models/transactions/index.ts | 4 +++ .../transactions/mptokenIssuanceSet.test.ts | 8 +++-- .../test/models/MPTokenIssuanceSet.test.ts | 3 +- 4 files changed, 29 insertions(+), 18 deletions(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts index 8555e6d423..61922681a5 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -29,7 +29,9 @@ export enum MPTokenIssuanceSetFlags { * If set, indicates that issuer unlocks the MPT */ tfMPTUnlock = 0x00000002, - // The below flags are used in the MutableFlags field +} + +export enum MPTokenIssuanceSetMutableFlags { /* Sets the lsfMPTCanLock flag. Enables the token to be locked both individually and globally. */ tmfMPTSetCanLock = 0x00000001, /* Clears the lsfMPTCanLock flag. Disables both individual and global locking of the token. */ @@ -58,18 +60,18 @@ export enum MPTokenIssuanceSetFlags { /* eslint-disable no-bitwise -- Need bitwise operations to replicate rippled behavior */ export const tmfMPTokenIssuanceSetMutableMask = ~( - MPTokenIssuanceSetFlags.tmfMPTSetCanLock | - MPTokenIssuanceSetFlags.tmfMPTClearCanLock | - MPTokenIssuanceSetFlags.tmfMPTSetRequireAuth | - MPTokenIssuanceSetFlags.tmfMPTClearRequireAuth | - MPTokenIssuanceSetFlags.tmfMPTSetCanEscrow | - MPTokenIssuanceSetFlags.tmfMPTClearCanEscrow | - MPTokenIssuanceSetFlags.tmfMPTSetCanTrade | - MPTokenIssuanceSetFlags.tmfMPTClearCanTrade | - MPTokenIssuanceSetFlags.tmfMPTSetCanTransfer | - MPTokenIssuanceSetFlags.tmfMPTClearCanTransfer | - MPTokenIssuanceSetFlags.tmfMPTSetCanClawback | - MPTokenIssuanceSetFlags.tmfMPTClearCanClawback + MPTokenIssuanceSetMutableFlags.tmfMPTSetCanLock | + MPTokenIssuanceSetMutableFlags.tmfMPTClearCanLock | + MPTokenIssuanceSetMutableFlags.tmfMPTSetRequireAuth | + MPTokenIssuanceSetMutableFlags.tmfMPTClearRequireAuth | + MPTokenIssuanceSetMutableFlags.tmfMPTSetCanEscrow | + MPTokenIssuanceSetMutableFlags.tmfMPTClearCanEscrow | + MPTokenIssuanceSetMutableFlags.tmfMPTSetCanTrade | + MPTokenIssuanceSetMutableFlags.tmfMPTClearCanTrade | + MPTokenIssuanceSetMutableFlags.tmfMPTSetCanTransfer | + MPTokenIssuanceSetMutableFlags.tmfMPTClearCanTransfer | + MPTokenIssuanceSetMutableFlags.tmfMPTSetCanClawback | + MPTokenIssuanceSetMutableFlags.tmfMPTClearCanClawback ) /* eslint-enable no-bitwise */ @@ -82,7 +84,9 @@ export const tmfMPTokenIssuanceSetMutableMask = ~( export interface MPTokenIssuanceSetFlagsInterface extends GlobalFlagsInterface { tfMPTLock?: boolean tfMPTUnlock?: boolean - // The below flags are used in the MutableFlags field +} + +export interface MPTokenIssuanceSetMutableFlagsInterface { /* Sets the lsfMPTCanLock flag. Enables the token to be locked both individually and globally. */ tmfMPTSetCanLock?: boolean /* Clears the lsfMPTCanLock flag. Disables both individual and global locking of the token. */ diff --git a/packages/xrpl/src/models/transactions/index.ts b/packages/xrpl/src/models/transactions/index.ts index fa8e73afed..e3e700e6a6 100644 --- a/packages/xrpl/src/models/transactions/index.ts +++ b/packages/xrpl/src/models/transactions/index.ts @@ -58,12 +58,16 @@ export { MPTokenIssuanceCreate, MPTokenIssuanceCreateFlags, MPTokenIssuanceCreateFlagsInterface, + MPTokenIssuanceCreateMutableFlags, + MPTokenIssuanceCreateMutableFlagsInterface, } from './MPTokenIssuanceCreate' export { MPTokenIssuanceDestroy } from './MPTokenIssuanceDestroy' export { MPTokenIssuanceSet, MPTokenIssuanceSetFlags, MPTokenIssuanceSetFlagsInterface, + MPTokenIssuanceSetMutableFlags, + MPTokenIssuanceSetMutableFlagsInterface, } from './MPTokenIssuanceSet' export { NFTokenAcceptOffer } from './NFTokenAcceptOffer' export { NFTokenBurn } from './NFTokenBurn' diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts index dd7d9986d0..fbe44f8331 100644 --- a/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts @@ -6,6 +6,8 @@ import { MPTokenIssuanceCreateFlags, MPTokenIssuanceSetFlags, TransactionMetadata, + MPTokenIssuanceCreateMutableFlags, + MPTokenIssuanceSetMutableFlags, } from '../../../src' import serverUrl from '../serverUrl' import { @@ -84,8 +86,8 @@ describe('MPTokenIssuanceDestroy', function () { Account: testContext.wallet.classicAddress, Flags: MPTokenIssuanceCreateFlags.tfMPTCanTransfer, MutableFlags: - MPTokenIssuanceCreateFlags.tmfMPTCanMutateTransferFee + - MPTokenIssuanceCreateFlags.tmfMPTCanMutateCanTransfer, + MPTokenIssuanceCreateMutableFlags.tmfMPTCanMutateTransferFee + + MPTokenIssuanceCreateMutableFlags.tmfMPTCanMutateCanTransfer, } const mptCreateRes = await testTransaction( @@ -125,7 +127,7 @@ describe('MPTokenIssuanceDestroy', function () { TransactionType: 'MPTokenIssuanceSet', Account: testContext.wallet.classicAddress, MPTokenIssuanceID: mptID!, - MutableFlags: MPTokenIssuanceSetFlags.tmfMPTClearCanTransfer, + MutableFlags: MPTokenIssuanceSetMutableFlags.tmfMPTClearCanTransfer, } await testTransaction( diff --git a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts index 226a776496..8ffdb457d5 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts @@ -6,6 +6,7 @@ import { MAX_TRANSFER_FEE } from '../../src/models/transactions/MPTokenIssuanceC import { validateMPTokenIssuanceSet, tmfMPTokenIssuanceSetMutableMask, + MPTokenIssuanceSetMutableFlags, } from '../../src/models/transactions/MPTokenIssuanceSet' import { assertTxIsValid, assertTxValidationError } from '../testUtils' @@ -56,7 +57,7 @@ describe('MPTokenIssuanceSet', function () { TransactionType: 'MPTokenIssuanceSet', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', MPTokenIssuanceID: TOKEN_ID, - MutableFlags: MPTokenIssuanceSetFlags.tmfMPTClearCanTransfer, + MutableFlags: MPTokenIssuanceSetMutableFlags.tmfMPTClearCanTransfer, } as any) }) From 13f9ec9a21156eacc6331aa6d1db589c127b7326 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Tue, 7 Oct 2025 12:49:53 -0700 Subject: [PATCH 16/17] Additional validations+tests for MPTokenIssuanceCreate transaction --- .../transactions/MPTokenIssuanceCreate.ts | 20 ++++++++- .../xrpl/src/models/transactions/common.ts | 3 +- .../test/models/MPTokenIssuanceCreate.test.ts | 44 +++++++++++++++++++ 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index ffdbe03575..db2a60c83a 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -1,5 +1,5 @@ import { ValidationError } from '../../errors' -import { isHex, INTEGER_SANITY_CHECK, isFlagEnabled } from '../utils' +import { isHex, INTEGER_SANITY_CHECK, isFlagEnabled, hasFlag } from '../utils' import { BaseTransaction, @@ -11,6 +11,7 @@ import { MAX_MPT_META_BYTE_LENGTH, MPT_META_WARNING_HEADER, validateMPTokenMetadata, + isDomainID, } from './common' import type { TransactionMetadataBase } from './metadata' @@ -227,6 +228,7 @@ export interface MPTokenIssuanceCreate extends BaseTransaction { Flags?: number | MPTokenIssuanceCreateFlagsInterface MutableFlags?: number + DomainID?: string } export interface MPTokenIssuanceCreateMetadata extends TransactionMetadataBase { @@ -249,6 +251,20 @@ export function validateMPTokenIssuanceCreate( validateOptionalField(tx, 'TransferFee', isNumber) validateOptionalField(tx, 'AssetScale', isNumber) validateOptionalField(tx, 'MutableFlags', isNumber) + validateOptionalField(tx, 'DomainID', isDomainID) + + if ( + tx.DomainID != null && + !hasFlag( + tx, + MPTokenIssuanceCreateFlags.tfMPTRequireAuth, + 'tfMPTRequireAuth', + ) + ) { + throw new ValidationError( + 'MPTokenIssuanceCreate: Cannot set DomainID unless tfMPTRequireAuth flag is set.', + ) + } if ( tx.MutableFlags != null && @@ -275,7 +291,7 @@ export function validateMPTokenIssuanceCreate( throw new ValidationError('MPTokenIssuanceCreate: Invalid MaximumAmount') } else if ( BigInt(tx.MaximumAmount) > BigInt(MAX_AMT) || - BigInt(tx.MaximumAmount) < BigInt(`0`) + BigInt(tx.MaximumAmount) <= BigInt(`0`) ) { throw new ValidationError( 'MPTokenIssuanceCreate: MaximumAmount out of range', diff --git a/packages/xrpl/src/models/transactions/common.ts b/packages/xrpl/src/models/transactions/common.ts index 660ae19460..b5645af3b5 100644 --- a/packages/xrpl/src/models/transactions/common.ts +++ b/packages/xrpl/src/models/transactions/common.ts @@ -774,7 +774,8 @@ export function isDomainID(domainID: unknown): domainID is string { return ( isString(domainID) && domainID.length === _DOMAIN_ID_LENGTH && - isHex(domainID) + isHex(domainID) && + domainID !== '0'.repeat(_DOMAIN_ID_LENGTH) ) } diff --git a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts index 0f6a0591eb..6dfc178587 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts @@ -158,6 +158,50 @@ describe('MPTokenIssuanceCreate', function () { assertInvalid(invalid, 'MPTokenIssuanceCreate: Invalid MutableFlags value') }) + + it(`throws with Zero MaximumAmount`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MaximumAmount: '0', + } as any + + assertInvalid(invalid, 'MPTokenIssuanceCreate: MaximumAmount out of range') + }) + + it(`throws with Zero DomainID`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + DomainID: '0'.repeat(64), + } as any + + assertInvalid(invalid, 'MPTokenIssuanceCreate: invalid field DomainID') + }) + + it(`throws with DomainID and tfMPTRequireAuth flag not set`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + DomainID: '1'.repeat(64), + Flags: 0, + } as any + + assertInvalid( + invalid, + 'MPTokenIssuanceCreate: Cannot set DomainID unless tfMPTRequireAuth flag is set.', + ) + }) + + it(`throws with invalid type of DomainID`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + DomainID: 1, + } as any + + assertInvalid(invalid, 'MPTokenIssuanceCreate: invalid field DomainID') + }) }) /** From 7260f2f8f67c7f22baf397adb195f4e0151693d7 Mon Sep 17 00:00:00 2001 From: Chenna Keshava B S Date: Tue, 7 Oct 2025 13:56:57 -0700 Subject: [PATCH 17/17] Additional validations and tests for MPTokenIssuanceSet txn --- .../models/transactions/MPTokenIssuanceSet.ts | 90 ++++++++- packages/xrpl/src/models/utils/flags.ts | 1 + .../test/models/MPTokenIssuanceSet.test.ts | 181 +++++++++++++++++- 3 files changed, 260 insertions(+), 12 deletions(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts index 61922681a5..bc124a2803 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -1,5 +1,7 @@ import { ValidationError } from '../../errors' import { isFlagEnabled, isHex } from '../utils' +// eslint-disable-next-line import/no-cycle -- this method is needed to convert txn flags to number +import { convertTxFlagsToNumber } from '../utils/flags' import { BaseTransaction, @@ -12,9 +14,12 @@ import { GlobalFlagsInterface, isNumber, MAX_MPT_META_BYTE_LENGTH, + isDomainID, } from './common' import { MAX_TRANSFER_FEE } from './MPTokenIssuanceCreate' +import type { Transaction } from '.' + /** * Transaction Flags for an MPTokenIssuanceSet Transaction. * @@ -133,9 +138,10 @@ export interface MPTokenIssuanceSet extends BaseTransaction { MPTokenMetadata?: string TransferFee?: number MutableFlags?: number + DomainID?: string } -/* eslint-disable max-lines-per-function -- All validation rules are needed */ +/* eslint-disable max-lines-per-function, max-statements -- All validation rules are needed */ /** * Verify the form and type of an MPTokenIssuanceSet at runtime. * @@ -149,7 +155,13 @@ export function validateMPTokenIssuanceSet(tx: Record): void { validateOptionalField(tx, 'MPTokenMetadata', isString) validateOptionalField(tx, 'TransferFee', isNumber) validateOptionalField(tx, 'MutableFlags', isNumber) + validateOptionalField(tx, 'DomainID', isDomainID) + if (tx.DomainID != null && tx.Holder != null) { + throw new ValidationError( + 'MPTokenIssuanceSet: Cannot set both DomainID and Holder fields.', + ) + } if ( tx.MutableFlags != null && // eslint-disable-next-line no-bitwise -- Need bitwise operations to replicate rippled behavior @@ -174,6 +186,80 @@ export function validateMPTokenIssuanceSet(tx: Record): void { throw new ValidationError('MPTokenIssuanceSet: flag conflict') } + if (tx.Holder != null && tx.Holder === tx.Account) { + throw new ValidationError( + 'MPTokenIssuanceSet: Holder cannot be the same as the Account.', + ) + } + + const isMutate = + tx.MutableFlags != null || + tx.MPTokenMetadata != null || + tx.TransferFee != null + if ( + (tx.Flags === 0 || tx.Flags === undefined) && + tx.DomainID == null && + !isMutate + ) { + throw new ValidationError( + 'MPTokenIssuanceSet: Transaction does not change the state of the MPTokenIssuance ledger object.', + ) + } + + if (isMutate && tx.Holder != null) { + throw new ValidationError( + 'MPTokenIssuanceSet: Holder field is not allowed when mutating MPTokenIssuance.', + ) + } + + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Pseudo-Txn missing in BaseTransaction type. + if (isMutate && convertTxFlagsToNumber(tx as Transaction) !== 0) { + throw new ValidationError( + 'MPTokenIssuanceSet: Can not set flags when mutating MPTokenIssuance.', + ) + } + + const MPTMutabilityFlags: Array<{ setFlag: number; clearFlag: number }> = [ + { + setFlag: MPTokenIssuanceSetMutableFlags.tmfMPTSetCanLock, + clearFlag: MPTokenIssuanceSetMutableFlags.tmfMPTClearCanLock, + }, + { + setFlag: MPTokenIssuanceSetMutableFlags.tmfMPTSetRequireAuth, + clearFlag: MPTokenIssuanceSetMutableFlags.tmfMPTClearRequireAuth, + }, + { + setFlag: MPTokenIssuanceSetMutableFlags.tmfMPTSetCanEscrow, + clearFlag: MPTokenIssuanceSetMutableFlags.tmfMPTClearCanEscrow, + }, + { + setFlag: MPTokenIssuanceSetMutableFlags.tmfMPTSetCanTrade, + clearFlag: MPTokenIssuanceSetMutableFlags.tmfMPTClearCanTrade, + }, + { + setFlag: MPTokenIssuanceSetMutableFlags.tmfMPTSetCanTransfer, + clearFlag: MPTokenIssuanceSetMutableFlags.tmfMPTClearCanTransfer, + }, + { + setFlag: MPTokenIssuanceSetMutableFlags.tmfMPTSetCanClawback, + clearFlag: MPTokenIssuanceSetMutableFlags.tmfMPTClearCanClawback, + }, + ] + + // Can not set and clear the same flag + if (tx.MutableFlags != null) { + for (const flagPair of MPTMutabilityFlags) { + if ( + isFlagEnabled(tx.MutableFlags, flagPair.setFlag) && + isFlagEnabled(tx.MutableFlags, flagPair.clearFlag) + ) { + throw new ValidationError( + 'MPTokenIssuanceSet: Can not set and clear the same flag.', + ) + } + } + } + if (typeof tx.TransferFee === 'number') { if (tx.TransferFee < 0 || tx.TransferFee > MAX_TRANSFER_FEE) { throw new ValidationError( @@ -192,4 +278,4 @@ export function validateMPTokenIssuanceSet(tx: Record): void { ) } } -/* eslint-enable max-lines-per-function */ +/* eslint-enable max-lines-per-function, max-statements */ diff --git a/packages/xrpl/src/models/utils/flags.ts b/packages/xrpl/src/models/utils/flags.ts index ff04bf86f4..f044817638 100644 --- a/packages/xrpl/src/models/utils/flags.ts +++ b/packages/xrpl/src/models/utils/flags.ts @@ -12,6 +12,7 @@ import { BatchFlags } from '../transactions/batch' import { GlobalFlags } from '../transactions/common' import { MPTokenAuthorizeFlags } from '../transactions/MPTokenAuthorize' import { MPTokenIssuanceCreateFlags } from '../transactions/MPTokenIssuanceCreate' +// eslint-disable-next-line import/no-cycle -- this method is needed to map txn flags import { MPTokenIssuanceSetFlags } from '../transactions/MPTokenIssuanceSet' import { NFTokenCreateOfferFlags } from '../transactions/NFTokenCreateOffer' import { NFTokenMintFlags } from '../transactions/NFTokenMint' diff --git a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts index 8ffdb457d5..913f2bc1e6 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts @@ -43,16 +43,6 @@ describe('MPTokenIssuanceSet', function () { assertValid(validMPTokenIssuanceSet) - // It's fine to not specify any flag, it means only tx fee is deducted - validMPTokenIssuanceSet = { - TransactionType: 'MPTokenIssuanceSet', - Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', - MPTokenIssuanceID: TOKEN_ID, - Holder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', - } as any - - assertValid(validMPTokenIssuanceSet) - assertValid({ TransactionType: 'MPTokenIssuanceSet', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', @@ -190,4 +180,175 @@ describe('MPTokenIssuanceSet', function () { `MPTokenIssuanceSet: MPTokenMetadata (hex format) must be non-empty and no more than ${MAX_MPT_META_BYTE_LENGTH} bytes.`, ) }) + + it(`Throws w/ invalid type of DomainID`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + DomainID: 1, + } as any + + assertInvalid(invalid, 'MPTokenIssuanceSet: invalid field DomainID') + }) + + it(`throws w/ identical holder and account ID`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + Holder: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + } as any + + assertInvalid( + invalid, + 'MPTokenIssuanceSet: Holder cannot be the same as the Account.', + ) + }) + + it(`Throws w/ no changes to the MPTokenIssuance ledger object`, function () { + const noOpMPTokenIssuanceSet = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + Holder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + } as any + + assertInvalid( + noOpMPTokenIssuanceSet, + 'MPTokenIssuanceSet: Transaction does not change the state of the MPTokenIssuance ledger object.', + ) + }) + + it(`Throws w/ Holder field and mutating the MPTokenIssuance ledger object`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MutableFlags: MPTokenIssuanceSetMutableFlags.tmfMPTClearCanTransfer, + Holder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + } as any + + assertInvalid( + invalid, + 'MPTokenIssuanceSet: Holder field is not allowed when mutating MPTokenIssuance.', + ) + }) + + it(`Throws w/ Flags field and mutating the MPTokenIssuance ledger object`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MutableFlags: MPTokenIssuanceSetMutableFlags.tmfMPTClearCanTransfer, + Flags: MPTokenIssuanceSetFlags.tfMPTLock, + } as any + + assertInvalid( + invalid, + 'MPTokenIssuanceSet: Can not set flags when mutating MPTokenIssuance.', + ) + }) + + it(`Throws w/ setting and clearing the tmfMPTCanLock flag`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MutableFlags: + // eslint-disable-next-line no-bitwise -- required to OR the flags + MPTokenIssuanceSetMutableFlags.tmfMPTSetCanLock | + MPTokenIssuanceSetMutableFlags.tmfMPTClearCanLock, + } as any + + assertInvalid( + invalid, + 'MPTokenIssuanceSet: Can not set and clear the same flag.', + ) + }) + + it(`Throws w/ setting and clearing the tmfMPTCanTransfer flag`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MutableFlags: + // eslint-disable-next-line no-bitwise -- required to OR the flags + MPTokenIssuanceSetMutableFlags.tmfMPTSetCanTransfer | + MPTokenIssuanceSetMutableFlags.tmfMPTClearCanTransfer, + } as any + + assertInvalid( + invalid, + 'MPTokenIssuanceSet: Can not set and clear the same flag.', + ) + }) + + it(`Throws w/ setting and clearing the tmfMPTCanClawback flag`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MutableFlags: + // eslint-disable-next-line no-bitwise -- required to OR the flags + MPTokenIssuanceSetMutableFlags.tmfMPTSetCanClawback | + MPTokenIssuanceSetMutableFlags.tmfMPTClearCanClawback, + } as any + + assertInvalid( + invalid, + 'MPTokenIssuanceSet: Can not set and clear the same flag.', + ) + }) + + it(`Throws w/ setting and clearing the tmfMPTCanEscrow flag`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MutableFlags: + // eslint-disable-next-line no-bitwise -- required to OR the flags + MPTokenIssuanceSetMutableFlags.tmfMPTSetCanEscrow | + MPTokenIssuanceSetMutableFlags.tmfMPTClearCanEscrow, + } as any + + assertInvalid( + invalid, + 'MPTokenIssuanceSet: Can not set and clear the same flag.', + ) + }) + + it(`Throws w/ setting and clearing the tmfMPTCanTrade flag`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MutableFlags: + // eslint-disable-next-line no-bitwise -- required to OR the flags + MPTokenIssuanceSetMutableFlags.tmfMPTSetCanTrade | + MPTokenIssuanceSetMutableFlags.tmfMPTClearCanTrade, + } as any + + assertInvalid( + invalid, + 'MPTokenIssuanceSet: Can not set and clear the same flag.', + ) + }) + + it(`Throws w/ setting and clearing the tmfMPTCanRequireAuth flag`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MutableFlags: + // eslint-disable-next-line no-bitwise -- required to OR the flags + MPTokenIssuanceSetMutableFlags.tmfMPTSetRequireAuth | + MPTokenIssuanceSetMutableFlags.tmfMPTClearRequireAuth, + } as any + + assertInvalid( + invalid, + 'MPTokenIssuanceSet: Can not set and clear the same flag.', + ) + }) })