From 2f0498344f1a79b02bbc6f17f55f4810fd47b85b Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 13 Feb 2024 14:48:27 -0500 Subject: [PATCH 01/53] initial commit --- .../ripple-binary-codec/src/types/amount.ts | 71 ++++++++++++++++--- .../ripple-binary-codec/src/types/hash-192.ts | 19 +++++ 2 files changed, 82 insertions(+), 8 deletions(-) create mode 100644 packages/ripple-binary-codec/src/types/hash-192.ts diff --git a/packages/ripple-binary-codec/src/types/amount.ts b/packages/ripple-binary-codec/src/types/amount.ts index b92ebf4fc2..64a012745e 100644 --- a/packages/ripple-binary-codec/src/types/amount.ts +++ b/packages/ripple-binary-codec/src/types/amount.ts @@ -6,6 +6,7 @@ import { JsonObject, SerializedType } from './serialized-type' import BigNumber from 'bignumber.js' import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils' import { readUInt32BE, writeUInt32BE } from '../utils' +import { Hash192 } from './hash-192' /** * Constants for validating amounts @@ -16,7 +17,7 @@ const MAX_IOU_PRECISION = 16 const MAX_DROPS = new BigNumber('1e17') const MIN_XRP = new BigNumber('1e-6') const mask = BigInt(0x00000000ffffffff) - +const MAX_MPT = new BigNumber(9223372036854775807) /** * BigNumber configuration for Amount IOUs */ @@ -27,20 +28,28 @@ BigNumber.config({ ], }) -/** - * Interface for JSON objects that represent amounts - */ -interface AmountObject extends JsonObject { +interface AmountObjectWithCurrency extends JsonObject { value: string currency: string issuer: string } +interface AmountObjectWithID extends JsonObject { + value: string + mptIssuanceID: string +} + +/** + * Interface for JSON objects that represent amounts + */ +type AmountObject = AmountObjectWithCurrency | AmountObjectWithID + /** - * Type guard for AmountObject + * Type guard for AmountObjectWithCurrency */ -function isAmountObject(arg): arg is AmountObject { +function isAmountObjectWithCurrency(arg): arg is AmountObjectWithCurrency { const keys = Object.keys(arg).sort() + return ( keys.length === 3 && keys[0] === 'currency' && @@ -49,6 +58,15 @@ function isAmountObject(arg): arg is AmountObject { ) } +/** + * Type guard for AmountObjectWithID + */ +function isAmountObjectWithID(arg): arg is AmountObjectWithID { + const keys = Object.keys(arg).sort() + + return keys.length === 2 && keys[0] === 'mptIssuanceID' && keys[1] === 'value' +} + /** * Class for serializing/Deserializing Amounts */ @@ -88,7 +106,7 @@ class Amount extends SerializedType { return new Amount(amount) } - if (isAmountObject(value)) { + if (isAmountObjectWithCurrency(value)) { const number = new BigNumber(value.value) Amount.assertIouIsValid(number) @@ -124,6 +142,29 @@ class Amount extends SerializedType { return new Amount(concat([amount, currency, issuer])) } + if (isAmountObjectWithID(value)) { + amount = new Uint8Array(9) + const number = new BigNumber(value.value) + Amount.assertMptIsValid(number) + + if (number.isZero()) { + amount[0] |= 0x60 + } else { + const num = BigInt(value.value) + + const intBuf = [new Uint8Array(1), new Uint8Array(4), new Uint8Array(4)] + writeUInt32BE(intBuf[1], Number(num >> BigInt(32)), 0) + writeUInt32BE(intBuf[2], Number(num & BigInt(mask)), 0) + + amount = concat(intBuf) + + amount[0] |= 0x60 + + const mptIssuanceID = Hash192.from(value.mptIssuanceID).toBytes() + return new Amount(concat([amount, mptIssuanceID])) + } + } + throw new Error('Invalid type to construct an Amount') } @@ -224,6 +265,20 @@ class Amount extends SerializedType { } } + /** + * Validate MPT.value amount + * + * @param decimal BigNumber object representing MPT.value + * @returns void, but will throw if invalid amount + */ + private static assertMptIsValid(decimal: BigNumber): void { + if (!decimal.isZero()) { + if (decimal.gt(MAX_MPT)) + throw new Error(`${decimal.toString()} is an illegal amount`) + this.verifyNoDecimal(decimal) + } + } + /** * Ensure that the value after being multiplied by the exponent does not * contain a decimal. diff --git a/packages/ripple-binary-codec/src/types/hash-192.ts b/packages/ripple-binary-codec/src/types/hash-192.ts new file mode 100644 index 0000000000..6e05385a78 --- /dev/null +++ b/packages/ripple-binary-codec/src/types/hash-192.ts @@ -0,0 +1,19 @@ +import { Hash } from './hash' + +/** + * Hash with a width of 192 bits + */ +class Hash192 extends Hash { + static readonly width = 24 + static readonly ZERO_192: Hash192 = new Hash192(new Uint8Array(Hash192.width)) + + constructor(bytes?: Uint8Array) { + if (bytes && bytes.byteLength === 0) { + bytes = Hash192.ZERO_192.bytes + } + + super(bytes ?? Hash192.ZERO_192.bytes) + } +} + +export { Hash192 } From cd73e1b501d4adac01c5bddb42c209088a08717e Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 14 Feb 2024 16:07:18 -0500 Subject: [PATCH 02/53] wip --- .../ripple-binary-codec/src/types/amount.ts | 119 ++++++++++++------ 1 file changed, 82 insertions(+), 37 deletions(-) diff --git a/packages/ripple-binary-codec/src/types/amount.ts b/packages/ripple-binary-codec/src/types/amount.ts index 64a012745e..0eba944198 100644 --- a/packages/ripple-binary-codec/src/types/amount.ts +++ b/packages/ripple-binary-codec/src/types/amount.ts @@ -18,6 +18,8 @@ const MAX_DROPS = new BigNumber('1e17') const MIN_XRP = new BigNumber('1e-6') const mask = BigInt(0x00000000ffffffff) const MAX_MPT = new BigNumber(9223372036854775807) + +const mptMask = BigInt(0x7fffffffffffffff) /** * BigNumber configuration for Amount IOUs */ @@ -28,13 +30,13 @@ BigNumber.config({ ], }) -interface AmountObjectWithCurrency extends JsonObject { +interface AmountObjectIOU extends JsonObject { value: string currency: string issuer: string } -interface AmountObjectWithID extends JsonObject { +interface AmountObjectMPT extends JsonObject { value: string mptIssuanceID: string } @@ -42,12 +44,12 @@ interface AmountObjectWithID extends JsonObject { /** * Interface for JSON objects that represent amounts */ -type AmountObject = AmountObjectWithCurrency | AmountObjectWithID +type AmountObject = AmountObjectIOU | AmountObjectMPT /** - * Type guard for AmountObjectWithCurrency + * Type guard for AmountObjectIOU */ -function isAmountObjectWithCurrency(arg): arg is AmountObjectWithCurrency { +function isAmountObjectIOU(arg): arg is AmountObjectIOU { const keys = Object.keys(arg).sort() return ( @@ -59,9 +61,9 @@ function isAmountObjectWithCurrency(arg): arg is AmountObjectWithCurrency { } /** - * Type guard for AmountObjectWithID + * Type guard for AmountObjectMPT */ -function isAmountObjectWithID(arg): arg is AmountObjectWithID { +function isAmountObjectMPT(arg): arg is AmountObjectMPT { const keys = Object.keys(arg).sort() return keys.length === 2 && keys[0] === 'mptIssuanceID' && keys[1] === 'value' @@ -106,13 +108,13 @@ class Amount extends SerializedType { return new Amount(amount) } - if (isAmountObjectWithCurrency(value)) { + if (isAmountObjectIOU(value)) { const number = new BigNumber(value.value) Amount.assertIouIsValid(number) - if (number.isZero()) { - amount[0] |= 0x80 - } else { + amount[0] |= 0x80 + + if (!number.isZero()) { const integerNumberString = number .times(`1e${-((number.e || 0) - 15)}`) .abs() @@ -125,8 +127,6 @@ class Amount extends SerializedType { amount = concat(intBuf) - amount[0] |= 0x80 - if (number.gt(new BigNumber(0))) { amount[0] |= 0x40 } @@ -142,27 +142,22 @@ class Amount extends SerializedType { return new Amount(concat([amount, currency, issuer])) } - if (isAmountObjectWithID(value)) { - amount = new Uint8Array(9) - const number = new BigNumber(value.value) - Amount.assertMptIsValid(number) + if (isAmountObjectMPT(value)) { + Amount.assertMptIsValid(value.value) - if (number.isZero()) { - amount[0] |= 0x60 - } else { - const num = BigInt(value.value) + let leadingByte = new Uint8Array(1) + leadingByte[0] |= 0x60 - const intBuf = [new Uint8Array(1), new Uint8Array(4), new Uint8Array(4)] - writeUInt32BE(intBuf[1], Number(num >> BigInt(32)), 0) - writeUInt32BE(intBuf[2], Number(num & BigInt(mask)), 0) + const num = BigInt(value.value) - amount = concat(intBuf) + const intBuf = [new Uint8Array(4), new Uint8Array(4)] + writeUInt32BE(intBuf[0], Number(num >> BigInt(32)), 0) + writeUInt32BE(intBuf[1], Number(num & BigInt(mask)), 0) - amount[0] |= 0x60 + amount = concat(intBuf) - const mptIssuanceID = Hash192.from(value.mptIssuanceID).toBytes() - return new Amount(concat([amount, mptIssuanceID])) - } + const mptIssuanceID = Hash192.from(value.mptIssuanceID).toBytes() + return new Amount(concat([leadingByte, amount, mptIssuanceID])) } throw new Error('Invalid type to construct an Amount') @@ -175,8 +170,12 @@ class Amount extends SerializedType { * @returns An Amount object */ static fromParser(parser: BinaryParser): Amount { - const isXRP = parser.peek() & 0x80 - const numBytes = isXRP ? 48 : 8 + const isIOU = parser.peek() & 0x80 + if (isIOU) return new Amount(parser.read(48)) + + // the amount can be either MPT or XRP at this point + const isMPT = parser.peek() & 0x20 + const numBytes = isMPT ? 33 : 8 return new Amount(parser.read(numBytes)) } @@ -197,7 +196,9 @@ class Amount extends SerializedType { const num = (msb << BigInt(32)) | lsb return `${sign}${num.toString()}` - } else { + } + + if (this.isIOU()) { const parser = new BinaryParser(this.toString()) const mantissa = parser.read(8) const currency = Currency.fromParser(parser) as Currency @@ -223,6 +224,27 @@ class Amount extends SerializedType { issuer: issuer.toJSON(), } } + + if (this.isMPT()) { + const parser = new BinaryParser(this.toString()) + const leadingByte = parser.read(1) + const amount = parser.read(8) + const mptID = Hash192.fromParser(parser) as Hash192 + + const isPositive = leadingByte[0] & 0x40 + const sign = isPositive ? '' : '-' + + const msb = BigInt(readUInt32BE(amount.slice(0, 4), 0)) + const lsb = BigInt(readUInt32BE(amount.slice(4), 0)) + const num = (msb << BigInt(32)) | lsb + + return { + value: `${sign}${num.toString()}`, + mptIssuanceID: mptID.toString(), + } + } + + throw new Error('Invalid amount to construct JSON') } /** @@ -271,11 +293,16 @@ class Amount extends SerializedType { * @param decimal BigNumber object representing MPT.value * @returns void, but will throw if invalid amount */ - private static assertMptIsValid(decimal: BigNumber): void { + private static assertMptIsValid(amount: string): void { + if (amount.indexOf('.') !== -1) { + throw new Error(`${amount.toString()} is an illegal amount`) + } + + const decimal = new BigNumber(amount) if (!decimal.isZero()) { - if (decimal.gt(MAX_MPT)) - throw new Error(`${decimal.toString()} is an illegal amount`) - this.verifyNoDecimal(decimal) + if (Number(BigInt(amount) & ~BigInt(mptMask)) != 0) { + throw new Error(`${amount.toString()} is an illegal amount`) + } } } @@ -303,7 +330,25 @@ class Amount extends SerializedType { * @returns true if Native (XRP) */ private isNative(): boolean { - return (this.bytes[0] & 0x80) === 0 + return (this.bytes[0] & 0x80) === 0 && (this.bytes[0] | 0x20) === 0 + } + + /** + * Test if this amount is in units of MPT + * + * @returns true if MPT + */ + private isMPT(): boolean { + return (this.bytes[0] & 0x80) === 0 && (this.bytes[0] & 0x20) !== 0 + } + + /** + * Test if this amount is in units of IOU + * + * @returns true if IOU + */ + private isIOU(): boolean { + return (this.bytes[0] & 0x80) !== 0 } } From da51954b3ada057defd9c69bbb01dacb8a0d31fb Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 14 Feb 2024 21:44:34 -0500 Subject: [PATCH 03/53] revert bad changes --- packages/ripple-binary-codec/src/types/amount.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/ripple-binary-codec/src/types/amount.ts b/packages/ripple-binary-codec/src/types/amount.ts index 0eba944198..eb7615412b 100644 --- a/packages/ripple-binary-codec/src/types/amount.ts +++ b/packages/ripple-binary-codec/src/types/amount.ts @@ -17,9 +17,8 @@ const MAX_IOU_PRECISION = 16 const MAX_DROPS = new BigNumber('1e17') const MIN_XRP = new BigNumber('1e-6') const mask = BigInt(0x00000000ffffffff) -const MAX_MPT = new BigNumber(9223372036854775807) - const mptMask = BigInt(0x7fffffffffffffff) + /** * BigNumber configuration for Amount IOUs */ @@ -112,9 +111,9 @@ class Amount extends SerializedType { const number = new BigNumber(value.value) Amount.assertIouIsValid(number) - amount[0] |= 0x80 - - if (!number.isZero()) { + if (number.isZero()) { + amount[0] |= 0x80 + } else { const integerNumberString = number .times(`1e${-((number.e || 0) - 15)}`) .abs() @@ -127,6 +126,8 @@ class Amount extends SerializedType { amount = concat(intBuf) + amount[0] |= 0x80 + if (number.gt(new BigNumber(0))) { amount[0] |= 0x40 } From 0d2af8573a376e1e541de547ecf32742b7f562c4 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 15 Feb 2024 11:38:43 -0500 Subject: [PATCH 04/53] definitions --- .../src/enums/definitions.json | 110 ++++++++++++++++-- 1 file changed, 100 insertions(+), 10 deletions(-) diff --git a/packages/ripple-binary-codec/src/enums/definitions.json b/packages/ripple-binary-codec/src/enums/definitions.json index b8fd9a8a1b..1b6647f64a 100644 --- a/packages/ripple-binary-codec/src/enums/definitions.json +++ b/packages/ripple-binary-codec/src/enums/definitions.json @@ -18,7 +18,7 @@ "PathSet": 18, "Vector256": 19, "UInt96": 20, - "UInt192": 21, + "Hash192": 21, "UInt384": 22, "UInt512": 23, "Issue": 24, @@ -51,6 +51,8 @@ "NFTokenOffer": 55, "AMM": 121, "DID": 73, + "MPTokenIssuance": 126, + "MPToken": 127, "Any": -3, "Child": -2, "Nickname": 110, @@ -248,6 +250,16 @@ "type": "UInt8" } ], + [ + "AssetScale", + { + "nth": 20, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], [ "LedgerEntryType", { @@ -1028,6 +1040,46 @@ "type": "UInt64" } ], + [ + "MaximumAmount", + { + "nth": 23, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "OutstandingAmount", + { + "nth": 24, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "LockedAmount", + { + "nth": 25, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], + [ + "MPTAmount", + { + "nth": 26, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], [ "EmailHash", { @@ -1078,6 +1130,16 @@ "type": "Hash160" } ], + [ + "MPTokenIssuanceID", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Hash192" + } + ], [ "LedgerHash", { @@ -1918,6 +1980,16 @@ "type": "Blob" } ], + [ + "MPTokenMetadata", + { + "nth": 28, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], [ "Account", { @@ -2008,6 +2080,16 @@ "type": "AccountID" } ], + [ + "MPTokenHolder", + { + "nth": 11, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "AccountID" + } + ], [ "HookAccount", { @@ -2695,14 +2777,15 @@ "temUNKNOWN": -264, "temSEQ_AND_TICKET": -263, "temBAD_NFTOKEN_TRANSFER_FEE": -262, - "temBAD_AMM_TOKENS": -261, - "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -260, - "temXCHAIN_BAD_PROOF": -259, - "temXCHAIN_BRIDGE_BAD_ISSUES": -258, - "temXCHAIN_BRIDGE_NONDOOR_OWNER": -257, - "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -256, - "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255, - "temEMPTY_DID": -254, + "temBAD_MPTOKEN_TRANSFER_FEE": -261, + "temBAD_AMM_TOKENS": -260, + "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -259, + "temXCHAIN_BAD_PROOF": -258, + "temXCHAIN_BRIDGE_BAD_ISSUES": -257, + "temXCHAIN_BRIDGE_NONDOOR_OWNER": -256, + "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -255, + "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -254, + "temEMPTY_DID": -253, "tefFAILURE": -199, "tefALREADY": -198, @@ -2815,7 +2898,10 @@ "tecXCHAIN_SELF_COMMIT": 184, "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185, "tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186, - "tecEMPTY_DID": 187 + "tecEMPTY_DID": 187, + "tecMPTOKEN_EXISTS": 188, + "tecMPT_MAX_AMOUNT_EXCEEDED": 189, + "tecMPT_LOCKED": 190 }, "TRANSACTION_TYPES": { "Invalid": -1, @@ -2864,6 +2950,10 @@ "XChainCreateBridge": 48, "DIDSet": 49, "DIDDelete": 50, + "MPTokenIssuanceCreate": 51, + "MPTokenIssuanceDestroy": 52, + "MPTokenAuthorize": 53, + "MPTokenIssuanceSet": 54, "EnableAmendment": 100, "SetFee": 101, "UNLModify": 102 From 2ee5d8860ed75a31f30135ce3e4c903890aa4cac Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 15 Feb 2024 13:37:58 -0500 Subject: [PATCH 05/53] export --- packages/ripple-binary-codec/src/types/amount.ts | 2 +- packages/ripple-binary-codec/src/types/index.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/ripple-binary-codec/src/types/amount.ts b/packages/ripple-binary-codec/src/types/amount.ts index eb7615412b..0c4302d309 100644 --- a/packages/ripple-binary-codec/src/types/amount.ts +++ b/packages/ripple-binary-codec/src/types/amount.ts @@ -79,7 +79,7 @@ class Amount extends SerializedType { } /** - * Construct an amount from an IOU or string amount + * Construct an amount from an IOU, MPT or string amount * * @param value An Amount, object representing an IOU, or a string * representing an integer amount diff --git a/packages/ripple-binary-codec/src/types/index.ts b/packages/ripple-binary-codec/src/types/index.ts index 2f2efef471..bc99a64711 100644 --- a/packages/ripple-binary-codec/src/types/index.ts +++ b/packages/ripple-binary-codec/src/types/index.ts @@ -4,6 +4,7 @@ import { Blob } from './blob' import { Currency } from './currency' import { Hash128 } from './hash-128' import { Hash160 } from './hash-160' +import { Hash192 } from './hash-192' import { Hash256 } from './hash-256' import { Issue } from './issue' import { PathSet } from './path-set' @@ -25,6 +26,7 @@ const coreTypes: Record = { Currency, Hash128, Hash160, + Hash192, Hash256, Issue, PathSet, @@ -51,6 +53,7 @@ export { Currency, Hash128, Hash160, + Hash192, Hash256, PathSet, STArray, From 134b977e5bd70187c99d981def2308465e44a7b1 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 16 Feb 2024 16:51:29 -0500 Subject: [PATCH 06/53] MPT create (wip) --- .../ripple-binary-codec/src/types/amount.ts | 482 ++++++++++++++---- .../transactions/MPTokenIssuanceCreate.ts | 117 +++++ .../transactions/MPTokenIssuanceDestroy.ts | 35 ++ .../xrpl/src/models/transactions/index.ts | 5 + .../xrpl/src/models/transactions/metadata.ts | 6 + .../src/models/transactions/transaction.ts | 9 + packages/xrpl/src/models/utils/flags.ts | 2 + packages/xrpl/src/utils/index.ts | 2 + packages/xrpl/src/utils/mptConversion.ts | 43 ++ .../transactions/mptIssuanceCreate.test.ts | 67 +++ 10 files changed, 655 insertions(+), 113 deletions(-) create mode 100644 packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts create mode 100644 packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts create mode 100644 packages/xrpl/src/utils/mptConversion.ts create mode 100644 packages/xrpl/test/integration/transactions/mptIssuanceCreate.test.ts diff --git a/packages/ripple-binary-codec/src/types/amount.ts b/packages/ripple-binary-codec/src/types/amount.ts index 0c4302d309..6a62ce4652 100644 --- a/packages/ripple-binary-codec/src/types/amount.ts +++ b/packages/ripple-binary-codec/src/types/amount.ts @@ -6,7 +6,6 @@ import { JsonObject, SerializedType } from './serialized-type' import BigNumber from 'bignumber.js' import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils' import { readUInt32BE, writeUInt32BE } from '../utils' -import { Hash192 } from './hash-192' /** * Constants for validating amounts @@ -17,7 +16,7 @@ const MAX_IOU_PRECISION = 16 const MAX_DROPS = new BigNumber('1e17') const MIN_XRP = new BigNumber('1e-6') const mask = BigInt(0x00000000ffffffff) -const mptMask = BigInt(0x7fffffffffffffff) +//const mptMask = BigInt(0x7fffffffffffffff) /** * BigNumber configuration for Amount IOUs @@ -29,28 +28,20 @@ BigNumber.config({ ], }) -interface AmountObjectIOU extends JsonObject { +/** + * Interface for JSON objects that represent amounts + */ +interface AmountObject extends JsonObject { value: string currency: string issuer: string } -interface AmountObjectMPT extends JsonObject { - value: string - mptIssuanceID: string -} - /** - * Interface for JSON objects that represent amounts + * Type guard for AmountObject */ -type AmountObject = AmountObjectIOU | AmountObjectMPT - -/** - * Type guard for AmountObjectIOU - */ -function isAmountObjectIOU(arg): arg is AmountObjectIOU { +function isAmountObject(arg): arg is AmountObject { const keys = Object.keys(arg).sort() - return ( keys.length === 3 && keys[0] === 'currency' && @@ -59,15 +50,6 @@ function isAmountObjectIOU(arg): arg is AmountObjectIOU { ) } -/** - * Type guard for AmountObjectMPT - */ -function isAmountObjectMPT(arg): arg is AmountObjectMPT { - const keys = Object.keys(arg).sort() - - return keys.length === 2 && keys[0] === 'mptIssuanceID' && keys[1] === 'value' -} - /** * Class for serializing/Deserializing Amounts */ @@ -79,7 +61,7 @@ class Amount extends SerializedType { } /** - * Construct an amount from an IOU, MPT or string amount + * Construct an amount from an IOU or string amount * * @param value An Amount, object representing an IOU, or a string * representing an integer amount @@ -107,7 +89,7 @@ class Amount extends SerializedType { return new Amount(amount) } - if (isAmountObjectIOU(value)) { + if (isAmountObject(value)) { const number = new BigNumber(value.value) Amount.assertIouIsValid(number) @@ -143,24 +125,6 @@ class Amount extends SerializedType { return new Amount(concat([amount, currency, issuer])) } - if (isAmountObjectMPT(value)) { - Amount.assertMptIsValid(value.value) - - let leadingByte = new Uint8Array(1) - leadingByte[0] |= 0x60 - - const num = BigInt(value.value) - - const intBuf = [new Uint8Array(4), new Uint8Array(4)] - writeUInt32BE(intBuf[0], Number(num >> BigInt(32)), 0) - writeUInt32BE(intBuf[1], Number(num & BigInt(mask)), 0) - - amount = concat(intBuf) - - const mptIssuanceID = Hash192.from(value.mptIssuanceID).toBytes() - return new Amount(concat([leadingByte, amount, mptIssuanceID])) - } - throw new Error('Invalid type to construct an Amount') } @@ -171,12 +135,8 @@ class Amount extends SerializedType { * @returns An Amount object */ static fromParser(parser: BinaryParser): Amount { - const isIOU = parser.peek() & 0x80 - if (isIOU) return new Amount(parser.read(48)) - - // the amount can be either MPT or XRP at this point - const isMPT = parser.peek() & 0x20 - const numBytes = isMPT ? 33 : 8 + const isXRP = parser.peek() & 0x80 + const numBytes = isXRP ? 48 : 8 return new Amount(parser.read(numBytes)) } @@ -197,9 +157,7 @@ class Amount extends SerializedType { const num = (msb << BigInt(32)) | lsb return `${sign}${num.toString()}` - } - - if (this.isIOU()) { + } else { const parser = new BinaryParser(this.toString()) const mantissa = parser.read(8) const currency = Currency.fromParser(parser) as Currency @@ -225,27 +183,6 @@ class Amount extends SerializedType { issuer: issuer.toJSON(), } } - - if (this.isMPT()) { - const parser = new BinaryParser(this.toString()) - const leadingByte = parser.read(1) - const amount = parser.read(8) - const mptID = Hash192.fromParser(parser) as Hash192 - - const isPositive = leadingByte[0] & 0x40 - const sign = isPositive ? '' : '-' - - const msb = BigInt(readUInt32BE(amount.slice(0, 4), 0)) - const lsb = BigInt(readUInt32BE(amount.slice(4), 0)) - const num = (msb << BigInt(32)) | lsb - - return { - value: `${sign}${num.toString()}`, - mptIssuanceID: mptID.toString(), - } - } - - throw new Error('Invalid amount to construct JSON') } /** @@ -288,25 +225,6 @@ class Amount extends SerializedType { } } - /** - * Validate MPT.value amount - * - * @param decimal BigNumber object representing MPT.value - * @returns void, but will throw if invalid amount - */ - private static assertMptIsValid(amount: string): void { - if (amount.indexOf('.') !== -1) { - throw new Error(`${amount.toString()} is an illegal amount`) - } - - const decimal = new BigNumber(amount) - if (!decimal.isZero()) { - if (Number(BigInt(amount) & ~BigInt(mptMask)) != 0) { - throw new Error(`${amount.toString()} is an illegal amount`) - } - } - } - /** * Ensure that the value after being multiplied by the exponent does not * contain a decimal. @@ -331,26 +249,364 @@ class Amount extends SerializedType { * @returns true if Native (XRP) */ private isNative(): boolean { - return (this.bytes[0] & 0x80) === 0 && (this.bytes[0] | 0x20) === 0 - } - - /** - * Test if this amount is in units of MPT - * - * @returns true if MPT - */ - private isMPT(): boolean { - return (this.bytes[0] & 0x80) === 0 && (this.bytes[0] & 0x20) !== 0 - } - - /** - * Test if this amount is in units of IOU - * - * @returns true if IOU - */ - private isIOU(): boolean { - return (this.bytes[0] & 0x80) !== 0 + return (this.bytes[0] & 0x80) === 0 } } export { Amount, AmountObject } +// import { BinaryParser } from '../serdes/binary-parser' + +// import { AccountID } from './account-id' +// import { Currency } from './currency' +// import { JsonObject, SerializedType } from './serialized-type' +// import BigNumber from 'bignumber.js' +// import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils' +// import { readUInt32BE, writeUInt32BE } from '../utils' +// import { Hash192 } from './hash-192' + +// /** +// * Constants for validating amounts +// */ +// const MIN_IOU_EXPONENT = -96 +// const MAX_IOU_EXPONENT = 80 +// const MAX_IOU_PRECISION = 16 +// const MAX_DROPS = new BigNumber('1e17') +// const MIN_XRP = new BigNumber('1e-6') +// const mask = BigInt(0x00000000ffffffff) +// const mptMask = BigInt(0x7fffffffffffffff) + +// /** +// * BigNumber configuration for Amount IOUs +// */ +// BigNumber.config({ +// EXPONENTIAL_AT: [ +// MIN_IOU_EXPONENT - MAX_IOU_PRECISION, +// MAX_IOU_EXPONENT + MAX_IOU_PRECISION, +// ], +// }) + +// interface AmountObjectIOU extends JsonObject { +// value: string +// currency: string +// issuer: string +// } + +// interface AmountObjectMPT extends JsonObject { +// value: string +// mptIssuanceID: string +// } + +// /** +// * Interface for JSON objects that represent amounts +// */ +// type AmountObject = AmountObjectIOU | AmountObjectMPT + +// /** +// * Type guard for AmountObjectIOU +// */ +// function isAmountObjectIOU(arg): arg is AmountObjectIOU { +// const keys = Object.keys(arg).sort() + +// return ( +// keys.length === 3 && +// keys[0] === 'currency' && +// keys[1] === 'issuer' && +// keys[2] === 'value' +// ) +// } + +// /** +// * Type guard for AmountObjectMPT +// */ +// function isAmountObjectMPT(arg): arg is AmountObjectMPT { +// const keys = Object.keys(arg).sort() + +// return keys.length === 2 && keys[0] === 'mptIssuanceID' && keys[1] === 'value' +// } + +// /** +// * Class for serializing/Deserializing Amounts +// */ +// class Amount extends SerializedType { +// static defaultAmount: Amount = new Amount(hexToBytes('4000000000000000')) + +// constructor(bytes: Uint8Array) { +// super(bytes ?? Amount.defaultAmount.bytes) +// } + +// /** +// * Construct an amount from an IOU, MPT or string amount +// * +// * @param value An Amount, object representing an IOU, or a string +// * representing an integer amount +// * @returns An Amount object +// */ +// static from(value: T): Amount { +// if (value instanceof Amount) { +// return value +// } + +// let amount = new Uint8Array(8) +// if (typeof value === 'string') { +// Amount.assertXrpIsValid(value) + +// const number = BigInt(value) + +// const intBuf = [new Uint8Array(4), new Uint8Array(4)] +// writeUInt32BE(intBuf[0], Number(number >> BigInt(32)), 0) +// writeUInt32BE(intBuf[1], Number(number & BigInt(mask)), 0) + +// amount = concat(intBuf) + +// amount[0] |= 0x40 + +// return new Amount(amount) +// } + +// if (isAmountObjectIOU(value)) { +// const number = new BigNumber(value.value) +// Amount.assertIouIsValid(number) + +// if (number.isZero()) { +// amount[0] |= 0x80 +// } else { +// const integerNumberString = number +// .times(`1e${-((number.e || 0) - 15)}`) +// .abs() +// .toString() + +// const num = BigInt(integerNumberString) +// const intBuf = [new Uint8Array(4), new Uint8Array(4)] +// writeUInt32BE(intBuf[0], Number(num >> BigInt(32)), 0) +// writeUInt32BE(intBuf[1], Number(num & BigInt(mask)), 0) + +// amount = concat(intBuf) + +// amount[0] |= 0x80 + +// if (number.gt(new BigNumber(0))) { +// amount[0] |= 0x40 +// } + +// const exponent = (number.e || 0) - 15 +// const exponentByte = 97 + exponent +// amount[0] |= exponentByte >>> 2 +// amount[1] |= (exponentByte & 0x03) << 6 +// } + +// const currency = Currency.from(value.currency).toBytes() +// const issuer = AccountID.from(value.issuer).toBytes() +// return new Amount(concat([amount, currency, issuer])) +// } + +// if (isAmountObjectMPT(value)) { +// Amount.assertMptIsValid(value.value) + +// let leadingByte = new Uint8Array(1) +// leadingByte[0] |= 0x60 + +// const num = BigInt(value.value) + +// const intBuf = [new Uint8Array(4), new Uint8Array(4)] +// writeUInt32BE(intBuf[0], Number(num >> BigInt(32)), 0) +// writeUInt32BE(intBuf[1], Number(num & BigInt(mask)), 0) + +// amount = concat(intBuf) + +// const mptIssuanceID = Hash192.from(value.mptIssuanceID).toBytes() +// return new Amount(concat([leadingByte, amount, mptIssuanceID])) +// } + +// throw new Error('Invalid type to construct an Amount') +// } + +// /** +// * Read an amount from a BinaryParser +// * +// * @param parser BinaryParser to read the Amount from +// * @returns An Amount object +// */ +// static fromParser(parser: BinaryParser): Amount { +// const isIOU = parser.peek() & 0x80 +// if (isIOU) return new Amount(parser.read(48)) + +// // the amount can be either MPT or XRP at this point +// const isMPT = parser.peek() & 0x20 +// const numBytes = isMPT ? 33 : 8 +// return new Amount(parser.read(numBytes)) +// } + +// /** +// * Get the JSON representation of this Amount +// * +// * @returns the JSON interpretation of this.bytes +// */ +// toJSON(): AmountObject | string { +// if (this.isNative()) { +// const bytes = this.bytes +// const isPositive = bytes[0] & 0x40 +// const sign = isPositive ? '' : '-' +// bytes[0] &= 0x3f + +// const msb = BigInt(readUInt32BE(bytes.slice(0, 4), 0)) +// const lsb = BigInt(readUInt32BE(bytes.slice(4), 0)) +// const num = (msb << BigInt(32)) | lsb + +// return `${sign}${num.toString()}` +// } + +// if (this.isIOU()) { +// const parser = new BinaryParser(this.toString()) +// const mantissa = parser.read(8) +// const currency = Currency.fromParser(parser) as Currency +// const issuer = AccountID.fromParser(parser) as AccountID + +// const b1 = mantissa[0] +// const b2 = mantissa[1] + +// const isPositive = b1 & 0x40 +// const sign = isPositive ? '' : '-' +// const exponent = ((b1 & 0x3f) << 2) + ((b2 & 0xff) >> 6) - 97 + +// mantissa[0] = 0 +// mantissa[1] &= 0x3f +// const value = new BigNumber(`${sign}0x${bytesToHex(mantissa)}`).times( +// `1e${exponent}`, +// ) +// Amount.assertIouIsValid(value) + +// return { +// value: value.toString(), +// currency: currency.toJSON(), +// issuer: issuer.toJSON(), +// } +// } + +// if (this.isMPT()) { +// const parser = new BinaryParser(this.toString()) +// const leadingByte = parser.read(1) +// const amount = parser.read(8) +// const mptID = Hash192.fromParser(parser) as Hash192 + +// const isPositive = leadingByte[0] & 0x40 +// const sign = isPositive ? '' : '-' + +// const msb = BigInt(readUInt32BE(amount.slice(0, 4), 0)) +// const lsb = BigInt(readUInt32BE(amount.slice(4), 0)) +// const num = (msb << BigInt(32)) | lsb + +// return { +// value: `${sign}${num.toString()}`, +// mptIssuanceID: mptID.toString(), +// } +// } + +// throw new Error('Invalid amount to construct JSON') +// } + +// /** +// * Validate XRP amount +// * +// * @param amount String representing XRP amount +// * @returns void, but will throw if invalid amount +// */ +// private static assertXrpIsValid(amount: string): void { +// if (amount.indexOf('.') !== -1) { +// throw new Error(`${amount.toString()} is an illegal amount`) +// } + +// const decimal = new BigNumber(amount) +// if (!decimal.isZero()) { +// if (decimal.lt(MIN_XRP) || decimal.gt(MAX_DROPS)) { +// throw new Error(`${amount.toString()} is an illegal amount`) +// } +// } +// } + +// /** +// * Validate IOU.value amount +// * +// * @param decimal BigNumber object representing IOU.value +// * @returns void, but will throw if invalid amount +// */ +// private static assertIouIsValid(decimal: BigNumber): void { +// if (!decimal.isZero()) { +// const p = decimal.precision() +// const e = (decimal.e || 0) - 15 +// if ( +// p > MAX_IOU_PRECISION || +// e > MAX_IOU_EXPONENT || +// e < MIN_IOU_EXPONENT +// ) { +// throw new Error('Decimal precision out of range') +// } +// this.verifyNoDecimal(decimal) +// } +// } + +// /** +// * Validate MPT.value amount +// * +// * @param decimal BigNumber object representing MPT.value +// * @returns void, but will throw if invalid amount +// */ +// private static assertMptIsValid(amount: string): void { +// if (amount.indexOf('.') !== -1) { +// throw new Error(`${amount.toString()} is an illegal amount`) +// } + +// const decimal = new BigNumber(amount) +// if (!decimal.isZero()) { +// if (Number(BigInt(amount) & BigInt(0x8000000000000000) != 0) { +// throw new Error(`${amount.toString()} is an illegal amount`) +// } +// } +// } + +// /** +// * Ensure that the value after being multiplied by the exponent does not +// * contain a decimal. +// * +// * @param decimal a Decimal object +// * @returns a string of the object without a decimal +// */ +// private static verifyNoDecimal(decimal: BigNumber): void { +// const integerNumberString = decimal +// .times(`1e${-((decimal.e || 0) - 15)}`) +// .abs() +// .toString() + +// if (integerNumberString.indexOf('.') !== -1) { +// throw new Error('Decimal place found in integerNumberString') +// } +// } + +// /** +// * Test if this amount is in units of Native Currency(XRP) +// * +// * @returns true if Native (XRP) +// */ +// private isNative(): boolean { +// return (this.bytes[0] & 0x80) === 0 && (this.bytes[0] | 0x20) === 0 +// } + +// /** +// * Test if this amount is in units of MPT +// * +// * @returns true if MPT +// */ +// private isMPT(): boolean { +// return (this.bytes[0] & 0x80) === 0 && (this.bytes[0] & 0x20) !== 0 +// } + +// /** +// * Test if this amount is in units of IOU +// * +// * @returns true if IOU +// */ +// private isIOU(): boolean { +// return (this.bytes[0] & 0x80) !== 0 +// } +// } + +// export { Amount, AmountObject } diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts new file mode 100644 index 0000000000..2353870031 --- /dev/null +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -0,0 +1,117 @@ +import { ValidationError } from '../../errors' +import { isHex } from '../utils' + +import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common' +import type { TransactionMetadataBase } from './metadata' + +/** + * Transaction Flags for an MPTokenIssuanceCreate Transaction. + * + * @category Transaction Flags + */ +export enum MPTokenIssuanceCreateFlags { + /** + * 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 = 0x00000002, + /** + * If set, indicates that individual holders must be authorized. + * This enables issuers to limit who can hold their assets. + */ + tfMPTRequireAuth = 0x00000004, + /** + * If set, indicates that individual holders can place their balances into an escrow. + */ + tfMPTCanEscrow = 0x00000008, + /** + * If set, indicates that individual holders can trade their balances + * using the XRP Ledger DEX or AMM. + */ + tfMPTCanTrade = 0x00000010, + /** + * If set, indicates that tokens may be transferred to other accounts + * that are not the issuer. + */ + tfMPTCanTransfer = 0x00000020, + /** + * If set, indicates that the issuer may use the Clawback transaction + * to clawback value from individual holders. + */ + tfMPTCanClawback = 0x00000040, +} + +/** + * Map of flags to boolean values representing {@link MPTokenIssuanceCreate} transaction + * flags. + * + * @category Transaction Flags + */ +export interface MPTokenIssuanceCreateFlagsInterface extends GlobalFlags { + tfMPTCanLock?: boolean + tfMPTRequireAuth?: boolean + tfMPTCanEscrow?: boolean + tfMPTCanTrade?: boolean + tfMPTCanTransfer?: boolean + tfMPTCanClawback?: boolean +} + +/** + * The MPTokenIssuanceCreate transaction creates a MPTokenIssuance object + * and adds it to the relevant directory node of the creator account. + * This transaction is the only opportunity an issuer has to specify any token fields + * that are defined as immutable (e.g., MPT Flags). If the transaction is successful, + * the newly created token will be owned by the account (the creator account) which + * executed the transaction. + */ +export interface MPTokenIssuanceCreate extends BaseTransaction { + TransactionType: 'MPTokenIssuanceCreate' + /** + * An asset scale is the difference, in orders of magnitude, between a standard unit and + * a corresponding fractional unit. More formally, the asset scale is a non-negative integer + * (0, 1, 2, …) such that one standard unit equals 10^(-scale) of a corresponding + * fractional unit. If the fractional unit equals the standard unit, then the asset scale is 0. + * Note that this value is optional, and will default to 0 if not supplied. + */ + AssetScale?: number + /** + * Specifies the hex-encoded maximum asset amount of this token that should ever be issued. + * It is a non-negative integer that can store a range of up to 63 bits. If not set, the max + * amount will default to the largest unsigned 63-bit integer (0x7FFFFFFFFFFFFFFF) + */ + MaximumAmount?: string + /** + * Specifies the fee to charged by the issuer for secondary sales of the Token, + * if such sales are allowed. Valid values for this field are between 0 and 50,000 inclusive, + * allowing transfer rates of between 0.000% and 50.000% in increments of 0.001. + * The field must NOT be present if the `tfMPTCanTransfer` flag is not set. + */ + TransferFee?: number + /** + * Arbitrary metadata about this issuance, in hex format. + */ + MPTokenMetadata?: string | null + Flags?: number | MPTokenIssuanceCreateFlagsInterface +} + +export interface MPTokenIssuanceCreateMetadata extends TransactionMetadataBase { + mpt_issuance_id?: string +} + +/** + * Verify the form and type of an MPTokenIssuanceCreate at runtime. + * + * @param tx - An MPTokenIssuanceCreate Transaction. + * @throws When the MPTokenIssuanceCreate is Malformed. + */ +export function validateMPTokenIssuanceCreate( + tx: Record, +): void { + validateBaseTransaction(tx) + + if (typeof tx.MPTokenMetadata === 'string' && !isHex(tx.MPTokenMetadata)) { + throw new ValidationError( + 'MPTokenIssuanceCreate: MPTokenMetadata must be in hex format', + ) + } +} diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts new file mode 100644 index 0000000000..3b3d12fb72 --- /dev/null +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts @@ -0,0 +1,35 @@ +import { + BaseTransaction, + isString, + validateBaseTransaction, + validateRequiredField, +} from './common' + +/** + * The MPTokenIssuanceDestroy transaction is used to remove an MPTokenIssuance object + * from the directory node in which it is being held, effectively removing the token + * from the ledger. If this operation succeeds, the corresponding + * MPTokenIssuance is removed and the owner’s reserve requirement is reduced by one. + * This operation must fail if there are any holders of the MPT in question. + */ +export interface MPTokenIssuanceDestroy extends BaseTransaction { + TransactionType: 'MPTokenIssuanceDestroy' + + /** + * Identifies the MPTokenIssuance object to be removed by the transaction. + */ + MPTokenIssuanceID?: string +} + +/** + * Verify the form and type of an MPTokenIssuanceDestroy at runtime. + * + * @param tx - An MPTokenIssuanceDestroy Transaction. + * @throws When the MPTokenIssuanceDestroy is Malformed. + */ +export function validateMPTokenIssuanceDestroy( + tx: Record, +): void { + validateBaseTransaction(tx) + validateRequiredField(tx, 'MPTokenIssuanceID', isString) +} diff --git a/packages/xrpl/src/models/transactions/index.ts b/packages/xrpl/src/models/transactions/index.ts index 5f77dfd2bc..5dd1ba45c2 100644 --- a/packages/xrpl/src/models/transactions/index.ts +++ b/packages/xrpl/src/models/transactions/index.ts @@ -39,6 +39,11 @@ export { EscrowCancel } from './escrowCancel' export { EscrowCreate } from './escrowCreate' export { EscrowFinish } from './escrowFinish' export { EnableAmendment, EnableAmendmentFlags } from './enableAmendment' +export { + MPTokenIssuanceCreate, + MPTokenIssuanceCreateFlags, + MPTokenIssuanceCreateFlagsInterface, +} from './MPTokenIssuanceCreate' export { NFTokenAcceptOffer } from './NFTokenAcceptOffer' export { NFTokenBurn } from './NFTokenBurn' export { NFTokenCancelOffer } from './NFTokenCancelOffer' diff --git a/packages/xrpl/src/models/transactions/metadata.ts b/packages/xrpl/src/models/transactions/metadata.ts index 78aa32616e..ebe7579505 100644 --- a/packages/xrpl/src/models/transactions/metadata.ts +++ b/packages/xrpl/src/models/transactions/metadata.ts @@ -14,6 +14,10 @@ import { NFTokenCreateOfferMetadata, } from './NFTokenCreateOffer' import { NFTokenMint, NFTokenMintMetadata } from './NFTokenMint' +import { + MPTokenIssuanceCreate, + MPTokenIssuanceCreateMetadata, +} from './MPTokenIssuanceCreate' import { Payment, PaymentMetadata } from './payment' import type { Transaction } from './transaction' @@ -96,4 +100,6 @@ export type TransactionMetadata = ? NFTokenAcceptOfferMetadata : T extends NFTokenCancelOffer ? NFTokenCancelOfferMetadata + : T extends MPTokenIssuanceCreate + ? MPTokenIssuanceCreateMetadata : TransactionMetadataBase diff --git a/packages/xrpl/src/models/transactions/transaction.ts b/packages/xrpl/src/models/transactions/transaction.ts index 1207d81a19..c60ca37ae4 100644 --- a/packages/xrpl/src/models/transactions/transaction.ts +++ b/packages/xrpl/src/models/transactions/transaction.ts @@ -88,6 +88,10 @@ import { XChainModifyBridge, validateXChainModifyBridge, } from './XChainModifyBridge' +import { + MPTokenIssuanceCreate, + validateMPTokenIssuanceCreate, +} from './MPTokenIssuanceCreate' /** * Transactions that can be submitted by clients @@ -113,6 +117,7 @@ export type SubmittableTransaction = | EscrowCancel | EscrowCreate | EscrowFinish + | MPTokenIssuanceCreate | NFTokenAcceptOffer | NFTokenBurn | NFTokenCancelOffer @@ -302,6 +307,10 @@ export function validate(transaction: Record): void { validateEscrowFinish(tx) break + case 'MPTokenIssuanceCreate': + validateMPTokenIssuanceCreate(tx) + break + case 'NFTokenAcceptOffer': validateNFTokenAcceptOffer(tx) break diff --git a/packages/xrpl/src/models/utils/flags.ts b/packages/xrpl/src/models/utils/flags.ts index 15f1cbb245..20f92d0ac7 100644 --- a/packages/xrpl/src/models/utils/flags.ts +++ b/packages/xrpl/src/models/utils/flags.ts @@ -18,6 +18,7 @@ import { PaymentChannelClaimFlags } from '../transactions/paymentChannelClaim' import type { Transaction } from '../transactions/transaction' import { TrustSetFlags } from '../transactions/trustSet' import { XChainModifyBridgeFlags } from '../transactions/XChainModifyBridge' +import { MPTokenIssuanceCreateFlags } from '../transactions/MPTokenIssuanceCreate' import { isFlagEnabled } from '.' @@ -51,6 +52,7 @@ const txToFlag = { AMMWithdraw: AMMWithdrawFlags, NFTokenCreateOffer: NFTokenCreateOfferFlags, NFTokenMint: NFTokenMintFlags, + MPTokenIssuanceCreate: MPTokenIssuanceCreateFlags, OfferCreate: OfferCreateFlags, PaymentChannelClaim: PaymentChannelClaimFlags, Payment: PaymentFlags, diff --git a/packages/xrpl/src/utils/index.ts b/packages/xrpl/src/utils/index.ts index c96afd4151..a194927e86 100644 --- a/packages/xrpl/src/utils/index.ts +++ b/packages/xrpl/src/utils/index.ts @@ -65,6 +65,7 @@ import { } from './timeConversion' import verifyPaymentChannelClaim from './verifyPaymentChannelClaim' import { xrpToDrops, dropsToXrp } from './xrpConversion' +import { mptToHex } from './mptConversion' /** * Check if a secret is valid. @@ -228,4 +229,5 @@ export { getNFTokenID, parseNFTokenID, getXChainClaimID, + mptToHex, } diff --git a/packages/xrpl/src/utils/mptConversion.ts b/packages/xrpl/src/utils/mptConversion.ts new file mode 100644 index 0000000000..720144f7e7 --- /dev/null +++ b/packages/xrpl/src/utils/mptConversion.ts @@ -0,0 +1,43 @@ +import { ValidationError } from '../errors' + +/** + * Convert an amount in XRP to an amount in drops. + * + * @param number - Amount in MPT. + * @returns Amount in drops. + * @throws When amount in xrp is invalid. + * @category Utilities + */ +export function mptToHex(number: string): string { + // mpts are only whole units + if (number.includes('.')) { + throw new ValidationError( + `mptToHex: value '${number}' has too many decimal places.`, + ) + } + + console.log(BigInt(number)) + + if (Number(BigInt(number) & BigInt('0x8000000000000000')) != 0) + throw new ValidationError( + `mptToHex: invalid value '${number}', should be within 63-bit range.`, + ) + + return BigInt(number).toString(16) +} +// export function mptToHex(number: number): string { +// // mpts are only whole units +// if (number.toString().includes('.')) { +// throw new ValidationError( +// `mptToHex: value '${number}' has too many decimal places.`, +// ) +// } + +// console.log('printttttt', BigInt(number)) +// if (Number(BigInt(number) & BigInt(0x8000000000000000)) != 0) +// throw new ValidationError( +// `mptToHex: invalid value '${number}', should be within 63-bit range.`, +// ) + +// return number.toString(16) +// } diff --git a/packages/xrpl/test/integration/transactions/mptIssuanceCreate.test.ts b/packages/xrpl/test/integration/transactions/mptIssuanceCreate.test.ts new file mode 100644 index 0000000000..a9c2229969 --- /dev/null +++ b/packages/xrpl/test/integration/transactions/mptIssuanceCreate.test.ts @@ -0,0 +1,67 @@ +import { assert } from 'chai' + +import { MPTokenIssuanceCreate } from '../../../src' +import serverUrl from '../serverUrl' +import { + setupClient, + teardownClient, + type XrplIntegrationTestContext, +} from '../setup' +import { testTransaction } from '../utils' +import { mptToHex } from '../../../src/utils' + +// how long before each test case times out +const TIMEOUT = 20000 + +describe('MPTokenIssuanceCreate', function () { + let testContext: XrplIntegrationTestContext + + beforeEach(async () => { + testContext = await setupClient(serverUrl) + }) + afterEach(async () => teardownClient(testContext)) + + it( + 'base', + async () => { + const tx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + MaximumAmount: mptToHex('9223372036854775807'), // 0x7fffffffffffffff + } + + await testTransaction(testContext.client, tx, testContext.wallet) + + // confirm that the offer actually went through + let accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + }) + assert.lengthOf( + accountObjectsResponse.result.account_objects!, + 1, + 'Should be exactly one issuance on the ledger', + ) + + const bigAmtTx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + MaximumAmount: mptToHex('9223372036854775808'), + } + + await testTransaction(testContext.client, bigAmtTx, testContext.wallet) + + // confirm that the offer actually went through + accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + }) + assert.lengthOf( + accountObjectsResponse.result.account_objects!, + 2, + 'Should be exactly one issuance on the ledger', + ) + }, + TIMEOUT, + ) +}) From ba890ca64d6476d4d665cec63db8ce8ed8227b7c Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 21 Feb 2024 10:42:17 -0500 Subject: [PATCH 07/53] issuancecreate --- .../transactions/MPTokenIssuanceCreate.ts | 6 ++ .../transactions/MPTokenIssuanceDestroy.ts | 3 +- packages/xrpl/src/utils/index.ts | 4 +- packages/xrpl/src/utils/mptConversion.ts | 56 +++++++++------- .../transactions/mptIssuanceCreate.test.ts | 26 ++------ .../test/models/MPTokenIssuanceCreate.test.ts | 60 +++++++++++++++++ .../xrpl/test/utils/mptDecimalToHex.test.ts | 65 +++++++++++++++++++ 7 files changed, 171 insertions(+), 49 deletions(-) create mode 100644 packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts create mode 100644 packages/xrpl/test/utils/mptDecimalToHex.test.ts diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index 2353870031..1001a74174 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -109,6 +109,12 @@ export function validateMPTokenIssuanceCreate( ): void { validateBaseTransaction(tx) + if (typeof tx.MPTokenMetadata === 'string' && tx.MPTokenMetadata === '') { + throw new ValidationError( + 'MPTokenIssuanceCreate: MPTokenMetadata must not be empty string', + ) + } + if (typeof tx.MPTokenMetadata === 'string' && !isHex(tx.MPTokenMetadata)) { throw new ValidationError( 'MPTokenIssuanceCreate: MPTokenMetadata must be in hex format', diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts index 3b3d12fb72..dc727e26fc 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts @@ -10,11 +10,10 @@ import { * from the directory node in which it is being held, effectively removing the token * from the ledger. If this operation succeeds, the corresponding * MPTokenIssuance is removed and the owner’s reserve requirement is reduced by one. - * This operation must fail if there are any holders of the MPT in question. + * This operation must fail if there are any holders who have non-zero balances. */ export interface MPTokenIssuanceDestroy extends BaseTransaction { TransactionType: 'MPTokenIssuanceDestroy' - /** * Identifies the MPTokenIssuance object to be removed by the transaction. */ diff --git a/packages/xrpl/src/utils/index.ts b/packages/xrpl/src/utils/index.ts index a194927e86..b2e1d0966c 100644 --- a/packages/xrpl/src/utils/index.ts +++ b/packages/xrpl/src/utils/index.ts @@ -65,7 +65,7 @@ import { } from './timeConversion' import verifyPaymentChannelClaim from './verifyPaymentChannelClaim' import { xrpToDrops, dropsToXrp } from './xrpConversion' -import { mptToHex } from './mptConversion' +import { mptDecimalToHex } from './mptConversion' /** * Check if a secret is valid. @@ -229,5 +229,5 @@ export { getNFTokenID, parseNFTokenID, getXChainClaimID, - mptToHex, + mptDecimalToHex, } diff --git a/packages/xrpl/src/utils/mptConversion.ts b/packages/xrpl/src/utils/mptConversion.ts index 720144f7e7..83e2e30519 100644 --- a/packages/xrpl/src/utils/mptConversion.ts +++ b/packages/xrpl/src/utils/mptConversion.ts @@ -1,43 +1,51 @@ +import BigNumber from 'bignumber.js' import { ValidationError } from '../errors' +const SANITY_CHECK = /^[0-9]+$/u + /** - * Convert an amount in XRP to an amount in drops. + * Convert an integer string to hex string. * - * @param number - Amount in MPT. - * @returns Amount in drops. - * @throws When amount in xrp is invalid. + * @param numberToConvert - Non-negative number string. + * @returns Amount in hex string. + * @throws When amount is invalid. * @category Utilities */ -export function mptToHex(number: string): string { +export function mptDecimalToHex(numberToConvert: string): string { + // convert to base 10 string first for inputs like scientific notation + const number = new BigNumber(numberToConvert).toString(10) + + // check that the value is valid and actually a number + if (typeof numberToConvert === 'string' && number === 'NaN') { + throw new ValidationError( + `mptDecimalToHex: invalid value '${numberToConvert}', should be a string-encoded number.`, + ) + } + // mpts are only whole units if (number.includes('.')) { throw new ValidationError( - `mptToHex: value '${number}' has too many decimal places.`, + `mptDecimalToHex: value '${numberToConvert}' has too many decimal places.`, + ) + } + if (number.includes('-')) { + throw new ValidationError( + `mptDecimalToHex: value '${numberToConvert}' cannot be negative.`, ) } - console.log(BigInt(number)) + if (!SANITY_CHECK.exec(number)) { + throw new ValidationError( + `mptDecimalToHex: failed sanity check -` + + ` value '${numberToConvert}',` + + ` does not match (^[0-9]+$).`, + ) + } if (Number(BigInt(number) & BigInt('0x8000000000000000')) != 0) throw new ValidationError( - `mptToHex: invalid value '${number}', should be within 63-bit range.`, + `mptDecimalToHex: invalid value '${numberToConvert}', should be within 63-bit range.`, ) return BigInt(number).toString(16) } -// export function mptToHex(number: number): string { -// // mpts are only whole units -// if (number.toString().includes('.')) { -// throw new ValidationError( -// `mptToHex: value '${number}' has too many decimal places.`, -// ) -// } - -// console.log('printttttt', BigInt(number)) -// if (Number(BigInt(number) & BigInt(0x8000000000000000)) != 0) -// throw new ValidationError( -// `mptToHex: invalid value '${number}', should be within 63-bit range.`, -// ) - -// return number.toString(16) -// } diff --git a/packages/xrpl/test/integration/transactions/mptIssuanceCreate.test.ts b/packages/xrpl/test/integration/transactions/mptIssuanceCreate.test.ts index a9c2229969..22c5955f0f 100644 --- a/packages/xrpl/test/integration/transactions/mptIssuanceCreate.test.ts +++ b/packages/xrpl/test/integration/transactions/mptIssuanceCreate.test.ts @@ -8,7 +8,7 @@ import { type XrplIntegrationTestContext, } from '../setup' import { testTransaction } from '../utils' -import { mptToHex } from '../../../src/utils' +import { mptDecimalToHex } from '../../../src/utils' // how long before each test case times out const TIMEOUT = 20000 @@ -27,7 +27,10 @@ describe('MPTokenIssuanceCreate', function () { const tx: MPTokenIssuanceCreate = { TransactionType: 'MPTokenIssuanceCreate', Account: testContext.wallet.classicAddress, - MaximumAmount: mptToHex('9223372036854775807'), // 0x7fffffffffffffff + MaximumAmount: mptDecimalToHex('9223372036854775807'), // 0x7fffffffffffffff + AssetScale: 2, + TransferFee: 1, + Flags: 2, } await testTransaction(testContext.client, tx, testContext.wallet) @@ -42,25 +45,6 @@ describe('MPTokenIssuanceCreate', function () { 1, 'Should be exactly one issuance on the ledger', ) - - const bigAmtTx: MPTokenIssuanceCreate = { - TransactionType: 'MPTokenIssuanceCreate', - Account: testContext.wallet.classicAddress, - MaximumAmount: mptToHex('9223372036854775808'), - } - - await testTransaction(testContext.client, bigAmtTx, testContext.wallet) - - // confirm that the offer actually went through - accountObjectsResponse = await testContext.client.request({ - command: 'account_objects', - account: testContext.wallet.classicAddress, - }) - assert.lengthOf( - accountObjectsResponse.result.account_objects!, - 2, - 'Should be exactly one issuance on the ledger', - ) }, TIMEOUT, ) diff --git a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts new file mode 100644 index 0000000000..6a45cd1d73 --- /dev/null +++ b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts @@ -0,0 +1,60 @@ +import { assert } from 'chai' + +import { + convertStringToHex, + validate, + ValidationError, + MPTokenIssuanceCreateFlags, + mptDecimalToHex, +} from '../../src' + +/** + * MPTokenIssuanceCreate Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('MPTokenIssuanceCreate', function () { + it(`verifies valid MPTokenIssuanceCreate`, function () { + const validMPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MaximumAmount: mptDecimalToHex('9223372036854775807'), // 0x7fffffffffffffff + AssetScale: 2, + TransferFee: 1, + Flags: 2, + MPTokenMetadata: convertStringToHex('http://xrpl.org'), + } as any + + assert.doesNotThrow(() => validate(validMPTokenIssuanceCreate)) + }) + + it(`throws w/ MPTokenMetadata being an empty string`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + Flags: MPTokenIssuanceCreateFlags.tfMPTCanLock, + MPTokenMetadata: '', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: MPTokenMetadata must not be empty string', + ) + }) + + it(`throws w/ MPTokenMetadata not in hex format`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + Flags: MPTokenIssuanceCreateFlags.tfMPTCanLock, + MPTokenMetadata: 'http://xrpl.org', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: MPTokenMetadata must be in hex format', + ) + }) +}) diff --git a/packages/xrpl/test/utils/mptDecimalToHex.test.ts b/packages/xrpl/test/utils/mptDecimalToHex.test.ts new file mode 100644 index 0000000000..b0b5bccf2c --- /dev/null +++ b/packages/xrpl/test/utils/mptDecimalToHex.test.ts @@ -0,0 +1,65 @@ +import { assert } from 'chai' + +import { mptDecimalToHex } from '../../src/utils' + +describe('mptDecimalToHex', function () { + it('works with a typical amount', function () { + let hexStr = mptDecimalToHex('1000') + assert.strictEqual(hexStr, '3e8', '1000 equals to 3e8 in hex') + + hexStr = mptDecimalToHex('9223372036854775807') + assert.strictEqual( + hexStr, + '7fffffffffffffff', + '9223372036854775807 equals to 7fffffffffffffff in hex', + ) + }) + + it('works with zero', function () { + let hexStr = mptDecimalToHex('0') + assert.strictEqual(hexStr, '0', '0 equals to 0 in hex') + + hexStr = mptDecimalToHex('000000000') + assert.strictEqual(hexStr, '0', '000000000 equals 0 in hex') + + hexStr = mptDecimalToHex('-0') + assert.strictEqual(hexStr, '0', '-0 equals 0 in hex') + }) + + it('throws with a negative value', function () { + assert.throws(() => { + mptDecimalToHex('-1') + }, "mptDecimalToHex: value '-1' cannot be negative.") + }) + + it('checks decimal value', function () { + assert.throws(() => { + mptDecimalToHex('20000.1') + }, "mptDecimalToHex: value '20000.1' has too many decimal places.") + + const hexStr = mptDecimalToHex('20000.') + assert.strictEqual(hexStr, '4e20', '20000. equals 4e20 in hex') + }) + + it('works with scientific notation', function () { + const hexStr = mptDecimalToHex('1e6') + assert.strictEqual(hexStr, 'f4240', '1e6 equals f4240 in hex') + }) + + it('throws with an value with 64-bit range', function () { + assert.throws(() => { + mptDecimalToHex('9223372036854775808') + }, "mptDecimalToHex: invalid value '9223372036854775808', should be within 63-bit range.") + }) + + it('throws with an value with invalid format', function () { + assert.throws(() => { + mptDecimalToHex('1a') + }, "mptDecimalToHex: invalid value '1a', should be a string-encoded number.") + }) + + it('works with a hex value', function () { + const hexStr = mptDecimalToHex('0x1a') + assert.strictEqual(hexStr, '1a', '0x1a equals 1a in hex') + }) +}) From 54648a8909234c6f7a6a9605422a665c30400b5d Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 21 Feb 2024 11:28:15 -0500 Subject: [PATCH 08/53] destroy tx --- .../transactions/MPTokenIssuanceDestroy.ts | 2 +- .../xrpl/src/models/transactions/index.ts | 1 + .../src/models/transactions/transaction.ts | 9 ++ ....test.ts => mptokenIssuanceCreate.test.ts} | 3 - .../mptokenIssuanceDestroy.test.ts | 83 +++++++++++++++++++ .../models/MPTokenIssuanceDestroy.test.ts | 35 ++++++++ 6 files changed, 129 insertions(+), 4 deletions(-) rename packages/xrpl/test/integration/transactions/{mptIssuanceCreate.test.ts => mptokenIssuanceCreate.test.ts} (93%) create mode 100644 packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts create mode 100644 packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts index dc727e26fc..06cbfe9471 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceDestroy.ts @@ -17,7 +17,7 @@ export interface MPTokenIssuanceDestroy extends BaseTransaction { /** * Identifies the MPTokenIssuance object to be removed by the transaction. */ - MPTokenIssuanceID?: string + MPTokenIssuanceID: string } /** diff --git a/packages/xrpl/src/models/transactions/index.ts b/packages/xrpl/src/models/transactions/index.ts index 5dd1ba45c2..74a7f3ae8a 100644 --- a/packages/xrpl/src/models/transactions/index.ts +++ b/packages/xrpl/src/models/transactions/index.ts @@ -44,6 +44,7 @@ export { MPTokenIssuanceCreateFlags, MPTokenIssuanceCreateFlagsInterface, } from './MPTokenIssuanceCreate' +export { MPTokenIssuanceDestroy } from './MPTokenIssuanceDestroy' export { NFTokenAcceptOffer } from './NFTokenAcceptOffer' export { NFTokenBurn } from './NFTokenBurn' export { NFTokenCancelOffer } from './NFTokenCancelOffer' diff --git a/packages/xrpl/src/models/transactions/transaction.ts b/packages/xrpl/src/models/transactions/transaction.ts index c60ca37ae4..c7418b67ee 100644 --- a/packages/xrpl/src/models/transactions/transaction.ts +++ b/packages/xrpl/src/models/transactions/transaction.ts @@ -92,6 +92,10 @@ import { MPTokenIssuanceCreate, validateMPTokenIssuanceCreate, } from './MPTokenIssuanceCreate' +import { + MPTokenIssuanceDestroy, + validateMPTokenIssuanceDestroy, +} from './MPTokenIssuanceDestroy' /** * Transactions that can be submitted by clients @@ -118,6 +122,7 @@ export type SubmittableTransaction = | EscrowCreate | EscrowFinish | MPTokenIssuanceCreate + | MPTokenIssuanceDestroy | NFTokenAcceptOffer | NFTokenBurn | NFTokenCancelOffer @@ -311,6 +316,10 @@ export function validate(transaction: Record): void { validateMPTokenIssuanceCreate(tx) break + case 'MPTokenIssuanceDestroy': + validateMPTokenIssuanceDestroy(tx) + break + case 'NFTokenAcceptOffer': validateNFTokenAcceptOffer(tx) break diff --git a/packages/xrpl/test/integration/transactions/mptIssuanceCreate.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts similarity index 93% rename from packages/xrpl/test/integration/transactions/mptIssuanceCreate.test.ts rename to packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts index 22c5955f0f..3d7d80eb68 100644 --- a/packages/xrpl/test/integration/transactions/mptIssuanceCreate.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts @@ -29,13 +29,10 @@ describe('MPTokenIssuanceCreate', function () { Account: testContext.wallet.classicAddress, MaximumAmount: mptDecimalToHex('9223372036854775807'), // 0x7fffffffffffffff AssetScale: 2, - TransferFee: 1, - Flags: 2, } await testTransaction(testContext.client, tx, testContext.wallet) - // confirm that the offer actually went through let accountObjectsResponse = await testContext.client.request({ command: 'account_objects', account: testContext.wallet.classicAddress, diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts new file mode 100644 index 0000000000..5bcb1458d6 --- /dev/null +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts @@ -0,0 +1,83 @@ +import { assert } from 'chai' + +import { + MPTokenIssuanceCreate, + MPTokenIssuanceDestroy, + TransactionMetadata, +} from '../../../src' +import serverUrl from '../serverUrl' +import { + setupClient, + teardownClient, + type XrplIntegrationTestContext, +} from '../setup' +import { testTransaction } from '../utils' + +// how long before each test case times out +const TIMEOUT = 20000 + +describe('MPTokenIssuanceDestroy', function () { + let testContext: XrplIntegrationTestContext + + beforeEach(async () => { + testContext = await setupClient(serverUrl) + }) + afterEach(async () => teardownClient(testContext)) + + it( + 'base', + async () => { + const createTx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + } + + const mptCreateRes = await testTransaction( + testContext.client, + createTx, + testContext.wallet, + ) + + const txHash = mptCreateRes.result.tx_json.hash + + let txResponse = await testContext.client.request({ + command: 'tx', + transaction: txHash, + }) + + const meta = txResponse.result + .meta as TransactionMetadata + + const mptID = meta.mpt_issuance_id + + let accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + }) + assert.lengthOf( + accountObjectsResponse.result.account_objects!, + 1, + 'Should be exactly one issuance on the ledger', + ) + + const destroyTx: MPTokenIssuanceDestroy = { + TransactionType: 'MPTokenIssuanceDestroy', + Account: testContext.wallet.classicAddress, + MPTokenIssuanceID: mptID!, + } + + await testTransaction(testContext.client, destroyTx, testContext.wallet) + + accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + }) + assert.lengthOf( + accountObjectsResponse.result.account_objects!, + 0, + 'Should be zero issuance on the ledger', + ) + }, + TIMEOUT, + ) +}) diff --git a/packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts b/packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts new file mode 100644 index 0000000000..34c8939e32 --- /dev/null +++ b/packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts @@ -0,0 +1,35 @@ +import { assert } from 'chai' + +import { validate, ValidationError } from '../../src' + +const TOKEN_ID = '000004C463C52827307480341125DA0577DEFC38405B0E3E' + +/** + * MPTokenIssuanceDestroy Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('MPTokenIssuanceDestroy', function () { + it(`verifies valid MPTokenIssuanceDestroy`, function () { + const validMPTokenIssuanceDestroy = { + TransactionType: 'MPTokenIssuanceDestroy', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + } as any + + assert.doesNotThrow(() => validate(validMPTokenIssuanceDestroy)) + }) + + it(`throws w/ missing MPTOkenIssuanceID`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceDestroy', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceDestroy: missing field MPTokenIssuanceID', + ) + }) +}) From 05a831ca5ee1e46580a5887a1de088391119770c Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 21 Feb 2024 16:57:53 -0500 Subject: [PATCH 09/53] mptset tx --- .../models/transactions/MPTokenIssuanceSet.ts | 78 +++++++++++++++++++ .../xrpl/src/models/transactions/index.ts | 5 ++ .../src/models/transactions/transaction.ts | 9 +++ packages/xrpl/src/models/utils/flags.ts | 4 +- .../transactions/mptokenIssuanceSet.test.ts | 77 ++++++++++++++++++ .../models/MPTokenIssuanceDestroy.test.ts | 2 +- .../test/models/MPTokenIssuanceSet.test.ts | 71 +++++++++++++++++ 7 files changed, 244 insertions(+), 2 deletions(-) create mode 100644 packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts create mode 100644 packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts create mode 100644 packages/xrpl/test/models/MPTokenIssuanceSet.test.ts diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts new file mode 100644 index 0000000000..cd80cf89fd --- /dev/null +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -0,0 +1,78 @@ +import { + BaseTransaction, + isString, + validateBaseTransaction, + validateRequiredField, + Account, + validateOptionalField, + isAccount, + GlobalFlags, +} from './common' +import { ValidationError } from '../../errors' + +/** + * Transaction Flags for an MPTokenIssuanceSet Transaction. + * + * @category Transaction Flags + */ +export enum MPTokenIssuanceSetFlags { + /** + * If set, indicates that the MPT can be locked both individually and globally. + * If not set, the MPT cannot be locked in any way. + */ + tfMPTLock = 0x00000001, + /** + * If set, indicates that the MPT can be locked both individually and globally. + * If not set, the MPT cannot be locked in any way. + */ + tfMPTUnlock = 0x00000002, +} + +/** + * Map of flags to boolean values representing {@link MPTokenIssuanceSet} transaction + * flags. + * + * @category Transaction Flags + */ +export interface MPTokenIssuanceSetFlagsInterface extends GlobalFlags { + tfMPTLock?: boolean + tfMPTUnlock?: boolean +} + +/** + * The MPTokenIssuanceSet transaction is used to globally lock/unlock a MPTokenIssuance, + * or lock/unlock an individual's MPToken. + */ +export interface MPTokenIssuanceSet extends BaseTransaction { + TransactionType: 'MPTokenIssuanceSet' + /** + * Identifies the MPTokenIssuance + */ + MPTokenIssuanceID: string + /** + * An optional XRPL Address of an individual token holder balance to lock/unlock. + * If omitted, this transaction will apply to all any accounts holding MPTs. + */ + MPTokenHolder?: Account + Flags?: number | MPTokenIssuanceSetFlagsInterface +} + +/** + * Verify the form and type of an MPTokenIssuanceSet at runtime. + * + * @param tx - An MPTokenIssuanceSet Transaction. + * @throws When the MPTokenIssuanceSet is Malformed. + */ +export function validateMPTokenIssuanceSet(tx: Record): void { + validateBaseTransaction(tx) + validateRequiredField(tx, 'MPTokenIssuanceID', isString) + validateOptionalField(tx, 'MPTokenHolder', isAccount) + + const flags = tx.Flags as number + if ( + BigInt(flags) & BigInt(MPTokenIssuanceSetFlags.tfMPTLock) && + BigInt(flags) & BigInt(MPTokenIssuanceSetFlags.tfMPTUnlock) + ) { + throw new ValidationError('MPTokenIssuanceSet: flag conflict') + } +} diff --git a/packages/xrpl/src/models/transactions/index.ts b/packages/xrpl/src/models/transactions/index.ts index 74a7f3ae8a..265086a62f 100644 --- a/packages/xrpl/src/models/transactions/index.ts +++ b/packages/xrpl/src/models/transactions/index.ts @@ -45,6 +45,11 @@ export { MPTokenIssuanceCreateFlagsInterface, } from './MPTokenIssuanceCreate' export { MPTokenIssuanceDestroy } from './MPTokenIssuanceDestroy' +export { + MPTokenIssuanceSet, + MPTokenIssuanceSetFlags, + MPTokenIssuanceSetFlagsInterface, +} from './MPTokenIssuanceSet' export { NFTokenAcceptOffer } from './NFTokenAcceptOffer' export { NFTokenBurn } from './NFTokenBurn' export { NFTokenCancelOffer } from './NFTokenCancelOffer' diff --git a/packages/xrpl/src/models/transactions/transaction.ts b/packages/xrpl/src/models/transactions/transaction.ts index c7418b67ee..998fa2642f 100644 --- a/packages/xrpl/src/models/transactions/transaction.ts +++ b/packages/xrpl/src/models/transactions/transaction.ts @@ -96,6 +96,10 @@ import { MPTokenIssuanceDestroy, validateMPTokenIssuanceDestroy, } from './MPTokenIssuanceDestroy' +import { + MPTokenIssuanceSet, + validateMPTokenIssuanceSet, +} from './MPTokenIssuanceSet' /** * Transactions that can be submitted by clients @@ -123,6 +127,7 @@ export type SubmittableTransaction = | EscrowFinish | MPTokenIssuanceCreate | MPTokenIssuanceDestroy + | MPTokenIssuanceSet | NFTokenAcceptOffer | NFTokenBurn | NFTokenCancelOffer @@ -320,6 +325,10 @@ export function validate(transaction: Record): void { validateMPTokenIssuanceDestroy(tx) break + case 'MPTokenIssuanceSet': + validateMPTokenIssuanceSet(tx) + break + case 'NFTokenAcceptOffer': validateNFTokenAcceptOffer(tx) break diff --git a/packages/xrpl/src/models/utils/flags.ts b/packages/xrpl/src/models/utils/flags.ts index 20f92d0ac7..20eb96f98c 100644 --- a/packages/xrpl/src/models/utils/flags.ts +++ b/packages/xrpl/src/models/utils/flags.ts @@ -19,6 +19,7 @@ import type { Transaction } from '../transactions/transaction' import { TrustSetFlags } from '../transactions/trustSet' import { XChainModifyBridgeFlags } from '../transactions/XChainModifyBridge' import { MPTokenIssuanceCreateFlags } from '../transactions/MPTokenIssuanceCreate' +import { MPTokenIssuanceSetFlags } from '../transactions/MPTokenIssuanceSet' import { isFlagEnabled } from '.' @@ -50,9 +51,10 @@ const txToFlag = { AccountSet: AccountSetTfFlags, AMMDeposit: AMMDepositFlags, AMMWithdraw: AMMWithdrawFlags, + MPTokenIssuanceCreate: MPTokenIssuanceCreateFlags, + MPTokenIssuanceSet: MPTokenIssuanceSetFlags, NFTokenCreateOffer: NFTokenCreateOfferFlags, NFTokenMint: NFTokenMintFlags, - MPTokenIssuanceCreate: MPTokenIssuanceCreateFlags, OfferCreate: OfferCreateFlags, PaymentChannelClaim: PaymentChannelClaimFlags, Payment: PaymentFlags, diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts new file mode 100644 index 0000000000..22107f9595 --- /dev/null +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts @@ -0,0 +1,77 @@ +import { assert } from 'chai' + +import { + MPTokenIssuanceCreate, + MPTokenIssuanceSet, + MPTokenIssuanceCreateFlags, + MPTokenIssuanceSetFlags, + TransactionMetadata, +} from '../../../src' +import serverUrl from '../serverUrl' +import { + setupClient, + teardownClient, + type XrplIntegrationTestContext, +} from '../setup' +import { testTransaction } from '../utils' + +// how long before each test case times out +const TIMEOUT = 20000 + +describe('MPTokenIssuanceDestroy', function () { + let testContext: XrplIntegrationTestContext + + beforeEach(async () => { + testContext = await setupClient(serverUrl) + }) + afterEach(async () => teardownClient(testContext)) + + it( + 'base', + async () => { + const createTx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + Flags: MPTokenIssuanceCreateFlags.tfMPTCanLock, + } + + const mptCreateRes = await testTransaction( + testContext.client, + createTx, + testContext.wallet, + ) + + const txHash = mptCreateRes.result.tx_json.hash + + let txResponse = await testContext.client.request({ + command: 'tx', + transaction: txHash, + }) + + const meta = txResponse.result + .meta as TransactionMetadata + + const mptID = meta.mpt_issuance_id + + let accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + }) + assert.lengthOf( + accountObjectsResponse.result.account_objects!, + 1, + 'Should be exactly one issuance on the ledger', + ) + + const setTx: MPTokenIssuanceSet = { + TransactionType: 'MPTokenIssuanceSet', + Account: testContext.wallet.classicAddress, + MPTokenIssuanceID: mptID!, + Flags: MPTokenIssuanceSetFlags.tfMPTLock, + } + + await testTransaction(testContext.client, setTx, testContext.wallet) + }, + TIMEOUT, + ) +}) diff --git a/packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts b/packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts index 34c8939e32..46bd53814c 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts @@ -20,7 +20,7 @@ describe('MPTokenIssuanceDestroy', function () { assert.doesNotThrow(() => validate(validMPTokenIssuanceDestroy)) }) - it(`throws w/ missing MPTOkenIssuanceID`, function () { + it(`throws w/ missing MPTokenIssuanceID`, function () { const invalid = { TransactionType: 'MPTokenIssuanceDestroy', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', diff --git a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts new file mode 100644 index 0000000000..22c2067dde --- /dev/null +++ b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts @@ -0,0 +1,71 @@ +import { assert } from 'chai' + +import { validate, ValidationError, MPTokenIssuanceSetFlags } from '../../src' + +const TOKEN_ID = '000004C463C52827307480341125DA0577DEFC38405B0E3E' + +/** + * MPTokenIssuanceSet Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('MPTokenIssuanceSet', function () { + it(`verifies valid MPTokenIssuanceSet`, function () { + let validMPTokenIssuanceSet = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + Flags: MPTokenIssuanceSetFlags.tfMPTLock, + } as any + + assert.doesNotThrow(() => validate(validMPTokenIssuanceSet)) + validMPTokenIssuanceSet = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MPTokenHolder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + Flags: MPTokenIssuanceSetFlags.tfMPTLock, + } as any + + assert.doesNotThrow(() => validate(validMPTokenIssuanceSet)) + + // It's fine to not specify any flag, it means only tx fee is deducted + validMPTokenIssuanceSet = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MPTokenHolder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + } as any + + assert.doesNotThrow(() => validate(validMPTokenIssuanceSet)) + }) + + it(`throws w/ missing MPTokenIssuanceID`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceSet: missing field MPTokenIssuanceID', + ) + }) + + it(`throws w/ conflicting flags`, function () { + const invalid = { + TransactionType: 'MPTokenIssuanceSet', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + Flags: + MPTokenIssuanceSetFlags.tfMPTLock | MPTokenIssuanceSetFlags.tfMPTUnlock, + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceSet: flag conflict', + ) + }) +}) From 24dc1378f9ca7c967c4407c4f2c8a25403ee4357 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 22 Feb 2024 11:19:35 -0500 Subject: [PATCH 10/53] authorize tx --- .../models/transactions/MPTokenAuthorize.ts | 67 ++++++++++ .../xrpl/src/models/transactions/index.ts | 5 + .../src/models/transactions/transaction.ts | 6 + packages/xrpl/src/models/utils/flags.ts | 2 + .../transactions/mptokenAuthorize.test.ts | 117 ++++++++++++++++++ .../xrpl/test/models/MPTokenAuthorize.test.ts | 72 +++++++++++ 6 files changed, 269 insertions(+) create mode 100644 packages/xrpl/src/models/transactions/MPTokenAuthorize.ts create mode 100644 packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts create mode 100644 packages/xrpl/test/models/MPTokenAuthorize.test.ts diff --git a/packages/xrpl/src/models/transactions/MPTokenAuthorize.ts b/packages/xrpl/src/models/transactions/MPTokenAuthorize.ts new file mode 100644 index 0000000000..447c8f93f4 --- /dev/null +++ b/packages/xrpl/src/models/transactions/MPTokenAuthorize.ts @@ -0,0 +1,67 @@ +import { + BaseTransaction, + isString, + validateBaseTransaction, + validateRequiredField, + Account, + validateOptionalField, + isAccount, + GlobalFlags, +} from './common' + +/** + * Transaction Flags for an MPTokenAuthorize Transaction. + * + * @category Transaction Flags + */ +export enum MPTokenAuthorizeFlags { + /** + * If set and transaction is submitted by a holder, it indicates that the holder no + * longer wants to hold the MPToken, which will be deleted as a result. If the the holder's + * MPToken has non-zero balance while trying to set this flag, the transaction will fail. On + * the other hand, if set and transaction is submitted by an issuer, it would mean that the + * issuer wants to unauthorize the holder (only applicable for allow-listing), + * which would unset the lsfMPTAuthorized flag on the MPToken. + */ + tfMPTUnauthorize = 0x00000001, +} + +/** + * Map of flags to boolean values representing {@link MPTokenAuthorize} transaction + * flags. + * + * @category Transaction Flags + */ +export interface MPTokenAuthorizeFlagsInterface extends GlobalFlags { + tfMPTUnauthorize?: boolean +} + +/** + * The MPTokenAuthorize transaction is used to globally lock/unlock a MPTokenIssuance, + * or lock/unlock an individual's MPToken. + */ +export interface MPTokenAuthorize extends BaseTransaction { + TransactionType: 'MPTokenAuthorize' + /** + * Identifies the MPTokenIssuance + */ + MPTokenIssuanceID: string + /** + * An optional XRPL Address of an individual token holder balance to lock/unlock. + * If omitted, this transaction will apply to all any accounts holding MPTs. + */ + MPTokenHolder?: Account + Flags?: number | MPTokenAuthorizeFlagsInterface +} + +/** + * Verify the form and type of an MPTokenAuthorize at runtime. + * + * @param tx - An MPTokenAuthorize Transaction. + * @throws When the MPTokenAuthorize is Malformed. + */ +export function validateMPTokenAuthorize(tx: Record): void { + validateBaseTransaction(tx) + validateRequiredField(tx, 'MPTokenIssuanceID', isString) + validateOptionalField(tx, 'MPTokenHolder', isAccount) +} diff --git a/packages/xrpl/src/models/transactions/index.ts b/packages/xrpl/src/models/transactions/index.ts index 265086a62f..2d96268eee 100644 --- a/packages/xrpl/src/models/transactions/index.ts +++ b/packages/xrpl/src/models/transactions/index.ts @@ -39,6 +39,11 @@ export { EscrowCancel } from './escrowCancel' export { EscrowCreate } from './escrowCreate' export { EscrowFinish } from './escrowFinish' export { EnableAmendment, EnableAmendmentFlags } from './enableAmendment' +export { + MPTokenAuthorize, + MPTokenAuthorizeFlags, + MPTokenAuthorizeFlagsInterface, +} from './MPTokenAuthorize' export { MPTokenIssuanceCreate, MPTokenIssuanceCreateFlags, diff --git a/packages/xrpl/src/models/transactions/transaction.ts b/packages/xrpl/src/models/transactions/transaction.ts index 998fa2642f..898ae59d8e 100644 --- a/packages/xrpl/src/models/transactions/transaction.ts +++ b/packages/xrpl/src/models/transactions/transaction.ts @@ -88,6 +88,7 @@ import { XChainModifyBridge, validateXChainModifyBridge, } from './XChainModifyBridge' +import { MPTokenAuthorize, validateMPTokenAuthorize } from './MPTokenAuthorize' import { MPTokenIssuanceCreate, validateMPTokenIssuanceCreate, @@ -125,6 +126,7 @@ export type SubmittableTransaction = | EscrowCancel | EscrowCreate | EscrowFinish + | MPTokenAuthorize | MPTokenIssuanceCreate | MPTokenIssuanceDestroy | MPTokenIssuanceSet @@ -317,6 +319,10 @@ export function validate(transaction: Record): void { validateEscrowFinish(tx) break + case 'MPTokenAuthorize': + validateMPTokenAuthorize(tx) + break + case 'MPTokenIssuanceCreate': validateMPTokenIssuanceCreate(tx) break diff --git a/packages/xrpl/src/models/utils/flags.ts b/packages/xrpl/src/models/utils/flags.ts index 20eb96f98c..919b8da41a 100644 --- a/packages/xrpl/src/models/utils/flags.ts +++ b/packages/xrpl/src/models/utils/flags.ts @@ -18,6 +18,7 @@ import { PaymentChannelClaimFlags } from '../transactions/paymentChannelClaim' import type { Transaction } from '../transactions/transaction' import { TrustSetFlags } from '../transactions/trustSet' import { XChainModifyBridgeFlags } from '../transactions/XChainModifyBridge' +import { MPTokenAuthorizeFlags } from '../transactions/MPTokenAuthorize' import { MPTokenIssuanceCreateFlags } from '../transactions/MPTokenIssuanceCreate' import { MPTokenIssuanceSetFlags } from '../transactions/MPTokenIssuanceSet' @@ -51,6 +52,7 @@ const txToFlag = { AccountSet: AccountSetTfFlags, AMMDeposit: AMMDepositFlags, AMMWithdraw: AMMWithdrawFlags, + MPTokenAuthorize: MPTokenAuthorizeFlags, MPTokenIssuanceCreate: MPTokenIssuanceCreateFlags, MPTokenIssuanceSet: MPTokenIssuanceSetFlags, NFTokenCreateOffer: NFTokenCreateOfferFlags, diff --git a/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts b/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts new file mode 100644 index 0000000000..523dd2308f --- /dev/null +++ b/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts @@ -0,0 +1,117 @@ +import { assert } from 'chai' + +import { + MPTokenIssuanceCreate, + MPTokenAuthorize, + MPTokenIssuanceCreateFlags, + MPTokenAuthorizeFlags, + TransactionMetadata, +} from '../../../src' +import serverUrl from '../serverUrl' +import { + setupClient, + teardownClient, + type XrplIntegrationTestContext, +} from '../setup' +import { testTransaction, generateFundedWallet } from '../utils' + +// how long before each test case times out +const TIMEOUT = 20000 + +describe('MPTokenIssuanceDestroy', function () { + let testContext: XrplIntegrationTestContext + + beforeEach(async () => { + testContext = await setupClient(serverUrl) + }) + afterEach(async () => teardownClient(testContext)) + + it( + 'base', + async () => { + const wallet2 = await generateFundedWallet(testContext.client) + + const createTx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + Flags: MPTokenIssuanceCreateFlags.tfMPTRequireAuth, + } + + const mptCreateRes = await testTransaction( + testContext.client, + createTx, + testContext.wallet, + ) + + const txHash = mptCreateRes.result.tx_json.hash + + let txResponse = await testContext.client.request({ + command: 'tx', + transaction: txHash, + }) + + const meta = txResponse.result + .meta as TransactionMetadata + + const mptID = meta.mpt_issuance_id + + let accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + }) + assert.lengthOf( + accountObjectsResponse.result.account_objects!, + 1, + 'Should be exactly one issuance on the ledger', + ) + + let authTx: MPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: wallet2.classicAddress, + MPTokenIssuanceID: mptID!, + } + + await testTransaction(testContext.client, authTx, wallet2) + + accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: wallet2.classicAddress, + }) + + assert.lengthOf( + accountObjectsResponse.result.account_objects!, + 1, + 'Holder owns 1 MPToken on the ledger', + ) + + authTx = { + TransactionType: 'MPTokenAuthorize', + Account: testContext.wallet.classicAddress, + MPTokenIssuanceID: mptID!, + MPTokenHolder: wallet2.classicAddress, + } + + await testTransaction(testContext.client, authTx, testContext.wallet) + authTx = { + TransactionType: 'MPTokenAuthorize', + Account: wallet2.classicAddress, + MPTokenIssuanceID: mptID!, + Flags: MPTokenAuthorizeFlags.tfMPTUnauthorize, + } + + await testTransaction(testContext.client, authTx, wallet2) + + accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: wallet2.classicAddress, + }) + + assert.lengthOf( + accountObjectsResponse.result.account_objects!, + 0, + 'Holder owns nothing on the ledger', + ) + }, + TIMEOUT, + ) +}) diff --git a/packages/xrpl/test/models/MPTokenAuthorize.test.ts b/packages/xrpl/test/models/MPTokenAuthorize.test.ts new file mode 100644 index 0000000000..a715548eff --- /dev/null +++ b/packages/xrpl/test/models/MPTokenAuthorize.test.ts @@ -0,0 +1,72 @@ +import { assert } from 'chai' + +import { validate, ValidationError, MPTokenAuthorizeFlags } from '../../src' + +const TOKEN_ID = '000004C463C52827307480341125DA0577DEFC38405B0E3E' + +/** + * MPTokenAuthorize Transaction Verification Testing. + * + * Providing runtime verification testing for each specific transaction type. + */ +describe('MPTokenAuthorize', function () { + it(`verifies valid MPTokenAuthorize`, function () { + let validMPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + } as any + + assert.doesNotThrow(() => validate(validMPTokenAuthorize)) + + validMPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenHolder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + MPTokenIssuanceID: TOKEN_ID, + } as any + + assert.doesNotThrow(() => validate(validMPTokenAuthorize)) + + validMPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + Flags: MPTokenAuthorizeFlags.tfMPTUnauthorize, + } as any + + assert.doesNotThrow(() => validate(validMPTokenAuthorize)) + + validMPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + Flags: MPTokenAuthorizeFlags.tfMPTUnauthorize, + } as any + + assert.doesNotThrow(() => validate(validMPTokenAuthorize)) + + validMPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenIssuanceID: TOKEN_ID, + MPTokenHolder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + Flags: MPTokenAuthorizeFlags.tfMPTUnauthorize, + } as any + + assert.doesNotThrow(() => validate(validMPTokenAuthorize)) + }) + + it(`throws w/ missing MPTokenIssuanceID`, function () { + const invalid = { + TransactionType: 'MPTokenAuthorize', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenAuthorize: missing field MPTokenIssuanceID', + ) + }) +}) From 7ea0aba2d8655c34e66317c6d97017caa3a080a6 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 6 Mar 2024 16:12:41 -0500 Subject: [PATCH 11/53] payment + clawback (wip) --- packages/xrpl/src/client/partialPayment.ts | 22 ++++-- packages/xrpl/src/models/common/index.ts | 7 +- .../xrpl/src/models/transactions/clawback.ts | 35 ++++++++-- .../xrpl/src/models/transactions/common.ts | 23 ++++++- .../xrpl/src/models/transactions/index.ts | 2 +- packages/xrpl/test/models/clawback.test.ts | 68 +++++++++++++++++++ packages/xrpl/test/models/payment.test.ts | 21 +++++- 7 files changed, 164 insertions(+), 14 deletions(-) diff --git a/packages/xrpl/src/client/partialPayment.ts b/packages/xrpl/src/client/partialPayment.ts index a21448e9cc..a0b249a9b4 100644 --- a/packages/xrpl/src/client/partialPayment.ts +++ b/packages/xrpl/src/client/partialPayment.ts @@ -7,10 +7,10 @@ import type { TransactionStream, TxResponse, } from '..' -import type { Amount } from '../models/common' +import type { Amount, IssuedCurrency, MPTAmount } from '../models/common' import type { RequestResponseMap } from '../models/methods' import { BaseRequest, BaseResponse } from '../models/methods/baseMethod' -import { PaymentFlags, Transaction } from '../models/transactions' +import { PaymentFlags, Transaction, isMPTAmount } from '../models/transactions' import type { TransactionMetadata } from '../models/transactions/metadata' import { isFlagEnabled } from '../models/utils' @@ -25,12 +25,26 @@ function amountsEqual(amt1: Amount, amt2: Amount): boolean { return false } + if (isMPTAmount(amt1) && isMPTAmount(amt2)) { + const aValue = new BigNumber(amt1.value) + const bValue = new BigNumber(amt2.value) + + return ( + (amt1 as MPTAmount).mpt_issuance_id === + (amt2 as MPTAmount).mpt_issuance_id && aValue.isEqualTo(bValue) + ) + } + + if (isMPTAmount(amt1) || isMPTAmount(amt2)) { + return false + } + const aValue = new BigNumber(amt1.value) const bValue = new BigNumber(amt2.value) return ( - amt1.currency === amt2.currency && - amt1.issuer === amt2.issuer && + (amt1 as IssuedCurrency).currency === (amt2 as IssuedCurrency).currency && + (amt1 as IssuedCurrency).issuer === (amt2 as IssuedCurrency).issuer && aValue.isEqualTo(bValue) ) } diff --git a/packages/xrpl/src/models/common/index.ts b/packages/xrpl/src/models/common/index.ts index a695bd8e13..9550583b05 100644 --- a/packages/xrpl/src/models/common/index.ts +++ b/packages/xrpl/src/models/common/index.ts @@ -16,7 +16,12 @@ export interface IssuedCurrencyAmount extends IssuedCurrency { value: string } -export type Amount = IssuedCurrencyAmount | string +export interface MPTAmount { + mpt_issuance_id: string + value: string +} + +export type Amount = IssuedCurrencyAmount | MPTAmount | string export interface Balance { currency: string diff --git a/packages/xrpl/src/models/transactions/clawback.ts b/packages/xrpl/src/models/transactions/clawback.ts index fb51859cfc..fd9e6f5146 100644 --- a/packages/xrpl/src/models/transactions/clawback.ts +++ b/packages/xrpl/src/models/transactions/clawback.ts @@ -1,10 +1,13 @@ import { ValidationError } from '../../errors' -import { IssuedCurrencyAmount } from '../common' +import { IssuedCurrencyAmount, MPTAmount } from '../common' import { BaseTransaction, validateBaseTransaction, isIssuedCurrency, + isMPTAmount, + isAccount, + validateOptionalField, } from './common' /** @@ -15,15 +18,20 @@ export interface Clawback extends BaseTransaction { TransactionType: 'Clawback' /** * Indicates the AccountID that submitted this transaction. The account MUST - * be the issuer of the currency. + * be the issuer of the currency or MPT. */ Account: string /** - * The amount of currency to deliver, and it must be non-XRP. The nested field - * names MUST be lower-case. The `issuer` field MUST be the holder's address, + * The amount of currency or MPT to clawback, and it must be non-XRP. The nested field + * names MUST be lower-case. If the amount is IOU, the `issuer` field MUST be the holder's address, * whom to be clawed back. */ - Amount: IssuedCurrencyAmount + Amount: IssuedCurrencyAmount | MPTAmount + /** + * Indicates the AccountID that the issuer wants to clawback. This field is only valid for clawing back + * MPTs. + */ + MPTokenHolder?: string } /** @@ -34,16 +42,31 @@ export interface Clawback extends BaseTransaction { */ export function validateClawback(tx: Record): void { validateBaseTransaction(tx) + validateOptionalField(tx, 'MPTokenHolder', isAccount) if (tx.Amount == null) { throw new ValidationError('Clawback: missing field Amount') } - if (!isIssuedCurrency(tx.Amount)) { + if (!isIssuedCurrency(tx.Amount) && !isMPTAmount(tx.Amount)) { throw new ValidationError('Clawback: invalid Amount') } if (isIssuedCurrency(tx.Amount) && tx.Account === tx.Amount.issuer) { throw new ValidationError('Clawback: invalid holder Account') } + + if (isMPTAmount(tx.Amount) && tx.Account === tx.MPTokenHolder) { + throw new ValidationError('Clawback: invalid holder Account') + } + + if (isIssuedCurrency(tx.Amount) && tx.MPTokenHolder) { + throw new ValidationError( + 'Clawback: cannot have MPTokenHolder for currency', + ) + } + + if (isMPTAmount(tx.Amount) && !tx.MPTokenHolder) { + throw new ValidationError('Clawback: missing MPTokenHolder') + } } diff --git a/packages/xrpl/src/models/transactions/common.ts b/packages/xrpl/src/models/transactions/common.ts index 5af72d038a..eb5b56fa1a 100644 --- a/packages/xrpl/src/models/transactions/common.ts +++ b/packages/xrpl/src/models/transactions/common.ts @@ -9,6 +9,7 @@ import { Memo, Signer, XChainBridge, + MPTAmount, } from '../common' import { onlyHasFields } from '../utils' @@ -59,6 +60,7 @@ const XRP_CURRENCY_SIZE = 1 const ISSUE_SIZE = 2 const ISSUED_CURRENCY_SIZE = 3 const XCHAIN_BRIDGE_SIZE = 4 +const MPTOKEN_SIZE = 2 function isRecord(value: unknown): value is Record { return value !== null && typeof value === 'object' @@ -119,6 +121,21 @@ export function isIssuedCurrency( ) } +/** + * Verify the form and type of an MPT at runtime. + * + * @param input - The input to check the form and type of. + * @returns Whether the MPTAmount is properly formed. + */ +export function isMPTAmount(input: unknown): input is MPTAmount { + return ( + isRecord(input) && + Object.keys(input).length === MPTOKEN_SIZE && + typeof input.value === 'string' && + typeof input.mpt_issuance_id === 'string' + ) +} + /** * Must be a valid account address */ @@ -144,7 +161,11 @@ export function isAccount(account: unknown): account is Account { * @returns Whether the Amount is properly formed. */ export function isAmount(amount: unknown): amount is Amount { - return typeof amount === 'string' || isIssuedCurrency(amount) + return ( + typeof amount === 'string' || + isIssuedCurrency(amount) || + isMPTAmount(amount) + ) } /** diff --git a/packages/xrpl/src/models/transactions/index.ts b/packages/xrpl/src/models/transactions/index.ts index 2d96268eee..5b429e33f2 100644 --- a/packages/xrpl/src/models/transactions/index.ts +++ b/packages/xrpl/src/models/transactions/index.ts @@ -1,4 +1,4 @@ -export { BaseTransaction } from './common' +export { BaseTransaction, isMPTAmount } from './common' export { validate, PseudoTransaction, diff --git a/packages/xrpl/test/models/clawback.test.ts b/packages/xrpl/test/models/clawback.test.ts index b60f765311..f2b2a52f8c 100644 --- a/packages/xrpl/test/models/clawback.test.ts +++ b/packages/xrpl/test/models/clawback.test.ts @@ -78,4 +78,72 @@ describe('Clawback', function () { 'Clawback: invalid holder Account', ) }) + + it(`verifies valid MPT Clawback`, function () { + const validClawback = { + TransactionType: 'Clawback', + Amount: { + mpt_issuance_id: '000004C463C52827307480341125DA0577DEFC38405B0E3E', + value: '10', + }, + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenHolder: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', + } as any + + assert.doesNotThrow(() => validate(validClawback)) + }) + + it(`throws w/ invalid MPTokenHolder Account`, function () { + const invalidAccount = { + TransactionType: 'Clawback', + Amount: { + mpt_issuance_id: '000004C463C52827307480341125DA0577DEFC38405B0E3E', + value: '10', + }, + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenHolder: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + } as any + + assert.throws( + () => validate(invalidAccount), + ValidationError, + 'Clawback: invalid holder Account', + ) + }) + + it(`throws w/ invalid MPTokenHolder`, function () { + const invalidAccount = { + TransactionType: 'Clawback', + Amount: { + mpt_issuance_id: '000004C463C52827307480341125DA0577DEFC38405B0E3E', + value: '10', + }, + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + } as any + + assert.throws( + () => validate(invalidAccount), + ValidationError, + 'Clawback: missing MPTokenHolder', + ) + }) + + it(`throws w/ invalid currency MPTokenHolder`, function () { + const invalidAccount = { + TransactionType: 'Clawback', + Amount: { + currency: 'DSH', + issuer: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', + value: '43.11584856965009', + }, + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MPTokenHolder: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', + } as any + + assert.throws( + () => validate(invalidAccount), + ValidationError, + 'Clawback: cannot have MPTokenHolder for currency', + ) + }) }) diff --git a/packages/xrpl/test/models/payment.test.ts b/packages/xrpl/test/models/payment.test.ts index a21eaddaf6..7ad9e26237 100644 --- a/packages/xrpl/test/models/payment.test.ts +++ b/packages/xrpl/test/models/payment.test.ts @@ -1,6 +1,11 @@ import { assert } from 'chai' -import { validate, PaymentFlags, ValidationError } from '../../src' +import { + validate, + PaymentFlags, + ValidationError, + mptDecimalToHex, +} from '../../src' import { validatePayment } from '../../src/models/transactions/payment' /** @@ -258,4 +263,18 @@ describe('Payment', function () { 'PaymentTransaction: tfPartialPayment flag required with DeliverMin', ) }) + + it(`verifies valid MPT PaymentTransaction`, function () { + const mptPaymentTransaction = { + TransactionType: 'Payment', + Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo', + Amount: { + mpt_issuance_id: '000004C463C52827307480341125DA0577DEFC38405B0E3E', + value: mptDecimalToHex('10'), + }, + Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', + } as any + assert.doesNotThrow(() => validatePayment(mptPaymentTransaction)) + assert.doesNotThrow(() => validate(mptPaymentTransaction)) + }) }) From 229a0c77a321e59371ff7f8560f6cbcef6c8ea96 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 7 Mar 2024 11:25:08 -0500 Subject: [PATCH 12/53] wip --- packages/xrpl/src/utils/mptConversion.ts | 3 +- .../integration/transactions/clawback.test.ts | 64 +++++++++++++++++++ packages/xrpl/test/models/payment.test.ts | 9 +-- 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/packages/xrpl/src/utils/mptConversion.ts b/packages/xrpl/src/utils/mptConversion.ts index 83e2e30519..9ad7f31023 100644 --- a/packages/xrpl/src/utils/mptConversion.ts +++ b/packages/xrpl/src/utils/mptConversion.ts @@ -4,7 +4,8 @@ import { ValidationError } from '../errors' const SANITY_CHECK = /^[0-9]+$/u /** - * Convert an integer string to hex string. + * Convert an 64-bit integer string to hex string. Mostly used for the MaximumAmount field + * in MPTokenIssuanceCreate. * * @param numberToConvert - Non-negative number string. * @returns Amount in hex string. diff --git a/packages/xrpl/test/integration/transactions/clawback.test.ts b/packages/xrpl/test/integration/transactions/clawback.test.ts index e0b3aedc43..b8bd8ffacc 100644 --- a/packages/xrpl/test/integration/transactions/clawback.test.ts +++ b/packages/xrpl/test/integration/transactions/clawback.test.ts @@ -6,6 +6,11 @@ import { TrustSet, Payment, Clawback, + MPTokenIssuanceCreate, + MPTokenIssuanceCreateFlags, + MPTokenAuthorize, + TransactionMetadata, + mptDecimalToHex, } from '../../../src' import serverUrl from '../serverUrl' import { @@ -112,4 +117,63 @@ describe('Clawback', function () { }, TIMEOUT, ) + + it( + 'MPToken', + async () => { + const wallet2 = await generateFundedWallet(testContext.client) + const createTx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + Flags: MPTokenIssuanceCreateFlags.tfMPTCanClawback, + } + + let mptCreateRes = await testTransaction( + testContext.client, + createTx, + testContext.wallet, + ) + const txHash = mptCreateRes.result.tx_json.hash + + let txResponse = await testContext.client.request({ + command: 'tx', + transaction: txHash, + }) + + const meta = txResponse.result + .meta as TransactionMetadata + + const mptID = meta.mpt_issuance_id + + const holderAuthTx: MPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: wallet2.classicAddress, + MPTokenIssuanceID: mptID!, + } + + await testTransaction(testContext.client, holderAuthTx, wallet2) + + const paymentTx: Payment = { + TransactionType: 'Payment', + Account: testContext.wallet.classicAddress, + Amount: { mpt_issuance_id: mptID!, value: mptDecimalToHex('10') }, + Destination: wallet2.classicAddress, + } + + await testTransaction(testContext.client, paymentTx, testContext.wallet) + + // actual test - clawback + const clawTx: Clawback = { + TransactionType: 'Clawback', + Account: testContext.wallet.classicAddress, + Amount: { + mpt_issuance_id: mptID!, + issuer: wallet2.classicAddress, + value: '500', + }, + } + await testTransaction(testContext.client, clawTx, testContext.wallet) + }, + TIMEOUT, + ) }) diff --git a/packages/xrpl/test/models/payment.test.ts b/packages/xrpl/test/models/payment.test.ts index 7ad9e26237..d4e36d5486 100644 --- a/packages/xrpl/test/models/payment.test.ts +++ b/packages/xrpl/test/models/payment.test.ts @@ -1,11 +1,6 @@ import { assert } from 'chai' -import { - validate, - PaymentFlags, - ValidationError, - mptDecimalToHex, -} from '../../src' +import { validate, PaymentFlags, ValidationError } from '../../src' import { validatePayment } from '../../src/models/transactions/payment' /** @@ -270,7 +265,7 @@ describe('Payment', function () { Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo', Amount: { mpt_issuance_id: '000004C463C52827307480341125DA0577DEFC38405B0E3E', - value: mptDecimalToHex('10'), + value: '10', }, Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', } as any From cfc797945563db8e7a41b45bcc383af6166c4b7f Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 7 Mar 2024 12:35:52 -0500 Subject: [PATCH 13/53] fix binary codec test fail --- .../ripple-binary-codec/src/types/amount.ts | 482 ++++-------------- 1 file changed, 113 insertions(+), 369 deletions(-) diff --git a/packages/ripple-binary-codec/src/types/amount.ts b/packages/ripple-binary-codec/src/types/amount.ts index 6a62ce4652..5a66db7772 100644 --- a/packages/ripple-binary-codec/src/types/amount.ts +++ b/packages/ripple-binary-codec/src/types/amount.ts @@ -6,6 +6,7 @@ import { JsonObject, SerializedType } from './serialized-type' import BigNumber from 'bignumber.js' import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils' import { readUInt32BE, writeUInt32BE } from '../utils' +import { Hash192 } from './hash-192' /** * Constants for validating amounts @@ -16,7 +17,7 @@ const MAX_IOU_PRECISION = 16 const MAX_DROPS = new BigNumber('1e17') const MIN_XRP = new BigNumber('1e-6') const mask = BigInt(0x00000000ffffffff) -//const mptMask = BigInt(0x7fffffffffffffff) +const mptMask = BigInt(0x8000000000000000) /** * BigNumber configuration for Amount IOUs @@ -28,20 +29,28 @@ BigNumber.config({ ], }) -/** - * Interface for JSON objects that represent amounts - */ -interface AmountObject extends JsonObject { +interface AmountObjectIOU extends JsonObject { value: string currency: string issuer: string } +interface AmountObjectMPT extends JsonObject { + value: string + mptIssuanceID: string +} + /** - * Type guard for AmountObject + * Interface for JSON objects that represent amounts */ -function isAmountObject(arg): arg is AmountObject { +type AmountObject = AmountObjectIOU | AmountObjectMPT + +/** + * Type guard for AmountObjectIOU + */ +function isAmountObjectIOU(arg): arg is AmountObjectIOU { const keys = Object.keys(arg).sort() + return ( keys.length === 3 && keys[0] === 'currency' && @@ -50,6 +59,15 @@ function isAmountObject(arg): arg is AmountObject { ) } +/** + * Type guard for AmountObjectMPT + */ +function isAmountObjectMPT(arg): arg is AmountObjectMPT { + const keys = Object.keys(arg).sort() + + return keys.length === 2 && keys[0] === 'mptIssuanceID' && keys[1] === 'value' +} + /** * Class for serializing/Deserializing Amounts */ @@ -61,7 +79,7 @@ class Amount extends SerializedType { } /** - * Construct an amount from an IOU or string amount + * Construct an amount from an IOU, MPT or string amount * * @param value An Amount, object representing an IOU, or a string * representing an integer amount @@ -89,7 +107,7 @@ class Amount extends SerializedType { return new Amount(amount) } - if (isAmountObject(value)) { + if (isAmountObjectIOU(value)) { const number = new BigNumber(value.value) Amount.assertIouIsValid(number) @@ -125,6 +143,24 @@ class Amount extends SerializedType { return new Amount(concat([amount, currency, issuer])) } + if (isAmountObjectMPT(value)) { + Amount.assertMptIsValid(value.value) + + let leadingByte = new Uint8Array(1) + leadingByte[0] |= 0x60 + + const num = BigInt(value.value) + + const intBuf = [new Uint8Array(4), new Uint8Array(4)] + writeUInt32BE(intBuf[0], Number(num >> BigInt(32)), 0) + writeUInt32BE(intBuf[1], Number(num & BigInt(mask)), 0) + + amount = concat(intBuf) + + const mptIssuanceID = Hash192.from(value.mptIssuanceID).toBytes() + return new Amount(concat([leadingByte, amount, mptIssuanceID])) + } + throw new Error('Invalid type to construct an Amount') } @@ -135,8 +171,12 @@ class Amount extends SerializedType { * @returns An Amount object */ static fromParser(parser: BinaryParser): Amount { - const isXRP = parser.peek() & 0x80 - const numBytes = isXRP ? 48 : 8 + const isIOU = parser.peek() & 0x80 + if (isIOU) return new Amount(parser.read(48)) + + // the amount can be either MPT or XRP at this point + const isMPT = parser.peek() & 0x20 + const numBytes = isMPT ? 33 : 8 return new Amount(parser.read(numBytes)) } @@ -157,7 +197,9 @@ class Amount extends SerializedType { const num = (msb << BigInt(32)) | lsb return `${sign}${num.toString()}` - } else { + } + + if (this.isIOU()) { const parser = new BinaryParser(this.toString()) const mantissa = parser.read(8) const currency = Currency.fromParser(parser) as Currency @@ -183,6 +225,27 @@ class Amount extends SerializedType { issuer: issuer.toJSON(), } } + + if (this.isMPT()) { + const parser = new BinaryParser(this.toString()) + const leadingByte = parser.read(1) + const amount = parser.read(8) + const mptID = Hash192.fromParser(parser) as Hash192 + + const isPositive = leadingByte[0] & 0x40 + const sign = isPositive ? '' : '-' + + const msb = BigInt(readUInt32BE(amount.slice(0, 4), 0)) + const lsb = BigInt(readUInt32BE(amount.slice(4), 0)) + const num = (msb << BigInt(32)) | lsb + + return { + value: `${sign}${num.toString()}`, + mptIssuanceID: mptID.toString(), + } + } + + throw new Error('Invalid amount to construct JSON') } /** @@ -225,6 +288,25 @@ class Amount extends SerializedType { } } + /** + * Validate MPT.value amount + * + * @param decimal BigNumber object representing MPT.value + * @returns void, but will throw if invalid amount + */ + private static assertMptIsValid(amount: string): void { + if (amount.indexOf('.') !== -1) { + throw new Error(`${amount.toString()} is an illegal amount`) + } + + const decimal = new BigNumber(amount) + if (!decimal.isZero()) { + if (Number(BigInt(amount) & BigInt(mptMask)) != 0) { + throw new Error(`${amount.toString()} is an illegal amount`) + } + } + } + /** * Ensure that the value after being multiplied by the exponent does not * contain a decimal. @@ -249,364 +331,26 @@ class Amount extends SerializedType { * @returns true if Native (XRP) */ private isNative(): boolean { - return (this.bytes[0] & 0x80) === 0 + return (this.bytes[0] & 0x80) === 0 && (this.bytes[0] & 0x20) === 0 + } + + /** + * Test if this amount is in units of MPT + * + * @returns true if MPT + */ + private isMPT(): boolean { + return (this.bytes[0] & 0x80) === 0 && (this.bytes[0] & 0x20) !== 0 + } + + /** + * Test if this amount is in units of IOU + * + * @returns true if IOU + */ + private isIOU(): boolean { + return (this.bytes[0] & 0x80) !== 0 } } export { Amount, AmountObject } -// import { BinaryParser } from '../serdes/binary-parser' - -// import { AccountID } from './account-id' -// import { Currency } from './currency' -// import { JsonObject, SerializedType } from './serialized-type' -// import BigNumber from 'bignumber.js' -// import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils' -// import { readUInt32BE, writeUInt32BE } from '../utils' -// import { Hash192 } from './hash-192' - -// /** -// * Constants for validating amounts -// */ -// const MIN_IOU_EXPONENT = -96 -// const MAX_IOU_EXPONENT = 80 -// const MAX_IOU_PRECISION = 16 -// const MAX_DROPS = new BigNumber('1e17') -// const MIN_XRP = new BigNumber('1e-6') -// const mask = BigInt(0x00000000ffffffff) -// const mptMask = BigInt(0x7fffffffffffffff) - -// /** -// * BigNumber configuration for Amount IOUs -// */ -// BigNumber.config({ -// EXPONENTIAL_AT: [ -// MIN_IOU_EXPONENT - MAX_IOU_PRECISION, -// MAX_IOU_EXPONENT + MAX_IOU_PRECISION, -// ], -// }) - -// interface AmountObjectIOU extends JsonObject { -// value: string -// currency: string -// issuer: string -// } - -// interface AmountObjectMPT extends JsonObject { -// value: string -// mptIssuanceID: string -// } - -// /** -// * Interface for JSON objects that represent amounts -// */ -// type AmountObject = AmountObjectIOU | AmountObjectMPT - -// /** -// * Type guard for AmountObjectIOU -// */ -// function isAmountObjectIOU(arg): arg is AmountObjectIOU { -// const keys = Object.keys(arg).sort() - -// return ( -// keys.length === 3 && -// keys[0] === 'currency' && -// keys[1] === 'issuer' && -// keys[2] === 'value' -// ) -// } - -// /** -// * Type guard for AmountObjectMPT -// */ -// function isAmountObjectMPT(arg): arg is AmountObjectMPT { -// const keys = Object.keys(arg).sort() - -// return keys.length === 2 && keys[0] === 'mptIssuanceID' && keys[1] === 'value' -// } - -// /** -// * Class for serializing/Deserializing Amounts -// */ -// class Amount extends SerializedType { -// static defaultAmount: Amount = new Amount(hexToBytes('4000000000000000')) - -// constructor(bytes: Uint8Array) { -// super(bytes ?? Amount.defaultAmount.bytes) -// } - -// /** -// * Construct an amount from an IOU, MPT or string amount -// * -// * @param value An Amount, object representing an IOU, or a string -// * representing an integer amount -// * @returns An Amount object -// */ -// static from(value: T): Amount { -// if (value instanceof Amount) { -// return value -// } - -// let amount = new Uint8Array(8) -// if (typeof value === 'string') { -// Amount.assertXrpIsValid(value) - -// const number = BigInt(value) - -// const intBuf = [new Uint8Array(4), new Uint8Array(4)] -// writeUInt32BE(intBuf[0], Number(number >> BigInt(32)), 0) -// writeUInt32BE(intBuf[1], Number(number & BigInt(mask)), 0) - -// amount = concat(intBuf) - -// amount[0] |= 0x40 - -// return new Amount(amount) -// } - -// if (isAmountObjectIOU(value)) { -// const number = new BigNumber(value.value) -// Amount.assertIouIsValid(number) - -// if (number.isZero()) { -// amount[0] |= 0x80 -// } else { -// const integerNumberString = number -// .times(`1e${-((number.e || 0) - 15)}`) -// .abs() -// .toString() - -// const num = BigInt(integerNumberString) -// const intBuf = [new Uint8Array(4), new Uint8Array(4)] -// writeUInt32BE(intBuf[0], Number(num >> BigInt(32)), 0) -// writeUInt32BE(intBuf[1], Number(num & BigInt(mask)), 0) - -// amount = concat(intBuf) - -// amount[0] |= 0x80 - -// if (number.gt(new BigNumber(0))) { -// amount[0] |= 0x40 -// } - -// const exponent = (number.e || 0) - 15 -// const exponentByte = 97 + exponent -// amount[0] |= exponentByte >>> 2 -// amount[1] |= (exponentByte & 0x03) << 6 -// } - -// const currency = Currency.from(value.currency).toBytes() -// const issuer = AccountID.from(value.issuer).toBytes() -// return new Amount(concat([amount, currency, issuer])) -// } - -// if (isAmountObjectMPT(value)) { -// Amount.assertMptIsValid(value.value) - -// let leadingByte = new Uint8Array(1) -// leadingByte[0] |= 0x60 - -// const num = BigInt(value.value) - -// const intBuf = [new Uint8Array(4), new Uint8Array(4)] -// writeUInt32BE(intBuf[0], Number(num >> BigInt(32)), 0) -// writeUInt32BE(intBuf[1], Number(num & BigInt(mask)), 0) - -// amount = concat(intBuf) - -// const mptIssuanceID = Hash192.from(value.mptIssuanceID).toBytes() -// return new Amount(concat([leadingByte, amount, mptIssuanceID])) -// } - -// throw new Error('Invalid type to construct an Amount') -// } - -// /** -// * Read an amount from a BinaryParser -// * -// * @param parser BinaryParser to read the Amount from -// * @returns An Amount object -// */ -// static fromParser(parser: BinaryParser): Amount { -// const isIOU = parser.peek() & 0x80 -// if (isIOU) return new Amount(parser.read(48)) - -// // the amount can be either MPT or XRP at this point -// const isMPT = parser.peek() & 0x20 -// const numBytes = isMPT ? 33 : 8 -// return new Amount(parser.read(numBytes)) -// } - -// /** -// * Get the JSON representation of this Amount -// * -// * @returns the JSON interpretation of this.bytes -// */ -// toJSON(): AmountObject | string { -// if (this.isNative()) { -// const bytes = this.bytes -// const isPositive = bytes[0] & 0x40 -// const sign = isPositive ? '' : '-' -// bytes[0] &= 0x3f - -// const msb = BigInt(readUInt32BE(bytes.slice(0, 4), 0)) -// const lsb = BigInt(readUInt32BE(bytes.slice(4), 0)) -// const num = (msb << BigInt(32)) | lsb - -// return `${sign}${num.toString()}` -// } - -// if (this.isIOU()) { -// const parser = new BinaryParser(this.toString()) -// const mantissa = parser.read(8) -// const currency = Currency.fromParser(parser) as Currency -// const issuer = AccountID.fromParser(parser) as AccountID - -// const b1 = mantissa[0] -// const b2 = mantissa[1] - -// const isPositive = b1 & 0x40 -// const sign = isPositive ? '' : '-' -// const exponent = ((b1 & 0x3f) << 2) + ((b2 & 0xff) >> 6) - 97 - -// mantissa[0] = 0 -// mantissa[1] &= 0x3f -// const value = new BigNumber(`${sign}0x${bytesToHex(mantissa)}`).times( -// `1e${exponent}`, -// ) -// Amount.assertIouIsValid(value) - -// return { -// value: value.toString(), -// currency: currency.toJSON(), -// issuer: issuer.toJSON(), -// } -// } - -// if (this.isMPT()) { -// const parser = new BinaryParser(this.toString()) -// const leadingByte = parser.read(1) -// const amount = parser.read(8) -// const mptID = Hash192.fromParser(parser) as Hash192 - -// const isPositive = leadingByte[0] & 0x40 -// const sign = isPositive ? '' : '-' - -// const msb = BigInt(readUInt32BE(amount.slice(0, 4), 0)) -// const lsb = BigInt(readUInt32BE(amount.slice(4), 0)) -// const num = (msb << BigInt(32)) | lsb - -// return { -// value: `${sign}${num.toString()}`, -// mptIssuanceID: mptID.toString(), -// } -// } - -// throw new Error('Invalid amount to construct JSON') -// } - -// /** -// * Validate XRP amount -// * -// * @param amount String representing XRP amount -// * @returns void, but will throw if invalid amount -// */ -// private static assertXrpIsValid(amount: string): void { -// if (amount.indexOf('.') !== -1) { -// throw new Error(`${amount.toString()} is an illegal amount`) -// } - -// const decimal = new BigNumber(amount) -// if (!decimal.isZero()) { -// if (decimal.lt(MIN_XRP) || decimal.gt(MAX_DROPS)) { -// throw new Error(`${amount.toString()} is an illegal amount`) -// } -// } -// } - -// /** -// * Validate IOU.value amount -// * -// * @param decimal BigNumber object representing IOU.value -// * @returns void, but will throw if invalid amount -// */ -// private static assertIouIsValid(decimal: BigNumber): void { -// if (!decimal.isZero()) { -// const p = decimal.precision() -// const e = (decimal.e || 0) - 15 -// if ( -// p > MAX_IOU_PRECISION || -// e > MAX_IOU_EXPONENT || -// e < MIN_IOU_EXPONENT -// ) { -// throw new Error('Decimal precision out of range') -// } -// this.verifyNoDecimal(decimal) -// } -// } - -// /** -// * Validate MPT.value amount -// * -// * @param decimal BigNumber object representing MPT.value -// * @returns void, but will throw if invalid amount -// */ -// private static assertMptIsValid(amount: string): void { -// if (amount.indexOf('.') !== -1) { -// throw new Error(`${amount.toString()} is an illegal amount`) -// } - -// const decimal = new BigNumber(amount) -// if (!decimal.isZero()) { -// if (Number(BigInt(amount) & BigInt(0x8000000000000000) != 0) { -// throw new Error(`${amount.toString()} is an illegal amount`) -// } -// } -// } - -// /** -// * Ensure that the value after being multiplied by the exponent does not -// * contain a decimal. -// * -// * @param decimal a Decimal object -// * @returns a string of the object without a decimal -// */ -// private static verifyNoDecimal(decimal: BigNumber): void { -// const integerNumberString = decimal -// .times(`1e${-((decimal.e || 0) - 15)}`) -// .abs() -// .toString() - -// if (integerNumberString.indexOf('.') !== -1) { -// throw new Error('Decimal place found in integerNumberString') -// } -// } - -// /** -// * Test if this amount is in units of Native Currency(XRP) -// * -// * @returns true if Native (XRP) -// */ -// private isNative(): boolean { -// return (this.bytes[0] & 0x80) === 0 && (this.bytes[0] | 0x20) === 0 -// } - -// /** -// * Test if this amount is in units of MPT -// * -// * @returns true if MPT -// */ -// private isMPT(): boolean { -// return (this.bytes[0] & 0x80) === 0 && (this.bytes[0] & 0x20) !== 0 -// } - -// /** -// * Test if this amount is in units of IOU -// * -// * @returns true if IOU -// */ -// private isIOU(): boolean { -// return (this.bytes[0] & 0x80) !== 0 -// } -// } - -// export { Amount, AmountObject } From d53ef0ede520c949bd4e42455b66fd46f14d21bf Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 7 Mar 2024 15:17:11 -0500 Subject: [PATCH 14/53] has192 test --- .../ripple-binary-codec/test/hash.test.ts | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/ripple-binary-codec/test/hash.test.ts b/packages/ripple-binary-codec/test/hash.test.ts index 246ac4c7c0..6b0447fe5b 100644 --- a/packages/ripple-binary-codec/test/hash.test.ts +++ b/packages/ripple-binary-codec/test/hash.test.ts @@ -1,4 +1,11 @@ -import { Hash128, Hash160, Hash256, AccountID, Currency } from '../src/types' +import { + Hash128, + Hash160, + Hash192, + Hash256, + AccountID, + Currency, +} from '../src/types' describe('Hash128', function () { it('has a static width member', function () { @@ -51,6 +58,33 @@ describe('Hash160', function () { }) }) +describe('Hash192', function () { + it('has a static width member', function () { + expect(Hash192.width).toBe(24) + }) + it('has a ZERO_192 member', function () { + expect(Hash192.ZERO_192.toJSON()).toBe( + '000000000000000000000000000000000000000000000000', + ) + }) + it('can be compared against another', function () { + const h1 = Hash192.from('100000000000000000000000000000000000000000000000') + const h2 = Hash192.from('200000000000000000000000000000000000000000000000') + const h3 = Hash192.from('000000000000000000000000000000000000000000000003') + expect(h1.lt(h2)).toBe(true) + expect(h3.lt(h2)).toBe(true) + }) + + it('throws when constructed from invalid hash length', () => { + expect(() => + Hash192.from('10000000000000000000000000000000000000000000000'), + ).toThrow(new Error('Invalid Hash length 23')) + expect(() => + Hash192.from('10000000000000000000000000000000000000000000000000'), + ).toThrow(new Error('Invalid Hash length 25')) + }) +}) + describe('Hash256', function () { it('has a static width member', function () { expect(Hash256.width).toBe(32) From 034bebbdff932de9fa20f7485329b8f3ce76da19 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 7 Mar 2024 16:07:05 -0500 Subject: [PATCH 15/53] amount simple serialization test --- packages/ripple-binary-codec/src/types/amount.ts | 10 ++++++---- packages/ripple-binary-codec/test/amount.test.ts | 16 ++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/ripple-binary-codec/src/types/amount.ts b/packages/ripple-binary-codec/src/types/amount.ts index 5a66db7772..1d5482cb5d 100644 --- a/packages/ripple-binary-codec/src/types/amount.ts +++ b/packages/ripple-binary-codec/src/types/amount.ts @@ -37,7 +37,7 @@ interface AmountObjectIOU extends JsonObject { interface AmountObjectMPT extends JsonObject { value: string - mptIssuanceID: string + mpt_issuance_id: string } /** @@ -65,7 +65,9 @@ function isAmountObjectIOU(arg): arg is AmountObjectIOU { function isAmountObjectMPT(arg): arg is AmountObjectMPT { const keys = Object.keys(arg).sort() - return keys.length === 2 && keys[0] === 'mptIssuanceID' && keys[1] === 'value' + return ( + keys.length === 2 && keys[0] === 'mpt_issuance_id' && keys[1] === 'value' + ) } /** @@ -157,7 +159,7 @@ class Amount extends SerializedType { amount = concat(intBuf) - const mptIssuanceID = Hash192.from(value.mptIssuanceID).toBytes() + const mptIssuanceID = Hash192.from(value.mpt_issuance_id).toBytes() return new Amount(concat([leadingByte, amount, mptIssuanceID])) } @@ -241,7 +243,7 @@ class Amount extends SerializedType { return { value: `${sign}${num.toString()}`, - mptIssuanceID: mptID.toString(), + mpt_issuance_id: mptID.toString(), } } diff --git a/packages/ripple-binary-codec/test/amount.test.ts b/packages/ripple-binary-codec/test/amount.test.ts index 82b8c1c903..d99fb08103 100644 --- a/packages/ripple-binary-codec/test/amount.test.ts +++ b/packages/ripple-binary-codec/test/amount.test.ts @@ -38,5 +38,21 @@ describe('Amount', function () { } expect(amt.toJSON()).toEqual(rewritten) }) + + it('can be parsed from MPT', function () { + let fixture = { + value: '100', + mpt_issuance_id: '00002403C84A0A28E0190E208E982C352BBD5006600555CF', + } + let amt = Amount.from(fixture) + expect(amt.toJSON()).toEqual(fixture) + + fixture = { + value: '9223372036854775807', + mpt_issuance_id: '00002403C84A0A28E0190E208E982C352BBD5006600555CF', + } + amt = Amount.from(fixture) + expect(amt.toJSON()).toEqual(fixture) + }) amountErrorTests() }) From 8449439c15d7a2ca4e83b95adfb431737e37f391 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 8 Mar 2024 09:43:18 -0500 Subject: [PATCH 16/53] fix test --- packages/xrpl/test/integration/transactions/clawback.test.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/xrpl/test/integration/transactions/clawback.test.ts b/packages/xrpl/test/integration/transactions/clawback.test.ts index b8bd8ffacc..e0a77b4941 100644 --- a/packages/xrpl/test/integration/transactions/clawback.test.ts +++ b/packages/xrpl/test/integration/transactions/clawback.test.ts @@ -10,7 +10,6 @@ import { MPTokenIssuanceCreateFlags, MPTokenAuthorize, TransactionMetadata, - mptDecimalToHex, } from '../../../src' import serverUrl from '../serverUrl' import { @@ -156,7 +155,7 @@ describe('Clawback', function () { const paymentTx: Payment = { TransactionType: 'Payment', Account: testContext.wallet.classicAddress, - Amount: { mpt_issuance_id: mptID!, value: mptDecimalToHex('10') }, + Amount: { mpt_issuance_id: mptID!, value: '10' }, Destination: wallet2.classicAddress, } @@ -168,7 +167,6 @@ describe('Clawback', function () { Account: testContext.wallet.classicAddress, Amount: { mpt_issuance_id: mptID!, - issuer: wallet2.classicAddress, value: '500', }, } From a11a395f950011defc7ff7bd8398fcb4f868963b Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 11 Mar 2024 16:26:08 -0400 Subject: [PATCH 17/53] some amount tests --- .../ripple-binary-codec/src/types/amount.ts | 4 + .../test/binary-parser.test.ts | 14 ++- .../test/fixtures/data-driven-tests.json | 87 ++++++++++++++++++- 3 files changed, 102 insertions(+), 3 deletions(-) diff --git a/packages/ripple-binary-codec/src/types/amount.ts b/packages/ripple-binary-codec/src/types/amount.ts index 1d5482cb5d..44fb567441 100644 --- a/packages/ripple-binary-codec/src/types/amount.ts +++ b/packages/ripple-binary-codec/src/types/amount.ts @@ -303,6 +303,10 @@ class Amount extends SerializedType { const decimal = new BigNumber(amount) if (!decimal.isZero()) { + if (decimal < BigNumber(0)) { + throw new Error(`${amount.toString()} is an illegal amount`) + } + if (Number(BigInt(amount) & BigInt(mptMask)) != 0) { throw new Error(`${amount.toString()} is an illegal amount`) } diff --git a/packages/ripple-binary-codec/test/binary-parser.test.ts b/packages/ripple-binary-codec/test/binary-parser.test.ts index eef94c4f1c..422be52091 100644 --- a/packages/ripple-binary-codec/test/binary-parser.test.ts +++ b/packages/ripple-binary-codec/test/binary-parser.test.ts @@ -22,6 +22,7 @@ function assertEqualAmountJSON(actual, expected) { } expect(actual.currency).toEqual(expected.currency) expect(actual.issuer).toEqual(expected.issuer) + expect(actual.mpt_issuance_id).toEqual(expected.mpt_issuance_id) expect( actual.value === expected.value || new BigNumber(actual.value).eq(new BigNumber(expected.value)), @@ -207,12 +208,12 @@ function amountParsingTests() { return } const parser = makeParser(f.expected_hex) - const testName = `values_tests[${i}] parses ${f.expected_hex.slice( + const hexToJsonTestName = `values_tests[${i}] parses ${f.expected_hex.slice( 0, 16, )}... as ${JSON.stringify(f.test_json)}` - it(testName, () => { + it(hexToJsonTestName, () => { const value = parser.readType(Amount) // May not actually be in canonical form. The fixtures are to be used // also for json -> binary; @@ -223,6 +224,15 @@ function amountParsingTests() { expect((exponent.e ?? 0) - 15).toEqual(f?.exponent) } }) + + const jsonToHexTestName = `values_tests[${i}] parses ${JSON.stringify( + f.test_json, + )} ${f.expected_hex!.slice(0, 16)}... + as ${JSON.stringify(f.test_json)}` + it(jsonToHexTestName, () => { + const amt = Amount.from(f.test_json) + expect(amt.toHex()).toEqual(f.expected_hex) + }) }) } diff --git a/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json b/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json index 658c15660c..c6dac50ee3 100644 --- a/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json +++ b/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json @@ -2499,7 +2499,7 @@ "type_id": 6, "is_native": true, "type": "Amount", - "expected_hex": "0000000000000001", + "error": "Value is negative", "is_negative": true }, { @@ -2914,6 +2914,91 @@ "type": "Amount", "error": "10000000000000000000 absolute XRP is bigger than max native value 100000000000.0", "is_negative": true + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "9223372036854775808" + }, + "type": "Amount", + "error": "Value is too large" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "-1" + }, + "type": "Amount", + "error": "Value is negative" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "10.1" + }, + "type": "Amount", + "error": "Value has decimal point" + }, + { + "test_json": { + "mpt_issuance_id": "10", + "value": "10" + }, + "type": "Amount", + "error": "mpt_issuance_id has invalid hash length" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "10", + "issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji" + }, + "type": "Amount", + "error": "Issuer not valid for MPT" + }, + { + "test_json": { + "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "9223372036854775807" + }, + "type_id": 6, + "is_native": false, + "type": "Amount", + "expected_hex": "607FFFFFFFFFFFFFFF00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "is_negative": false + }, + { + "test_json": { + "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "0" + }, + "type_id": 6, + "is_native": false, + "type": "Amount", + "expected_hex": "60000000000000000000002403C84A0A28E0190E208E982C352BBD5006600555CF", + "is_negative": false + }, + { + "test_json": { + "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "-0" + }, + "type_id": 6, + "is_native": false, + "type": "Amount", + "expected_hex": "60000000000000000000002403C84A0A28E0190E208E982C352BBD5006600555CF", + "is_negative": false + }, + { + "test_json": { + "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "100" + }, + "type_id": 6, + "is_native": false, + "type": "Amount", + "expected_hex": "60000000000000006400002403C84A0A28E0190E208E982C352BBD5006600555CF", + "is_negative": false } ] } From 0ff4169d266b154b8b809d71762de03f96968571 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 12 Mar 2024 11:21:29 -0400 Subject: [PATCH 18/53] more tests --- .../test/binary-parser.test.ts | 4 +- .../test/fixtures/data-driven-tests.json | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/ripple-binary-codec/test/binary-parser.test.ts b/packages/ripple-binary-codec/test/binary-parser.test.ts index 422be52091..845c4ddd20 100644 --- a/packages/ripple-binary-codec/test/binary-parser.test.ts +++ b/packages/ripple-binary-codec/test/binary-parser.test.ts @@ -227,8 +227,8 @@ function amountParsingTests() { const jsonToHexTestName = `values_tests[${i}] parses ${JSON.stringify( f.test_json, - )} ${f.expected_hex!.slice(0, 16)}... - as ${JSON.stringify(f.test_json)}` + )}... + as ${f.expected_hex.slice(0, 16)}` it(jsonToHexTestName, () => { const amt = Amount.from(f.test_json) expect(amt.toHex()).toEqual(f.expected_hex) diff --git a/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json b/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json index c6dac50ee3..26e2f76641 100644 --- a/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json +++ b/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json @@ -2956,6 +2956,39 @@ "type": "Amount", "error": "Issuer not valid for MPT" }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "10", + "currency": "USD" + }, + "type": "Amount", + "error": "Currency not valid for MPT" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "a" + }, + "type": "Amount", + "error": "Value has incorrect hex format" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "0xy" + }, + "type": "Amount", + "error": "Value has bad hex character" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "/" + }, + "type": "Amount", + "error": "Value has bad character" + }, { "test_json": { "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", @@ -2999,6 +3032,17 @@ "type": "Amount", "expected_hex": "60000000000000006400002403C84A0A28E0190E208E982C352BBD5006600555CF", "is_negative": false + }, + { + "test_json": { + "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "0xa" + }, + "type_id": 6, + "is_native": false, + "type": "Amount", + "expected_hex": "60000000000000000A00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "is_negative": false } ] } From 02d78ee90a640e9439d4881097109f086676bef8 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 12 Mar 2024 16:14:30 -0400 Subject: [PATCH 19/53] some hex value tests --- .../test/fixtures/data-driven-tests.json | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json b/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json index 26e2f76641..e7403768de 100644 --- a/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json +++ b/packages/ripple-binary-codec/test/fixtures/data-driven-tests.json @@ -2923,6 +2923,14 @@ "type": "Amount", "error": "Value is too large" }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "18446744073709551615" + }, + "type": "Amount", + "error": "Value is too large" + }, { "test_json": { "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", @@ -2989,6 +2997,22 @@ "type": "Amount", "error": "Value has bad character" }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "0x8000000000000000" + }, + "type": "Amount", + "error": "Hex value out of range" + }, + { + "test_json": { + "mpt_issuance_id": "00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "0xFFFFFFFFFFFFFFFF" + }, + "type": "Amount", + "error": "Hex value out of range" + }, { "test_json": { "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", @@ -3043,6 +3067,17 @@ "type": "Amount", "expected_hex": "60000000000000000A00002403C84A0A28E0190E208E982C352BBD5006600555CF", "is_negative": false + }, + { + "test_json": { + "mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "value": "0x7FFFFFFFFFFFFFFF" + }, + "type_id": 6, + "is_native": false, + "type": "Amount", + "expected_hex": "607FFFFFFFFFFFFFFF00002403C84A0A28E0190E208E982C352BBD5006600555CF", + "is_negative": false } ] } From 11ce3683eb48f9cc0d74868dbff2dd278a7847ab Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 13 Mar 2024 15:52:42 -0400 Subject: [PATCH 20/53] negative amount test --- .../ripple-binary-codec/test/amount.test.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/packages/ripple-binary-codec/test/amount.test.ts b/packages/ripple-binary-codec/test/amount.test.ts index d99fb08103..7a7518655f 100644 --- a/packages/ripple-binary-codec/test/amount.test.ts +++ b/packages/ripple-binary-codec/test/amount.test.ts @@ -1,5 +1,7 @@ import { coreTypes } from '../src/types' import fixtures from './fixtures/data-driven-tests.json' + +import { makeParser } from '../src/binary' const { Amount } = coreTypes function amountErrorTests() { @@ -25,6 +27,16 @@ describe('Amount', function () { it('can be parsed from', function () { expect(Amount.from('1000000') instanceof Amount).toBe(true) expect(Amount.from('1000000').toJSON()).toEqual('1000000') + + // it not valid to have negative XRP. But we test it anyways + // to ensure logic correctness for toJSON of the Amount class + { + const parser = makeParser('0000000000000001') + const value = parser.readType(Amount) + const json = value.toJSON() + expect(json).toEqual('-1') + } + const fixture = { value: '1', issuer: '0000000000000000000000000000000000000000', @@ -53,6 +65,20 @@ describe('Amount', function () { } amt = Amount.from(fixture) expect(amt.toJSON()).toEqual(fixture) + + // it not valid to have negative MPT. But we test it anyways + // to ensure logic correctness for toJSON of the Amount class + { + const parser = makeParser( + '20000000000000006400002403C84A0A28E0190E208E982C352BBD5006600555CF', + ) + const value = parser.readType(Amount) + const json = value.toJSON() + expect(json).toEqual({ + mpt_issuance_id: '00002403C84A0A28E0190E208E982C352BBD5006600555CF', + value: '-100', + }) + } }) amountErrorTests() }) From 3d56868f3b5094f7fc69be500457b9c6ad1a076e Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 14 Mar 2024 11:46:20 -0400 Subject: [PATCH 21/53] rename mptDecimalToHex --- packages/xrpl/src/utils/index.ts | 4 +-- packages/xrpl/src/utils/mptConversion.ts | 14 ++++---- .../mptokenIssuanceCreate.test.ts | 4 +-- .../test/models/MPTokenIssuanceCreate.test.ts | 4 +-- .../xrpl/test/utils/mptDecimalToHex.test.ts | 36 +++++++++---------- 5 files changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/xrpl/src/utils/index.ts b/packages/xrpl/src/utils/index.ts index b2e1d0966c..5cf117089a 100644 --- a/packages/xrpl/src/utils/index.ts +++ b/packages/xrpl/src/utils/index.ts @@ -65,7 +65,7 @@ import { } from './timeConversion' import verifyPaymentChannelClaim from './verifyPaymentChannelClaim' import { xrpToDrops, dropsToXrp } from './xrpConversion' -import { mptDecimalToHex } from './mptConversion' +import { mptUint64ToHex } from './mptConversion' /** * Check if a secret is valid. @@ -229,5 +229,5 @@ export { getNFTokenID, parseNFTokenID, getXChainClaimID, - mptDecimalToHex, + mptUint64ToHex, } diff --git a/packages/xrpl/src/utils/mptConversion.ts b/packages/xrpl/src/utils/mptConversion.ts index 9ad7f31023..156a0e4241 100644 --- a/packages/xrpl/src/utils/mptConversion.ts +++ b/packages/xrpl/src/utils/mptConversion.ts @@ -4,7 +4,7 @@ import { ValidationError } from '../errors' const SANITY_CHECK = /^[0-9]+$/u /** - * Convert an 64-bit integer string to hex string. Mostly used for the MaximumAmount field + * Convert an unsigned 64-bit integer string to hex string. Mostly used for the MaximumAmount field * in MPTokenIssuanceCreate. * * @param numberToConvert - Non-negative number string. @@ -12,32 +12,32 @@ const SANITY_CHECK = /^[0-9]+$/u * @throws When amount is invalid. * @category Utilities */ -export function mptDecimalToHex(numberToConvert: string): string { +export function mptUint64ToHex(numberToConvert: string): string { // convert to base 10 string first for inputs like scientific notation const number = new BigNumber(numberToConvert).toString(10) // check that the value is valid and actually a number if (typeof numberToConvert === 'string' && number === 'NaN') { throw new ValidationError( - `mptDecimalToHex: invalid value '${numberToConvert}', should be a string-encoded number.`, + `mptUint64ToHex: invalid value '${numberToConvert}', should be a string-encoded number.`, ) } // mpts are only whole units if (number.includes('.')) { throw new ValidationError( - `mptDecimalToHex: value '${numberToConvert}' has too many decimal places.`, + `mptUint64ToHex: value '${numberToConvert}' has too many decimal places.`, ) } if (number.includes('-')) { throw new ValidationError( - `mptDecimalToHex: value '${numberToConvert}' cannot be negative.`, + `mptUint64ToHex: value '${numberToConvert}' cannot be negative.`, ) } if (!SANITY_CHECK.exec(number)) { throw new ValidationError( - `mptDecimalToHex: failed sanity check -` + + `mptUint64ToHex: failed sanity check -` + ` value '${numberToConvert}',` + ` does not match (^[0-9]+$).`, ) @@ -45,7 +45,7 @@ export function mptDecimalToHex(numberToConvert: string): string { if (Number(BigInt(number) & BigInt('0x8000000000000000')) != 0) throw new ValidationError( - `mptDecimalToHex: invalid value '${numberToConvert}', should be within 63-bit range.`, + `mptUint64ToHex: invalid value '${numberToConvert}', should be within 63-bit range.`, ) return BigInt(number).toString(16) diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts index 3d7d80eb68..d316b9a259 100644 --- a/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts @@ -8,7 +8,7 @@ import { type XrplIntegrationTestContext, } from '../setup' import { testTransaction } from '../utils' -import { mptDecimalToHex } from '../../../src/utils' +import { mptUint64ToHex } from '../../../src/utils' // how long before each test case times out const TIMEOUT = 20000 @@ -27,7 +27,7 @@ describe('MPTokenIssuanceCreate', function () { const tx: MPTokenIssuanceCreate = { TransactionType: 'MPTokenIssuanceCreate', Account: testContext.wallet.classicAddress, - MaximumAmount: mptDecimalToHex('9223372036854775807'), // 0x7fffffffffffffff + MaximumAmount: mptUint64ToHex('9223372036854775807'), // 0x7fffffffffffffff AssetScale: 2, } diff --git a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts index 6a45cd1d73..534e6e877b 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts @@ -5,7 +5,7 @@ import { validate, ValidationError, MPTokenIssuanceCreateFlags, - mptDecimalToHex, + mptUint64ToHex, } from '../../src' /** @@ -18,7 +18,7 @@ describe('MPTokenIssuanceCreate', function () { const validMPTokenIssuanceCreate = { TransactionType: 'MPTokenIssuanceCreate', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', - MaximumAmount: mptDecimalToHex('9223372036854775807'), // 0x7fffffffffffffff + MaximumAmount: mptUint64ToHex('9223372036854775807'), // 0x7fffffffffffffff AssetScale: 2, TransferFee: 1, Flags: 2, diff --git a/packages/xrpl/test/utils/mptDecimalToHex.test.ts b/packages/xrpl/test/utils/mptDecimalToHex.test.ts index b0b5bccf2c..88cb0b0a59 100644 --- a/packages/xrpl/test/utils/mptDecimalToHex.test.ts +++ b/packages/xrpl/test/utils/mptDecimalToHex.test.ts @@ -1,13 +1,13 @@ import { assert } from 'chai' -import { mptDecimalToHex } from '../../src/utils' +import { mptUint64ToHex } from '../../src/utils' -describe('mptDecimalToHex', function () { +describe('mptUint64ToHex', function () { it('works with a typical amount', function () { - let hexStr = mptDecimalToHex('1000') + let hexStr = mptUint64ToHex('1000') assert.strictEqual(hexStr, '3e8', '1000 equals to 3e8 in hex') - hexStr = mptDecimalToHex('9223372036854775807') + hexStr = mptUint64ToHex('9223372036854775807') assert.strictEqual( hexStr, '7fffffffffffffff', @@ -16,50 +16,50 @@ describe('mptDecimalToHex', function () { }) it('works with zero', function () { - let hexStr = mptDecimalToHex('0') + let hexStr = mptUint64ToHex('0') assert.strictEqual(hexStr, '0', '0 equals to 0 in hex') - hexStr = mptDecimalToHex('000000000') + hexStr = mptUint64ToHex('000000000') assert.strictEqual(hexStr, '0', '000000000 equals 0 in hex') - hexStr = mptDecimalToHex('-0') + hexStr = mptUint64ToHex('-0') assert.strictEqual(hexStr, '0', '-0 equals 0 in hex') }) it('throws with a negative value', function () { assert.throws(() => { - mptDecimalToHex('-1') - }, "mptDecimalToHex: value '-1' cannot be negative.") + mptUint64ToHex('-1') + }, "mptUint64ToHex: value '-1' cannot be negative.") }) it('checks decimal value', function () { assert.throws(() => { - mptDecimalToHex('20000.1') - }, "mptDecimalToHex: value '20000.1' has too many decimal places.") + mptUint64ToHex('20000.1') + }, "mptUint64ToHex: value '20000.1' has too many decimal places.") - const hexStr = mptDecimalToHex('20000.') + const hexStr = mptUint64ToHex('20000.') assert.strictEqual(hexStr, '4e20', '20000. equals 4e20 in hex') }) it('works with scientific notation', function () { - const hexStr = mptDecimalToHex('1e6') + const hexStr = mptUint64ToHex('1e6') assert.strictEqual(hexStr, 'f4240', '1e6 equals f4240 in hex') }) it('throws with an value with 64-bit range', function () { assert.throws(() => { - mptDecimalToHex('9223372036854775808') - }, "mptDecimalToHex: invalid value '9223372036854775808', should be within 63-bit range.") + mptUint64ToHex('9223372036854775808') + }, "mptUint64ToHex: invalid value '9223372036854775808', should be within 63-bit range.") }) it('throws with an value with invalid format', function () { assert.throws(() => { - mptDecimalToHex('1a') - }, "mptDecimalToHex: invalid value '1a', should be a string-encoded number.") + mptUint64ToHex('1a') + }, "mptUint64ToHex: invalid value '1a', should be a string-encoded number.") }) it('works with a hex value', function () { - const hexStr = mptDecimalToHex('0x1a') + const hexStr = mptUint64ToHex('0x1a') assert.strictEqual(hexStr, '1a', '0x1a equals 1a in hex') }) }) From a9d5a9bf98cc25ebd788f6c02a64f82c20b5b06a Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 22 Apr 2024 14:28:37 -0400 Subject: [PATCH 22/53] history.md --- package-lock.json | 8 ++++---- packages/ripple-binary-codec/HISTORY.md | 4 ++++ packages/ripple-binary-codec/package.json | 2 +- packages/xrpl/HISTORY.md | 7 ++++++- packages/xrpl/package.json | 4 ++-- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index b73a164962..d0ca16c883 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15304,7 +15304,7 @@ } }, "packages/ripple-binary-codec": { - "version": "2.0.0", + "version": "1.0.0-mpt-beta", "license": "ISC", "dependencies": { "@xrplf/isomorphic": "^1.0.0", @@ -15337,7 +15337,7 @@ } }, "packages/xrpl": { - "version": "3.0.0", + "version": "1.0.0-mpt-beta", "license": "ISC", "dependencies": { "@scure/bip32": "^1.3.1", @@ -15348,7 +15348,7 @@ "cross-fetch": "^4.0.0", "eventemitter3": "^5.0.1", "ripple-address-codec": "^5.0.0", - "ripple-binary-codec": "^2.0.0", + "ripple-binary-codec": "^1.0.0-mpt-beta", "ripple-keypairs": "^2.0.0" }, "devDependencies": { @@ -27191,7 +27191,7 @@ "lodash": "^4.17.4", "react": "^18.2.0", "ripple-address-codec": "^5.0.0", - "ripple-binary-codec": "^2.0.0", + "ripple-binary-codec": "^1.0.0-mpt-beta", "ripple-keypairs": "^2.0.0", "run-s": "^0.0.0", "typedoc": "0.25.0", diff --git a/packages/ripple-binary-codec/HISTORY.md b/packages/ripple-binary-codec/HISTORY.md index 6ba00b82c1..4965804118 100644 --- a/packages/ripple-binary-codec/HISTORY.md +++ b/packages/ripple-binary-codec/HISTORY.md @@ -2,6 +2,10 @@ ## Unreleased +## 1.0.0-mpt-beta (2024-04-22) +### Non-Breaking Changes +* Added support for `MPTAmount` in `Amount` + ## 2.0.0 (2024-02-01) ### BREAKING CHANGES diff --git a/packages/ripple-binary-codec/package.json b/packages/ripple-binary-codec/package.json index ebc59834fa..adc3e58d32 100644 --- a/packages/ripple-binary-codec/package.json +++ b/packages/ripple-binary-codec/package.json @@ -1,6 +1,6 @@ { "name": "ripple-binary-codec", - "version": "2.0.0", + "version": "1.0.0-mpt-beta", "description": "XRP Ledger binary codec", "files": [ "dist/*", diff --git a/packages/xrpl/HISTORY.md b/packages/xrpl/HISTORY.md index 6384c47679..371c8dbc7c 100644 --- a/packages/xrpl/HISTORY.md +++ b/packages/xrpl/HISTORY.md @@ -4,6 +4,11 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr ## Unreleased +## 1.0.0-mpt-beta (2024-04-22) +### Non-Breaking Changes +* Added new MPT transaction models +* New `MPTAmount` type support for `Payment` and `Clawback` transactions + ## 3.0.0 (2024-02-01) ### BREAKING CHANGES @@ -52,7 +57,7 @@ Bundler configurations are much more simplified. See [../UNIQUE_STEPS](Unique St * Deprecated: * `convertHexToString` in favor of `@xrplf/isomorphic/utils`'s `hexToString` * `convertStringToHex` in favor of `@xrplf/isomorphic/utils`'s `stringToHex` - + ## 3.0.0 Beta 1 (2023-11-30) ### Breaking Changes diff --git a/packages/xrpl/package.json b/packages/xrpl/package.json index 5b466a7971..67ba4e2758 100644 --- a/packages/xrpl/package.json +++ b/packages/xrpl/package.json @@ -1,6 +1,6 @@ { "name": "xrpl", - "version": "3.0.0", + "version": "1.0.0-mpt-beta", "license": "ISC", "description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser", "files": [ @@ -30,7 +30,7 @@ "cross-fetch": "^4.0.0", "eventemitter3": "^5.0.1", "ripple-address-codec": "^5.0.0", - "ripple-binary-codec": "^2.0.0", + "ripple-binary-codec": "^1.0.0-mpt-beta", "ripple-keypairs": "^2.0.0" }, "devDependencies": { From 3093e3830704329a39645dc9367af9c6b7d0a8f7 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 22 Apr 2024 15:35:09 -0400 Subject: [PATCH 23/53] only apply mptamount for payment and claw --- packages/xrpl/src/client/partialPayment.ts | 5 ++++- packages/xrpl/src/models/common/index.ts | 2 +- packages/xrpl/src/models/transactions/metadata.ts | 6 +++--- packages/xrpl/src/models/transactions/payment.ts | 8 ++++---- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/packages/xrpl/src/client/partialPayment.ts b/packages/xrpl/src/client/partialPayment.ts index a0b249a9b4..314b6933cc 100644 --- a/packages/xrpl/src/client/partialPayment.ts +++ b/packages/xrpl/src/client/partialPayment.ts @@ -16,7 +16,10 @@ import { isFlagEnabled } from '../models/utils' const WARN_PARTIAL_PAYMENT_CODE = 2001 -function amountsEqual(amt1: Amount, amt2: Amount): boolean { +function amountsEqual( + amt1: Amount | MPTAmount, + amt2: Amount | MPTAmount, +): boolean { if (typeof amt1 === 'string' && typeof amt2 === 'string') { return amt1 === amt2 } diff --git a/packages/xrpl/src/models/common/index.ts b/packages/xrpl/src/models/common/index.ts index 9550583b05..51f5880bdd 100644 --- a/packages/xrpl/src/models/common/index.ts +++ b/packages/xrpl/src/models/common/index.ts @@ -21,7 +21,7 @@ export interface MPTAmount { value: string } -export type Amount = IssuedCurrencyAmount | MPTAmount | string +export type Amount = IssuedCurrencyAmount | string export interface Balance { currency: string diff --git a/packages/xrpl/src/models/transactions/metadata.ts b/packages/xrpl/src/models/transactions/metadata.ts index ebe7579505..6c22934a55 100644 --- a/packages/xrpl/src/models/transactions/metadata.ts +++ b/packages/xrpl/src/models/transactions/metadata.ts @@ -1,4 +1,4 @@ -import { Amount } from '../common' +import { Amount, MPTAmount } from '../common' import { BaseTransaction } from './common' import { @@ -82,9 +82,9 @@ export function isDeletedNode(node: Node): node is DeletedNode { export interface TransactionMetadataBase { AffectedNodes: Node[] - DeliveredAmount?: Amount + DeliveredAmount?: Amount | MPTAmount // "unavailable" possible for transactions before 2014-01-20 - delivered_amount?: Amount | 'unavailable' + delivered_amount?: Amount | MPTAmount | 'unavailable' TransactionIndex: number TransactionResult: string } diff --git a/packages/xrpl/src/models/transactions/payment.ts b/packages/xrpl/src/models/transactions/payment.ts index 4c1d66b379..28096b1144 100644 --- a/packages/xrpl/src/models/transactions/payment.ts +++ b/packages/xrpl/src/models/transactions/payment.ts @@ -1,5 +1,5 @@ import { ValidationError } from '../../errors' -import { Amount, Path } from '../common' +import { Amount, Path, MPTAmount } from '../common' import { isFlagEnabled } from '../utils' import { @@ -116,7 +116,7 @@ export interface Payment extends BaseTransaction { * names MUST be lower-case. If the tfPartialPayment flag is set, deliver up * to this amount instead. */ - Amount: Amount + Amount: Amount | MPTAmount /** The unique address of the account receiving the payment. */ Destination: Account /** @@ -153,8 +153,8 @@ export interface Payment extends BaseTransaction { } export interface PaymentMetadata extends TransactionMetadataBase { - DeliveredAmount?: Amount - delivered_amount?: Amount | 'unavailable' + DeliveredAmount?: Amount | MPTAmount + delivered_amount?: Amount | MPTAmount | 'unavailable' } /** From 58aca14d38959a00403d93cd7fc60be354d322ce Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 8 Jul 2024 17:52:43 -0400 Subject: [PATCH 24/53] fix test and update to latest definition --- .../src/enums/definitions.json | 138 ++++++++++++++++-- .../xrpl/src/models/methods/ledgerEntry.ts | 16 ++ .../integration/transactions/clawback.test.ts | 33 ++++- 3 files changed, 173 insertions(+), 14 deletions(-) diff --git a/packages/ripple-binary-codec/src/enums/definitions.json b/packages/ripple-binary-codec/src/enums/definitions.json index 1b6647f64a..25d2a5af51 100644 --- a/packages/ripple-binary-codec/src/enums/definitions.json +++ b/packages/ripple-binary-codec/src/enums/definitions.json @@ -23,6 +23,7 @@ "UInt512": 23, "Issue": 24, "XChainBridge": 25, + "Currency": 26, "Transaction": 10001, "LedgerEntry": 10002, "Validation": 10003, @@ -51,6 +52,7 @@ "NFTokenOffer": 55, "AMM": 121, "DID": 73, + "Oracle": 128, "MPTokenIssuance": 126, "MPToken": 127, "Any": -3, @@ -210,6 +212,16 @@ "type": "UInt8" } ], + [ + "Scale", + { + "nth": 4, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt8" + } + ], [ "TickSize", { @@ -510,6 +522,16 @@ "type": "UInt32" } ], + [ + "LastUpdateTime", + { + "nth": 15, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], [ "HighQualityIn", { @@ -840,6 +862,16 @@ "type": "UInt32" } ], + [ + "OracleDocumentID", + { + "nth": 51, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt32" + } + ], [ "IndexNext", { @@ -1041,7 +1073,7 @@ } ], [ - "MaximumAmount", + "AssetPrice", { "nth": 23, "isVLEncoded": false, @@ -1051,7 +1083,7 @@ } ], [ - "OutstandingAmount", + "MaximumAmount", { "nth": 24, "isVLEncoded": false, @@ -1061,7 +1093,7 @@ } ], [ - "LockedAmount", + "OutstandingAmount", { "nth": 25, "isVLEncoded": false, @@ -1071,7 +1103,7 @@ } ], [ - "MPTAmount", + "LockedAmount", { "nth": 26, "isVLEncoded": false, @@ -1080,6 +1112,16 @@ "type": "UInt64" } ], + [ + "MPTAmount", + { + "nth": 27, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt64" + } + ], [ "EmailHash", { @@ -1981,7 +2023,7 @@ } ], [ - "MPTokenMetadata", + "AssetClass", { "nth": 28, "isVLEncoded": true, @@ -1990,6 +2032,26 @@ "type": "Blob" } ], + [ + "Provider", + { + "nth": 29, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], + [ + "MPTokenMetadata", + { + "nth": 30, + "isVLEncoded": true, + "isSerialized": true, + "isSigningField": true, + "type": "Blob" + } + ], [ "Account", { @@ -2210,6 +2272,26 @@ "type": "PathSet" } ], + [ + "BaseAsset", + { + "nth": 1, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Currency" + } + ], + [ + "QuoteAsset", + { + "nth": 2, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "Currency" + } + ], [ "LockingChainIssue", { @@ -2540,6 +2622,16 @@ "type": "STObject" } ], + [ + "PriceData", + { + "nth": 32, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STObject" + } + ], [ "Signers", { @@ -2710,6 +2802,16 @@ "type": "STArray" } ], + [ + "PriceDataSeries", + { + "nth": 24, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "STArray" + } + ], [ "AuthAccounts", { @@ -2738,6 +2840,7 @@ "telWRONG_NETWORK": -386, "telREQUIRES_NETWORK_ID": -385, "telNETWORK_ID_MAKES_TX_NON_CANONICAL": -384, + "telENV_RPC_FAILED": -383, "temMALFORMED": -299, "temBAD_AMOUNT": -298, @@ -2786,6 +2889,9 @@ "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -255, "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -254, "temEMPTY_DID": -253, + "temARRAY_EMPTY": -252, + "temARRAY_TOO_LARGE": -251, + "temMPT_NOT_SUPPORTED": -250, "tefFAILURE": -199, "tefALREADY": -198, @@ -2822,7 +2928,6 @@ "terQUEUED": -89, "terPRE_TICKET": -88, "terNO_AMM": -87, - "terSUBMITTED": -86, "tesSUCCESS": 0, @@ -2899,9 +3004,14 @@ "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185, "tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186, "tecEMPTY_DID": 187, - "tecMPTOKEN_EXISTS": 188, - "tecMPT_MAX_AMOUNT_EXCEEDED": 189, - "tecMPT_LOCKED": 190 + "tecINVALID_UPDATE_TIME": 188, + "tecTOKEN_PAIR_NOT_FOUND": 189, + "tecARRAY_EMPTY": 190, + "tecARRAY_TOO_LARGE": 191, + "tecMPTOKEN_EXISTS": 192, + "tecMPT_MAX_AMOUNT_EXCEEDED": 193, + "tecMPT_LOCKED": 194, + "tecMPT_NOT_SUPPORTED": 195 }, "TRANSACTION_TYPES": { "Invalid": -1, @@ -2950,10 +3060,12 @@ "XChainCreateBridge": 48, "DIDSet": 49, "DIDDelete": 50, - "MPTokenIssuanceCreate": 51, - "MPTokenIssuanceDestroy": 52, - "MPTokenAuthorize": 53, - "MPTokenIssuanceSet": 54, + "OracleSet": 51, + "OracleDelete": 52, + "MPTokenIssuanceCreate": 53, + "MPTokenIssuanceDestroy": 54, + "MPTokenAuthorize": 55, + "MPTokenIssuanceSet": 56, "EnableAmendment": 100, "SetFee": 101, "UNLModify": 102 diff --git a/packages/xrpl/src/models/methods/ledgerEntry.ts b/packages/xrpl/src/models/methods/ledgerEntry.ts index 2e845f6696..1a92b9525d 100644 --- a/packages/xrpl/src/models/methods/ledgerEntry.ts +++ b/packages/xrpl/src/models/methods/ledgerEntry.ts @@ -21,6 +21,22 @@ import { BaseRequest, BaseResponse, LookupByLedgerRequest } from './baseMethod' */ export interface LedgerEntryRequest extends BaseRequest, LookupByLedgerRequest { command: 'ledger_entry' + + /** + * Retrieve a MPTokenIssuance object from the ledger. + */ + mpt_issuance?: string + + /** + * Retrieve a MPToken object from the ledger. + */ + mptoken?: + | { + mpt_issuance_id: string + account: string + } + | string + /** * Retrieve an Automated Market Maker (AMM) object from the ledger. * This is similar to amm_info method, but the ledger_entry version returns only the ledger entry as stored. diff --git a/packages/xrpl/test/integration/transactions/clawback.test.ts b/packages/xrpl/test/integration/transactions/clawback.test.ts index e0a77b4941..9d78861540 100644 --- a/packages/xrpl/test/integration/transactions/clawback.test.ts +++ b/packages/xrpl/test/integration/transactions/clawback.test.ts @@ -18,6 +18,7 @@ import { type XrplIntegrationTestContext, } from '../setup' import { generateFundedWallet, testTransaction } from '../utils' +import { LedgerEntryResponse } from '../../../src' // how long before each test case times out const TIMEOUT = 20000 @@ -155,12 +156,27 @@ describe('Clawback', function () { const paymentTx: Payment = { TransactionType: 'Payment', Account: testContext.wallet.classicAddress, - Amount: { mpt_issuance_id: mptID!, value: '10' }, + Amount: { mpt_issuance_id: mptID!, value: '9223372036854775807' }, Destination: wallet2.classicAddress, } await testTransaction(testContext.client, paymentTx, testContext.wallet) + let ledgerEntryResponse: LedgerEntryResponse = + await testContext.client.request({ + command: 'ledger_entry', + mptoken: { + mpt_issuance_id: mptID!, + account: wallet2.classicAddress, + }, + }) + + assert.equal( + // @ts-ignore + ledgerEntryResponse.result.node.MPTAmount, + '7fffffffffffffff', + ) + // actual test - clawback const clawTx: Clawback = { TransactionType: 'Clawback', @@ -169,8 +185,23 @@ describe('Clawback', function () { mpt_issuance_id: mptID!, value: '500', }, + MPTokenHolder: wallet2.classicAddress, } await testTransaction(testContext.client, clawTx, testContext.wallet) + + ledgerEntryResponse = await testContext.client.request({ + command: 'ledger_entry', + mptoken: { + mpt_issuance_id: mptID!, + account: wallet2.classicAddress, + }, + }) + + assert.equal( + // @ts-ignore + ledgerEntryResponse.result.node.MPTAmount, + '7ffffffffffffe0b', + ) }, TIMEOUT, ) From 542c2019b18eeb9a02e5c441b72ea1c69470c155 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 21 Aug 2024 09:49:05 -0400 Subject: [PATCH 25/53] update definitoin.json --- packages/ripple-binary-codec/src/enums/definitions.json | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ripple-binary-codec/src/enums/definitions.json b/packages/ripple-binary-codec/src/enums/definitions.json index 25d2a5af51..7889f35d40 100644 --- a/packages/ripple-binary-codec/src/enums/definitions.json +++ b/packages/ripple-binary-codec/src/enums/definitions.json @@ -2891,7 +2891,6 @@ "temEMPTY_DID": -253, "temARRAY_EMPTY": -252, "temARRAY_TOO_LARGE": -251, - "temMPT_NOT_SUPPORTED": -250, "tefFAILURE": -199, "tefALREADY": -198, From 637347ce884acb0c626477f14a59d482d34e4386 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 21 Aug 2024 11:00:05 -0400 Subject: [PATCH 26/53] add object type def and filter by type --- packages/xrpl/src/models/ledger/LedgerEntry.ts | 2 ++ packages/xrpl/src/models/ledger/MPToken.ts | 12 ++++++++++++ packages/xrpl/src/models/ledger/MPTokenIssuance.ts | 14 ++++++++++++++ packages/xrpl/src/models/ledger/index.ts | 4 ++++ .../transactions/mptokenAuthorize.test.ts | 2 ++ .../transactions/mptokenIssuanceCreate.test.ts | 1 + .../transactions/mptokenIssuanceDestroy.test.ts | 2 ++ .../transactions/mptokenIssuanceSet.test.ts | 1 + 8 files changed, 38 insertions(+) create mode 100644 packages/xrpl/src/models/ledger/MPToken.ts create mode 100644 packages/xrpl/src/models/ledger/MPTokenIssuance.ts diff --git a/packages/xrpl/src/models/ledger/LedgerEntry.ts b/packages/xrpl/src/models/ledger/LedgerEntry.ts index 1f8f3ec32b..ce9917db59 100644 --- a/packages/xrpl/src/models/ledger/LedgerEntry.ts +++ b/packages/xrpl/src/models/ledger/LedgerEntry.ts @@ -51,6 +51,8 @@ type LedgerEntryFilter = | 'escrow' | 'fee' | 'hashes' + | 'mpt_issuance' + | 'mptoken' | 'nft_offer' | 'nft_page' | 'offer' diff --git a/packages/xrpl/src/models/ledger/MPToken.ts b/packages/xrpl/src/models/ledger/MPToken.ts new file mode 100644 index 0000000000..6b1fd013fc --- /dev/null +++ b/packages/xrpl/src/models/ledger/MPToken.ts @@ -0,0 +1,12 @@ +import { MPTAmount } from '../common' + +import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry' + +export interface MPToken extends BaseLedgerEntry, HasPreviousTxnID { + LedgerEntryType: 'MPToken' + MPTokenIssuanceID: string + MPTAmount: MPTAmount + LockedAmount?: MPTAmount + Flags: number + OwnerNode?: string +} diff --git a/packages/xrpl/src/models/ledger/MPTokenIssuance.ts b/packages/xrpl/src/models/ledger/MPTokenIssuance.ts new file mode 100644 index 0000000000..6774933ea6 --- /dev/null +++ b/packages/xrpl/src/models/ledger/MPTokenIssuance.ts @@ -0,0 +1,14 @@ +import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry' + +export interface MPTokenIssuance extends BaseLedgerEntry, HasPreviousTxnID { + LedgerEntryType: 'MPTokenIssuance' + Flags: number + Issuer: string + AssetScale?: number + MaximumAmount?: string + OutstandingAmount: string + LockedAmount?: string + TransferFee?: number + MPTokenMetadata?: string + OwnerNode?: string +} diff --git a/packages/xrpl/src/models/ledger/index.ts b/packages/xrpl/src/models/ledger/index.ts index 4433c2cc8a..2cb591b3df 100644 --- a/packages/xrpl/src/models/ledger/index.ts +++ b/packages/xrpl/src/models/ledger/index.ts @@ -19,6 +19,8 @@ import { Ledger, LedgerV1 } from './Ledger' import { LedgerEntry, LedgerEntryFilter } from './LedgerEntry' import LedgerHashes from './LedgerHashes' import NegativeUNL, { NEGATIVE_UNL_ID } from './NegativeUNL' +import { MPTokenIssuance } from './MPTokenIssuance' +import { MPToken } from './MPToken' import { NFTokenOffer } from './NFTokenOffer' import { NFToken, NFTokenPage } from './NFTokenPage' import Offer, { OfferFlags } from './Offer' @@ -55,6 +57,8 @@ export { Majority, NEGATIVE_UNL_ID, NegativeUNL, + MPTokenIssuance, + MPToken, NFTokenOffer, NFTokenPage, NFToken, diff --git a/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts b/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts index 523dd2308f..abddcd2d6a 100644 --- a/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts @@ -58,6 +58,7 @@ describe('MPTokenIssuanceDestroy', function () { let accountObjectsResponse = await testContext.client.request({ command: 'account_objects', account: testContext.wallet.classicAddress, + type: 'mpt_issuance', }) assert.lengthOf( accountObjectsResponse.result.account_objects!, @@ -76,6 +77,7 @@ describe('MPTokenIssuanceDestroy', function () { accountObjectsResponse = await testContext.client.request({ command: 'account_objects', account: wallet2.classicAddress, + type: 'mptoken', }) assert.lengthOf( diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts index d316b9a259..ff9fb69e1d 100644 --- a/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts @@ -36,6 +36,7 @@ describe('MPTokenIssuanceCreate', function () { let accountObjectsResponse = await testContext.client.request({ command: 'account_objects', account: testContext.wallet.classicAddress, + type: 'mpt_issuance', }) assert.lengthOf( accountObjectsResponse.result.account_objects!, diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts index 5bcb1458d6..b18a914df8 100644 --- a/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts @@ -53,6 +53,7 @@ describe('MPTokenIssuanceDestroy', function () { let accountObjectsResponse = await testContext.client.request({ command: 'account_objects', account: testContext.wallet.classicAddress, + type: 'mpt_issuance', }) assert.lengthOf( accountObjectsResponse.result.account_objects!, @@ -71,6 +72,7 @@ describe('MPTokenIssuanceDestroy', function () { accountObjectsResponse = await testContext.client.request({ command: 'account_objects', account: testContext.wallet.classicAddress, + type: 'mpt_issuance', }) assert.lengthOf( accountObjectsResponse.result.account_objects!, diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts index 22107f9595..96c1f5401b 100644 --- a/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts @@ -56,6 +56,7 @@ describe('MPTokenIssuanceDestroy', function () { let accountObjectsResponse = await testContext.client.request({ command: 'account_objects', account: testContext.wallet.classicAddress, + type: 'mpt_issuance', }) assert.lengthOf( accountObjectsResponse.result.account_objects!, From 7d43a149e38812977cbd8276c64f6e19ea9349c0 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 21 Aug 2024 12:02:34 -0400 Subject: [PATCH 27/53] add mpt payment --- .../integration/transactions/payment.test.ts | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/packages/xrpl/test/integration/transactions/payment.test.ts b/packages/xrpl/test/integration/transactions/payment.test.ts index 391ab317b2..2d58c77a5c 100644 --- a/packages/xrpl/test/integration/transactions/payment.test.ts +++ b/packages/xrpl/test/integration/transactions/payment.test.ts @@ -8,6 +8,11 @@ import { type XrplIntegrationTestContext, } from '../setup' import { generateFundedWallet, testTransaction } from '../utils' +import { + MPTokenIssuanceCreate, + MPTokenAuthorize, + TransactionMetadata, +} from '../../../src' // how long before each test case times out const TIMEOUT = 20000 @@ -102,4 +107,78 @@ describe('Payment', function () { }, TIMEOUT, ) + + it( + 'Validate MPT Payment ', + async () => { + const wallet2 = await generateFundedWallet(testContext.client) + + const createTx: MPTokenIssuanceCreate = { + TransactionType: 'MPTokenIssuanceCreate', + Account: testContext.wallet.classicAddress, + } + + const mptCreateRes = await testTransaction( + testContext.client, + createTx, + testContext.wallet, + ) + + const txHash = mptCreateRes.result.tx_json.hash + + let txResponse = await testContext.client.request({ + command: 'tx', + transaction: txHash, + }) + + const meta = txResponse.result + .meta as TransactionMetadata + + const mptID = meta.mpt_issuance_id + + let accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + type: 'mpt_issuance', + }) + assert.lengthOf( + accountObjectsResponse.result.account_objects!, + 1, + 'Should be exactly one issuance on the ledger', + ) + + let authTx: MPTokenAuthorize = { + TransactionType: 'MPTokenAuthorize', + Account: wallet2.classicAddress, + MPTokenIssuanceID: mptID!, + } + + await testTransaction(testContext.client, authTx, wallet2) + + accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: wallet2.classicAddress, + type: 'mptoken', + }) + + assert.lengthOf( + accountObjectsResponse.result.account_objects!, + 1, + 'Holder owns 1 MPToken on the ledger', + ) + + let payTx: Payment = { + TransactionType: 'Payment', + Account: testContext.wallet.classicAddress, + Destination: wallet2.classicAddress, + Amount: { + mpt_issuance_id: mptID!, + value: '100', + }, + } + + await testTransaction(testContext.client, payTx, testContext.wallet) + }, + TIMEOUT, + ) }) From e885c3a6330f341aba48138c898fa86eca0a85c6 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 21 Aug 2024 12:23:38 -0400 Subject: [PATCH 28/53] fix bad comment and improvements --- .../src/models/transactions/MPTokenIssuanceCreate.ts | 9 +++++++++ .../src/models/transactions/MPTokenIssuanceSet.ts | 6 ++---- packages/xrpl/src/utils/mptConversion.ts | 11 ++++++++++- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index 1001a74174..06135c918b 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -78,6 +78,15 @@ export interface MPTokenIssuanceCreate extends BaseTransaction { * Specifies the hex-encoded maximum asset amount of this token that should ever be issued. * It is a non-negative integer that can store a range of up to 63 bits. If not set, the max * amount will default to the largest unsigned 63-bit integer (0x7FFFFFFFFFFFFFFF) + * + * Helper function `mptUint64ToHex` can be used to help converting from base 10 or 16 string + * to a valid value. + * + * Example: + * ``` + * MaximumAmount: '3e8' // 0x3E8 in hex or 1000 in decimal + * MaximumAmount: mptUint64ToHex('1000') // 1000 in decimal using helper function + * ``` */ MaximumAmount?: string /** diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts index cd80cf89fd..8f922f6a79 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -17,13 +17,11 @@ import { ValidationError } from '../../errors' */ export enum MPTokenIssuanceSetFlags { /** - * If set, indicates that the MPT can be locked both individually and globally. - * If not set, the MPT cannot be locked in any way. + * If set, indicates that issuer locks the MPT */ tfMPTLock = 0x00000001, /** - * If set, indicates that the MPT can be locked both individually and globally. - * If not set, the MPT cannot be locked in any way. + * If set, indicates that issuer unlocks the MPT */ tfMPTUnlock = 0x00000002, } diff --git a/packages/xrpl/src/utils/mptConversion.ts b/packages/xrpl/src/utils/mptConversion.ts index 156a0e4241..824382945c 100644 --- a/packages/xrpl/src/utils/mptConversion.ts +++ b/packages/xrpl/src/utils/mptConversion.ts @@ -4,13 +4,22 @@ import { ValidationError } from '../errors' const SANITY_CHECK = /^[0-9]+$/u /** - * Convert an unsigned 64-bit integer string to hex string. Mostly used for the MaximumAmount field + * Convert an unsigned 64-bit decimal string to hex string. Mostly used for the MaximumAmount field * in MPTokenIssuanceCreate. * * @param numberToConvert - Non-negative number string. * @returns Amount in hex string. * @throws When amount is invalid. * @category Utilities + * + * @example + * ```typescript + * const decimalString = mptUint64ToHex("1000"); + * console.log(decimalString); // Output: "3e8" + * + * const hexString = mptUint64ToHex("0x3e8"); + * console.log(hexString); // Output: "3e8" + * ``` */ export function mptUint64ToHex(numberToConvert: string): string { // convert to base 10 string first for inputs like scientific notation From ca53e51404dcea3eb324f49b3ae0c4220373d974 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 4 Sep 2024 10:34:48 -0400 Subject: [PATCH 29/53] update definition with ledgerstatefix --- .../src/enums/definitions.json | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/ripple-binary-codec/src/enums/definitions.json b/packages/ripple-binary-codec/src/enums/definitions.json index 7889f35d40..255ff3e79c 100644 --- a/packages/ripple-binary-codec/src/enums/definitions.json +++ b/packages/ripple-binary-codec/src/enums/definitions.json @@ -382,6 +382,16 @@ "type": "UInt16" } ], + [ + "LedgerFixType", + { + "nth": 21, + "isVLEncoded": false, + "isSerialized": true, + "isSigningField": true, + "type": "UInt16" + } + ], [ "NetworkID", { @@ -2913,6 +2923,7 @@ "tefTOO_BIG": -181, "tefNO_TICKET": -180, "tefNFTOKEN_IS_NOT_TRANSFERABLE": -179, + "tefINVALID_LEDGER_FIX_TYPE": -178, "terRETRY": -99, "terFUNDS_SPENT": -98, @@ -3061,10 +3072,11 @@ "DIDDelete": 50, "OracleSet": 51, "OracleDelete": 52, - "MPTokenIssuanceCreate": 53, - "MPTokenIssuanceDestroy": 54, - "MPTokenAuthorize": 55, - "MPTokenIssuanceSet": 56, + "LedgerStateFix": 53, + "MPTokenIssuanceCreate": 54, + "MPTokenIssuanceDestroy": 55, + "MPTokenAuthorize": 56, + "MPTokenIssuanceSet": 57, "EnableAmendment": 100, "SetFee": 101, "UNLModify": 102 From 25ea3d3a56fd6ddd34b2a357507374726fec39e0 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 13 Sep 2024 10:06:53 -0400 Subject: [PATCH 30/53] wip --- .../src/types/serialized-type.ts | 2 +- .../src/types/st-object.ts | 11 +++++++-- .../ripple-binary-codec/src/types/uint-64.ts | 24 +++++++++++++++---- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/ripple-binary-codec/src/types/serialized-type.ts b/packages/ripple-binary-codec/src/types/serialized-type.ts index eb27f5d5a7..2f039f0cd1 100644 --- a/packages/ripple-binary-codec/src/types/serialized-type.ts +++ b/packages/ripple-binary-codec/src/types/serialized-type.ts @@ -67,7 +67,7 @@ class SerializedType { * Can be customized for sidechains and amendments. * @returns any type, if not overloaded returns hexString representation of bytes */ - toJSON(_definitions?: XrplDefinitionsBase): JSON { + toJSON(_definitions?: XrplDefinitionsBase, _fieldName?: string): JSON { return this.toHex() } diff --git a/packages/ripple-binary-codec/src/types/st-object.ts b/packages/ripple-binary-codec/src/types/st-object.ts index 75fec3a86c..71c5b15661 100644 --- a/packages/ripple-binary-codec/src/types/st-object.ts +++ b/packages/ripple-binary-codec/src/types/st-object.ts @@ -10,6 +10,7 @@ import { BinaryParser } from '../serdes/binary-parser' import { BinarySerializer, BytesList } from '../serdes/binary-serializer' import { STArray } from './st-array' +import { UInt64 } from './uint-64' const OBJECT_END_MARKER_BYTE = Uint8Array.from([0xe1]) const OBJECT_END_MARKER = 'ObjectEndMarker' @@ -137,6 +138,8 @@ class STObject extends SerializedType { ? this.from(xAddressDecoded[field.name], undefined, definitions) : field.type.name === 'STArray' ? STArray.from(xAddressDecoded[field.name], definitions) + : field.type.name === 'UInt64' + ? UInt64.from(xAddressDecoded[field.name], field.name) : field.associatedType.from(xAddressDecoded[field.name]) if (associatedValue == undefined) { @@ -155,7 +158,11 @@ class STObject extends SerializedType { // Account field // The Account field must not be a part of the UNLModify pseudotransaction encoding, due to a bug in rippled const isUnlModifyWorkaround = field.name == 'Account' && isUnlModify - bytes.writeFieldAndValue(field, associatedValue, isUnlModifyWorkaround) + bytes.writeFieldAndValue( + field, + associatedValue as SerializedType, + isUnlModifyWorkaround, + ) if (field.type.name === ST_OBJECT) { bytes.put(OBJECT_END_MARKER_BYTE) } @@ -182,7 +189,7 @@ class STObject extends SerializedType { accumulator[field.name] = objectParser .readFieldValue(field) - .toJSON(definitions) + .toJSON(definitions, field.name) } return accumulator diff --git a/packages/ripple-binary-codec/src/types/uint-64.ts b/packages/ripple-binary-codec/src/types/uint-64.ts index 584b468d65..d9d5f0a6a9 100644 --- a/packages/ripple-binary-codec/src/types/uint-64.ts +++ b/packages/ripple-binary-codec/src/types/uint-64.ts @@ -2,8 +2,10 @@ import { UInt } from './uint' import { BinaryParser } from '../serdes/binary-parser' import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils' import { readUInt32BE, writeUInt32BE } from '../utils' +import { XrplDefinitionsBase } from '../enums' const HEX_REGEX = /^[a-fA-F0-9]{1,16}$/ +const BASE10_REGEX = /^[0-9]{1,20}$/ const mask = BigInt(0x00000000ffffffff) /** @@ -29,7 +31,10 @@ class UInt64 extends UInt { * @param val A UInt64, hex-string, bigInt, or number * @returns A UInt64 object */ - static from(val: T): UInt64 { + static from( + val: T, + fieldName: string = '', + ): UInt64 { if (val instanceof UInt64) { return val } @@ -51,11 +56,18 @@ class UInt64 extends UInt { } if (typeof val === 'string') { - if (!HEX_REGEX.test(val)) { + if (fieldName == 'MaximumAmount') { + if (!BASE10_REGEX.test(val)) { + throw new Error(`${fieldName} ${val} is not a valid base 10 string`) + } + val = BigInt(val).toString(16) as T + } + + if (typeof val === 'string' && !HEX_REGEX.test(val)) { throw new Error(`${val} is not a valid hex-string`) } - const strBuf = val.padStart(16, '0') + const strBuf = (val as string).padStart(16, '0') buf = hexToBytes(strBuf) return new UInt64(buf) } @@ -76,7 +88,11 @@ class UInt64 extends UInt { * * @returns a hex-string */ - toJSON(): string { + toJSON(_definitions?: XrplDefinitionsBase, fieldName: string = ''): string { + if (fieldName == 'MaximumAmount') { + const buf = Buffer.from(this.bytes) + return buf.toString() + } return bytesToHex(this.bytes) } From a6f3738b0bbde689565659a04a98372b6f850752 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 13 Sep 2024 15:16:37 -0400 Subject: [PATCH 31/53] binary codec test --- .../ripple-binary-codec/src/types/uint-64.ts | 28 +++++++++--- .../ripple-binary-codec/test/uint.test.ts | 44 ++++++++++++++++++- .../mptokenIssuanceCreate.test.ts | 8 +++- 3 files changed, 70 insertions(+), 10 deletions(-) diff --git a/packages/ripple-binary-codec/src/types/uint-64.ts b/packages/ripple-binary-codec/src/types/uint-64.ts index d9d5f0a6a9..ab68caea92 100644 --- a/packages/ripple-binary-codec/src/types/uint-64.ts +++ b/packages/ripple-binary-codec/src/types/uint-64.ts @@ -2,7 +2,7 @@ import { UInt } from './uint' import { BinaryParser } from '../serdes/binary-parser' import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils' import { readUInt32BE, writeUInt32BE } from '../utils' -import { XrplDefinitionsBase } from '../enums' +import { DEFAULT_DEFINITIONS, XrplDefinitionsBase } from '../enums' const HEX_REGEX = /^[a-fA-F0-9]{1,16}$/ const BASE10_REGEX = /^[0-9]{1,20}$/ @@ -56,7 +56,12 @@ class UInt64 extends UInt { } if (typeof val === 'string') { - if (fieldName == 'MaximumAmount') { + if ( + fieldName == 'MaximumAmount' || + fieldName == 'OutstandingAmount' || + fieldName == 'LockedAmount' || + fieldName == 'MPTAmount' + ) { if (!BASE10_REGEX.test(val)) { throw new Error(`${fieldName} ${val} is not a valid base 10 string`) } @@ -88,12 +93,21 @@ class UInt64 extends UInt { * * @returns a hex-string */ - toJSON(_definitions?: XrplDefinitionsBase, fieldName: string = ''): string { - if (fieldName == 'MaximumAmount') { - const buf = Buffer.from(this.bytes) - return buf.toString() + toJSON( + _definitions: XrplDefinitionsBase = DEFAULT_DEFINITIONS, + fieldName: string = '', + ): string { + const hexString = bytesToHex(this.bytes) + if ( + fieldName == 'MaximumAmount' || + fieldName == 'OutstandingAmount' || + fieldName == 'LockedAmount' || + fieldName == 'MPTAmount' + ) { + return BigInt('0x' + hexString).toString(10) } - return bytesToHex(this.bytes) + + return hexString } /** diff --git a/packages/ripple-binary-codec/test/uint.test.ts b/packages/ripple-binary-codec/test/uint.test.ts index debfad4a87..f700bb56fd 100644 --- a/packages/ripple-binary-codec/test/uint.test.ts +++ b/packages/ripple-binary-codec/test/uint.test.ts @@ -1,5 +1,5 @@ import { UInt8, UInt64 } from '../src/types' -import { encode } from '../src' +import { encode, decode } from '../src' const binary = '11007222000300003700000000000000003800000000000000006280000000000000000000000000000000000000005553440000000000000000000000000000000000000000000000000166D5438D7EA4C680000000000000000000000000005553440000000000AE123A8556F3CF91154711376AFB0F894F832B3D67D5438D7EA4C680000000000000000000000000005553440000000000F51DFC2A09D62CBBA1DFBDD4691DAC96AD98B90F' @@ -96,6 +96,40 @@ const jsonEntry2 = { index: '0000041EFD027808D3F78C8352F97E324CB816318E00B977C74ECDDC7CD975B2', } +const mptIssuanceEntryBinary = + '11007E22000000222400000333250000033934000000000000000030187FFFFFFFFFFFFFFF3019000000000000006455854FCC08F5C602AEAF4F577316248BECCBEC82310B30DC66E1438E07F86E6E7D701EC1EC7B226E616D65223A2255532054726561737572792042696C6C20546F6B656E222C2273796D626F6C223A225553544254222C22646563696D616C73223A322C22746F74616C537570706C79223A313030303030302C22697373756572223A225553205472656173757279222C22697373756544617465223A22323032342D30332D3235222C226D6174757269747944617465223A22323032352D30332D3235222C226661636556616C7565223A2231303030222C22696E74657265737452617465223A22322E35222C22696E7465726573744672657175656E6379223A22517561727465726C79222C22636F6C6C61746572616C223A22555320476F7665726E6D656E74222C226A7572697364696374696F6E223A22556E6974656420537461746573222C22726567756C61746F7279436F6D706C69616E6365223A2253454320526567756C6174696F6E73222C22736563757269747954797065223A2254726561737572792042696C6C222C2265787465726E616C5F75726C223A2268747470733A2F2F6578616D706C652E636F6D2F742D62696C6C2D746F6B656E2D6D657461646174612E6A736F6E227D8414F52140C352B66DC5507C7C0236F7241469E56C1300101403' + +const mptIssuanceEntryJson = { + AssetScale: 3, + Flags: 34, + Issuer: 'rPM3PTTfpJVjEcb3aSoZGizLbVBz2B7LQF', + LedgerEntryType: 'MPTokenIssuance', + MPTokenMetadata: + '7B226E616D65223A2255532054726561737572792042696C6C20546F6B656E222C2273796D626F6C223A225553544254222C22646563696D616C73223A322C22746F74616C537570706C79223A313030303030302C22697373756572223A225553205472656173757279222C22697373756544617465223A22323032342D30332D3235222C226D6174757269747944617465223A22323032352D30332D3235222C226661636556616C7565223A2231303030222C22696E74657265737452617465223A22322E35222C22696E7465726573744672657175656E6379223A22517561727465726C79222C22636F6C6C61746572616C223A22555320476F7665726E6D656E74222C226A7572697364696374696F6E223A22556E6974656420537461746573222C22726567756C61746F7279436F6D706C69616E6365223A2253454320526567756C6174696F6E73222C22736563757269747954797065223A2254726561737572792042696C6C222C2265787465726E616C5F75726C223A2268747470733A2F2F6578616D706C652E636F6D2F742D62696C6C2D746F6B656E2D6D657461646174612E6A736F6E227D', + MaximumAmount: '9223372036854775807', + OutstandingAmount: '100', + OwnerNode: '0000000000000000', + PreviousTxnID: + '854FCC08F5C602AEAF4F577316248BECCBEC82310B30DC66E1438E07F86E6E7D', + PreviousTxnLgrSeq: 825, + Sequence: 819, +} + +const mptokenEntryJson = { + Account: 'rhWhvQ4kMaYB9sX25S13PYGJnF1H1pAmRp', + Flags: 0, + LedgerEntryType: 'MPToken', + MPTAmount: '100', + MPTokenIssuanceID: '0000032A0BEB4029FD60F77F484D8D16C2505E861DB5394B', + OwnerNode: '0000000000000000', + PreviousTxnID: + 'FB4131A4FE8EE2E0A48C7BB668980DD975F529EABF55C190F3F1867469534669', + PreviousTxnLgrSeq: 816, +} + +const mptokenEntryBinary = + '11007F22000000002500000330340000000000000000301B000000000000006455FB4131A4FE8EE2E0A48C7BB668980DD975F529EABF55C190F3F186746953466981142667B68E9BEA7242611581F0EB4630F2E844226901150000032A0BEB4029FD60F77F484D8D16C2505E861DB5394B' + it('compareToTests[0]', () => { expect(UInt8.from(124).compareTo(UInt64.from(124))).toBe(0) }) @@ -144,3 +178,11 @@ it('valueOf tests', () => { expect(val.valueOf() | 0x2).toBe(3) }) + +it('UInt64 is parsed as base 10 for MPT amounts', () => { + expect(encode(mptIssuanceEntryJson)).toEqual(mptIssuanceEntryBinary) + expect(decode(mptIssuanceEntryBinary)).toEqual(mptIssuanceEntryJson) + + expect(encode(mptokenEntryJson)).toEqual(mptokenEntryBinary) + expect(decode(mptokenEntryBinary)).toEqual(mptokenEntryJson) +}) diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts index ff9fb69e1d..9d009defa6 100644 --- a/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts @@ -8,7 +8,6 @@ import { type XrplIntegrationTestContext, } from '../setup' import { testTransaction } from '../utils' -import { mptUint64ToHex } from '../../../src/utils' // how long before each test case times out const TIMEOUT = 20000 @@ -27,7 +26,7 @@ describe('MPTokenIssuanceCreate', function () { const tx: MPTokenIssuanceCreate = { TransactionType: 'MPTokenIssuanceCreate', Account: testContext.wallet.classicAddress, - MaximumAmount: mptUint64ToHex('9223372036854775807'), // 0x7fffffffffffffff + MaximumAmount: '9223372036854775807', // 0x7fffffffffffffff AssetScale: 2, } @@ -43,6 +42,11 @@ describe('MPTokenIssuanceCreate', function () { 1, 'Should be exactly one issuance on the ledger', ) + assert.equal( + // @ts-ignore + accountObjectsResponse.result.account_objects[0].MaximumAmount, + `9223372036854775807`, + ) }, TIMEOUT, ) From cb928065acbcd0687f54159c0d89e85049633b6a Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Tue, 1 Oct 2024 10:49:06 -0400 Subject: [PATCH 32/53] tests --- .../transactions/MPTokenIssuanceCreate.ts | 24 +++++-- packages/xrpl/src/utils/index.ts | 2 - packages/xrpl/src/utils/mptConversion.ts | 61 ----------------- .../integration/transactions/clawback.test.ts | 4 +- .../integration/transactions/payment.test.ts | 11 ++++ .../test/models/MPTokenIssuanceCreate.test.ts | 41 +++++++++++- .../xrpl/test/utils/mptDecimalToHex.test.ts | 65 ------------------- 7 files changed, 69 insertions(+), 139 deletions(-) delete mode 100644 packages/xrpl/src/utils/mptConversion.ts delete mode 100644 packages/xrpl/test/utils/mptDecimalToHex.test.ts diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index 06135c918b..b997ff8a05 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -4,6 +4,8 @@ import { isHex } from '../utils' import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common' import type { TransactionMetadataBase } from './metadata' +const SANITY_CHECK = /^[0-9]+$/u + /** * Transaction Flags for an MPTokenIssuanceCreate Transaction. * @@ -75,17 +77,13 @@ export interface MPTokenIssuanceCreate extends BaseTransaction { */ AssetScale?: number /** - * Specifies the hex-encoded maximum asset amount of this token that should ever be issued. + * Specifies the maximum asset amount of this token that should ever be issued. * It is a non-negative integer that can store a range of up to 63 bits. If not set, the max - * amount will default to the largest unsigned 63-bit integer (0x7FFFFFFFFFFFFFFF) - * - * Helper function `mptUint64ToHex` can be used to help converting from base 10 or 16 string - * to a valid value. + * amount will default to the largest unsigned 63-bit integer (0x7FFFFFFFFFFFFFFF or 9223372036854775807) * * Example: * ``` - * MaximumAmount: '3e8' // 0x3E8 in hex or 1000 in decimal - * MaximumAmount: mptUint64ToHex('1000') // 1000 in decimal using helper function + * MaximumAmount: '9223372036854775807' * ``` */ MaximumAmount?: string @@ -129,4 +127,16 @@ export function validateMPTokenIssuanceCreate( 'MPTokenIssuanceCreate: MPTokenMetadata must be in hex format', ) } + + if (typeof tx.MaximumAmount === 'string') { + if (!SANITY_CHECK.exec(tx.MaximumAmount)) + throw new ValidationError('MPTokenIssuanceCreate: Invalid MaximumAmount') + else if ( + BigInt(tx.MaximumAmount) > BigInt(`9223372036854775807`) || + BigInt(tx.MaximumAmount) < BigInt(`0`) + ) + throw new ValidationError( + 'MPTokenIssuanceCreate: MaximumAmount out of range', + ) + } } diff --git a/packages/xrpl/src/utils/index.ts b/packages/xrpl/src/utils/index.ts index 83abc56931..2304384e90 100644 --- a/packages/xrpl/src/utils/index.ts +++ b/packages/xrpl/src/utils/index.ts @@ -66,7 +66,6 @@ import { } from './timeConversion' import verifyPaymentChannelClaim from './verifyPaymentChannelClaim' import { xrpToDrops, dropsToXrp } from './xrpConversion' -import { mptUint64ToHex } from './mptConversion' /** * Check if a secret is valid. @@ -230,5 +229,4 @@ export { getNFTokenID, parseNFTokenID, getXChainClaimID, - mptUint64ToHex, } diff --git a/packages/xrpl/src/utils/mptConversion.ts b/packages/xrpl/src/utils/mptConversion.ts deleted file mode 100644 index 824382945c..0000000000 --- a/packages/xrpl/src/utils/mptConversion.ts +++ /dev/null @@ -1,61 +0,0 @@ -import BigNumber from 'bignumber.js' -import { ValidationError } from '../errors' - -const SANITY_CHECK = /^[0-9]+$/u - -/** - * Convert an unsigned 64-bit decimal string to hex string. Mostly used for the MaximumAmount field - * in MPTokenIssuanceCreate. - * - * @param numberToConvert - Non-negative number string. - * @returns Amount in hex string. - * @throws When amount is invalid. - * @category Utilities - * - * @example - * ```typescript - * const decimalString = mptUint64ToHex("1000"); - * console.log(decimalString); // Output: "3e8" - * - * const hexString = mptUint64ToHex("0x3e8"); - * console.log(hexString); // Output: "3e8" - * ``` - */ -export function mptUint64ToHex(numberToConvert: string): string { - // convert to base 10 string first for inputs like scientific notation - const number = new BigNumber(numberToConvert).toString(10) - - // check that the value is valid and actually a number - if (typeof numberToConvert === 'string' && number === 'NaN') { - throw new ValidationError( - `mptUint64ToHex: invalid value '${numberToConvert}', should be a string-encoded number.`, - ) - } - - // mpts are only whole units - if (number.includes('.')) { - throw new ValidationError( - `mptUint64ToHex: value '${numberToConvert}' has too many decimal places.`, - ) - } - if (number.includes('-')) { - throw new ValidationError( - `mptUint64ToHex: value '${numberToConvert}' cannot be negative.`, - ) - } - - if (!SANITY_CHECK.exec(number)) { - throw new ValidationError( - `mptUint64ToHex: failed sanity check -` + - ` value '${numberToConvert}',` + - ` does not match (^[0-9]+$).`, - ) - } - - if (Number(BigInt(number) & BigInt('0x8000000000000000')) != 0) - throw new ValidationError( - `mptUint64ToHex: invalid value '${numberToConvert}', should be within 63-bit range.`, - ) - - return BigInt(number).toString(16) -} diff --git a/packages/xrpl/test/integration/transactions/clawback.test.ts b/packages/xrpl/test/integration/transactions/clawback.test.ts index 9d78861540..3d9cbcda01 100644 --- a/packages/xrpl/test/integration/transactions/clawback.test.ts +++ b/packages/xrpl/test/integration/transactions/clawback.test.ts @@ -174,7 +174,7 @@ describe('Clawback', function () { assert.equal( // @ts-ignore ledgerEntryResponse.result.node.MPTAmount, - '7fffffffffffffff', + '9223372036854775807', ) // actual test - clawback @@ -200,7 +200,7 @@ describe('Clawback', function () { assert.equal( // @ts-ignore ledgerEntryResponse.result.node.MPTAmount, - '7ffffffffffffe0b', + '9223372036854775307', ) }, TIMEOUT, diff --git a/packages/xrpl/test/integration/transactions/payment.test.ts b/packages/xrpl/test/integration/transactions/payment.test.ts index 2d58c77a5c..5540c1685e 100644 --- a/packages/xrpl/test/integration/transactions/payment.test.ts +++ b/packages/xrpl/test/integration/transactions/payment.test.ts @@ -178,6 +178,17 @@ describe('Payment', function () { } await testTransaction(testContext.client, payTx, testContext.wallet) + + accountObjectsResponse = await testContext.client.request({ + command: 'account_objects', + account: testContext.wallet.classicAddress, + type: 'mpt_issuance', + }) + assert.equal( + // @ts-ignore + accountObjectsResponse.result.account_objects[0].OutstandingAmount, + `100`, + ) }, TIMEOUT, ) diff --git a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts index 534e6e877b..b24b90b70b 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts @@ -5,7 +5,6 @@ import { validate, ValidationError, MPTokenIssuanceCreateFlags, - mptUint64ToHex, } from '../../src' /** @@ -18,7 +17,7 @@ describe('MPTokenIssuanceCreate', function () { const validMPTokenIssuanceCreate = { TransactionType: 'MPTokenIssuanceCreate', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', - MaximumAmount: mptUint64ToHex('9223372036854775807'), // 0x7fffffffffffffff + MaximumAmount: '9223372036854775807', // 0x7fffffffffffffff AssetScale: 2, TransferFee: 1, Flags: 2, @@ -57,4 +56,42 @@ describe('MPTokenIssuanceCreate', function () { 'MPTokenIssuanceCreate: MPTokenMetadata must be in hex format', ) }) + + it(`throws w/ Invalid MaximumAmount`, function () { + let invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MaximumAmount: '9223372036854775808', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: MaximumAmount out of range', + ) + + invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MaximumAmount: '-1', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: Invalid MaximumAmount', + ) + + invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + MaximumAmount: '0x12', + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: Invalid MaximumAmount', + ) + }) }) diff --git a/packages/xrpl/test/utils/mptDecimalToHex.test.ts b/packages/xrpl/test/utils/mptDecimalToHex.test.ts deleted file mode 100644 index 88cb0b0a59..0000000000 --- a/packages/xrpl/test/utils/mptDecimalToHex.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { assert } from 'chai' - -import { mptUint64ToHex } from '../../src/utils' - -describe('mptUint64ToHex', function () { - it('works with a typical amount', function () { - let hexStr = mptUint64ToHex('1000') - assert.strictEqual(hexStr, '3e8', '1000 equals to 3e8 in hex') - - hexStr = mptUint64ToHex('9223372036854775807') - assert.strictEqual( - hexStr, - '7fffffffffffffff', - '9223372036854775807 equals to 7fffffffffffffff in hex', - ) - }) - - it('works with zero', function () { - let hexStr = mptUint64ToHex('0') - assert.strictEqual(hexStr, '0', '0 equals to 0 in hex') - - hexStr = mptUint64ToHex('000000000') - assert.strictEqual(hexStr, '0', '000000000 equals 0 in hex') - - hexStr = mptUint64ToHex('-0') - assert.strictEqual(hexStr, '0', '-0 equals 0 in hex') - }) - - it('throws with a negative value', function () { - assert.throws(() => { - mptUint64ToHex('-1') - }, "mptUint64ToHex: value '-1' cannot be negative.") - }) - - it('checks decimal value', function () { - assert.throws(() => { - mptUint64ToHex('20000.1') - }, "mptUint64ToHex: value '20000.1' has too many decimal places.") - - const hexStr = mptUint64ToHex('20000.') - assert.strictEqual(hexStr, '4e20', '20000. equals 4e20 in hex') - }) - - it('works with scientific notation', function () { - const hexStr = mptUint64ToHex('1e6') - assert.strictEqual(hexStr, 'f4240', '1e6 equals f4240 in hex') - }) - - it('throws with an value with 64-bit range', function () { - assert.throws(() => { - mptUint64ToHex('9223372036854775808') - }, "mptUint64ToHex: invalid value '9223372036854775808', should be within 63-bit range.") - }) - - it('throws with an value with invalid format', function () { - assert.throws(() => { - mptUint64ToHex('1a') - }, "mptUint64ToHex: invalid value '1a', should be a string-encoded number.") - }) - - it('works with a hex value', function () { - const hexStr = mptUint64ToHex('0x1a') - assert.strictEqual(hexStr, '1a', '0x1a equals 1a in hex') - }) -}) From f69fc7425b4f535ac0787b1759c4871f4c6bcfb1 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 9 Oct 2024 11:22:04 -0400 Subject: [PATCH 33/53] update definitions.json --- .../src/enums/definitions.json | 39 +++++++------------ .../ripple-binary-codec/src/types/uint-64.ts | 22 +++++------ 2 files changed, 23 insertions(+), 38 deletions(-) diff --git a/packages/ripple-binary-codec/src/enums/definitions.json b/packages/ripple-binary-codec/src/enums/definitions.json index 255ff3e79c..33ed8788c7 100644 --- a/packages/ripple-binary-codec/src/enums/definitions.json +++ b/packages/ripple-binary-codec/src/enums/definitions.json @@ -1112,20 +1112,10 @@ "type": "UInt64" } ], - [ - "LockedAmount", - { - "nth": 26, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt64" - } - ], [ "MPTAmount", { - "nth": 27, + "nth": 26, "isVLEncoded": false, "isSerialized": true, "isSigningField": true, @@ -2890,17 +2880,17 @@ "temUNKNOWN": -264, "temSEQ_AND_TICKET": -263, "temBAD_NFTOKEN_TRANSFER_FEE": -262, - "temBAD_MPTOKEN_TRANSFER_FEE": -261, - "temBAD_AMM_TOKENS": -260, - "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -259, - "temXCHAIN_BAD_PROOF": -258, - "temXCHAIN_BRIDGE_BAD_ISSUES": -257, - "temXCHAIN_BRIDGE_NONDOOR_OWNER": -256, - "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -255, - "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -254, - "temEMPTY_DID": -253, - "temARRAY_EMPTY": -252, - "temARRAY_TOO_LARGE": -251, + "temBAD_AMM_TOKENS": -261, + "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -260, + "temXCHAIN_BAD_PROOF": -259, + "temXCHAIN_BRIDGE_BAD_ISSUES": -258, + "temXCHAIN_BRIDGE_NONDOOR_OWNER": -257, + "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -256, + "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255, + "temEMPTY_DID": -254, + "temARRAY_EMPTY": -253, + "temARRAY_TOO_LARGE": -252, + "temBAD_TRANSFER_FEE": -251, "tefFAILURE": -199, "tefALREADY": -198, @@ -3018,10 +3008,7 @@ "tecTOKEN_PAIR_NOT_FOUND": 189, "tecARRAY_EMPTY": 190, "tecARRAY_TOO_LARGE": 191, - "tecMPTOKEN_EXISTS": 192, - "tecMPT_MAX_AMOUNT_EXCEEDED": 193, - "tecMPT_LOCKED": 194, - "tecMPT_NOT_SUPPORTED": 195 + "tecLOCKED": 192 }, "TRANSACTION_TYPES": { "Invalid": -1, diff --git a/packages/ripple-binary-codec/src/types/uint-64.ts b/packages/ripple-binary-codec/src/types/uint-64.ts index ab68caea92..ec574179f1 100644 --- a/packages/ripple-binary-codec/src/types/uint-64.ts +++ b/packages/ripple-binary-codec/src/types/uint-64.ts @@ -8,6 +8,14 @@ const HEX_REGEX = /^[a-fA-F0-9]{1,16}$/ const BASE10_REGEX = /^[0-9]{1,20}$/ const mask = BigInt(0x00000000ffffffff) +function useBase10(fieldName: string): boolean { + return ( + fieldName === 'MaximumAmount' || + fieldName === 'OutstandingAmount' || + fieldName === 'MPTAmount' + ) +} + /** * Derived UInt class for serializing/deserializing 64 bit UInt */ @@ -56,12 +64,7 @@ class UInt64 extends UInt { } if (typeof val === 'string') { - if ( - fieldName == 'MaximumAmount' || - fieldName == 'OutstandingAmount' || - fieldName == 'LockedAmount' || - fieldName == 'MPTAmount' - ) { + if (useBase10(fieldName)) { if (!BASE10_REGEX.test(val)) { throw new Error(`${fieldName} ${val} is not a valid base 10 string`) } @@ -98,12 +101,7 @@ class UInt64 extends UInt { fieldName: string = '', ): string { const hexString = bytesToHex(this.bytes) - if ( - fieldName == 'MaximumAmount' || - fieldName == 'OutstandingAmount' || - fieldName == 'LockedAmount' || - fieldName == 'MPTAmount' - ) { + if (useBase10(fieldName)) { return BigInt('0x' + hexString).toString(10) } From 458f0b81dc8c0351673abb7dbf35438c556f1c1a Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 9 Oct 2024 11:43:21 -0400 Subject: [PATCH 34/53] fix failed test --- packages/ripple-binary-codec/test/uint.test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/ripple-binary-codec/test/uint.test.ts b/packages/ripple-binary-codec/test/uint.test.ts index f700bb56fd..e3a816a064 100644 --- a/packages/ripple-binary-codec/test/uint.test.ts +++ b/packages/ripple-binary-codec/test/uint.test.ts @@ -116,19 +116,19 @@ const mptIssuanceEntryJson = { } const mptokenEntryJson = { - Account: 'rhWhvQ4kMaYB9sX25S13PYGJnF1H1pAmRp', + Account: 'raDQsd1s8rqGjL476g59a9vVNi1rSwrC44', Flags: 0, LedgerEntryType: 'MPToken', MPTAmount: '100', - MPTokenIssuanceID: '0000032A0BEB4029FD60F77F484D8D16C2505E861DB5394B', + MPTokenIssuanceID: '000002DF71CAE59C9B7E56587FFF74D4EA5830D9BE3CE0CC', OwnerNode: '0000000000000000', PreviousTxnID: - 'FB4131A4FE8EE2E0A48C7BB668980DD975F529EABF55C190F3F1867469534669', - PreviousTxnLgrSeq: 816, + '222EF3C7E82D8A44984A66E2B8E357CB536EC2547359CCF70E56E14BC4C284C8', + PreviousTxnLgrSeq: 741, } const mptokenEntryBinary = - '11007F22000000002500000330340000000000000000301B000000000000006455FB4131A4FE8EE2E0A48C7BB668980DD975F529EABF55C190F3F186746953466981142667B68E9BEA7242611581F0EB4630F2E844226901150000032A0BEB4029FD60F77F484D8D16C2505E861DB5394B' + '11007F220000000025000002E5340000000000000000301A000000000000006455222EF3C7E82D8A44984A66E2B8E357CB536EC2547359CCF70E56E14BC4C284C881143930DB9A74C26D96CB58ADFFD7E8BB78BCFE62340115000002DF71CAE59C9B7E56587FFF74D4EA5830D9BE3CE0CC' it('compareToTests[0]', () => { expect(UInt8.from(124).compareTo(UInt64.from(124))).toBe(0) From 32ea011f586b77935b6ab02baf5e3a499db160c6 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 9 Oct 2024 12:01:55 -0400 Subject: [PATCH 35/53] fix lint --- packages/ripple-binary-codec/src/types/st-object.ts | 6 +----- packages/ripple-binary-codec/src/types/uint-64.ts | 4 ++-- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/packages/ripple-binary-codec/src/types/st-object.ts b/packages/ripple-binary-codec/src/types/st-object.ts index 71c5b15661..2bc596c196 100644 --- a/packages/ripple-binary-codec/src/types/st-object.ts +++ b/packages/ripple-binary-codec/src/types/st-object.ts @@ -158,11 +158,7 @@ class STObject extends SerializedType { // Account field // The Account field must not be a part of the UNLModify pseudotransaction encoding, due to a bug in rippled const isUnlModifyWorkaround = field.name == 'Account' && isUnlModify - bytes.writeFieldAndValue( - field, - associatedValue as SerializedType, - isUnlModifyWorkaround, - ) + bytes.writeFieldAndValue(field, associatedValue, isUnlModifyWorkaround) if (field.type.name === ST_OBJECT) { bytes.put(OBJECT_END_MARKER_BYTE) } diff --git a/packages/ripple-binary-codec/src/types/uint-64.ts b/packages/ripple-binary-codec/src/types/uint-64.ts index ec574179f1..f6c9e523f7 100644 --- a/packages/ripple-binary-codec/src/types/uint-64.ts +++ b/packages/ripple-binary-codec/src/types/uint-64.ts @@ -41,7 +41,7 @@ class UInt64 extends UInt { */ static from( val: T, - fieldName: string = '', + fieldName = '', ): UInt64 { if (val instanceof UInt64) { return val @@ -98,7 +98,7 @@ class UInt64 extends UInt { */ toJSON( _definitions: XrplDefinitionsBase = DEFAULT_DEFINITIONS, - fieldName: string = '', + fieldName = '', ): string { const hexString = bytesToHex(this.bytes) if (useBase10(fieldName)) { From 7529657c6e322ff8cd5ff481cbe2726e7e1cc5b7 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 9 Oct 2024 13:14:56 -0400 Subject: [PATCH 36/53] fix lint errors --- packages/xrpl/src/client/partialPayment.ts | 11 +++++--- packages/xrpl/src/models/ledger/index.ts | 4 +-- .../transactions/MPTokenIssuanceCreate.ts | 7 ++--- .../models/transactions/MPTokenIssuanceSet.ts | 6 ++++- .../xrpl/src/models/transactions/metadata.ts | 8 +++--- .../src/models/transactions/transaction.ts | 26 +++++++++---------- packages/xrpl/src/models/utils/flags.ts | 6 ++--- .../integration/transactions/clawback.test.ts | 10 +++---- .../transactions/mptokenAuthorize.test.ts | 8 +++--- .../mptokenIssuanceCreate.test.ts | 6 ++--- .../mptokenIssuanceDestroy.test.ts | 8 +++--- .../transactions/mptokenIssuanceSet.test.ts | 6 ++--- .../integration/transactions/payment.test.ts | 25 +++++++++--------- .../test/models/MPTokenIssuanceSet.test.ts | 2 ++ 14 files changed, 73 insertions(+), 60 deletions(-) diff --git a/packages/xrpl/src/client/partialPayment.ts b/packages/xrpl/src/client/partialPayment.ts index 7257de8178..11531b5afd 100644 --- a/packages/xrpl/src/client/partialPayment.ts +++ b/packages/xrpl/src/client/partialPayment.ts @@ -6,7 +6,13 @@ import type { TransactionStream, TxResponse, } from '..' -import type { Amount, IssuedCurrency,APIVersion, DEFAULT_API_VERSION , MPTAmount } from '../models/common' +import type { + Amount, + IssuedCurrency, + APIVersion, + DEFAULT_API_VERSION, + MPTAmount, +} from '../models/common' import type { AccountTxTransaction, RequestResponseMap, @@ -36,8 +42,7 @@ function amountsEqual( const bValue = new BigNumber(amt2.value) return ( - (amt1 as MPTAmount).mpt_issuance_id === - (amt2 as MPTAmount).mpt_issuance_id && aValue.isEqualTo(bValue) + amt1.mpt_issuance_id === amt2.mpt_issuance_id && aValue.isEqualTo(bValue) ) } diff --git a/packages/xrpl/src/models/ledger/index.ts b/packages/xrpl/src/models/ledger/index.ts index 2cb591b3df..4907babbba 100644 --- a/packages/xrpl/src/models/ledger/index.ts +++ b/packages/xrpl/src/models/ledger/index.ts @@ -18,9 +18,9 @@ import FeeSettings, { import { Ledger, LedgerV1 } from './Ledger' import { LedgerEntry, LedgerEntryFilter } from './LedgerEntry' import LedgerHashes from './LedgerHashes' -import NegativeUNL, { NEGATIVE_UNL_ID } from './NegativeUNL' -import { MPTokenIssuance } from './MPTokenIssuance' import { MPToken } from './MPToken' +import { MPTokenIssuance } from './MPTokenIssuance' +import NegativeUNL, { NEGATIVE_UNL_ID } from './NegativeUNL' import { NFTokenOffer } from './NFTokenOffer' import { NFToken, NFTokenPage } from './NFTokenPage' import Offer, { OfferFlags } from './Offer' diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index b997ff8a05..f52a6d362b 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -129,14 +129,15 @@ export function validateMPTokenIssuanceCreate( } if (typeof tx.MaximumAmount === 'string') { - if (!SANITY_CHECK.exec(tx.MaximumAmount)) + if (!SANITY_CHECK.exec(tx.MaximumAmount)) { throw new ValidationError('MPTokenIssuanceCreate: Invalid MaximumAmount') - else if ( + } else if ( BigInt(tx.MaximumAmount) > BigInt(`9223372036854775807`) || BigInt(tx.MaximumAmount) < BigInt(`0`) - ) + ) { throw new ValidationError( 'MPTokenIssuanceCreate: MaximumAmount out of range', ) + } } } diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts index 8f922f6a79..89f42919e1 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -1,3 +1,5 @@ +import { ValidationError } from '../../errors' + import { BaseTransaction, isString, @@ -8,7 +10,6 @@ import { isAccount, GlobalFlags, } from './common' -import { ValidationError } from '../../errors' /** * Transaction Flags for an MPTokenIssuanceSet Transaction. @@ -67,10 +68,13 @@ export function validateMPTokenIssuanceSet(tx: Record): void { validateOptionalField(tx, 'MPTokenHolder', isAccount) const flags = tx.Flags as number + + /* eslint-disable no-bitwise -- We need bitwise operations for flag checks here */ if ( BigInt(flags) & BigInt(MPTokenIssuanceSetFlags.tfMPTLock) && BigInt(flags) & BigInt(MPTokenIssuanceSetFlags.tfMPTUnlock) ) { throw new ValidationError('MPTokenIssuanceSet: flag conflict') } + /* eslint-enable no-bitwise -- Re-enable bitwise rule after this block */ } diff --git a/packages/xrpl/src/models/transactions/metadata.ts b/packages/xrpl/src/models/transactions/metadata.ts index 93a77132cc..75551d1a06 100644 --- a/packages/xrpl/src/models/transactions/metadata.ts +++ b/packages/xrpl/src/models/transactions/metadata.ts @@ -1,6 +1,10 @@ import { Amount, MPTAmount } from '../common' import { BaseTransaction } from './common' +import { + MPTokenIssuanceCreate, + MPTokenIssuanceCreateMetadata, +} from './MPTokenIssuanceCreate' import { NFTokenAcceptOffer, NFTokenAcceptOfferMetadata, @@ -14,10 +18,6 @@ import { NFTokenCreateOfferMetadata, } from './NFTokenCreateOffer' import { NFTokenMint, NFTokenMintMetadata } from './NFTokenMint' -import { - MPTokenIssuanceCreate, - MPTokenIssuanceCreateMetadata, -} from './MPTokenIssuanceCreate' import { Payment, PaymentMetadata } from './payment' import type { Transaction } from './transaction' diff --git a/packages/xrpl/src/models/transactions/transaction.ts b/packages/xrpl/src/models/transactions/transaction.ts index 7eacdc7318..f199930c6b 100644 --- a/packages/xrpl/src/models/transactions/transaction.ts +++ b/packages/xrpl/src/models/transactions/transaction.ts @@ -27,6 +27,19 @@ import { EscrowCancel, validateEscrowCancel } from './escrowCancel' import { EscrowCreate, validateEscrowCreate } from './escrowCreate' import { EscrowFinish, validateEscrowFinish } from './escrowFinish' import { TransactionMetadata } from './metadata' +import { MPTokenAuthorize, validateMPTokenAuthorize } from './MPTokenAuthorize' +import { + MPTokenIssuanceCreate, + validateMPTokenIssuanceCreate, +} from './MPTokenIssuanceCreate' +import { + MPTokenIssuanceDestroy, + validateMPTokenIssuanceDestroy, +} from './MPTokenIssuanceDestroy' +import { + MPTokenIssuanceSet, + validateMPTokenIssuanceSet, +} from './MPTokenIssuanceSet' import { NFTokenAcceptOffer, validateNFTokenAcceptOffer, @@ -90,19 +103,6 @@ import { XChainModifyBridge, validateXChainModifyBridge, } from './XChainModifyBridge' -import { MPTokenAuthorize, validateMPTokenAuthorize } from './MPTokenAuthorize' -import { - MPTokenIssuanceCreate, - validateMPTokenIssuanceCreate, -} from './MPTokenIssuanceCreate' -import { - MPTokenIssuanceDestroy, - validateMPTokenIssuanceDestroy, -} from './MPTokenIssuanceDestroy' -import { - MPTokenIssuanceSet, - validateMPTokenIssuanceSet, -} from './MPTokenIssuanceSet' /** * Transactions that can be submitted by clients diff --git a/packages/xrpl/src/models/utils/flags.ts b/packages/xrpl/src/models/utils/flags.ts index 67586a480a..0d04b0ecad 100644 --- a/packages/xrpl/src/models/utils/flags.ts +++ b/packages/xrpl/src/models/utils/flags.ts @@ -9,6 +9,9 @@ import { AccountSetTfFlags } from '../transactions/accountSet' import { AMMDepositFlags } from '../transactions/AMMDeposit' import { AMMWithdrawFlags } from '../transactions/AMMWithdraw' import { GlobalFlags } from '../transactions/common' +import { MPTokenAuthorizeFlags } from '../transactions/MPTokenAuthorize' +import { MPTokenIssuanceCreateFlags } from '../transactions/MPTokenIssuanceCreate' +import { MPTokenIssuanceSetFlags } from '../transactions/MPTokenIssuanceSet' import { NFTokenCreateOfferFlags } from '../transactions/NFTokenCreateOffer' import { NFTokenMintFlags } from '../transactions/NFTokenMint' import { OfferCreateFlags } from '../transactions/offerCreate' @@ -17,9 +20,6 @@ import { PaymentChannelClaimFlags } from '../transactions/paymentChannelClaim' import type { Transaction } from '../transactions/transaction' import { TrustSetFlags } from '../transactions/trustSet' import { XChainModifyBridgeFlags } from '../transactions/XChainModifyBridge' -import { MPTokenAuthorizeFlags } from '../transactions/MPTokenAuthorize' -import { MPTokenIssuanceCreateFlags } from '../transactions/MPTokenIssuanceCreate' -import { MPTokenIssuanceSetFlags } from '../transactions/MPTokenIssuanceSet' import { isFlagEnabled } from '.' diff --git a/packages/xrpl/test/integration/transactions/clawback.test.ts b/packages/xrpl/test/integration/transactions/clawback.test.ts index 3d9cbcda01..034e50c6b3 100644 --- a/packages/xrpl/test/integration/transactions/clawback.test.ts +++ b/packages/xrpl/test/integration/transactions/clawback.test.ts @@ -10,6 +10,7 @@ import { MPTokenIssuanceCreateFlags, MPTokenAuthorize, TransactionMetadata, + LedgerEntryResponse, } from '../../../src' import serverUrl from '../serverUrl' import { @@ -18,7 +19,6 @@ import { type XrplIntegrationTestContext, } from '../setup' import { generateFundedWallet, testTransaction } from '../utils' -import { LedgerEntryResponse } from '../../../src' // how long before each test case times out const TIMEOUT = 20000 @@ -128,14 +128,14 @@ describe('Clawback', function () { Flags: MPTokenIssuanceCreateFlags.tfMPTCanClawback, } - let mptCreateRes = await testTransaction( + const mptCreateRes = await testTransaction( testContext.client, createTx, testContext.wallet, ) const txHash = mptCreateRes.result.tx_json.hash - let txResponse = await testContext.client.request({ + const txResponse = await testContext.client.request({ command: 'tx', transaction: txHash, }) @@ -172,7 +172,7 @@ describe('Clawback', function () { }) assert.equal( - // @ts-ignore + // @ts-expect-error: Known issue with unknown object type ledgerEntryResponse.result.node.MPTAmount, '9223372036854775807', ) @@ -198,7 +198,7 @@ describe('Clawback', function () { }) assert.equal( - // @ts-ignore + // @ts-expect-error: Known issue with unknown object type ledgerEntryResponse.result.node.MPTAmount, '9223372036854775307', ) diff --git a/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts b/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts index abddcd2d6a..af543f4fd1 100644 --- a/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts @@ -45,7 +45,7 @@ describe('MPTokenIssuanceDestroy', function () { const txHash = mptCreateRes.result.tx_json.hash - let txResponse = await testContext.client.request({ + const txResponse = await testContext.client.request({ command: 'tx', transaction: txHash, }) @@ -61,7 +61,7 @@ describe('MPTokenIssuanceDestroy', function () { type: 'mpt_issuance', }) assert.lengthOf( - accountObjectsResponse.result.account_objects!, + accountObjectsResponse.result.account_objects, 1, 'Should be exactly one issuance on the ledger', ) @@ -81,7 +81,7 @@ describe('MPTokenIssuanceDestroy', function () { }) assert.lengthOf( - accountObjectsResponse.result.account_objects!, + accountObjectsResponse.result.account_objects, 1, 'Holder owns 1 MPToken on the ledger', ) @@ -109,7 +109,7 @@ describe('MPTokenIssuanceDestroy', function () { }) assert.lengthOf( - accountObjectsResponse.result.account_objects!, + accountObjectsResponse.result.account_objects, 0, 'Holder owns nothing on the ledger', ) diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts index 9d009defa6..8f509479b3 100644 --- a/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts @@ -32,18 +32,18 @@ describe('MPTokenIssuanceCreate', function () { await testTransaction(testContext.client, tx, testContext.wallet) - let accountObjectsResponse = await testContext.client.request({ + const accountObjectsResponse = await testContext.client.request({ command: 'account_objects', account: testContext.wallet.classicAddress, type: 'mpt_issuance', }) assert.lengthOf( - accountObjectsResponse.result.account_objects!, + accountObjectsResponse.result.account_objects, 1, 'Should be exactly one issuance on the ledger', ) assert.equal( - // @ts-ignore + // @ts-expect-error: Known issue with unknown object type accountObjectsResponse.result.account_objects[0].MaximumAmount, `9223372036854775807`, ) diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts index b18a914df8..d42b252078 100644 --- a/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts @@ -40,7 +40,7 @@ describe('MPTokenIssuanceDestroy', function () { const txHash = mptCreateRes.result.tx_json.hash - let txResponse = await testContext.client.request({ + const txResponse = await testContext.client.request({ command: 'tx', transaction: txHash, }) @@ -50,13 +50,13 @@ describe('MPTokenIssuanceDestroy', function () { const mptID = meta.mpt_issuance_id - let accountObjectsResponse = await testContext.client.request({ + const accountObjectsResponse = await testContext.client.request({ command: 'account_objects', account: testContext.wallet.classicAddress, type: 'mpt_issuance', }) assert.lengthOf( - accountObjectsResponse.result.account_objects!, + accountObjectsResponse.result.account_objects, 1, 'Should be exactly one issuance on the ledger', ) @@ -75,7 +75,7 @@ describe('MPTokenIssuanceDestroy', function () { type: 'mpt_issuance', }) assert.lengthOf( - accountObjectsResponse.result.account_objects!, + accountObjectsResponse.result.account_objects, 0, 'Should be zero issuance on the ledger', ) diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts index 96c1f5401b..195fbe5949 100644 --- a/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceSet.test.ts @@ -43,7 +43,7 @@ describe('MPTokenIssuanceDestroy', function () { const txHash = mptCreateRes.result.tx_json.hash - let txResponse = await testContext.client.request({ + const txResponse = await testContext.client.request({ command: 'tx', transaction: txHash, }) @@ -53,13 +53,13 @@ describe('MPTokenIssuanceDestroy', function () { const mptID = meta.mpt_issuance_id - let accountObjectsResponse = await testContext.client.request({ + const accountObjectsResponse = await testContext.client.request({ command: 'account_objects', account: testContext.wallet.classicAddress, type: 'mpt_issuance', }) assert.lengthOf( - accountObjectsResponse.result.account_objects!, + accountObjectsResponse.result.account_objects, 1, 'Should be exactly one issuance on the ledger', ) diff --git a/packages/xrpl/test/integration/transactions/payment.test.ts b/packages/xrpl/test/integration/transactions/payment.test.ts index 5540c1685e..17fba05f8a 100644 --- a/packages/xrpl/test/integration/transactions/payment.test.ts +++ b/packages/xrpl/test/integration/transactions/payment.test.ts @@ -1,6 +1,12 @@ import { assert } from 'chai' -import { Payment, Wallet } from '../../../src' +import { + Payment, + Wallet, + MPTokenIssuanceCreate, + MPTokenAuthorize, + TransactionMetadata, +} from '../../../src' import serverUrl from '../serverUrl' import { setupClient, @@ -8,11 +14,6 @@ import { type XrplIntegrationTestContext, } from '../setup' import { generateFundedWallet, testTransaction } from '../utils' -import { - MPTokenIssuanceCreate, - MPTokenAuthorize, - TransactionMetadata, -} from '../../../src' // how long before each test case times out const TIMEOUT = 20000 @@ -126,7 +127,7 @@ describe('Payment', function () { const txHash = mptCreateRes.result.tx_json.hash - let txResponse = await testContext.client.request({ + const txResponse = await testContext.client.request({ command: 'tx', transaction: txHash, }) @@ -142,12 +143,12 @@ describe('Payment', function () { type: 'mpt_issuance', }) assert.lengthOf( - accountObjectsResponse.result.account_objects!, + accountObjectsResponse.result.account_objects, 1, 'Should be exactly one issuance on the ledger', ) - let authTx: MPTokenAuthorize = { + const authTx: MPTokenAuthorize = { TransactionType: 'MPTokenAuthorize', Account: wallet2.classicAddress, MPTokenIssuanceID: mptID!, @@ -162,12 +163,12 @@ describe('Payment', function () { }) assert.lengthOf( - accountObjectsResponse.result.account_objects!, + accountObjectsResponse.result.account_objects, 1, 'Holder owns 1 MPToken on the ledger', ) - let payTx: Payment = { + const payTx: Payment = { TransactionType: 'Payment', Account: testContext.wallet.classicAddress, Destination: wallet2.classicAddress, @@ -185,7 +186,7 @@ describe('Payment', function () { type: 'mpt_issuance', }) assert.equal( - // @ts-ignore + // @ts-expect-error -- Object type not known accountObjectsResponse.result.account_objects[0].OutstandingAmount, `100`, ) diff --git a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts index 22c2067dde..838fc60ae3 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts @@ -54,6 +54,7 @@ describe('MPTokenIssuanceSet', function () { }) it(`throws w/ conflicting flags`, function () { + /* eslint-disable no-bitwise -- Bitwise operation needed for flag combination */ const invalid = { TransactionType: 'MPTokenIssuanceSet', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', @@ -61,6 +62,7 @@ describe('MPTokenIssuanceSet', function () { Flags: MPTokenIssuanceSetFlags.tfMPTLock | MPTokenIssuanceSetFlags.tfMPTUnlock, } as any + /* eslint-enable no-bitwise -- Re-enable bitwise rule */ assert.throws( () => validate(invalid), From 46c66e50c8679a8f4e5f62202eb547415f113383 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 9 Oct 2024 13:34:07 -0400 Subject: [PATCH 37/53] fix more lint --- packages/xrpl/src/client/partialPayment.ts | 7 +++++++ .../transactions/MPTokenIssuanceCreate.ts | 2 +- .../models/transactions/MPTokenIssuanceSet.ts | 17 ++++++++++------- .../transactions/mptokenIssuanceCreate.test.ts | 3 ++- .../test/models/MPTokenIssuanceCreate.test.ts | 3 ++- 5 files changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/xrpl/src/client/partialPayment.ts b/packages/xrpl/src/client/partialPayment.ts index 11531b5afd..36d3d57d1c 100644 --- a/packages/xrpl/src/client/partialPayment.ts +++ b/packages/xrpl/src/client/partialPayment.ts @@ -25,10 +25,13 @@ import { isFlagEnabled } from '../models/utils' const WARN_PARTIAL_PAYMENT_CODE = 2001 +/* eslint-disable complexity -- check different token types */ +/* eslint-disable @typescript-eslint/consistent-type-assertions -- known currency type */ function amountsEqual( amt1: Amount | MPTAmount, amt2: Amount | MPTAmount, ): boolean { + // Compare XRP if (typeof amt1 === 'string' && typeof amt2 === 'string') { return amt1 === amt2 } @@ -37,6 +40,7 @@ function amountsEqual( return false } + // Compare MPTs if (isMPTAmount(amt1) && isMPTAmount(amt2)) { const aValue = new BigNumber(amt1.value) const bValue = new BigNumber(amt2.value) @@ -50,6 +54,7 @@ function amountsEqual( return false } + // Compare issued currency (IOU) const aValue = new BigNumber(amt1.value) const bValue = new BigNumber(amt2.value) @@ -59,6 +64,8 @@ function amountsEqual( aValue.isEqualTo(bValue) ) } +/* eslint-enable complexity */ +/* eslint-enable @typescript-eslint/consistent-type-assertions */ function isPartialPayment( tx?: Transaction, diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index f52a6d362b..bb837ebe6a 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -27,7 +27,7 @@ export enum MPTokenIssuanceCreateFlags { */ tfMPTCanEscrow = 0x00000008, /** - * If set, indicates that individual holders can trade their balances + * If set, indicates that individual holders can trade their balances * using the XRP Ledger DEX or AMM. */ tfMPTCanTrade = 0x00000010, diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts index 89f42919e1..91b299e1c7 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -67,14 +67,17 @@ export function validateMPTokenIssuanceSet(tx: Record): void { validateRequiredField(tx, 'MPTokenIssuanceID', isString) validateOptionalField(tx, 'MPTokenHolder', isAccount) - const flags = tx.Flags as number - /* eslint-disable no-bitwise -- We need bitwise operations for flag checks here */ - if ( - BigInt(flags) & BigInt(MPTokenIssuanceSetFlags.tfMPTLock) && - BigInt(flags) & BigInt(MPTokenIssuanceSetFlags.tfMPTUnlock) - ) { - throw new ValidationError('MPTokenIssuanceSet: flag conflict') + if (typeof tx.Flags === 'number') { + const flags = tx.Flags + if ( + BigInt(flags) & BigInt(MPTokenIssuanceSetFlags.tfMPTLock) && + BigInt(flags) & BigInt(MPTokenIssuanceSetFlags.tfMPTUnlock) + ) { + throw new ValidationError('MPTokenIssuanceSet: flag conflict') + } + } else { + throw new Error('tx.Flags is not a number') } /* eslint-enable no-bitwise -- Re-enable bitwise rule after this block */ } diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts index 8f509479b3..59088d8dd9 100644 --- a/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceCreate.test.ts @@ -26,7 +26,8 @@ describe('MPTokenIssuanceCreate', function () { const tx: MPTokenIssuanceCreate = { TransactionType: 'MPTokenIssuanceCreate', Account: testContext.wallet.classicAddress, - MaximumAmount: '9223372036854775807', // 0x7fffffffffffffff + // 0x7fffffffffffffff + MaximumAmount: '9223372036854775807', AssetScale: 2, } diff --git a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts index b24b90b70b..fba53ce672 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts @@ -17,7 +17,8 @@ describe('MPTokenIssuanceCreate', function () { const validMPTokenIssuanceCreate = { TransactionType: 'MPTokenIssuanceCreate', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', - MaximumAmount: '9223372036854775807', // 0x7fffffffffffffff + // 0x7fffffffffffffff + MaximumAmount: '9223372036854775807', AssetScale: 2, TransferFee: 1, Flags: 2, From 9bfb200fb773cfb90226e31cfd31fc2b82e46b7e Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Thu, 10 Oct 2024 10:10:36 -0400 Subject: [PATCH 38/53] const to let --- .../integration/transactions/mptokenIssuanceDestroy.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts b/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts index d42b252078..03e5d30a78 100644 --- a/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenIssuanceDestroy.test.ts @@ -50,7 +50,7 @@ describe('MPTokenIssuanceDestroy', function () { const mptID = meta.mpt_issuance_id - const accountObjectsResponse = await testContext.client.request({ + let accountObjectsResponse = await testContext.client.request({ command: 'account_objects', account: testContext.wallet.classicAddress, type: 'mpt_issuance', From 92efe1371643f2f21920bfd78d234b99f1c2aa41 Mon Sep 17 00:00:00 2001 From: Shawn Xie <35279399+shawnxie999@users.noreply.github.com> Date: Mon, 28 Oct 2024 16:57:53 -0400 Subject: [PATCH 39/53] Rename MPTokenHolder to Holder (#2) * rename * update definition.json --- .../src/enums/definitions.json | 2399 ++++++++--------- packages/xrpl/src/models/ledger/MPToken.ts | 3 +- .../xrpl/src/models/ledger/MPTokenIssuance.ts | 1 - .../models/transactions/MPTokenAuthorize.ts | 4 +- .../models/transactions/MPTokenIssuanceSet.ts | 4 +- .../xrpl/src/models/transactions/clawback.ts | 16 +- .../integration/transactions/clawback.test.ts | 2 +- .../transactions/mptokenAuthorize.test.ts | 2 +- .../xrpl/test/models/MPTokenAuthorize.test.ts | 4 +- .../test/models/MPTokenIssuanceSet.test.ts | 4 +- packages/xrpl/test/models/clawback.test.ts | 16 +- 11 files changed, 1213 insertions(+), 1242 deletions(-) diff --git a/packages/ripple-binary-codec/src/enums/definitions.json b/packages/ripple-binary-codec/src/enums/definitions.json index 33ed8788c7..15e23d9f69 100644 --- a/packages/ripple-binary-codec/src/enums/definitions.json +++ b/packages/ripple-binary-codec/src/enums/definitions.json @@ -1,3071 +1,3046 @@ { - "TYPES": { - "Done": -1, - "Unknown": -2, - "NotPresent": 0, - "UInt16": 1, - "UInt32": 2, - "UInt64": 3, - "Hash128": 4, - "Hash256": 5, - "Amount": 6, - "Blob": 7, - "AccountID": 8, - "STObject": 14, - "STArray": 15, - "UInt8": 16, - "Hash160": 17, - "PathSet": 18, - "Vector256": 19, - "UInt96": 20, - "Hash192": 21, - "UInt384": 22, - "UInt512": 23, - "Issue": 24, - "XChainBridge": 25, - "Currency": 26, - "Transaction": 10001, - "LedgerEntry": 10002, - "Validation": 10003, - "Metadata": 10004 - }, - "LEDGER_ENTRY_TYPES": { - "Invalid": -1, - "AccountRoot": 97, - "DirectoryNode": 100, - "RippleState": 114, - "Ticket": 84, - "SignerList": 83, - "Offer": 111, - "Bridge": 105, - "LedgerHashes": 104, - "Amendments": 102, - "XChainOwnedClaimID": 113, - "XChainOwnedCreateAccountClaimID": 116, - "FeeSettings": 115, - "Escrow": 117, - "PayChannel": 120, - "Check": 67, - "DepositPreauth": 112, - "NegativeUNL": 78, - "NFTokenPage": 80, - "NFTokenOffer": 55, - "AMM": 121, - "DID": 73, - "Oracle": 128, - "MPTokenIssuance": 126, - "MPToken": 127, - "Any": -3, - "Child": -2, - "Nickname": 110, - "Contract": 99, - "GeneratorMap": 103 - }, "FIELDS": [ [ "Generic", { - "nth": 0, - "isVLEncoded": false, "isSerialized": false, "isSigningField": false, + "isVLEncoded": false, + "nth": 0, "type": "Unknown" } ], [ "Invalid", { - "nth": -1, - "isVLEncoded": false, "isSerialized": false, "isSigningField": false, + "isVLEncoded": false, + "nth": -1, "type": "Unknown" } ], [ "ObjectEndMarker", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 1, "type": "STObject" } ], [ "ArrayEndMarker", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 1, "type": "STArray" } ], [ - "hash", + "taker_gets_funded", { - "nth": 257, - "isVLEncoded": false, "isSerialized": false, "isSigningField": false, - "type": "Hash256" - } - ], - [ - "index", - { - "nth": 258, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, - "type": "Hash256" - } - ], - [ - "taker_gets_funded", - { "nth": 258, - "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, "type": "Amount" } ], [ "taker_pays_funded", { - "nth": 259, - "isVLEncoded": false, "isSerialized": false, "isSigningField": false, + "isVLEncoded": false, + "nth": 259, "type": "Amount" } ], [ - "LedgerEntry", + "LedgerEntryType", { - "nth": 257, + "isSerialized": true, + "isSigningField": true, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, - "type": "LedgerEntry" + "nth": 1, + "type": "UInt16" } ], [ - "Transaction", + "TransactionType", { - "nth": 257, + "isSerialized": true, + "isSigningField": true, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, - "type": "Transaction" + "nth": 2, + "type": "UInt16" } ], [ - "Validation", + "SignerWeight", { - "nth": 257, + "isSerialized": true, + "isSigningField": true, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, - "type": "Validation" + "nth": 3, + "type": "UInt16" } ], [ - "Metadata", + "TransferFee", { - "nth": 257, + "isSerialized": true, + "isSigningField": true, "isVLEncoded": false, - "isSerialized": false, - "isSigningField": false, - "type": "Metadata" + "nth": 4, + "type": "UInt16" } ], [ - "CloseResolution", + "TradingFee", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 5, + "type": "UInt16" } ], [ - "Method", + "DiscountedFee", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 6, + "type": "UInt16" } ], [ - "TransactionResult", + "Version", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 16, + "type": "UInt16" } ], [ - "Scale", + "HookStateChangeCount", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 17, + "type": "UInt16" } ], [ - "TickSize", + "HookEmitCount", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 18, + "type": "UInt16" } ], [ - "UNLModifyDisabling", + "HookExecutionIndex", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 19, + "type": "UInt16" } ], [ - "HookResult", + "HookApiVersion", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 20, + "type": "UInt16" } ], [ - "WasLockingChainSend", + "LedgerFixType", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 21, + "type": "UInt16" } ], [ - "AssetScale", + "NetworkID", { - "nth": 20, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt8" + "isVLEncoded": false, + "nth": 1, + "type": "UInt32" } ], [ - "LedgerEntryType", + "Flags", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 2, + "type": "UInt32" } ], [ - "TransactionType", + "SourceTag", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 3, + "type": "UInt32" } ], [ - "SignerWeight", + "Sequence", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 4, + "type": "UInt32" } ], [ - "TransferFee", + "PreviousTxnLgrSeq", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 5, + "type": "UInt32" } ], [ - "TradingFee", + "LedgerSequence", { - "nth": 5, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 6, + "type": "UInt32" } ], [ - "DiscountedFee", + "CloseTime", { - "nth": 6, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 7, + "type": "UInt32" } ], [ - "Version", + "ParentCloseTime", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 8, + "type": "UInt32" } ], [ - "HookStateChangeCount", + "SigningTime", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 9, + "type": "UInt32" } ], [ - "HookEmitCount", + "Expiration", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 10, + "type": "UInt32" } ], [ - "HookExecutionIndex", + "TransferRate", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 11, + "type": "UInt32" } ], [ - "HookApiVersion", + "WalletSize", { - "nth": 20, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 12, + "type": "UInt32" } ], [ - "LedgerFixType", + "OwnerCount", { - "nth": 21, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt16" + "isVLEncoded": false, + "nth": 13, + "type": "UInt32" } ], [ - "NetworkID", + "DestinationTag", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 14, "type": "UInt32" } ], [ - "Flags", + "LastUpdateTime", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 15, "type": "UInt32" } ], [ - "SourceTag", + "HighQualityIn", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 16, "type": "UInt32" } ], [ - "Sequence", + "HighQualityOut", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 17, "type": "UInt32" } ], [ - "PreviousTxnLgrSeq", + "LowQualityIn", { - "nth": 5, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 18, "type": "UInt32" } ], [ - "LedgerSequence", + "LowQualityOut", { - "nth": 6, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 19, "type": "UInt32" } ], [ - "CloseTime", + "QualityIn", { - "nth": 7, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 20, "type": "UInt32" } ], [ - "ParentCloseTime", + "QualityOut", { - "nth": 8, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 21, "type": "UInt32" } ], [ - "SigningTime", + "StampEscrow", { - "nth": 9, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 22, "type": "UInt32" } ], [ - "Expiration", + "BondAmount", { - "nth": 10, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 23, "type": "UInt32" } ], [ - "TransferRate", + "LoadFee", { - "nth": 11, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 24, "type": "UInt32" } ], [ - "WalletSize", + "OfferSequence", { - "nth": 12, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 25, "type": "UInt32" } ], [ - "OwnerCount", + "FirstLedgerSequence", { - "nth": 13, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 26, "type": "UInt32" } ], [ - "DestinationTag", + "LastLedgerSequence", { - "nth": 14, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 27, "type": "UInt32" } ], [ - "LastUpdateTime", + "TransactionIndex", { - "nth": 15, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 28, "type": "UInt32" } ], [ - "HighQualityIn", + "OperationLimit", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 29, "type": "UInt32" } ], [ - "HighQualityOut", + "ReferenceFeeUnits", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 30, "type": "UInt32" } ], [ - "LowQualityIn", + "ReserveBase", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 31, "type": "UInt32" } ], [ - "LowQualityOut", + "ReserveIncrement", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 32, "type": "UInt32" } ], [ - "QualityIn", + "SetFlag", { - "nth": 20, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 33, "type": "UInt32" } ], [ - "QualityOut", + "ClearFlag", { - "nth": 21, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 34, "type": "UInt32" } ], [ - "StampEscrow", + "SignerQuorum", { - "nth": 22, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 35, "type": "UInt32" } ], [ - "BondAmount", + "CancelAfter", { - "nth": 23, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 36, "type": "UInt32" } ], [ - "LoadFee", + "FinishAfter", { - "nth": 24, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 37, "type": "UInt32" } ], [ - "OfferSequence", + "SignerListID", { - "nth": 25, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 38, "type": "UInt32" } ], [ - "FirstLedgerSequence", + "SettleDelay", { - "nth": 26, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 39, "type": "UInt32" } ], [ - "LastLedgerSequence", + "TicketCount", { - "nth": 27, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 40, "type": "UInt32" } ], [ - "TransactionIndex", + "TicketSequence", { - "nth": 28, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 41, "type": "UInt32" } ], [ - "OperationLimit", + "NFTokenTaxon", { - "nth": 29, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 42, "type": "UInt32" } ], [ - "ReferenceFeeUnits", + "MintedNFTokens", { - "nth": 30, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 43, "type": "UInt32" } ], [ - "ReserveBase", + "BurnedNFTokens", { - "nth": 31, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 44, "type": "UInt32" } ], [ - "ReserveIncrement", + "HookStateCount", { - "nth": 32, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 45, "type": "UInt32" } ], [ - "SetFlag", + "EmitGeneration", { - "nth": 33, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 46, "type": "UInt32" } ], [ - "ClearFlag", + "VoteWeight", { - "nth": 34, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 48, "type": "UInt32" } ], [ - "SignerQuorum", + "FirstNFTokenSequence", { - "nth": 35, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "UInt32" - } - ], - [ - "CancelAfter", - { - "nth": 36, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "FinishAfter", - { - "nth": 37, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "SignerListID", - { - "nth": 38, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "SettleDelay", - { - "nth": 39, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "TicketCount", - { - "nth": 40, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "TicketSequence", - { - "nth": 41, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "NFTokenTaxon", - { - "nth": 42, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "MintedNFTokens", - { - "nth": 43, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "BurnedNFTokens", - { - "nth": 44, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "HookStateCount", - { - "nth": 45, "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "EmitGeneration", - { - "nth": 46, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "VoteWeight", - { - "nth": 48, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "UInt32" - } - ], - [ - "FirstNFTokenSequence", - { "nth": 50, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, "type": "UInt32" } ], [ "OracleDocumentID", { - "nth": 51, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 51, "type": "UInt32" } ], [ "IndexNext", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 1, "type": "UInt64" } ], [ "IndexPrevious", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 2, "type": "UInt64" } ], [ "BookNode", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 3, "type": "UInt64" } ], [ "OwnerNode", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 4, "type": "UInt64" } ], [ "BaseFee", { - "nth": 5, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 5, "type": "UInt64" } ], [ "ExchangeRate", { - "nth": 6, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 6, "type": "UInt64" } ], [ "LowNode", { - "nth": 7, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 7, "type": "UInt64" } ], [ "HighNode", { - "nth": 8, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 8, "type": "UInt64" } ], [ "DestinationNode", { - "nth": 9, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 9, "type": "UInt64" } ], [ "Cookie", { - "nth": 10, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 10, "type": "UInt64" } ], [ "ServerVersion", { - "nth": 11, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 11, "type": "UInt64" } ], [ "NFTokenOfferNode", { - "nth": 12, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 12, "type": "UInt64" } ], [ "EmitBurden", { - "nth": 13, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 13, "type": "UInt64" } ], [ "HookOn", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 16, "type": "UInt64" } ], [ "HookInstructionCount", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 17, "type": "UInt64" } ], [ "HookReturnCode", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 18, "type": "UInt64" } ], [ "ReferenceCount", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 19, "type": "UInt64" } ], [ "XChainClaimID", { - "nth": 20, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 20, "type": "UInt64" } ], [ "XChainAccountCreateCount", { - "nth": 21, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 21, "type": "UInt64" } ], [ "XChainAccountClaimCount", { - "nth": 22, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 22, "type": "UInt64" } ], [ "AssetPrice", { - "nth": 23, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 23, "type": "UInt64" } ], [ "MaximumAmount", { - "nth": 24, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 24, "type": "UInt64" } ], [ "OutstandingAmount", { - "nth": 25, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 25, "type": "UInt64" } ], [ "MPTAmount", { - "nth": 26, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 26, "type": "UInt64" } ], [ "EmailHash", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 1, "type": "Hash128" } ], [ - "TakerPaysCurrency", + "LedgerHash", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash160" + "isVLEncoded": false, + "nth": 1, + "type": "Hash256" } ], [ - "TakerPaysIssuer", + "ParentHash", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash160" + "isVLEncoded": false, + "nth": 2, + "type": "Hash256" } ], [ - "TakerGetsCurrency", + "TransactionHash", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash160" + "isVLEncoded": false, + "nth": 3, + "type": "Hash256" } ], [ - "TakerGetsIssuer", - { - "nth": 4, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "Hash160" - } - ], - [ - "MPTokenIssuanceID", - { - "nth": 1, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "Hash192" - } - ], - [ - "LedgerHash", - { - "nth": 1, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "Hash256" - } - ], - [ - "ParentHash", + "AccountHash", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Hash256" - } - ], - [ - "TransactionHash", - { - "nth": 3, "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "Hash256" - } - ], - [ - "AccountHash", - { "nth": 4, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, "type": "Hash256" } ], [ "PreviousTxnID", { - "nth": 5, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 5, "type": "Hash256" } ], [ "LedgerIndex", { - "nth": 6, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 6, "type": "Hash256" } ], [ "WalletLocator", { - "nth": 7, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 7, "type": "Hash256" } ], [ "RootIndex", { - "nth": 8, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 8, "type": "Hash256" } ], [ "AccountTxnID", { - "nth": 9, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 9, "type": "Hash256" } ], [ "NFTokenID", { - "nth": 10, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 10, "type": "Hash256" } ], [ "EmitParentTxnID", { - "nth": 11, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 11, "type": "Hash256" } ], [ "EmitNonce", { - "nth": 12, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 12, "type": "Hash256" } ], [ "EmitHookHash", { - "nth": 13, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 13, "type": "Hash256" } ], [ "AMMID", { - "nth": 14, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 14, "type": "Hash256" } ], [ "BookDirectory", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 16, "type": "Hash256" } ], [ "InvoiceID", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 17, "type": "Hash256" } ], [ "Nickname", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 18, "type": "Hash256" } ], [ "Amendment", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 19, "type": "Hash256" } ], [ "Digest", { - "nth": 21, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 21, "type": "Hash256" } ], [ "Channel", { - "nth": 22, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 22, "type": "Hash256" } ], [ "ConsensusHash", { - "nth": 23, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 23, "type": "Hash256" } ], [ "CheckID", { - "nth": 24, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 24, "type": "Hash256" } ], [ "ValidatedHash", { - "nth": 25, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 25, "type": "Hash256" } ], [ "PreviousPageMin", { - "nth": 26, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 26, "type": "Hash256" } ], [ "NextPageMin", { - "nth": 27, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 27, "type": "Hash256" } ], [ "NFTokenBuyOffer", { - "nth": 28, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 28, "type": "Hash256" } ], [ "NFTokenSellOffer", { - "nth": 29, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 29, "type": "Hash256" } ], [ "HookStateKey", { - "nth": 30, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 30, "type": "Hash256" } ], [ "HookHash", { - "nth": 31, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 31, "type": "Hash256" } ], [ "HookNamespace", { - "nth": 32, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 32, "type": "Hash256" } ], [ "HookSetTxnID", { - "nth": 33, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 33, "type": "Hash256" } ], [ - "Amount", + "hash", { - "nth": 1, + "isSerialized": false, + "isSigningField": false, "isVLEncoded": false, + "nth": 257, + "type": "Hash256" + } + ], + [ + "Amount", + { "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 1, "type": "Amount" } ], [ "Balance", { - "nth": 2, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 2, "type": "Amount" } ], [ "LimitAmount", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 3, "type": "Amount" } ], [ "TakerPays", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 4, "type": "Amount" } ], [ "TakerGets", { - "nth": 5, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 5, "type": "Amount" } ], [ "LowLimit", { - "nth": 6, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 6, "type": "Amount" } ], [ "HighLimit", { - "nth": 7, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 7, "type": "Amount" } ], [ "Fee", { - "nth": 8, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 8, "type": "Amount" } ], [ "SendMax", { - "nth": 9, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 9, "type": "Amount" } ], [ "DeliverMin", { - "nth": 10, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 10, "type": "Amount" } ], [ "Amount2", { - "nth": 11, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 11, "type": "Amount" } ], [ "BidMin", { - "nth": 12, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 12, "type": "Amount" } ], [ "BidMax", { - "nth": 13, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 13, "type": "Amount" } ], [ "MinimumOffer", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 16, "type": "Amount" } ], [ "RippleEscrow", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 17, "type": "Amount" } ], [ "DeliveredAmount", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 18, "type": "Amount" } ], [ "NFTokenBrokerFee", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 19, "type": "Amount" } ], [ "BaseFeeDrops", { - "nth": 22, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 22, "type": "Amount" } ], [ "ReserveBaseDrops", { - "nth": 23, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 23, "type": "Amount" } ], [ "ReserveIncrementDrops", { - "nth": 24, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 24, "type": "Amount" } ], [ "LPTokenOut", { - "nth": 25, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 25, "type": "Amount" } ], [ "LPTokenIn", { - "nth": 26, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Amount" + "isVLEncoded": false, + "nth": 26, + "type": "Amount" } ], [ "EPrice", { - "nth": 27, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 27, "type": "Amount" } ], [ "Price", { - "nth": 28, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 28, "type": "Amount" } ], [ "SignatureReward", { - "nth": 29, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 29, "type": "Amount" } ], [ "MinAccountCreateAmount", { - "nth": 30, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 30, "type": "Amount" } ], [ "LPTokenBalance", { - "nth": 31, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 31, "type": "Amount" } ], [ "PublicKey", { - "nth": 1, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 1, "type": "Blob" } ], [ "MessageKey", { - "nth": 2, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 2, "type": "Blob" } ], [ "SigningPubKey", { - "nth": 3, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 3, "type": "Blob" } ], [ "TxnSignature", { - "nth": 4, - "isVLEncoded": true, "isSerialized": true, "isSigningField": false, + "isVLEncoded": true, + "nth": 4, "type": "Blob" } ], [ "URI", { - "nth": 5, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 5, "type": "Blob" } ], [ "Signature", { - "nth": 6, - "isVLEncoded": true, "isSerialized": true, "isSigningField": false, + "isVLEncoded": true, + "nth": 6, "type": "Blob" } ], [ "Domain", { - "nth": 7, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 7, "type": "Blob" } ], [ "FundCode", { - "nth": 8, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 8, "type": "Blob" } ], [ "RemoveCode", { - "nth": 9, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 9, "type": "Blob" } ], [ "ExpireCode", { - "nth": 10, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 10, "type": "Blob" } ], [ "CreateCode", { - "nth": 11, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 11, "type": "Blob" } ], [ "MemoType", { - "nth": 12, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 12, "type": "Blob" } ], [ "MemoData", { - "nth": 13, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 13, "type": "Blob" } ], [ "MemoFormat", { - "nth": 14, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 14, "type": "Blob" } ], [ "Fulfillment", { - "nth": 16, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 16, "type": "Blob" } ], [ "Condition", { - "nth": 17, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 17, "type": "Blob" } ], [ "MasterSignature", { - "nth": 18, - "isVLEncoded": true, "isSerialized": true, "isSigningField": false, + "isVLEncoded": true, + "nth": 18, "type": "Blob" } ], [ "UNLModifyValidator", { - "nth": 19, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 19, "type": "Blob" } ], [ "ValidatorToDisable", { - "nth": 20, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 20, "type": "Blob" } ], [ "ValidatorToReEnable", { - "nth": 21, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 21, "type": "Blob" } ], [ "HookStateData", { - "nth": 22, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 22, "type": "Blob" } ], [ "HookReturnString", { - "nth": 23, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 23, "type": "Blob" } ], [ "HookParameterName", { - "nth": 24, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 24, "type": "Blob" } ], [ "HookParameterValue", { - "nth": 25, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 25, "type": "Blob" } ], [ "DIDDocument", { - "nth": 26, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 26, "type": "Blob" } ], [ "Data", { - "nth": 27, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 27, "type": "Blob" } ], [ "AssetClass", { - "nth": 28, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 28, "type": "Blob" } ], [ "Provider", { - "nth": 29, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 29, "type": "Blob" } ], [ "MPTokenMetadata", { - "nth": 30, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 30, "type": "Blob" } ], [ "Account", { - "nth": 1, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 1, "type": "AccountID" } ], [ "Owner", { - "nth": 2, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 2, "type": "AccountID" } ], [ "Destination", { - "nth": 3, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 3, "type": "AccountID" } ], [ "Issuer", { - "nth": 4, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 4, "type": "AccountID" } ], [ "Authorize", { - "nth": 5, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 5, "type": "AccountID" } ], [ "Unauthorize", { - "nth": 6, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 6, "type": "AccountID" } ], [ "RegularKey", { - "nth": 8, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 8, "type": "AccountID" } ], [ "NFTokenMinter", { - "nth": 9, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 9, "type": "AccountID" } ], [ "EmitCallback", { - "nth": 10, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 10, "type": "AccountID" } ], [ - "MPTokenHolder", + "Holder", { - "nth": 11, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 11, "type": "AccountID" } ], [ "HookAccount", { - "nth": 16, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 16, "type": "AccountID" } ], [ "OtherChainSource", { - "nth": 18, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 18, "type": "AccountID" } ], [ "OtherChainDestination", { - "nth": 19, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 19, "type": "AccountID" } ], [ "AttestationSignerAccount", { - "nth": 20, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 20, "type": "AccountID" } ], [ "AttestationRewardAccount", { - "nth": 21, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 21, "type": "AccountID" } ], [ "LockingChainDoor", { - "nth": 22, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 22, "type": "AccountID" } ], [ "IssuingChainDoor", { - "nth": 23, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, + "isVLEncoded": true, + "nth": 23, "type": "AccountID" } ], [ - "Indexes", + "TransactionMetaData", { - "nth": 1, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "Vector256" + "isVLEncoded": false, + "nth": 2, + "type": "STObject" } ], [ - "Hashes", + "CreatedNode", { - "nth": 2, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "Vector256" + "isVLEncoded": false, + "nth": 3, + "type": "STObject" } ], [ - "Amendments", + "DeletedNode", { - "nth": 3, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "Vector256" + "isVLEncoded": false, + "nth": 4, + "type": "STObject" } ], [ - "NFTokenOffers", + "ModifiedNode", { - "nth": 4, - "isVLEncoded": true, "isSerialized": true, "isSigningField": true, - "type": "Vector256" + "isVLEncoded": false, + "nth": 5, + "type": "STObject" } ], [ - "Paths", + "PreviousFields", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "PathSet" + "isVLEncoded": false, + "nth": 6, + "type": "STObject" } ], [ - "BaseAsset", + "FinalFields", { - "nth": 1, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "Currency" + "isVLEncoded": false, + "nth": 7, + "type": "STObject" } ], [ - "QuoteAsset", - { - "nth": 2, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "Currency" - } - ], - [ - "LockingChainIssue", - { - "nth": 1, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "Issue" - } - ], - [ - "IssuingChainIssue", - { - "nth": 2, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "Issue" - } - ], - [ - "Asset", - { - "nth": 3, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "Issue" - } - ], - [ - "Asset2", - { - "nth": 4, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "Issue" - } - ], - [ - "XChainBridge", - { - "nth": 1, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "XChainBridge" - } - ], - [ - "TransactionMetaData", - { - "nth": 2, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "CreatedNode", - { - "nth": 3, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "DeletedNode", - { - "nth": 4, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "ModifiedNode", - { - "nth": 5, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "PreviousFields", + "NewFields", { - "nth": 6, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STObject" - } - ], - [ - "FinalFields", - { - "nth": 7, "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, - "type": "STObject" - } - ], - [ - "NewFields", - { "nth": 8, - "isVLEncoded": false, - "isSerialized": true, - "isSigningField": true, "type": "STObject" } ], [ "TemplateEntry", { - "nth": 9, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 9, "type": "STObject" } ], [ "Memo", { - "nth": 10, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 10, "type": "STObject" } ], [ "SignerEntry", { - "nth": 11, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 11, "type": "STObject" } ], [ "NFToken", { - "nth": 12, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 12, "type": "STObject" } ], [ "EmitDetails", { - "nth": 13, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 13, "type": "STObject" } ], [ "Hook", { - "nth": 14, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 14, "type": "STObject" } ], [ "Signer", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 16, "type": "STObject" } ], [ "Majority", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 18, "type": "STObject" } ], [ "DisabledValidator", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 19, "type": "STObject" } ], [ "EmittedTxn", { - "nth": 20, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 20, "type": "STObject" } ], [ "HookExecution", { - "nth": 21, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 21, "type": "STObject" } ], [ "HookDefinition", { - "nth": 22, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 22, "type": "STObject" } ], [ "HookParameter", { - "nth": 23, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 23, "type": "STObject" } ], [ "HookGrant", { - "nth": 24, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 24, "type": "STObject" } ], [ "VoteEntry", { - "nth": 25, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 25, "type": "STObject" } ], [ "AuctionSlot", { - "nth": 26, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 26, "type": "STObject" } ], [ "AuthAccount", { - "nth": 27, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 27, "type": "STObject" } ], [ "XChainClaimProofSig", { - "nth": 28, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 28, "type": "STObject" } ], [ "XChainCreateAccountProofSig", { - "nth": 29, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 29, "type": "STObject" } ], [ "XChainClaimAttestationCollectionElement", { - "nth": 30, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 30, "type": "STObject" } ], [ "XChainCreateAccountAttestationCollectionElement", { - "nth": 31, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 31, "type": "STObject" } ], [ "PriceData", { - "nth": 32, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 32, "type": "STObject" } ], [ "Signers", { - "nth": 3, - "isVLEncoded": false, "isSerialized": true, "isSigningField": false, + "isVLEncoded": false, + "nth": 3, "type": "STArray" } ], [ "SignerEntries", { - "nth": 4, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 4, "type": "STArray" } ], [ "Template", { - "nth": 5, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 5, "type": "STArray" } ], [ "Necessary", { - "nth": 6, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 6, "type": "STArray" } ], [ "Sufficient", { - "nth": 7, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 7, "type": "STArray" } ], [ "AffectedNodes", { - "nth": 8, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 8, "type": "STArray" } ], [ "Memos", { - "nth": 9, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 9, "type": "STArray" } ], [ "NFTokens", { - "nth": 10, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 10, "type": "STArray" } ], [ "Hooks", { - "nth": 11, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 11, "type": "STArray" } ], [ "VoteSlots", { - "nth": 12, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 12, "type": "STArray" } ], [ "Majorities", { - "nth": 16, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 16, "type": "STArray" } ], [ "DisabledValidators", { - "nth": 17, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, - "type": "STArray" + "isVLEncoded": false, + "nth": 17, + "type": "STArray" } ], [ "HookExecutions", { - "nth": 18, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 18, "type": "STArray" } ], [ "HookParameters", { - "nth": 19, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 19, "type": "STArray" } ], [ "HookGrants", { - "nth": 20, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 20, "type": "STArray" } ], [ "XChainClaimAttestations", { - "nth": 21, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 21, "type": "STArray" } ], [ "XChainCreateAccountAttestations", { - "nth": 22, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 22, "type": "STArray" } ], [ "PriceDataSeries", { - "nth": 24, - "isVLEncoded": false, "isSerialized": true, "isSigningField": true, + "isVLEncoded": false, + "nth": 24, "type": "STArray" } ], [ "AuthAccounts", { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, "nth": 25, + "type": "STArray" + } + ], + [ + "CloseResolution", + { + "isSerialized": true, + "isSigningField": true, "isVLEncoded": false, + "nth": 1, + "type": "UInt8" + } + ], + [ + "Method", + { "isSerialized": true, "isSigningField": true, - "type": "STArray" + "isVLEncoded": false, + "nth": 2, + "type": "UInt8" } - ] - ], - "TRANSACTION_RESULTS": { - "telLOCAL_ERROR": -399, - "telBAD_DOMAIN": -398, - "telBAD_PATH_COUNT": -397, - "telBAD_PUBLIC_KEY": -396, - "telFAILED_PROCESSING": -395, - "telINSUF_FEE_P": -394, - "telNO_DST_PARTIAL": -393, - "telCAN_NOT_QUEUE": -392, - "telCAN_NOT_QUEUE_BALANCE": -391, - "telCAN_NOT_QUEUE_BLOCKS": -390, - "telCAN_NOT_QUEUE_BLOCKED": -389, - "telCAN_NOT_QUEUE_FEE": -388, - "telCAN_NOT_QUEUE_FULL": -387, - "telWRONG_NETWORK": -386, - "telREQUIRES_NETWORK_ID": -385, - "telNETWORK_ID_MAKES_TX_NON_CANONICAL": -384, - "telENV_RPC_FAILED": -383, - - "temMALFORMED": -299, - "temBAD_AMOUNT": -298, - "temBAD_CURRENCY": -297, - "temBAD_EXPIRATION": -296, - "temBAD_FEE": -295, - "temBAD_ISSUER": -294, - "temBAD_LIMIT": -293, - "temBAD_OFFER": -292, - "temBAD_PATH": -291, - "temBAD_PATH_LOOP": -290, - "temBAD_REGKEY": -289, - "temBAD_SEND_XRP_LIMIT": -288, - "temBAD_SEND_XRP_MAX": -287, - "temBAD_SEND_XRP_NO_DIRECT": -286, - "temBAD_SEND_XRP_PARTIAL": -285, - "temBAD_SEND_XRP_PATHS": -284, - "temBAD_SEQUENCE": -283, - "temBAD_SIGNATURE": -282, - "temBAD_SRC_ACCOUNT": -281, - "temBAD_TRANSFER_RATE": -280, - "temDST_IS_SRC": -279, - "temDST_NEEDED": -278, - "temINVALID": -277, - "temINVALID_FLAG": -276, - "temREDUNDANT": -275, - "temRIPPLE_EMPTY": -274, - "temDISABLED": -273, - "temBAD_SIGNER": -272, - "temBAD_QUORUM": -271, - "temBAD_WEIGHT": -270, - "temBAD_TICK_SIZE": -269, - "temINVALID_ACCOUNT_ID": -268, - "temCANNOT_PREAUTH_SELF": -267, - "temINVALID_COUNT": -266, - "temUNCERTAIN": -265, - "temUNKNOWN": -264, - "temSEQ_AND_TICKET": -263, - "temBAD_NFTOKEN_TRANSFER_FEE": -262, - "temBAD_AMM_TOKENS": -261, - "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -260, - "temXCHAIN_BAD_PROOF": -259, - "temXCHAIN_BRIDGE_BAD_ISSUES": -258, - "temXCHAIN_BRIDGE_NONDOOR_OWNER": -257, - "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -256, - "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255, - "temEMPTY_DID": -254, - "temARRAY_EMPTY": -253, - "temARRAY_TOO_LARGE": -252, - "temBAD_TRANSFER_FEE": -251, - - "tefFAILURE": -199, - "tefALREADY": -198, - "tefBAD_ADD_AUTH": -197, - "tefBAD_AUTH": -196, - "tefBAD_LEDGER": -195, - "tefCREATED": -194, - "tefEXCEPTION": -193, - "tefINTERNAL": -192, - "tefNO_AUTH_REQUIRED": -191, - "tefPAST_SEQ": -190, - "tefWRONG_PRIOR": -189, - "tefMASTER_DISABLED": -188, - "tefMAX_LEDGER": -187, - "tefBAD_SIGNATURE": -186, - "tefBAD_QUORUM": -185, - "tefNOT_MULTI_SIGNING": -184, - "tefBAD_AUTH_MASTER": -183, - "tefINVARIANT_FAILED": -182, - "tefTOO_BIG": -181, - "tefNO_TICKET": -180, - "tefNFTOKEN_IS_NOT_TRANSFERABLE": -179, - "tefINVALID_LEDGER_FIX_TYPE": -178, - - "terRETRY": -99, - "terFUNDS_SPENT": -98, - "terINSUF_FEE_B": -97, - "terNO_ACCOUNT": -96, - "terNO_AUTH": -95, - "terNO_LINE": -94, - "terOWNERS": -93, - "terPRE_SEQ": -92, - "terLAST": -91, - "terNO_RIPPLE": -90, - "terQUEUED": -89, - "terPRE_TICKET": -88, - "terNO_AMM": -87, - - "tesSUCCESS": 0, - - "tecCLAIM": 100, - "tecPATH_PARTIAL": 101, - "tecUNFUNDED_ADD": 102, - "tecUNFUNDED_OFFER": 103, - "tecUNFUNDED_PAYMENT": 104, - "tecFAILED_PROCESSING": 105, - "tecDIR_FULL": 121, - "tecINSUF_RESERVE_LINE": 122, - "tecINSUF_RESERVE_OFFER": 123, - "tecNO_DST": 124, - "tecNO_DST_INSUF_XRP": 125, - "tecNO_LINE_INSUF_RESERVE": 126, - "tecNO_LINE_REDUNDANT": 127, - "tecPATH_DRY": 128, - "tecUNFUNDED": 129, - "tecNO_ALTERNATIVE_KEY": 130, - "tecNO_REGULAR_KEY": 131, - "tecOWNERS": 132, - "tecNO_ISSUER": 133, - "tecNO_AUTH": 134, - "tecNO_LINE": 135, - "tecINSUFF_FEE": 136, - "tecFROZEN": 137, - "tecNO_TARGET": 138, - "tecNO_PERMISSION": 139, - "tecNO_ENTRY": 140, - "tecINSUFFICIENT_RESERVE": 141, - "tecNEED_MASTER_KEY": 142, - "tecDST_TAG_NEEDED": 143, - "tecINTERNAL": 144, - "tecOVERSIZE": 145, - "tecCRYPTOCONDITION_ERROR": 146, - "tecINVARIANT_FAILED": 147, - "tecEXPIRED": 148, - "tecDUPLICATE": 149, - "tecKILLED": 150, - "tecHAS_OBLIGATIONS": 151, - "tecTOO_SOON": 152, - "tecHOOK_REJECTED": 153, - "tecMAX_SEQUENCE_REACHED": 154, - "tecNO_SUITABLE_NFTOKEN_PAGE": 155, - "tecNFTOKEN_BUY_SELL_MISMATCH": 156, - "tecNFTOKEN_OFFER_TYPE_MISMATCH": 157, - "tecCANT_ACCEPT_OWN_NFTOKEN_OFFER": 158, - "tecINSUFFICIENT_FUNDS": 159, - "tecOBJECT_NOT_FOUND": 160, - "tecINSUFFICIENT_PAYMENT": 161, - "tecUNFUNDED_AMM": 162, - "tecAMM_BALANCE": 163, - "tecAMM_FAILED": 164, - "tecAMM_INVALID_TOKENS": 165, - "tecAMM_EMPTY": 166, - "tecAMM_NOT_EMPTY": 167, - "tecAMM_ACCOUNT": 168, - "tecINCOMPLETE": 169, - "tecXCHAIN_BAD_TRANSFER_ISSUE": 170, - "tecXCHAIN_NO_CLAIM_ID": 171, - "tecXCHAIN_BAD_CLAIM_ID": 172, - "tecXCHAIN_CLAIM_NO_QUORUM": 173, - "tecXCHAIN_PROOF_UNKNOWN_KEY": 174, - "tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE": 175, - "tecXCHAIN_WRONG_CHAIN": 176, - "tecXCHAIN_REWARD_MISMATCH": 177, - "tecXCHAIN_NO_SIGNERS_LIST": 178, - "tecXCHAIN_SENDING_ACCOUNT_MISMATCH": 179, - "tecXCHAIN_INSUFF_CREATE_AMOUNT": 180, - "tecXCHAIN_ACCOUNT_CREATE_PAST": 181, - "tecXCHAIN_ACCOUNT_CREATE_TOO_MANY": 182, - "tecXCHAIN_PAYMENT_FAILED": 183, - "tecXCHAIN_SELF_COMMIT": 184, - "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185, - "tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186, - "tecEMPTY_DID": 187, - "tecINVALID_UPDATE_TIME": 188, - "tecTOKEN_PAIR_NOT_FOUND": 189, - "tecARRAY_EMPTY": 190, - "tecARRAY_TOO_LARGE": 191, - "tecLOCKED": 192 - }, - "TRANSACTION_TYPES": { - "Invalid": -1, - "Payment": 0, - "EscrowCreate": 1, - "EscrowFinish": 2, - "AccountSet": 3, - "EscrowCancel": 4, - "SetRegularKey": 5, - "NickNameSet": 6, - "OfferCreate": 7, - "OfferCancel": 8, - "Contract": 9, - "TicketCreate": 10, - "TicketCancel": 11, - "SignerListSet": 12, - "PaymentChannelCreate": 13, - "PaymentChannelFund": 14, - "PaymentChannelClaim": 15, - "CheckCreate": 16, - "CheckCash": 17, - "CheckCancel": 18, - "DepositPreauth": 19, - "TrustSet": 20, - "AccountDelete": 21, - "SetHook": 22, - "NFTokenMint": 25, - "NFTokenBurn": 26, - "NFTokenCreateOffer": 27, - "NFTokenCancelOffer": 28, - "NFTokenAcceptOffer": 29, - "Clawback": 30, + ], + [ + "TransactionResult", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 3, + "type": "UInt8" + } + ], + [ + "Scale", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 4, + "type": "UInt8" + } + ], + [ + "AssetScale", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 5, + "type": "UInt8" + } + ], + [ + "TickSize", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 16, + "type": "UInt8" + } + ], + [ + "UNLModifyDisabling", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 17, + "type": "UInt8" + } + ], + [ + "HookResult", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 18, + "type": "UInt8" + } + ], + [ + "WasLockingChainSend", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 19, + "type": "UInt8" + } + ], + [ + "TakerPaysCurrency", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 1, + "type": "Hash160" + } + ], + [ + "TakerPaysIssuer", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 2, + "type": "Hash160" + } + ], + [ + "TakerGetsCurrency", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 3, + "type": "Hash160" + } + ], + [ + "TakerGetsIssuer", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 4, + "type": "Hash160" + } + ], + [ + "Paths", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 1, + "type": "PathSet" + } + ], + [ + "Indexes", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": true, + "nth": 1, + "type": "Vector256" + } + ], + [ + "Hashes", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": true, + "nth": 2, + "type": "Vector256" + } + ], + [ + "Amendments", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": true, + "nth": 3, + "type": "Vector256" + } + ], + [ + "NFTokenOffers", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": true, + "nth": 4, + "type": "Vector256" + } + ], + [ + "MPTokenIssuanceID", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 1, + "type": "Hash192" + } + ], + [ + "LockingChainIssue", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 1, + "type": "Issue" + } + ], + [ + "IssuingChainIssue", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 2, + "type": "Issue" + } + ], + [ + "Asset", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 3, + "type": "Issue" + } + ], + [ + "Asset2", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 4, + "type": "Issue" + } + ], + [ + "XChainBridge", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 1, + "type": "XChainBridge" + } + ], + [ + "BaseAsset", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 1, + "type": "Currency" + } + ], + [ + "QuoteAsset", + { + "isSerialized": true, + "isSigningField": true, + "isVLEncoded": false, + "nth": 2, + "type": "Currency" + } + ], + [ + "Transaction", + { + "isSerialized": false, + "isSigningField": false, + "isVLEncoded": false, + "nth": 257, + "type": "Transaction" + } + ], + [ + "LedgerEntry", + { + "isSerialized": false, + "isSigningField": false, + "isVLEncoded": false, + "nth": 257, + "type": "LedgerEntry" + } + ], + [ + "Validation", + { + "isSerialized": false, + "isSigningField": false, + "isVLEncoded": false, + "nth": 257, + "type": "Validation" + } + ], + [ + "Metadata", + { + "isSerialized": false, + "isSigningField": false, + "isVLEncoded": false, + "nth": 257, + "type": "Metadata" + } + ] + ], + "LEDGER_ENTRY_TYPES": { + "AMM": 121, + "AccountRoot": 97, + "Amendments": 102, + "Bridge": 105, + "Check": 67, + "DID": 73, + "DepositPreauth": 112, + "DirectoryNode": 100, + "Escrow": 117, + "FeeSettings": 115, + "Invalid": -1, + "LedgerHashes": 104, + "MPToken": 127, + "MPTokenIssuance": 126, + "NFTokenOffer": 55, + "NFTokenPage": 80, + "NegativeUNL": 78, + "Offer": 111, + "Oracle": 128, + "PayChannel": 120, + "RippleState": 114, + "SignerList": 83, + "Ticket": 84, + "XChainOwnedClaimID": 113, + "XChainOwnedCreateAccountClaimID": 116 + }, + "TRANSACTION_RESULTS": { + "tecAMM_ACCOUNT": 168, + "tecAMM_BALANCE": 163, + "tecAMM_EMPTY": 166, + "tecAMM_FAILED": 164, + "tecAMM_INVALID_TOKENS": 165, + "tecAMM_NOT_EMPTY": 167, + "tecARRAY_EMPTY": 190, + "tecARRAY_TOO_LARGE": 191, + "tecCANT_ACCEPT_OWN_NFTOKEN_OFFER": 158, + "tecCLAIM": 100, + "tecCRYPTOCONDITION_ERROR": 146, + "tecDIR_FULL": 121, + "tecDST_TAG_NEEDED": 143, + "tecDUPLICATE": 149, + "tecEMPTY_DID": 187, + "tecEXPIRED": 148, + "tecFAILED_PROCESSING": 105, + "tecFROZEN": 137, + "tecHAS_OBLIGATIONS": 151, + "tecINCOMPLETE": 169, + "tecINSUFFICIENT_FUNDS": 159, + "tecINSUFFICIENT_PAYMENT": 161, + "tecINSUFFICIENT_RESERVE": 141, + "tecINSUFF_FEE": 136, + "tecINSUF_RESERVE_LINE": 122, + "tecINSUF_RESERVE_OFFER": 123, + "tecINTERNAL": 144, + "tecINVALID_UPDATE_TIME": 188, + "tecINVARIANT_FAILED": 147, + "tecKILLED": 150, + "tecLOCKED": 192, + "tecMAX_SEQUENCE_REACHED": 154, + "tecNEED_MASTER_KEY": 142, + "tecNFTOKEN_BUY_SELL_MISMATCH": 156, + "tecNFTOKEN_OFFER_TYPE_MISMATCH": 157, + "tecNO_ALTERNATIVE_KEY": 130, + "tecNO_AUTH": 134, + "tecNO_DST": 124, + "tecNO_DST_INSUF_XRP": 125, + "tecNO_ENTRY": 140, + "tecNO_ISSUER": 133, + "tecNO_LINE": 135, + "tecNO_LINE_INSUF_RESERVE": 126, + "tecNO_LINE_REDUNDANT": 127, + "tecNO_PERMISSION": 139, + "tecNO_REGULAR_KEY": 131, + "tecNO_SUITABLE_NFTOKEN_PAGE": 155, + "tecNO_TARGET": 138, + "tecOBJECT_NOT_FOUND": 160, + "tecOVERSIZE": 145, + "tecOWNERS": 132, + "tecPATH_DRY": 128, + "tecPATH_PARTIAL": 101, + "tecTOKEN_PAIR_NOT_FOUND": 189, + "tecTOO_SOON": 152, + "tecUNFUNDED": 129, + "tecUNFUNDED_ADD": 102, + "tecUNFUNDED_AMM": 162, + "tecUNFUNDED_OFFER": 103, + "tecUNFUNDED_PAYMENT": 104, + "tecXCHAIN_ACCOUNT_CREATE_PAST": 181, + "tecXCHAIN_ACCOUNT_CREATE_TOO_MANY": 182, + "tecXCHAIN_BAD_CLAIM_ID": 172, + "tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185, + "tecXCHAIN_BAD_TRANSFER_ISSUE": 170, + "tecXCHAIN_CLAIM_NO_QUORUM": 173, + "tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186, + "tecXCHAIN_CREATE_ACCOUNT_NONXRP_ISSUE": 175, + "tecXCHAIN_INSUFF_CREATE_AMOUNT": 180, + "tecXCHAIN_NO_CLAIM_ID": 171, + "tecXCHAIN_NO_SIGNERS_LIST": 178, + "tecXCHAIN_PAYMENT_FAILED": 183, + "tecXCHAIN_PROOF_UNKNOWN_KEY": 174, + "tecXCHAIN_REWARD_MISMATCH": 177, + "tecXCHAIN_SELF_COMMIT": 184, + "tecXCHAIN_SENDING_ACCOUNT_MISMATCH": 179, + "tecXCHAIN_WRONG_CHAIN": 176, + "tefALREADY": -198, + "tefBAD_ADD_AUTH": -197, + "tefBAD_AUTH": -196, + "tefBAD_AUTH_MASTER": -183, + "tefBAD_LEDGER": -195, + "tefBAD_QUORUM": -185, + "tefBAD_SIGNATURE": -186, + "tefCREATED": -194, + "tefEXCEPTION": -193, + "tefFAILURE": -199, + "tefINTERNAL": -192, + "tefINVALID_LEDGER_FIX_TYPE": -178, + "tefINVARIANT_FAILED": -182, + "tefMASTER_DISABLED": -188, + "tefMAX_LEDGER": -187, + "tefNFTOKEN_IS_NOT_TRANSFERABLE": -179, + "tefNOT_MULTI_SIGNING": -184, + "tefNO_AUTH_REQUIRED": -191, + "tefNO_TICKET": -180, + "tefPAST_SEQ": -190, + "tefTOO_BIG": -181, + "tefWRONG_PRIOR": -189, + "telBAD_DOMAIN": -398, + "telBAD_PATH_COUNT": -397, + "telBAD_PUBLIC_KEY": -396, + "telCAN_NOT_QUEUE": -392, + "telCAN_NOT_QUEUE_BALANCE": -391, + "telCAN_NOT_QUEUE_BLOCKED": -389, + "telCAN_NOT_QUEUE_BLOCKS": -390, + "telCAN_NOT_QUEUE_FEE": -388, + "telCAN_NOT_QUEUE_FULL": -387, + "telENV_RPC_FAILED": -383, + "telFAILED_PROCESSING": -395, + "telINSUF_FEE_P": -394, + "telLOCAL_ERROR": -399, + "telNETWORK_ID_MAKES_TX_NON_CANONICAL": -384, + "telNO_DST_PARTIAL": -393, + "telREQUIRES_NETWORK_ID": -385, + "telWRONG_NETWORK": -386, + "temARRAY_EMPTY": -253, + "temARRAY_TOO_LARGE": -252, + "temBAD_AMM_TOKENS": -261, + "temBAD_AMOUNT": -298, + "temBAD_CURRENCY": -297, + "temBAD_EXPIRATION": -296, + "temBAD_FEE": -295, + "temBAD_ISSUER": -294, + "temBAD_LIMIT": -293, + "temBAD_NFTOKEN_TRANSFER_FEE": -262, + "temBAD_OFFER": -292, + "temBAD_PATH": -291, + "temBAD_PATH_LOOP": -290, + "temBAD_QUORUM": -271, + "temBAD_REGKEY": -289, + "temBAD_SEND_XRP_LIMIT": -288, + "temBAD_SEND_XRP_MAX": -287, + "temBAD_SEND_XRP_NO_DIRECT": -286, + "temBAD_SEND_XRP_PARTIAL": -285, + "temBAD_SEND_XRP_PATHS": -284, + "temBAD_SEQUENCE": -283, + "temBAD_SIGNATURE": -282, + "temBAD_SIGNER": -272, + "temBAD_SRC_ACCOUNT": -281, + "temBAD_TICK_SIZE": -269, + "temBAD_TRANSFER_FEE": -251, + "temBAD_TRANSFER_RATE": -280, + "temBAD_WEIGHT": -270, + "temCANNOT_PREAUTH_SELF": -267, + "temDISABLED": -273, + "temDST_IS_SRC": -279, + "temDST_NEEDED": -278, + "temEMPTY_DID": -254, + "temINVALID": -277, + "temINVALID_ACCOUNT_ID": -268, + "temINVALID_COUNT": -266, + "temINVALID_FLAG": -276, + "temMALFORMED": -299, + "temREDUNDANT": -275, + "temRIPPLE_EMPTY": -274, + "temSEQ_AND_TICKET": -263, + "temUNCERTAIN": -265, + "temUNKNOWN": -264, + "temXCHAIN_BAD_PROOF": -259, + "temXCHAIN_BRIDGE_BAD_ISSUES": -258, + "temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -256, + "temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255, + "temXCHAIN_BRIDGE_NONDOOR_OWNER": -257, + "temXCHAIN_EQUAL_DOOR_ACCOUNTS": -260, + "terFUNDS_SPENT": -98, + "terINSUF_FEE_B": -97, + "terLAST": -91, + "terNO_ACCOUNT": -96, + "terNO_AMM": -87, + "terNO_AUTH": -95, + "terNO_LINE": -94, + "terNO_RIPPLE": -90, + "terOWNERS": -93, + "terPRE_SEQ": -92, + "terPRE_TICKET": -88, + "terQUEUED": -89, + "terRETRY": -99, + "tesSUCCESS": 0 + }, + "TRANSACTION_TYPES": { + "AMMBid": 39, "AMMCreate": 35, + "AMMDelete": 40, "AMMDeposit": 36, - "AMMWithdraw": 37, "AMMVote": 38, - "AMMBid": 39, - "AMMDelete": 40, - "XChainCreateClaimID": 41, - "XChainCommit": 42, - "XChainClaim": 43, - "XChainAccountCreateCommit": 44, - "XChainAddClaimAttestation": 45, - "XChainAddAccountCreateAttestation": 46, - "XChainModifyBridge": 47, - "XChainCreateBridge": 48, - "DIDSet": 49, + "AMMWithdraw": 37, + "AccountDelete": 21, + "AccountSet": 3, + "CheckCancel": 18, + "CheckCash": 17, + "CheckCreate": 16, + "Clawback": 30, "DIDDelete": 50, - "OracleSet": 51, - "OracleDelete": 52, + "DIDSet": 49, + "DepositPreauth": 19, + "EnableAmendment": 100, + "EscrowCancel": 4, + "EscrowCreate": 1, + "EscrowFinish": 2, + "Invalid": -1, "LedgerStateFix": 53, + "MPTokenAuthorize": 57, "MPTokenIssuanceCreate": 54, "MPTokenIssuanceDestroy": 55, - "MPTokenAuthorize": 56, - "MPTokenIssuanceSet": 57, - "EnableAmendment": 100, + "MPTokenIssuanceSet": 56, + "NFTokenAcceptOffer": 29, + "NFTokenBurn": 26, + "NFTokenCancelOffer": 28, + "NFTokenCreateOffer": 27, + "NFTokenMint": 25, + "OfferCancel": 8, + "OfferCreate": 7, + "OracleDelete": 52, + "OracleSet": 51, + "Payment": 0, + "PaymentChannelClaim": 15, + "PaymentChannelCreate": 13, + "PaymentChannelFund": 14, "SetFee": 101, - "UNLModify": 102 + "SetRegularKey": 5, + "SignerListSet": 12, + "TicketCreate": 10, + "TrustSet": 20, + "UNLModify": 102, + "XChainAccountCreateCommit": 44, + "XChainAddAccountCreateAttestation": 46, + "XChainAddClaimAttestation": 45, + "XChainClaim": 43, + "XChainCommit": 42, + "XChainCreateBridge": 48, + "XChainCreateClaimID": 41, + "XChainModifyBridge": 47 + }, + "TYPES": { + "AccountID": 8, + "Amount": 6, + "Blob": 7, + "Currency": 26, + "Done": -1, + "Hash128": 4, + "Hash160": 17, + "Hash256": 5, + "Issue": 24, + "LedgerEntry": 10002, + "Metadata": 10004, + "NotPresent": 0, + "PathSet": 18, + "STArray": 15, + "STObject": 14, + "Transaction": 10001, + "UInt16": 1, + "Hash192": 21, + "UInt32": 2, + "UInt384": 22, + "UInt512": 23, + "UInt64": 3, + "UInt8": 16, + "UInt96": 20, + "Unknown": -2, + "Validation": 10003, + "Vector256": 19, + "XChainBridge": 25 } } diff --git a/packages/xrpl/src/models/ledger/MPToken.ts b/packages/xrpl/src/models/ledger/MPToken.ts index 6b1fd013fc..0de8d09a1f 100644 --- a/packages/xrpl/src/models/ledger/MPToken.ts +++ b/packages/xrpl/src/models/ledger/MPToken.ts @@ -5,8 +5,7 @@ import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry' export interface MPToken extends BaseLedgerEntry, HasPreviousTxnID { LedgerEntryType: 'MPToken' MPTokenIssuanceID: string - MPTAmount: MPTAmount - LockedAmount?: MPTAmount + MPTAmount?: MPTAmount Flags: number OwnerNode?: string } diff --git a/packages/xrpl/src/models/ledger/MPTokenIssuance.ts b/packages/xrpl/src/models/ledger/MPTokenIssuance.ts index 6774933ea6..9bde70c7ab 100644 --- a/packages/xrpl/src/models/ledger/MPTokenIssuance.ts +++ b/packages/xrpl/src/models/ledger/MPTokenIssuance.ts @@ -7,7 +7,6 @@ export interface MPTokenIssuance extends BaseLedgerEntry, HasPreviousTxnID { AssetScale?: number MaximumAmount?: string OutstandingAmount: string - LockedAmount?: string TransferFee?: number MPTokenMetadata?: string OwnerNode?: string diff --git a/packages/xrpl/src/models/transactions/MPTokenAuthorize.ts b/packages/xrpl/src/models/transactions/MPTokenAuthorize.ts index 447c8f93f4..4453f571e7 100644 --- a/packages/xrpl/src/models/transactions/MPTokenAuthorize.ts +++ b/packages/xrpl/src/models/transactions/MPTokenAuthorize.ts @@ -50,7 +50,7 @@ export interface MPTokenAuthorize extends BaseTransaction { * An optional XRPL Address of an individual token holder balance to lock/unlock. * If omitted, this transaction will apply to all any accounts holding MPTs. */ - MPTokenHolder?: Account + Holder?: Account Flags?: number | MPTokenAuthorizeFlagsInterface } @@ -63,5 +63,5 @@ export interface MPTokenAuthorize extends BaseTransaction { export function validateMPTokenAuthorize(tx: Record): void { validateBaseTransaction(tx) validateRequiredField(tx, 'MPTokenIssuanceID', isString) - validateOptionalField(tx, 'MPTokenHolder', isAccount) + validateOptionalField(tx, 'Holder', isAccount) } diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts index 91b299e1c7..07650ff585 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -52,7 +52,7 @@ export interface MPTokenIssuanceSet extends BaseTransaction { * An optional XRPL Address of an individual token holder balance to lock/unlock. * If omitted, this transaction will apply to all any accounts holding MPTs. */ - MPTokenHolder?: Account + Holder?: Account Flags?: number | MPTokenIssuanceSetFlagsInterface } @@ -65,7 +65,7 @@ export interface MPTokenIssuanceSet extends BaseTransaction { export function validateMPTokenIssuanceSet(tx: Record): void { validateBaseTransaction(tx) validateRequiredField(tx, 'MPTokenIssuanceID', isString) - validateOptionalField(tx, 'MPTokenHolder', isAccount) + validateOptionalField(tx, 'Holder', isAccount) /* eslint-disable no-bitwise -- We need bitwise operations for flag checks here */ if (typeof tx.Flags === 'number') { diff --git a/packages/xrpl/src/models/transactions/clawback.ts b/packages/xrpl/src/models/transactions/clawback.ts index fd9e6f5146..759701118b 100644 --- a/packages/xrpl/src/models/transactions/clawback.ts +++ b/packages/xrpl/src/models/transactions/clawback.ts @@ -31,7 +31,7 @@ export interface Clawback extends BaseTransaction { * Indicates the AccountID that the issuer wants to clawback. This field is only valid for clawing back * MPTs. */ - MPTokenHolder?: string + Holder?: string } /** @@ -42,7 +42,7 @@ export interface Clawback extends BaseTransaction { */ export function validateClawback(tx: Record): void { validateBaseTransaction(tx) - validateOptionalField(tx, 'MPTokenHolder', isAccount) + validateOptionalField(tx, 'Holder', isAccount) if (tx.Amount == null) { throw new ValidationError('Clawback: missing field Amount') @@ -56,17 +56,15 @@ export function validateClawback(tx: Record): void { throw new ValidationError('Clawback: invalid holder Account') } - if (isMPTAmount(tx.Amount) && tx.Account === tx.MPTokenHolder) { + if (isMPTAmount(tx.Amount) && tx.Account === tx.Holder) { throw new ValidationError('Clawback: invalid holder Account') } - if (isIssuedCurrency(tx.Amount) && tx.MPTokenHolder) { - throw new ValidationError( - 'Clawback: cannot have MPTokenHolder for currency', - ) + if (isIssuedCurrency(tx.Amount) && tx.Holder) { + throw new ValidationError('Clawback: cannot have Holder for currency') } - if (isMPTAmount(tx.Amount) && !tx.MPTokenHolder) { - throw new ValidationError('Clawback: missing MPTokenHolder') + if (isMPTAmount(tx.Amount) && !tx.Holder) { + throw new ValidationError('Clawback: missing Holder') } } diff --git a/packages/xrpl/test/integration/transactions/clawback.test.ts b/packages/xrpl/test/integration/transactions/clawback.test.ts index 034e50c6b3..58935a2782 100644 --- a/packages/xrpl/test/integration/transactions/clawback.test.ts +++ b/packages/xrpl/test/integration/transactions/clawback.test.ts @@ -185,7 +185,7 @@ describe('Clawback', function () { mpt_issuance_id: mptID!, value: '500', }, - MPTokenHolder: wallet2.classicAddress, + Holder: wallet2.classicAddress, } await testTransaction(testContext.client, clawTx, testContext.wallet) diff --git a/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts b/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts index af543f4fd1..3bbabebd2b 100644 --- a/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts @@ -90,7 +90,7 @@ describe('MPTokenIssuanceDestroy', function () { TransactionType: 'MPTokenAuthorize', Account: testContext.wallet.classicAddress, MPTokenIssuanceID: mptID!, - MPTokenHolder: wallet2.classicAddress, + Holder: wallet2.classicAddress, } await testTransaction(testContext.client, authTx, testContext.wallet) diff --git a/packages/xrpl/test/models/MPTokenAuthorize.test.ts b/packages/xrpl/test/models/MPTokenAuthorize.test.ts index a715548eff..4167670646 100644 --- a/packages/xrpl/test/models/MPTokenAuthorize.test.ts +++ b/packages/xrpl/test/models/MPTokenAuthorize.test.ts @@ -22,7 +22,7 @@ describe('MPTokenAuthorize', function () { validMPTokenAuthorize = { TransactionType: 'MPTokenAuthorize', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', - MPTokenHolder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + Holder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', MPTokenIssuanceID: TOKEN_ID, } as any @@ -50,7 +50,7 @@ describe('MPTokenAuthorize', function () { TransactionType: 'MPTokenAuthorize', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', MPTokenIssuanceID: TOKEN_ID, - MPTokenHolder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + Holder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', Flags: MPTokenAuthorizeFlags.tfMPTUnauthorize, } as any diff --git a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts index 838fc60ae3..7989bb374f 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceSet.test.ts @@ -23,7 +23,7 @@ describe('MPTokenIssuanceSet', function () { TransactionType: 'MPTokenIssuanceSet', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', MPTokenIssuanceID: TOKEN_ID, - MPTokenHolder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + Holder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', Flags: MPTokenIssuanceSetFlags.tfMPTLock, } as any @@ -34,7 +34,7 @@ describe('MPTokenIssuanceSet', function () { TransactionType: 'MPTokenIssuanceSet', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', MPTokenIssuanceID: TOKEN_ID, - MPTokenHolder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', + Holder: 'rajgkBmMxmz161r8bWYH7CQAFZP5bA9oSG', } as any assert.doesNotThrow(() => validate(validMPTokenIssuanceSet)) diff --git a/packages/xrpl/test/models/clawback.test.ts b/packages/xrpl/test/models/clawback.test.ts index f2b2a52f8c..30abcdfcb9 100644 --- a/packages/xrpl/test/models/clawback.test.ts +++ b/packages/xrpl/test/models/clawback.test.ts @@ -87,13 +87,13 @@ describe('Clawback', function () { value: '10', }, Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', - MPTokenHolder: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', + Holder: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', } as any assert.doesNotThrow(() => validate(validClawback)) }) - it(`throws w/ invalid MPTokenHolder Account`, function () { + it(`throws w/ invalid Holder Account`, function () { const invalidAccount = { TransactionType: 'Clawback', Amount: { @@ -101,7 +101,7 @@ describe('Clawback', function () { value: '10', }, Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', - MPTokenHolder: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + Holder: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', } as any assert.throws( @@ -111,7 +111,7 @@ describe('Clawback', function () { ) }) - it(`throws w/ invalid MPTokenHolder`, function () { + it(`throws w/ invalid Holder`, function () { const invalidAccount = { TransactionType: 'Clawback', Amount: { @@ -124,11 +124,11 @@ describe('Clawback', function () { assert.throws( () => validate(invalidAccount), ValidationError, - 'Clawback: missing MPTokenHolder', + 'Clawback: missing Holder', ) }) - it(`throws w/ invalid currency MPTokenHolder`, function () { + it(`throws w/ invalid currency Holder`, function () { const invalidAccount = { TransactionType: 'Clawback', Amount: { @@ -137,13 +137,13 @@ describe('Clawback', function () { value: '43.11584856965009', }, Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', - MPTokenHolder: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', + Holder: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy', } as any assert.throws( () => validate(invalidAccount), ValidationError, - 'Clawback: cannot have MPTokenHolder for currency', + 'Clawback: cannot have Holder for currency', ) }) }) From 143de0884f91227b945c3f7c800a74bbf3b42310 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 1 Nov 2024 12:05:32 -0400 Subject: [PATCH 40/53] fix failing binary codec test --- packages/ripple-binary-codec/test/uint.test.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/ripple-binary-codec/test/uint.test.ts b/packages/ripple-binary-codec/test/uint.test.ts index e3a816a064..3ba7f7e046 100644 --- a/packages/ripple-binary-codec/test/uint.test.ts +++ b/packages/ripple-binary-codec/test/uint.test.ts @@ -97,12 +97,12 @@ const jsonEntry2 = { } const mptIssuanceEntryBinary = - '11007E22000000222400000333250000033934000000000000000030187FFFFFFFFFFFFFFF3019000000000000006455854FCC08F5C602AEAF4F577316248BECCBEC82310B30DC66E1438E07F86E6E7D701EC1EC7B226E616D65223A2255532054726561737572792042696C6C20546F6B656E222C2273796D626F6C223A225553544254222C22646563696D616C73223A322C22746F74616C537570706C79223A313030303030302C22697373756572223A225553205472656173757279222C22697373756544617465223A22323032342D30332D3235222C226D6174757269747944617465223A22323032352D30332D3235222C226661636556616C7565223A2231303030222C22696E74657265737452617465223A22322E35222C22696E7465726573744672657175656E6379223A22517561727465726C79222C22636F6C6C61746572616C223A22555320476F7665726E6D656E74222C226A7572697364696374696F6E223A22556E6974656420537461746573222C22726567756C61746F7279436F6D706C69616E6365223A2253454320526567756C6174696F6E73222C22736563757269747954797065223A2254726561737572792042696C6C222C2265787465726E616C5F75726C223A2268747470733A2F2F6578616D706C652E636F6D2F742D62696C6C2D746F6B656E2D6D657461646174612E6A736F6E227D8414F52140C352B66DC5507C7C0236F7241469E56C1300101403' + '11007E220000006224000002DF25000002E434000000000000000030187FFFFFFFFFFFFFFF30190000000000000064552E78C1FFBDDAEE077253CEB12CFEA83689AA0899F94762190A357208DADC76FE701EC1EC7B226E616D65223A2255532054726561737572792042696C6C20546F6B656E222C2273796D626F6C223A225553544254222C22646563696D616C73223A322C22746F74616C537570706C79223A313030303030302C22697373756572223A225553205472656173757279222C22697373756544617465223A22323032342D30332D3235222C226D6174757269747944617465223A22323032352D30332D3235222C226661636556616C7565223A2231303030222C22696E74657265737452617465223A22322E35222C22696E7465726573744672657175656E6379223A22517561727465726C79222C22636F6C6C61746572616C223A22555320476F7665726E6D656E74222C226A7572697364696374696F6E223A22556E6974656420537461746573222C22726567756C61746F7279436F6D706C69616E6365223A2253454320526567756C6174696F6E73222C22736563757269747954797065223A2254726561737572792042696C6C222C2265787465726E616C5F75726C223A2268747470733A2F2F6578616D706C652E636F6D2F742D62696C6C2D746F6B656E2D6D657461646174612E6A736F6E227D8414A4D893CFBC4DC6AE877EB585F90A3B47528B958D051003' const mptIssuanceEntryJson = { AssetScale: 3, - Flags: 34, - Issuer: 'rPM3PTTfpJVjEcb3aSoZGizLbVBz2B7LQF', + Flags: 98, + Issuer: 'rGpdGXDV2RFPeLEfWS9RFo5Nh9cpVDToZa', LedgerEntryType: 'MPTokenIssuance', MPTokenMetadata: '7B226E616D65223A2255532054726561737572792042696C6C20546F6B656E222C2273796D626F6C223A225553544254222C22646563696D616C73223A322C22746F74616C537570706C79223A313030303030302C22697373756572223A225553205472656173757279222C22697373756544617465223A22323032342D30332D3235222C226D6174757269747944617465223A22323032352D30332D3235222C226661636556616C7565223A2231303030222C22696E74657265737452617465223A22322E35222C22696E7465726573744672657175656E6379223A22517561727465726C79222C22636F6C6C61746572616C223A22555320476F7665726E6D656E74222C226A7572697364696374696F6E223A22556E6974656420537461746573222C22726567756C61746F7279436F6D706C69616E6365223A2253454320526567756C6174696F6E73222C22736563757269747954797065223A2254726561737572792042696C6C222C2265787465726E616C5F75726C223A2268747470733A2F2F6578616D706C652E636F6D2F742D62696C6C2D746F6B656E2D6D657461646174612E6A736F6E227D', @@ -110,9 +110,9 @@ const mptIssuanceEntryJson = { OutstandingAmount: '100', OwnerNode: '0000000000000000', PreviousTxnID: - '854FCC08F5C602AEAF4F577316248BECCBEC82310B30DC66E1438E07F86E6E7D', - PreviousTxnLgrSeq: 825, - Sequence: 819, + '2E78C1FFBDDAEE077253CEB12CFEA83689AA0899F94762190A357208DADC76FE', + PreviousTxnLgrSeq: 740, + Sequence: 735, } const mptokenEntryJson = { From e7da4dc4d5941427130cee4da5785b0a51f40234 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Wed, 13 Nov 2024 10:52:07 -0500 Subject: [PATCH 41/53] add index --- .../ripple-binary-codec/src/enums/definitions.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/ripple-binary-codec/src/enums/definitions.json b/packages/ripple-binary-codec/src/enums/definitions.json index 15e23d9f69..47379b4a6f 100644 --- a/packages/ripple-binary-codec/src/enums/definitions.json +++ b/packages/ripple-binary-codec/src/enums/definitions.json @@ -1240,6 +1240,16 @@ "type": "Hash256" } ], + [ + "index", + { + "isSerialized": false, + "isSigningField": false, + "isVLEncoded": false, + "nth": 258, + "type": "Hash256" + } + ], [ "Amount", { @@ -3021,6 +3031,7 @@ "Done": -1, "Hash128": 4, "Hash160": 17, + "Hash192": 21, "Hash256": 5, "Issue": 24, "LedgerEntry": 10002, @@ -3031,7 +3042,6 @@ "STObject": 14, "Transaction": 10001, "UInt16": 1, - "Hash192": 21, "UInt32": 2, "UInt384": 22, "UInt512": 23, From e42b0154d668d55451c98028be49f189872ed365 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 6 Dec 2024 11:56:28 -0500 Subject: [PATCH 42/53] Address code comments --- packages/ripple-binary-codec/HISTORY.md | 3 +++ packages/xrpl/HISTORY.md | 7 ++----- .../xrpl/src/models/transactions/MPTokenIssuanceCreate.ts | 8 ++++---- .../xrpl/src/models/transactions/MPTokenIssuanceSet.ts | 7 +++---- packages/xrpl/src/models/transactions/payment.ts | 4 ++-- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/ripple-binary-codec/HISTORY.md b/packages/ripple-binary-codec/HISTORY.md index a0710d4eba..755edfc758 100644 --- a/packages/ripple-binary-codec/HISTORY.md +++ b/packages/ripple-binary-codec/HISTORY.md @@ -2,6 +2,9 @@ ## Unreleased +### Added +* Support for the Multi-Purpose Token amendment (XLS-33) + ## 2.1.0 (2024-06-03) ### Added diff --git a/packages/xrpl/HISTORY.md b/packages/xrpl/HISTORY.md index 15f36b743f..e1854eeebe 100644 --- a/packages/xrpl/HISTORY.md +++ b/packages/xrpl/HISTORY.md @@ -6,6 +6,8 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr ### Added * parseTransactionFlags as a utility function in the xrpl package to streamline transactions flags-to-map conversion +* Added new MPT transaction definitions (XLS-33) +* New `MPTAmount` type support for `Payment` and `Clawback` transactions ### Fixed * `TransactionStream` model supports APIv2 @@ -44,11 +46,6 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr * Add missing `lsfAMMNode` flag to `RippleState` ledger object * Add `PreviousFields` to `DeletedNode` metadata type -## 1.0.0-mpt-beta (2024-04-22) -### Non-Breaking Changes -* Added new MPT transaction models -* New `MPTAmount` type support for `Payment` and `Clawback` transactions - ## 3.0.0 (2024-02-01) ### BREAKING CHANGES diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index bb837ebe6a..b238b62c56 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -3,9 +3,9 @@ import { isHex } from '../utils' import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common' import type { TransactionMetadataBase } from './metadata' +import { INTEGER_SANITY_CHECK } from '../utils' -const SANITY_CHECK = /^[0-9]+$/u - +const MAX_AMT = '9223372036854775807' // 2^63 - 1 /** * Transaction Flags for an MPTokenIssuanceCreate Transaction. * @@ -129,10 +129,10 @@ export function validateMPTokenIssuanceCreate( } if (typeof tx.MaximumAmount === 'string') { - if (!SANITY_CHECK.exec(tx.MaximumAmount)) { + if (!INTEGER_SANITY_CHECK.exec(tx.MaximumAmount)) { throw new ValidationError('MPTokenIssuanceCreate: Invalid MaximumAmount') } else if ( - BigInt(tx.MaximumAmount) > BigInt(`9223372036854775807`) || + BigInt(tx.MaximumAmount) > BigInt(MAX_AMT) || BigInt(tx.MaximumAmount) < BigInt(`0`) ) { throw new ValidationError( diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts index 07650ff585..4e4aa7177c 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts @@ -1,4 +1,5 @@ import { ValidationError } from '../../errors' +import { isFlagEnabled } from '../utils' import { BaseTransaction, @@ -67,17 +68,15 @@ export function validateMPTokenIssuanceSet(tx: Record): void { validateRequiredField(tx, 'MPTokenIssuanceID', isString) validateOptionalField(tx, 'Holder', isAccount) - /* eslint-disable no-bitwise -- We need bitwise operations for flag checks here */ if (typeof tx.Flags === 'number') { const flags = tx.Flags if ( - BigInt(flags) & BigInt(MPTokenIssuanceSetFlags.tfMPTLock) && - BigInt(flags) & BigInt(MPTokenIssuanceSetFlags.tfMPTUnlock) + isFlagEnabled(flags, MPTokenIssuanceSetFlags.tfMPTLock) && + isFlagEnabled(flags, MPTokenIssuanceSetFlags.tfMPTUnlock) ) { throw new ValidationError('MPTokenIssuanceSet: flag conflict') } } else { throw new Error('tx.Flags is not a number') } - /* eslint-enable no-bitwise -- Re-enable bitwise rule after this block */ } diff --git a/packages/xrpl/src/models/transactions/payment.ts b/packages/xrpl/src/models/transactions/payment.ts index 78a3851577..cd190ee9d7 100644 --- a/packages/xrpl/src/models/transactions/payment.ts +++ b/packages/xrpl/src/models/transactions/payment.ts @@ -142,13 +142,13 @@ export interface Payment extends BaseTransaction { * cross-currency/cross-issue payments. Must be omitted for XRP-to-XRP * Payments. */ - SendMax?: Amount + SendMax?: Amount | MPTAmount /** * Minimum amount of destination currency this transaction should deliver. * Only valid if this is a partial payment. For non-XRP amounts, the nested * field names are lower-case. */ - DeliverMin?: Amount + DeliverMin?: Amount | MPTAmount Flags?: number | PaymentFlagsInterface } From bd9b1901dfa6d36f51b87bd6a88367c30c048201 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 6 Dec 2024 11:56:40 -0500 Subject: [PATCH 43/53] Code comments --- packages/xrpl/src/models/utils/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/xrpl/src/models/utils/index.ts b/packages/xrpl/src/models/utils/index.ts index 5a1d7520f0..54029bbd3c 100644 --- a/packages/xrpl/src/models/utils/index.ts +++ b/packages/xrpl/src/models/utils/index.ts @@ -1,4 +1,5 @@ const HEX_REGEX = /^[0-9A-Fa-f]+$/u +export const INTEGER_SANITY_CHECK = /^[0-9]+$/u /** * Verify that all fields of an object are in fields. From e6b5c233ae56cc975881ae2c1f8538080eb6cd44 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 6 Dec 2024 11:56:58 -0500 Subject: [PATCH 44/53] docker --- .github/workflows/nodejs.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 176acc8cb8..47f2495c3f 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -4,7 +4,7 @@ name: Node.js CI env: - RIPPLED_DOCKER_IMAGE: rippleci/rippled:2.2.0-b3 + RIPPLED_DOCKER_IMAGE: rippleci/rippled:2.3.0-rc1 on: push: @@ -108,7 +108,7 @@ jobs: - name: Run docker in background run: | - docker run --detach --rm --name rippled-service -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/opt/ripple/etc/" --health-cmd="wget localhost:6006 || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env GITHUB_ACTIONS=true --env CI=true ${{ env.RIPPLED_DOCKER_IMAGE }} /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg + docker run --detach --rm -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/etc/opt/ripple/" --name rippled-service --health-cmd="rippled server_nfo || 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" - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 @@ -165,7 +165,7 @@ jobs: - name: Run docker in background run: | - docker run --detach --rm --name rippled-service -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/opt/ripple/etc/" --health-cmd="wget localhost:6006 || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env GITHUB_ACTIONS=true --env CI=true ${{ env.RIPPLED_DOCKER_IMAGE }} /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg + docker run --detach --rm -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/etc/opt/ripple/" --name rippled-service --health-cmd="rippled server_nfo || 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" - name: Setup npm version 9 run: | From 5111266d96549b62dc57bda7803d0c5163c80ca8 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 6 Dec 2024 12:41:16 -0500 Subject: [PATCH 45/53] rippled cfg --- .ci-config/rippled.cfg | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.ci-config/rippled.cfg b/.ci-config/rippled.cfg index 291f87b730..911c3ecd5d 100644 --- a/.ci-config/rippled.cfg +++ b/.ci-config/rippled.cfg @@ -178,3 +178,11 @@ PriceOracle fixEmptyDID fixXChainRewardRounding fixPreviousTxnID +fixAMMv1_1 +Credentials +NFTokenMintOffer +MPTokensV1 +fixNFTokenPageLinks +fixInnerObjTemplate2 +fixEnforceNFTokenTrustline +fixReducedOffersV2 From 1668e76fa6dd4c9951f04a7b6ec6fcd7cab1592d Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 6 Dec 2024 12:57:15 -0500 Subject: [PATCH 46/53] lint --- packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index b238b62c56..d9ebbf397e 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -1,9 +1,8 @@ import { ValidationError } from '../../errors' -import { isHex } from '../utils' +import { isHex, INTEGER_SANITY_CHECK } from '../utils' import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common' import type { TransactionMetadataBase } from './metadata' -import { INTEGER_SANITY_CHECK } from '../utils' const MAX_AMT = '9223372036854775807' // 2^63 - 1 /** From 0a660e9456fc621a0888034be0a10705c0466dcd Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 6 Dec 2024 13:04:59 -0500 Subject: [PATCH 47/53] lint --- .../xrpl/src/models/transactions/MPTokenIssuanceCreate.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index d9ebbf397e..74dcfc90c3 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -4,7 +4,9 @@ import { isHex, INTEGER_SANITY_CHECK } from '../utils' import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common' import type { TransactionMetadataBase } from './metadata' -const MAX_AMT = '9223372036854775807' // 2^63 - 1 +// 2^63 - 1 +const MAX_AMT = '9223372036854775807' + /** * Transaction Flags for an MPTokenIssuanceCreate Transaction. * From a8f5abfed715ee256a4f3751630537c63419ad03 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 6 Dec 2024 15:21:09 -0500 Subject: [PATCH 48/53] code rabbits --- .../ripple-binary-codec/test/uint.test.ts | 9 +++++++ .../transactions/MPTokenIssuanceCreate.ts | 7 +++++ .../transactions/mptokenAuthorize.test.ts | 2 +- .../xrpl/test/models/MPTokenAuthorize.test.ts | 9 ------- .../test/models/MPTokenIssuanceCreate.test.ts | 26 +++++++++++++++++++ 5 files changed, 43 insertions(+), 10 deletions(-) diff --git a/packages/ripple-binary-codec/test/uint.test.ts b/packages/ripple-binary-codec/test/uint.test.ts index 3ba7f7e046..e3165fa1ed 100644 --- a/packages/ripple-binary-codec/test/uint.test.ts +++ b/packages/ripple-binary-codec/test/uint.test.ts @@ -185,4 +185,13 @@ it('UInt64 is parsed as base 10 for MPT amounts', () => { expect(encode(mptokenEntryJson)).toEqual(mptokenEntryBinary) expect(decode(mptokenEntryBinary)).toEqual(mptokenEntryJson) + + const decodedIssuance = decode(mptIssuanceEntryBinary) + expect(typeof decodedIssuance.MaximumAmount).toBe('string') + expect(decodedIssuance.MaximumAmount).toBe('9223372036854775807') + expect(decodedIssuance.OutstandingAmount).toBe('100') + + const decodedToken = decode(mptokenEntryBinary) + expect(typeof decodedToken.MPTAmount).toBe('string') + expect(decodedToken.MPTAmount).toBe('100') }) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index 74dcfc90c3..07ae9690ae 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -141,4 +141,11 @@ export function validateMPTokenIssuanceCreate( ) } } + + if (typeof tx.TransferFee === 'number') { + if (tx.TransferFee < 0 || tx.TransferFee > 50000) + throw new ValidationError( + 'MPTokenIssuanceCreate: TransferFee out of range', + ) + } } diff --git a/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts b/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts index 3bbabebd2b..57d310c556 100644 --- a/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts +++ b/packages/xrpl/test/integration/transactions/mptokenAuthorize.test.ts @@ -18,7 +18,7 @@ import { testTransaction, generateFundedWallet } from '../utils' // how long before each test case times out const TIMEOUT = 20000 -describe('MPTokenIssuanceDestroy', function () { +describe('MPTokenAuthorize', function () { let testContext: XrplIntegrationTestContext beforeEach(async () => { diff --git a/packages/xrpl/test/models/MPTokenAuthorize.test.ts b/packages/xrpl/test/models/MPTokenAuthorize.test.ts index 4167670646..9e87dfa561 100644 --- a/packages/xrpl/test/models/MPTokenAuthorize.test.ts +++ b/packages/xrpl/test/models/MPTokenAuthorize.test.ts @@ -37,15 +37,6 @@ describe('MPTokenAuthorize', function () { assert.doesNotThrow(() => validate(validMPTokenAuthorize)) - validMPTokenAuthorize = { - TransactionType: 'MPTokenAuthorize', - Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', - MPTokenIssuanceID: TOKEN_ID, - Flags: MPTokenAuthorizeFlags.tfMPTUnauthorize, - } as any - - assert.doesNotThrow(() => validate(validMPTokenAuthorize)) - validMPTokenAuthorize = { TransactionType: 'MPTokenAuthorize', Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', diff --git a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts index fba53ce672..fbca8aa1ef 100644 --- a/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts +++ b/packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts @@ -95,4 +95,30 @@ describe('MPTokenIssuanceCreate', function () { 'MPTokenIssuanceCreate: Invalid MaximumAmount', ) }) + + it(`throws w/ Invalid TransferFee`, function () { + let invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + TransferFee: -1, + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: TransferFee out of range', + ) + + invalid = { + TransactionType: 'MPTokenIssuanceCreate', + Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm', + TransferFee: 50001, + } as any + + assert.throws( + () => validate(invalid), + ValidationError, + 'MPTokenIssuanceCreate: TransferFee out of range', + ) + }) }) From f7259bfbe59ada67f9ccf7f46e613bdf38913836 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 6 Dec 2024 15:34:51 -0500 Subject: [PATCH 49/53] lint --- packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index 07ae9690ae..c97b7a56c0 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -143,9 +143,10 @@ export function validateMPTokenIssuanceCreate( } if (typeof tx.TransferFee === 'number') { - if (tx.TransferFee < 0 || tx.TransferFee > 50000) + if (tx.TransferFee < 0 || tx.TransferFee > 50000) { throw new ValidationError( 'MPTokenIssuanceCreate: TransferFee out of range', ) + } } } From 0223d93805423f80f4475be537c7303f2dd927b2 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Fri, 6 Dec 2024 15:49:06 -0500 Subject: [PATCH 50/53] lint --- packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index c97b7a56c0..0ec91d7340 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -142,8 +142,9 @@ export function validateMPTokenIssuanceCreate( } } + const MAX_TRANSFER_FEE = 50000 if (typeof tx.TransferFee === 'number') { - if (tx.TransferFee < 0 || tx.TransferFee > 50000) { + if (tx.TransferFee < 0 || tx.TransferFee > MAX_TRANSFER_FEE) { throw new ValidationError( 'MPTokenIssuanceCreate: TransferFee out of range', ) From 7840311e0edb6f1e5de7c3449b6d78cb976bfe58 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 9 Dec 2024 09:50:28 -0500 Subject: [PATCH 51/53] config comment --- .ci-config/rippled.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/.ci-config/rippled.cfg b/.ci-config/rippled.cfg index 911c3ecd5d..023c80c2f3 100644 --- a/.ci-config/rippled.cfg +++ b/.ci-config/rippled.cfg @@ -178,6 +178,7 @@ PriceOracle fixEmptyDID fixXChainRewardRounding fixPreviousTxnID +# 2.3.0-rc1 Amendments fixAMMv1_1 Credentials NFTokenMintOffer From ccf54dea37eaa16fcb708b9957d41ac9ecec72b2 Mon Sep 17 00:00:00 2001 From: Shawn Xie Date: Mon, 9 Dec 2024 10:29:46 -0500 Subject: [PATCH 52/53] add optional field check --- .../src/models/transactions/MPTokenIssuanceCreate.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index 0ec91d7340..e8c6ba7b88 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -1,7 +1,13 @@ import { ValidationError } from '../../errors' import { isHex, INTEGER_SANITY_CHECK } from '../utils' -import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common' +import { + BaseTransaction, + GlobalFlags, + validateBaseTransaction, + validateOptionalField, + isString, +} from './common' import type { TransactionMetadataBase } from './metadata' // 2^63 - 1 @@ -116,6 +122,8 @@ export function validateMPTokenIssuanceCreate( tx: Record, ): void { validateBaseTransaction(tx) + validateOptionalField(tx, 'MaximumAmount', isString) + validateOptionalField(tx, 'MPTokenMetadata', isString) if (typeof tx.MPTokenMetadata === 'string' && tx.MPTokenMetadata === '') { throw new ValidationError( From 46814fae2c8a32747e6d79d62fb4c32dd20e388b Mon Sep 17 00:00:00 2001 From: Shawn Xie <35279399+shawnxie999@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:50:07 -0800 Subject: [PATCH 53/53] Update packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts Co-authored-by: Omar Khan --- packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts index e8c6ba7b88..e23ef22963 100644 --- a/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts +++ b/packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts @@ -85,7 +85,7 @@ export interface MPTokenIssuanceCreate extends BaseTransaction { AssetScale?: number /** * Specifies the maximum asset amount of this token that should ever be issued. - * It is a non-negative integer that can store a range of up to 63 bits. If not set, the max + * It is a non-negative integer string that can store a range of up to 63 bits. If not set, the max * amount will default to the largest unsigned 63-bit integer (0x7FFFFFFFFFFFFFFF or 9223372036854775807) * * Example: