mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-27 07:35:52 +00:00
@@ -178,3 +178,12 @@ PriceOracle
|
||||
fixEmptyDID
|
||||
fixXChainRewardRounding
|
||||
fixPreviousTxnID
|
||||
# 2.3.0-rc1 Amendments
|
||||
fixAMMv1_1
|
||||
Credentials
|
||||
NFTokenMintOffer
|
||||
MPTokensV1
|
||||
fixNFTokenPageLinks
|
||||
fixInnerObjTemplate2
|
||||
fixEnforceNFTokenTrustline
|
||||
fixReducedOffersV2
|
||||
|
||||
6
.github/workflows/nodejs.yml
vendored
6
.github/workflows/nodejs.yml
vendored
@@ -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: |
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
### Added
|
||||
* Support for the Multi-Purpose Token amendment (XLS-33)
|
||||
|
||||
## 2.1.0 (2024-06-03)
|
||||
|
||||
### Added
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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,6 +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(0x8000000000000000)
|
||||
|
||||
/**
|
||||
* BigNumber configuration for Amount IOUs
|
||||
@@ -27,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
|
||||
mpt_issuance_id: 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' &&
|
||||
@@ -49,6 +59,17 @@ 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] === 'mpt_issuance_id' && keys[1] === 'value'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Class for serializing/Deserializing Amounts
|
||||
*/
|
||||
@@ -60,7 +81,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
|
||||
@@ -88,7 +109,7 @@ class Amount extends SerializedType {
|
||||
return new Amount(amount)
|
||||
}
|
||||
|
||||
if (isAmountObject(value)) {
|
||||
if (isAmountObjectIOU(value)) {
|
||||
const number = new BigNumber(value.value)
|
||||
Amount.assertIouIsValid(number)
|
||||
|
||||
@@ -124,6 +145,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.mpt_issuance_id).toBytes()
|
||||
return new Amount(concat([leadingByte, amount, mptIssuanceID]))
|
||||
}
|
||||
|
||||
throw new Error('Invalid type to construct an Amount')
|
||||
}
|
||||
|
||||
@@ -134,8 +173,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))
|
||||
}
|
||||
|
||||
@@ -156,7 +199,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
|
||||
@@ -182,6 +227,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()}`,
|
||||
mpt_issuance_id: mptID.toString(),
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Invalid amount to construct JSON')
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -224,6 +290,29 @@ 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 (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`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure that the value after being multiplied by the exponent does not
|
||||
* contain a decimal.
|
||||
@@ -248,7 +337,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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
19
packages/ripple-binary-codec/src/types/hash-192.ts
Normal file
19
packages/ripple-binary-codec/src/types/hash-192.ts
Normal file
@@ -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 }
|
||||
@@ -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<string, typeof SerializedType> = {
|
||||
Currency,
|
||||
Hash128,
|
||||
Hash160,
|
||||
Hash192,
|
||||
Hash256,
|
||||
Issue,
|
||||
PathSet,
|
||||
@@ -51,6 +53,7 @@ export {
|
||||
Currency,
|
||||
Hash128,
|
||||
Hash160,
|
||||
Hash192,
|
||||
Hash256,
|
||||
PathSet,
|
||||
STArray,
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
@@ -182,7 +185,7 @@ class STObject extends SerializedType {
|
||||
|
||||
accumulator[field.name] = objectParser
|
||||
.readFieldValue(field)
|
||||
.toJSON(definitions)
|
||||
.toJSON(definitions, field.name)
|
||||
}
|
||||
|
||||
return accumulator
|
||||
|
||||
@@ -2,10 +2,20 @@ import { UInt } from './uint'
|
||||
import { BinaryParser } from '../serdes/binary-parser'
|
||||
import { bytesToHex, concat, hexToBytes } from '@xrplf/isomorphic/utils'
|
||||
import { readUInt32BE, writeUInt32BE } from '../utils'
|
||||
import { DEFAULT_DEFINITIONS, XrplDefinitionsBase } from '../enums'
|
||||
|
||||
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
|
||||
*/
|
||||
@@ -29,7 +39,10 @@ class UInt64 extends UInt {
|
||||
* @param val A UInt64, hex-string, bigInt, or number
|
||||
* @returns A UInt64 object
|
||||
*/
|
||||
static from<T extends UInt64 | string | bigint | number>(val: T): UInt64 {
|
||||
static from<T extends UInt64 | string | bigint | number>(
|
||||
val: T,
|
||||
fieldName = '',
|
||||
): UInt64 {
|
||||
if (val instanceof UInt64) {
|
||||
return val
|
||||
}
|
||||
@@ -51,11 +64,18 @@ class UInt64 extends UInt {
|
||||
}
|
||||
|
||||
if (typeof val === 'string') {
|
||||
if (!HEX_REGEX.test(val)) {
|
||||
if (useBase10(fieldName)) {
|
||||
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,8 +96,16 @@ class UInt64 extends UInt {
|
||||
*
|
||||
* @returns a hex-string
|
||||
*/
|
||||
toJSON(): string {
|
||||
return bytesToHex(this.bytes)
|
||||
toJSON(
|
||||
_definitions: XrplDefinitionsBase = DEFAULT_DEFINITIONS,
|
||||
fieldName = '',
|
||||
): string {
|
||||
const hexString = bytesToHex(this.bytes)
|
||||
if (useBase10(fieldName)) {
|
||||
return BigInt('0x' + hexString).toString(10)
|
||||
}
|
||||
|
||||
return hexString
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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',
|
||||
@@ -38,5 +50,35 @@ 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)
|
||||
|
||||
// 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()
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
)}...
|
||||
as ${f.expected_hex.slice(0, 16)}`
|
||||
it(jsonToHexTestName, () => {
|
||||
const amt = Amount.from(f.test_json)
|
||||
expect(amt.toHex()).toEqual(f.expected_hex)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -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,170 @@
|
||||
"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": "18446744073709551615"
|
||||
},
|
||||
"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": "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",
|
||||
"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",
|
||||
"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
|
||||
},
|
||||
{
|
||||
"test_json": {
|
||||
"mpt_issuance_id":"00002403C84A0A28E0190E208E982C352BBD5006600555CF",
|
||||
"value": "0xa"
|
||||
},
|
||||
"type_id": 6,
|
||||
"is_native": false,
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 =
|
||||
'11007E220000006224000002DF25000002E434000000000000000030187FFFFFFFFFFFFFFF30190000000000000064552E78C1FFBDDAEE077253CEB12CFEA83689AA0899F94762190A357208DADC76FE701EC1EC7B226E616D65223A2255532054726561737572792042696C6C20546F6B656E222C2273796D626F6C223A225553544254222C22646563696D616C73223A322C22746F74616C537570706C79223A313030303030302C22697373756572223A225553205472656173757279222C22697373756544617465223A22323032342D30332D3235222C226D6174757269747944617465223A22323032352D30332D3235222C226661636556616C7565223A2231303030222C22696E74657265737452617465223A22322E35222C22696E7465726573744672657175656E6379223A22517561727465726C79222C22636F6C6C61746572616C223A22555320476F7665726E6D656E74222C226A7572697364696374696F6E223A22556E6974656420537461746573222C22726567756C61746F7279436F6D706C69616E6365223A2253454320526567756C6174696F6E73222C22736563757269747954797065223A2254726561737572792042696C6C222C2265787465726E616C5F75726C223A2268747470733A2F2F6578616D706C652E636F6D2F742D62696C6C2D746F6B656E2D6D657461646174612E6A736F6E227D8414A4D893CFBC4DC6AE877EB585F90A3B47528B958D051003'
|
||||
|
||||
const mptIssuanceEntryJson = {
|
||||
AssetScale: 3,
|
||||
Flags: 98,
|
||||
Issuer: 'rGpdGXDV2RFPeLEfWS9RFo5Nh9cpVDToZa',
|
||||
LedgerEntryType: 'MPTokenIssuance',
|
||||
MPTokenMetadata:
|
||||
'7B226E616D65223A2255532054726561737572792042696C6C20546F6B656E222C2273796D626F6C223A225553544254222C22646563696D616C73223A322C22746F74616C537570706C79223A313030303030302C22697373756572223A225553205472656173757279222C22697373756544617465223A22323032342D30332D3235222C226D6174757269747944617465223A22323032352D30332D3235222C226661636556616C7565223A2231303030222C22696E74657265737452617465223A22322E35222C22696E7465726573744672657175656E6379223A22517561727465726C79222C22636F6C6C61746572616C223A22555320476F7665726E6D656E74222C226A7572697364696374696F6E223A22556E6974656420537461746573222C22726567756C61746F7279436F6D706C69616E6365223A2253454320526567756C6174696F6E73222C22736563757269747954797065223A2254726561737572792042696C6C222C2265787465726E616C5F75726C223A2268747470733A2F2F6578616D706C652E636F6D2F742D62696C6C2D746F6B656E2D6D657461646174612E6A736F6E227D',
|
||||
MaximumAmount: '9223372036854775807',
|
||||
OutstandingAmount: '100',
|
||||
OwnerNode: '0000000000000000',
|
||||
PreviousTxnID:
|
||||
'2E78C1FFBDDAEE077253CEB12CFEA83689AA0899F94762190A357208DADC76FE',
|
||||
PreviousTxnLgrSeq: 740,
|
||||
Sequence: 735,
|
||||
}
|
||||
|
||||
const mptokenEntryJson = {
|
||||
Account: 'raDQsd1s8rqGjL476g59a9vVNi1rSwrC44',
|
||||
Flags: 0,
|
||||
LedgerEntryType: 'MPToken',
|
||||
MPTAmount: '100',
|
||||
MPTokenIssuanceID: '000002DF71CAE59C9B7E56587FFF74D4EA5830D9BE3CE0CC',
|
||||
OwnerNode: '0000000000000000',
|
||||
PreviousTxnID:
|
||||
'222EF3C7E82D8A44984A66E2B8E357CB536EC2547359CCF70E56E14BC4C284C8',
|
||||
PreviousTxnLgrSeq: 741,
|
||||
}
|
||||
|
||||
const mptokenEntryBinary =
|
||||
'11007F220000000025000002E5340000000000000000301A000000000000006455222EF3C7E82D8A44984A66E2B8E357CB536EC2547359CCF70E56E14BC4C284C881143930DB9A74C26D96CB58ADFFD7E8BB78BCFE62340115000002DF71CAE59C9B7E56587FFF74D4EA5830D9BE3CE0CC'
|
||||
|
||||
it('compareToTests[0]', () => {
|
||||
expect(UInt8.from(124).compareTo(UInt64.from(124))).toBe(0)
|
||||
})
|
||||
@@ -144,3 +178,20 @@ 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)
|
||||
|
||||
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')
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -7,20 +7,32 @@ import type {
|
||||
TransactionV1Stream,
|
||||
TxResponse,
|
||||
} from '..'
|
||||
import type { Amount, APIVersion, DEFAULT_API_VERSION } from '../models/common'
|
||||
import type {
|
||||
Amount,
|
||||
IssuedCurrency,
|
||||
APIVersion,
|
||||
DEFAULT_API_VERSION,
|
||||
MPTAmount,
|
||||
} from '../models/common'
|
||||
import type {
|
||||
AccountTxTransaction,
|
||||
RequestResponseMap,
|
||||
} from '../models/methods'
|
||||
import { AccountTxVersionResponseMap } from '../models/methods/accountTx'
|
||||
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'
|
||||
|
||||
const WARN_PARTIAL_PAYMENT_CODE = 2001
|
||||
|
||||
function amountsEqual(amt1: Amount, amt2: Amount): boolean {
|
||||
/* 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
|
||||
}
|
||||
@@ -29,15 +41,32 @@ function amountsEqual(amt1: Amount, amt2: Amount): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compare MPTs
|
||||
if (isMPTAmount(amt1) && isMPTAmount(amt2)) {
|
||||
const aValue = new BigNumber(amt1.value)
|
||||
const bValue = new BigNumber(amt2.value)
|
||||
|
||||
return (
|
||||
amt1.mpt_issuance_id === amt2.mpt_issuance_id && aValue.isEqualTo(bValue)
|
||||
)
|
||||
}
|
||||
|
||||
if (isMPTAmount(amt1) || isMPTAmount(amt2)) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Compare issued currency (IOU)
|
||||
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)
|
||||
)
|
||||
}
|
||||
/* eslint-enable complexity */
|
||||
/* eslint-enable @typescript-eslint/consistent-type-assertions */
|
||||
|
||||
function isPartialPayment(
|
||||
tx?: Transaction,
|
||||
|
||||
@@ -20,6 +20,11 @@ export interface IssuedCurrencyAmount extends IssuedCurrency {
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface MPTAmount {
|
||||
mpt_issuance_id: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export type Amount = IssuedCurrencyAmount | string
|
||||
|
||||
export interface Balance {
|
||||
|
||||
@@ -51,6 +51,8 @@ type LedgerEntryFilter =
|
||||
| 'escrow'
|
||||
| 'fee'
|
||||
| 'hashes'
|
||||
| 'mpt_issuance'
|
||||
| 'mptoken'
|
||||
| 'nft_offer'
|
||||
| 'nft_page'
|
||||
| 'offer'
|
||||
|
||||
11
packages/xrpl/src/models/ledger/MPToken.ts
Normal file
11
packages/xrpl/src/models/ledger/MPToken.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { MPTAmount } from '../common'
|
||||
|
||||
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
|
||||
|
||||
export interface MPToken extends BaseLedgerEntry, HasPreviousTxnID {
|
||||
LedgerEntryType: 'MPToken'
|
||||
MPTokenIssuanceID: string
|
||||
MPTAmount?: MPTAmount
|
||||
Flags: number
|
||||
OwnerNode?: string
|
||||
}
|
||||
13
packages/xrpl/src/models/ledger/MPTokenIssuance.ts
Normal file
13
packages/xrpl/src/models/ledger/MPTokenIssuance.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
|
||||
|
||||
export interface MPTokenIssuance extends BaseLedgerEntry, HasPreviousTxnID {
|
||||
LedgerEntryType: 'MPTokenIssuance'
|
||||
Flags: number
|
||||
Issuer: string
|
||||
AssetScale?: number
|
||||
MaximumAmount?: string
|
||||
OutstandingAmount: string
|
||||
TransferFee?: number
|
||||
MPTokenMetadata?: string
|
||||
OwnerNode?: string
|
||||
}
|
||||
@@ -18,6 +18,8 @@ import FeeSettings, {
|
||||
import { Ledger, LedgerV1 } from './Ledger'
|
||||
import { LedgerEntry, LedgerEntryFilter } from './LedgerEntry'
|
||||
import LedgerHashes from './LedgerHashes'
|
||||
import { MPToken } from './MPToken'
|
||||
import { MPTokenIssuance } from './MPTokenIssuance'
|
||||
import NegativeUNL, { NEGATIVE_UNL_ID } from './NegativeUNL'
|
||||
import { NFTokenOffer } from './NFTokenOffer'
|
||||
import { NFToken, NFTokenPage } from './NFTokenPage'
|
||||
@@ -55,6 +57,8 @@ export {
|
||||
Majority,
|
||||
NEGATIVE_UNL_ID,
|
||||
NegativeUNL,
|
||||
MPTokenIssuance,
|
||||
MPToken,
|
||||
NFTokenOffer,
|
||||
NFTokenPage,
|
||||
NFToken,
|
||||
|
||||
@@ -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.
|
||||
|
||||
67
packages/xrpl/src/models/transactions/MPTokenAuthorize.ts
Normal file
67
packages/xrpl/src/models/transactions/MPTokenAuthorize.ts
Normal file
@@ -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.
|
||||
*/
|
||||
Holder?: 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<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
validateRequiredField(tx, 'MPTokenIssuanceID', isString)
|
||||
validateOptionalField(tx, 'Holder', isAccount)
|
||||
}
|
||||
161
packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts
Normal file
161
packages/xrpl/src/models/transactions/MPTokenIssuanceCreate.ts
Normal file
@@ -0,0 +1,161 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
import { isHex, INTEGER_SANITY_CHECK } from '../utils'
|
||||
|
||||
import {
|
||||
BaseTransaction,
|
||||
GlobalFlags,
|
||||
validateBaseTransaction,
|
||||
validateOptionalField,
|
||||
isString,
|
||||
} from './common'
|
||||
import type { TransactionMetadataBase } from './metadata'
|
||||
|
||||
// 2^63 - 1
|
||||
const MAX_AMT = '9223372036854775807'
|
||||
|
||||
/**
|
||||
* 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 maximum asset amount of this token that should ever be issued.
|
||||
* 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:
|
||||
* ```
|
||||
* MaximumAmount: '9223372036854775807'
|
||||
* ```
|
||||
*/
|
||||
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<string, unknown>,
|
||||
): void {
|
||||
validateBaseTransaction(tx)
|
||||
validateOptionalField(tx, 'MaximumAmount', isString)
|
||||
validateOptionalField(tx, 'MPTokenMetadata', isString)
|
||||
|
||||
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',
|
||||
)
|
||||
}
|
||||
|
||||
if (typeof tx.MaximumAmount === 'string') {
|
||||
if (!INTEGER_SANITY_CHECK.exec(tx.MaximumAmount)) {
|
||||
throw new ValidationError('MPTokenIssuanceCreate: Invalid MaximumAmount')
|
||||
} else if (
|
||||
BigInt(tx.MaximumAmount) > BigInt(MAX_AMT) ||
|
||||
BigInt(tx.MaximumAmount) < BigInt(`0`)
|
||||
) {
|
||||
throw new ValidationError(
|
||||
'MPTokenIssuanceCreate: MaximumAmount out of range',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const MAX_TRANSFER_FEE = 50000
|
||||
if (typeof tx.TransferFee === 'number') {
|
||||
if (tx.TransferFee < 0 || tx.TransferFee > MAX_TRANSFER_FEE) {
|
||||
throw new ValidationError(
|
||||
'MPTokenIssuanceCreate: TransferFee out of range',
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
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 who have non-zero balances.
|
||||
*/
|
||||
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<string, unknown>,
|
||||
): void {
|
||||
validateBaseTransaction(tx)
|
||||
validateRequiredField(tx, 'MPTokenIssuanceID', isString)
|
||||
}
|
||||
82
packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts
Normal file
82
packages/xrpl/src/models/transactions/MPTokenIssuanceSet.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
import { isFlagEnabled } from '../utils'
|
||||
|
||||
import {
|
||||
BaseTransaction,
|
||||
isString,
|
||||
validateBaseTransaction,
|
||||
validateRequiredField,
|
||||
Account,
|
||||
validateOptionalField,
|
||||
isAccount,
|
||||
GlobalFlags,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
* Transaction Flags for an MPTokenIssuanceSet Transaction.
|
||||
*
|
||||
* @category Transaction Flags
|
||||
*/
|
||||
export enum MPTokenIssuanceSetFlags {
|
||||
/**
|
||||
* If set, indicates that issuer locks the MPT
|
||||
*/
|
||||
tfMPTLock = 0x00000001,
|
||||
/**
|
||||
* If set, indicates that issuer unlocks the MPT
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
Holder?: 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<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
validateRequiredField(tx, 'MPTokenIssuanceID', isString)
|
||||
validateOptionalField(tx, 'Holder', isAccount)
|
||||
|
||||
if (typeof tx.Flags === 'number') {
|
||||
const flags = tx.Flags
|
||||
if (
|
||||
isFlagEnabled(flags, MPTokenIssuanceSetFlags.tfMPTLock) &&
|
||||
isFlagEnabled(flags, MPTokenIssuanceSetFlags.tfMPTUnlock)
|
||||
) {
|
||||
throw new ValidationError('MPTokenIssuanceSet: flag conflict')
|
||||
}
|
||||
} else {
|
||||
throw new Error('tx.Flags is not a number')
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
Holder?: string
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -34,16 +42,29 @@ export interface Clawback extends BaseTransaction {
|
||||
*/
|
||||
export function validateClawback(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
validateOptionalField(tx, 'Holder', 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.Holder) {
|
||||
throw new ValidationError('Clawback: invalid holder Account')
|
||||
}
|
||||
|
||||
if (isIssuedCurrency(tx.Amount) && tx.Holder) {
|
||||
throw new ValidationError('Clawback: cannot have Holder for currency')
|
||||
}
|
||||
|
||||
if (isMPTAmount(tx.Amount) && !tx.Holder) {
|
||||
throw new ValidationError('Clawback: missing Holder')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<string, unknown> {
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { BaseTransaction } from './common'
|
||||
export { BaseTransaction, isMPTAmount } from './common'
|
||||
export {
|
||||
validate,
|
||||
PseudoTransaction,
|
||||
@@ -39,6 +39,22 @@ 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,
|
||||
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'
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import { Amount } from '../common'
|
||||
import { Amount, MPTAmount } from '../common'
|
||||
|
||||
import { BaseTransaction } from './common'
|
||||
import {
|
||||
MPTokenIssuanceCreate,
|
||||
MPTokenIssuanceCreateMetadata,
|
||||
} from './MPTokenIssuanceCreate'
|
||||
import {
|
||||
NFTokenAcceptOffer,
|
||||
NFTokenAcceptOfferMetadata,
|
||||
@@ -79,9 +83,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
|
||||
}
|
||||
@@ -97,4 +101,6 @@ export type TransactionMetadata<T extends BaseTransaction = Transaction> =
|
||||
? NFTokenAcceptOfferMetadata
|
||||
: T extends NFTokenCancelOffer
|
||||
? NFTokenCancelOfferMetadata
|
||||
: T extends MPTokenIssuanceCreate
|
||||
? MPTokenIssuanceCreateMetadata
|
||||
: TransactionMetadataBase
|
||||
|
||||
@@ -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
|
||||
/**
|
||||
@@ -142,19 +142,19 @@ 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
|
||||
}
|
||||
|
||||
export interface PaymentMetadata extends TransactionMetadataBase {
|
||||
DeliveredAmount?: Amount
|
||||
delivered_amount?: Amount | 'unavailable'
|
||||
DeliveredAmount?: Amount | MPTAmount
|
||||
delivered_amount?: Amount | MPTAmount | 'unavailable'
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
@@ -115,6 +128,10 @@ export type SubmittableTransaction =
|
||||
| EscrowCancel
|
||||
| EscrowCreate
|
||||
| EscrowFinish
|
||||
| MPTokenAuthorize
|
||||
| MPTokenIssuanceCreate
|
||||
| MPTokenIssuanceDestroy
|
||||
| MPTokenIssuanceSet
|
||||
| NFTokenAcceptOffer
|
||||
| NFTokenBurn
|
||||
| NFTokenCancelOffer
|
||||
@@ -306,6 +323,22 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
validateEscrowFinish(tx)
|
||||
break
|
||||
|
||||
case 'MPTokenAuthorize':
|
||||
validateMPTokenAuthorize(tx)
|
||||
break
|
||||
|
||||
case 'MPTokenIssuanceCreate':
|
||||
validateMPTokenIssuanceCreate(tx)
|
||||
break
|
||||
|
||||
case 'MPTokenIssuanceDestroy':
|
||||
validateMPTokenIssuanceDestroy(tx)
|
||||
break
|
||||
|
||||
case 'MPTokenIssuanceSet':
|
||||
validateMPTokenIssuanceSet(tx)
|
||||
break
|
||||
|
||||
case 'NFTokenAcceptOffer':
|
||||
validateNFTokenAcceptOffer(tx)
|
||||
break
|
||||
|
||||
@@ -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'
|
||||
@@ -48,6 +51,9 @@ const txToFlag = {
|
||||
AccountSet: AccountSetTfFlags,
|
||||
AMMDeposit: AMMDepositFlags,
|
||||
AMMWithdraw: AMMWithdrawFlags,
|
||||
MPTokenAuthorize: MPTokenAuthorizeFlags,
|
||||
MPTokenIssuanceCreate: MPTokenIssuanceCreateFlags,
|
||||
MPTokenIssuanceSet: MPTokenIssuanceSetFlags,
|
||||
NFTokenCreateOffer: NFTokenCreateOfferFlags,
|
||||
NFTokenMint: NFTokenMintFlags,
|
||||
OfferCreate: OfferCreateFlags,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -6,6 +6,11 @@ import {
|
||||
TrustSet,
|
||||
Payment,
|
||||
Clawback,
|
||||
MPTokenIssuanceCreate,
|
||||
MPTokenIssuanceCreateFlags,
|
||||
MPTokenAuthorize,
|
||||
TransactionMetadata,
|
||||
LedgerEntryResponse,
|
||||
} from '../../../src'
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
@@ -112,4 +117,92 @@ 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,
|
||||
}
|
||||
|
||||
const mptCreateRes = await testTransaction(
|
||||
testContext.client,
|
||||
createTx,
|
||||
testContext.wallet,
|
||||
)
|
||||
const txHash = mptCreateRes.result.tx_json.hash
|
||||
|
||||
const txResponse = await testContext.client.request({
|
||||
command: 'tx',
|
||||
transaction: txHash,
|
||||
})
|
||||
|
||||
const meta = txResponse.result
|
||||
.meta as TransactionMetadata<MPTokenIssuanceCreate>
|
||||
|
||||
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: '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-expect-error: Known issue with unknown object type
|
||||
ledgerEntryResponse.result.node.MPTAmount,
|
||||
'9223372036854775807',
|
||||
)
|
||||
|
||||
// actual test - clawback
|
||||
const clawTx: Clawback = {
|
||||
TransactionType: 'Clawback',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptID!,
|
||||
value: '500',
|
||||
},
|
||||
Holder: 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-expect-error: Known issue with unknown object type
|
||||
ledgerEntryResponse.result.node.MPTAmount,
|
||||
'9223372036854775307',
|
||||
)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
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('MPTokenAuthorize', 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
|
||||
|
||||
const txResponse = await testContext.client.request({
|
||||
command: 'tx',
|
||||
transaction: txHash,
|
||||
})
|
||||
|
||||
const meta = txResponse.result
|
||||
.meta as TransactionMetadata<MPTokenIssuanceCreate>
|
||||
|
||||
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',
|
||||
)
|
||||
|
||||
authTx = {
|
||||
TransactionType: 'MPTokenAuthorize',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
MPTokenIssuanceID: mptID!,
|
||||
Holder: 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,
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,54 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { MPTokenIssuanceCreate } 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('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,
|
||||
// 0x7fffffffffffffff
|
||||
MaximumAmount: '9223372036854775807',
|
||||
AssetScale: 2,
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, tx, testContext.wallet)
|
||||
|
||||
const 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',
|
||||
)
|
||||
assert.equal(
|
||||
// @ts-expect-error: Known issue with unknown object type
|
||||
accountObjectsResponse.result.account_objects[0].MaximumAmount,
|
||||
`9223372036854775807`,
|
||||
)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,85 @@
|
||||
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
|
||||
|
||||
const txResponse = await testContext.client.request({
|
||||
command: 'tx',
|
||||
transaction: txHash,
|
||||
})
|
||||
|
||||
const meta = txResponse.result
|
||||
.meta as TransactionMetadata<MPTokenIssuanceCreate>
|
||||
|
||||
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',
|
||||
)
|
||||
|
||||
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,
|
||||
type: 'mpt_issuance',
|
||||
})
|
||||
assert.lengthOf(
|
||||
accountObjectsResponse.result.account_objects,
|
||||
0,
|
||||
'Should be zero issuance on the ledger',
|
||||
)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,78 @@
|
||||
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
|
||||
|
||||
const txResponse = await testContext.client.request({
|
||||
command: 'tx',
|
||||
transaction: txHash,
|
||||
})
|
||||
|
||||
const meta = txResponse.result
|
||||
.meta as TransactionMetadata<MPTokenIssuanceCreate>
|
||||
|
||||
const mptID = meta.mpt_issuance_id
|
||||
|
||||
const 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',
|
||||
)
|
||||
|
||||
const setTx: MPTokenIssuanceSet = {
|
||||
TransactionType: 'MPTokenIssuanceSet',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
MPTokenIssuanceID: mptID!,
|
||||
Flags: MPTokenIssuanceSetFlags.tfMPTLock,
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, setTx, testContext.wallet)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
@@ -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,
|
||||
@@ -102,4 +108,89 @@ 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
|
||||
|
||||
const txResponse = await testContext.client.request({
|
||||
command: 'tx',
|
||||
transaction: txHash,
|
||||
})
|
||||
|
||||
const meta = txResponse.result
|
||||
.meta as TransactionMetadata<MPTokenIssuanceCreate>
|
||||
|
||||
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',
|
||||
)
|
||||
|
||||
const 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',
|
||||
)
|
||||
|
||||
const 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)
|
||||
|
||||
accountObjectsResponse = await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: testContext.wallet.classicAddress,
|
||||
type: 'mpt_issuance',
|
||||
})
|
||||
assert.equal(
|
||||
// @ts-expect-error -- Object type not known
|
||||
accountObjectsResponse.result.account_objects[0].OutstandingAmount,
|
||||
`100`,
|
||||
)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
|
||||
63
packages/xrpl/test/models/MPTokenAuthorize.test.ts
Normal file
63
packages/xrpl/test/models/MPTokenAuthorize.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
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',
|
||||
Holder: '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,
|
||||
Holder: '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',
|
||||
)
|
||||
})
|
||||
})
|
||||
124
packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts
Normal file
124
packages/xrpl/test/models/MPTokenIssuanceCreate.test.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import {
|
||||
convertStringToHex,
|
||||
validate,
|
||||
ValidationError,
|
||||
MPTokenIssuanceCreateFlags,
|
||||
} 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',
|
||||
// 0x7fffffffffffffff
|
||||
MaximumAmount: '9223372036854775807',
|
||||
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',
|
||||
)
|
||||
})
|
||||
|
||||
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',
|
||||
)
|
||||
})
|
||||
|
||||
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',
|
||||
)
|
||||
})
|
||||
})
|
||||
35
packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts
Normal file
35
packages/xrpl/test/models/MPTokenIssuanceDestroy.test.ts
Normal file
@@ -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',
|
||||
)
|
||||
})
|
||||
})
|
||||
73
packages/xrpl/test/models/MPTokenIssuanceSet.test.ts
Normal file
73
packages/xrpl/test/models/MPTokenIssuanceSet.test.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
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,
|
||||
Holder: '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,
|
||||
Holder: '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 () {
|
||||
/* eslint-disable no-bitwise -- Bitwise operation needed for flag combination */
|
||||
const invalid = {
|
||||
TransactionType: 'MPTokenIssuanceSet',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
MPTokenIssuanceID: TOKEN_ID,
|
||||
Flags:
|
||||
MPTokenIssuanceSetFlags.tfMPTLock | MPTokenIssuanceSetFlags.tfMPTUnlock,
|
||||
} as any
|
||||
/* eslint-enable no-bitwise -- Re-enable bitwise rule */
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'MPTokenIssuanceSet: flag conflict',
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -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',
|
||||
Holder: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy',
|
||||
} as any
|
||||
|
||||
assert.doesNotThrow(() => validate(validClawback))
|
||||
})
|
||||
|
||||
it(`throws w/ invalid Holder Account`, function () {
|
||||
const invalidAccount = {
|
||||
TransactionType: 'Clawback',
|
||||
Amount: {
|
||||
mpt_issuance_id: '000004C463C52827307480341125DA0577DEFC38405B0E3E',
|
||||
value: '10',
|
||||
},
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Holder: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalidAccount),
|
||||
ValidationError,
|
||||
'Clawback: invalid holder Account',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid Holder`, function () {
|
||||
const invalidAccount = {
|
||||
TransactionType: 'Clawback',
|
||||
Amount: {
|
||||
mpt_issuance_id: '000004C463C52827307480341125DA0577DEFC38405B0E3E',
|
||||
value: '10',
|
||||
},
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalidAccount),
|
||||
ValidationError,
|
||||
'Clawback: missing Holder',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid currency Holder`, function () {
|
||||
const invalidAccount = {
|
||||
TransactionType: 'Clawback',
|
||||
Amount: {
|
||||
currency: 'DSH',
|
||||
issuer: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy',
|
||||
value: '43.11584856965009',
|
||||
},
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Holder: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy',
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalidAccount),
|
||||
ValidationError,
|
||||
'Clawback: cannot have Holder for currency',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -258,4 +258,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: '10',
|
||||
},
|
||||
Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy',
|
||||
} as any
|
||||
assert.doesNotThrow(() => validatePayment(mptPaymentTransaction))
|
||||
assert.doesNotThrow(() => validate(mptPaymentTransaction))
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user