+
+
{t('hash')}:
- {data?.raw.hash}
+ {data?.processed.hash}
- {data?.raw.tx.ctid && (
-
+ {data?.processed.tx.ctid && (
+
CTID:
- {data.raw.tx.ctid}
+ {data.processed.tx.ctid}
)}
@@ -126,7 +131,7 @@ export const Transaction = () => {
switch (tab) {
case 'detailed':
- body =
+ body =
break
case 'raw':
body =
@@ -149,7 +154,7 @@ export const Transaction = () => {
if (isError) {
const message = getErrorMessage(error)
body =
- } else if (data?.raw && data?.raw.hash) {
+ } else if (data?.processed && data?.processed.hash) {
body = renderTransaction()
} else if (!identifier) {
body = (
diff --git a/src/containers/Transactions/test/Description.test.tsx b/src/containers/Transactions/test/Description.test.tsx
index 04ae40ccb..4c6074494 100644
--- a/src/containers/Transactions/test/Description.test.tsx
+++ b/src/containers/Transactions/test/Description.test.tsx
@@ -1,19 +1,23 @@
import { mount } from 'enzyme'
import { BrowserRouter as Router } from 'react-router-dom'
import { I18nextProvider } from 'react-i18next'
+import { QueryClientProvider } from 'react-query'
import i18n from '../../../i18n/testConfigEnglish'
import { TransactionDescription } from '../DetailTab/Description'
import Transaction from './mock_data/Transaction.json'
import OfferCreateTicket from './mock_data/OfferCreateTicket.json'
import EmittedPayment from './mock_data/EmittedPayment.json'
+import { queryClient } from '../../shared/QueryClient'
describe('Description container', () => {
const createWrapper = (data = {}) =>
mount(
-
-
-
+
+
+
+
+
,
)
diff --git a/src/containers/Transactions/test/DetailTab.test.tsx b/src/containers/Transactions/test/DetailTab.test.tsx
index 0e734c06c..46ff5488c 100644
--- a/src/containers/Transactions/test/DetailTab.test.tsx
+++ b/src/containers/Transactions/test/DetailTab.test.tsx
@@ -1,6 +1,7 @@
import { mount } from 'enzyme'
import { BrowserRouter as Router } from 'react-router-dom'
import { I18nextProvider } from 'react-i18next'
+import { QueryClientProvider } from 'react-query'
import Transaction from '../../shared/components/Transaction/EscrowCreate/test/mock_data/EscrowCreate.json'
import FailedTransaction from '../../shared/components/Transaction/SignerListSet/test/mock_data/SignerListSet.json'
import HookPayment from './mock_data/HookPayment.json'
@@ -8,14 +9,17 @@ import EmittedPayment from './mock_data/EmittedPayment.json'
import { DetailTab } from '../DetailTab'
import i18n from '../../../i18n/testConfigEnglish'
import { convertHexToString } from '../../../rippled/lib/utils'
+import { queryClient } from '../../shared/QueryClient'
describe('DetailTab container', () => {
const createWrapper = (transaction: any = Transaction) =>
mount(
-
-
-
+
+
+
+
+
,
)
diff --git a/src/containers/Transactions/test/Meta.test.tsx b/src/containers/Transactions/test/Meta.test.tsx
index e6e2fe903..ac4195799 100644
--- a/src/containers/Transactions/test/Meta.test.tsx
+++ b/src/containers/Transactions/test/Meta.test.tsx
@@ -6,6 +6,7 @@ import Transaction from './mock_data/Transaction.json'
import OfferCancel from '../../shared/components/Transaction/OfferCancel/test/mock_data/OfferCancel.json'
import OfferCreateWithMissingPreviousFields from '../../shared/components/Transaction/OfferCreate/test/mock_data/OfferCreateWithMissingPreviousFields.json'
import PaymentChannelClaim from '../../shared/components/Transaction/PaymentChannelClaim/test/mock_data/PaymentChannelClaim.json'
+import DirectMPTPayment from './mock_data/DirectMPTPayment.json'
import { TransactionMeta } from '../DetailTab/Meta'
describe('TransactionMeta container', () => {
@@ -160,4 +161,32 @@ describe('TransactionMeta container', () => {
)
expect(w.find('li').length).toBe(4)
})
+
+ it('renders MPT Payment Meta', () => {
+ const w = createWrapper(DirectMPTPayment)
+
+ expect(w.find('.title').length).toBe(1)
+ expect(w.find('.detail-subsection').length).toBe(1)
+ expect(w.contains(
number_of_affected_node
)).toBe(true)
+ expect(w.contains(
nodes_type
)).toBe(
+ true,
+ )
+ expect(w.find('li').length).toBe(6)
+
+ expect(w.find('li').at(2).html()).toBe(
+ '
It modified an MPToken node ofrnNkvddM96FE2QsaFztLAn5xicjq5d6d8d',
+ )
+
+ expect(w.find('li').at(3).html()).toBe(
+ '
Balance changed by100from0to100',
+ )
+
+ expect(w.find('li').at(4).html()).toBe(
+ '
It modified an MPTokenIssuance node ofrwREfyDU1SbcjN3xXZDbm8uNJV77T2ruKw',
+ )
+
+ expect(w.find('li').at(5).html()).toBe(
+ '
Outstanding balance changed by100from0to100',
+ )
+ })
})
diff --git a/src/containers/Transactions/test/SimpleTab.test.tsx b/src/containers/Transactions/test/SimpleTab.test.tsx
index 5b983ebd6..bce4d5502 100644
--- a/src/containers/Transactions/test/SimpleTab.test.tsx
+++ b/src/containers/Transactions/test/SimpleTab.test.tsx
@@ -2,26 +2,43 @@ import { mount } from 'enzyme'
import { I18nextProvider } from 'react-i18next'
import { BrowserRouter as Router } from 'react-router-dom'
+import { QueryClientProvider } from 'react-query'
import EnableAmendment from './mock_data/EnableAmendment.json'
import Payment from '../../shared/components/Transaction/Payment/test/mock_data/Payment.json'
import { SimpleTab } from '../SimpleTab'
import summarize from '../../../rippled/lib/txSummary'
import i18n from '../../../i18n/testConfig'
import { expectSimpleRowText } from '../../shared/components/Transaction/test'
+import SocketContext from '../../shared/SocketContext'
+import MockWsClient from '../../test/mockWsClient'
+import { queryClient } from '../../shared/QueryClient'
describe('SimpleTab container', () => {
+ let client
const createWrapper = (tx, width = 1200) =>
mount(
-
-
-
+
+
+
+
+
+
+
,
)
+ beforeEach(() => {
+ client = new MockWsClient()
+ })
+
+ afterEach(() => {
+ client.close()
+ })
+
it('renders EnableAmendment without crashing', () => {
const wrapper = createWrapper(EnableAmendment)
wrapper.unmount()
diff --git a/src/containers/Transactions/test/Transaction.test.tsx b/src/containers/Transactions/test/Transaction.test.tsx
index 774d3da56..e4b471e95 100644
--- a/src/containers/Transactions/test/Transaction.test.tsx
+++ b/src/containers/Transactions/test/Transaction.test.tsx
@@ -104,7 +104,7 @@ describe('Transaction container', () => {
beforeEach(async () => {
const transaction = {
- raw: mockTransaction,
+ processed: mockTransaction,
summary: mockTransactionSummary,
}
diff --git a/src/containers/Transactions/test/mock_data/DirectMPTPayment.json b/src/containers/Transactions/test/mock_data/DirectMPTPayment.json
new file mode 100644
index 000000000..87d3c374b
--- /dev/null
+++ b/src/containers/Transactions/test/mock_data/DirectMPTPayment.json
@@ -0,0 +1,95 @@
+{
+ "tx": {
+ "Account": "rwREfyDU1SbcjN3xXZDbm8uNJV77T2ruKw",
+ "Amount": {
+ "mpt_issuance_id": "0000301C674EE6ECD0374A628E2C442EF8E3BBBEE8C58CF3",
+ "value": "100"
+ },
+ "DeliverMax": {
+ "mpt_issuance_id": "0000301C674EE6ECD0374A628E2C442EF8E3BBBEE8C58CF3",
+ "value": "100"
+ },
+ "Destination": "rnNkvddM96FE2QsaFztLAn5xicjq5d6d8d",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "Sequence": 12317,
+ "SigningPubKey": "ED1E43F90700506F98E45CC8E77563ACB8FF0338739229AC98F0E1AEB409E786F9",
+ "TransactionType": "Payment",
+ "TxnSignature": "9949EF3E718A6776586A1DD91256C4055E761CBF1CE7A351912C08FE1BB415F6638FA096CAAE88D30B32E684448CDC52DEB022A7E3576AFA0C6E5ABB7BE2FE02",
+ "ctid": "C000302000000000",
+ "date": 1712087804000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rwREfyDU1SbcjN3xXZDbm8uNJV77T2ruKw",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 12318
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "80909CC95DCD7E5E08631635C559E218D42C49721A1285192885B46E4737CF60",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 12317
+ },
+ "PreviousTxnID": "C9D0965E7A13F54186B6501D08ED54D74AA946A856D96BF7AAE18EA5FD5E93C2",
+ "PreviousTxnLgrSeq": 12318
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rnNkvddM96FE2QsaFztLAn5xicjq5d6d8d",
+ "Flags": 0,
+ "MPTAmount": "100",
+ "MPTokenIssuanceID": "0000301C674EE6ECD0374A628E2C442EF8E3BBBEE8C58CF3",
+ "OwnerNode": "0"
+ },
+ "LedgerEntryType": "MPToken",
+ "LedgerIndex": "9B2E5EA9ACF16B591B941CAE5323EBED55E42495B16C92DC9FBEC0997E8E6804",
+ "PreviousFields": {},
+ "PreviousTxnID": "4C41449D0C083746CC93DDA78F00E97AB8B857188E805F39C1A250F8C9467982",
+ "PreviousTxnLgrSeq": 12319
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Flags": 64,
+ "Issuer": "rwREfyDU1SbcjN3xXZDbm8uNJV77T2ruKw",
+ "OutstandingAmount": "100",
+ "OwnerNode": "0",
+ "Sequence": 12316
+ },
+ "LedgerEntryType": "MPTokenIssuance",
+ "LedgerIndex": "C38E54FD2C98FE848FE31CDA95F6F990A9A8715987171DA342A3296B7A9123B6",
+ "PreviousFields": {
+ "OutstandingAmount": "0"
+ },
+ "PreviousTxnID": "C9D0965E7A13F54186B6501D08ED54D74AA946A856D96BF7AAE18EA5FD5E93C2",
+ "PreviousTxnLgrSeq": 12318
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS",
+ "delivered_amount": {
+ "mpt_issuance_id": "0000301C674EE6ECD0374A628E2C442EF8E3BBBEE8C58CF3",
+ "value": "100"
+ }
+ },
+ "hash": "5E74603F7C2E11030E644E681508FD1F24CAEB4CC0CE1F35A6230689D9694E85",
+ "ledger_index": 12320,
+ "date": 1712087804000
+}
diff --git a/src/containers/shared/Interfaces.tsx b/src/containers/shared/Interfaces.tsx
index 201b0daea..83c03903f 100644
--- a/src/containers/shared/Interfaces.tsx
+++ b/src/containers/shared/Interfaces.tsx
@@ -36,6 +36,20 @@ export interface NFTFormattedInfo {
warnings?: string[]
}
+/**
+ * Values returned by 'formatMPTIssuanceInfo' from /src/rippled/lib/utils.js
+ */
+export interface MPTIssuanceFormattedInfo {
+ issuer: string
+ sequence: number
+ assetScale?: number
+ maxAmt?: string
+ outstandingAmt?: string
+ flags?: string[]
+ transferFee?: number
+ metadata?: string
+}
+
export interface ErrorMessage {
title: string
hints: string[]
diff --git a/src/containers/shared/amendmentUtils.ts b/src/containers/shared/amendmentUtils.ts
index 96e189e33..b9e21af55 100644
--- a/src/containers/shared/amendmentUtils.ts
+++ b/src/containers/shared/amendmentUtils.ts
@@ -1,14 +1,9 @@
-import { sha512 } from '@xrplf/isomorphic/sha512'
-import { hexToBytes, bytesToHex, stringToHex } from '@xrplf/isomorphic/utils'
import axios from 'axios'
import { localizeDate } from './utils'
-const cachedAmendmentIDs: any = {}
let cachedRippledVersions: any = {}
-const ACTIVE_AMENDMENT_REGEX = /^\s*REGISTER_F[A-Z]+\s*\((\S+),\s*.*$/
-const RETIRED_AMENDMENT_REGEX = /^ .*retireFeature\("(\S+)"\)[,;].*$/
const TIME_ZONE = 'UTC'
const DATE_OPTIONS = {
hour: 'numeric',
@@ -31,42 +26,6 @@ export function getExpectedDate(date: string, language: string) {
)
}
-async function fetchAmendmentNames() {
- const response = await axios.get(
- 'https://raw.githubusercontent.com/XRPLF/rippled/develop/src/libxrpl/protocol/Feature.cpp',
- )
- const text = response.data
-
- const amendmentNames: string[] = []
- text.split('\n').forEach((line: string) => {
- const name = line.match(ACTIVE_AMENDMENT_REGEX)
- if (name) {
- amendmentNames.push(name[1])
- } else {
- const name2 = line.match(RETIRED_AMENDMENT_REGEX)
- name2 && amendmentNames.push(name2[1])
- }
- })
- return amendmentNames
-}
-
-function sha512Half(bytes: Uint8Array) {
- return bytesToHex(sha512(bytes)).slice(0, 64)
-}
-
-export async function nameOfAmendmentID(id: string) {
- if (cachedAmendmentIDs[id]) {
- return cachedAmendmentIDs[id]
- }
- // The Amendment ID is the hash of the Amendment name
- const amendmentNames = await fetchAmendmentNames()
- amendmentNames.forEach((name) => {
- cachedAmendmentIDs[sha512Half(hexToBytes(stringToHex(name)))] = name
- })
-
- return cachedAmendmentIDs[id]
-}
-
async function fetchMinRippledVersions() {
const response = await axios.get(
'https://raw.githubusercontent.com/XRPLF/xrpl-dev-portal/master/resources/known-amendments.md',
diff --git a/src/containers/shared/analytics.ts b/src/containers/shared/analytics.ts
index bff40dcfb..b11d5c349 100644
--- a/src/containers/shared/analytics.ts
+++ b/src/containers/shared/analytics.ts
@@ -27,6 +27,7 @@ export interface AnalyticsFields {
search_term?: string
search_category?: string
validator?: string
+ mpt_issuance_id?: string
description?: string
page_title?: string
diff --git a/src/containers/shared/components/Amount.tsx b/src/containers/shared/components/Amount.tsx
index 164b06737..e7fe6ecd5 100644
--- a/src/containers/shared/components/Amount.tsx
+++ b/src/containers/shared/components/Amount.tsx
@@ -1,8 +1,15 @@
+import { useQuery } from 'react-query'
+import { useContext } from 'react'
import { CURRENCY_OPTIONS, XRP_BASE } from '../transactionUtils'
import { useLanguage } from '../hooks'
-import { localizeNumber } from '../utils'
+import { localizeNumber, convertScaledPrice } from '../utils'
import Currency from './Currency'
import { ExplorerAmount } from '../types'
+import { MPTIssuanceFormattedInfo } from '../Interfaces'
+import { getMPTIssuance } from '../../../rippled/lib/rippled'
+import { formatMPTIssuanceInfo } from '../../../rippled/lib/utils'
+import SocketContext from '../SocketContext'
+import { useAnalytics } from '../analytics'
export interface AmountProps {
value: ExplorerAmount | string
@@ -16,15 +23,17 @@ export const Amount = ({
value,
}: AmountProps) => {
const language = useLanguage()
+ const rippledSocket = useContext(SocketContext)
+ const { trackException } = useAnalytics()
const issuer = typeof value === 'string' ? undefined : value.issuer
const currency = typeof value === 'string' ? 'XRP' : value.currency
const amount =
typeof value === 'string' ? parseInt(value, 10) / XRP_BASE : value.amount
+ const isMPT = typeof value === 'string' ? false : value.isMPT
const options = { ...CURRENCY_OPTIONS, currency }
- const localizedAmount = localizeNumber(amount, language, options)
- return (
+ const renderAmount = (localizedAmount) => (
{modifier && {modifier}}
@@ -35,7 +44,43 @@ export const Amount = ({
currency={currency}
link
displaySymbol={false}
+ isMPT={isMPT}
/>
)
+
+ const mptID = isMPT ? (value as ExplorerAmount).currency : null
+
+ // fetch MPTIssuance only if isMPT is true
+ const { data: mptIssuanceData } =
+ useQuery(
+ ['getMPTIssuanceScale', mptID],
+ async () => {
+ const info = await getMPTIssuance(rippledSocket, mptID)
+ return formatMPTIssuanceInfo(info)
+ },
+ {
+ onError: (e: any) => {
+ trackException(`mptIssuance ${mptID} --- ${JSON.stringify(e)}`)
+ },
+ enabled: isMPT,
+ },
+ ) || {}
+
+ // if amount is MPT type, we need to fetch the scale from the MPTokenIssuance
+ // object so we can show the scaled amount
+ if (isMPT && typeof value !== 'string') {
+ if (mptIssuanceData) {
+ const scale = mptIssuanceData.assetScale ?? 0
+ const scaledAmount = convertScaledPrice(
+ parseInt(amount as string, 10).toString(16),
+ scale,
+ )
+
+ return renderAmount(localizeNumber(scaledAmount, language, {}, true))
+ }
+ return null
+ }
+
+ return renderAmount(localizeNumber(amount, language, options))
}
diff --git a/src/containers/shared/components/Currency.tsx b/src/containers/shared/components/Currency.tsx
index 64727ca0a..ac48efa7c 100644
--- a/src/containers/shared/components/Currency.tsx
+++ b/src/containers/shared/components/Currency.tsx
@@ -1,5 +1,5 @@
import { RouteLink } from '../routing'
-import { TOKEN_ROUTE } from '../../App/routes'
+import { TOKEN_ROUTE, MPT_ROUTE } from '../../App/routes'
// https://xrpl.org/currency-formats.html#nonstandard-currency-codes
const NON_STANDARD_CODE_LENGTH = 40
@@ -12,6 +12,7 @@ export interface Props {
link?: boolean
shortenIssuer?: boolean
displaySymbol?: boolean
+ isMPT?: boolean
}
/*
@@ -25,33 +26,46 @@ const Currency = (props: Props) => {
link = true,
shortenIssuer = false,
displaySymbol = true,
+ isMPT = false,
} = props
+ let content
- const currencyCode =
- currency?.length === NON_STANDARD_CODE_LENGTH &&
- currency?.substring(0, 2) !== LP_TOKEN_IDENTIFIER
- ? hexToString(currency)
- : currency
-
- let display = `${currencyCode}`
-
- if (currencyCode === XRP && displaySymbol) {
- display = `\uE900 ${display}`
- }
-
- if (issuer) {
- display += '.'
- display += shortenIssuer ? issuer.substring(0, 4) : issuer
- }
-
- const content =
- link && issuer ? (
-
+ if (isMPT) {
+ const display = `MPT (${currency})`
+ content = link ? (
+
{display}
) : (
display
)
+ } else {
+ const currencyCode =
+ currency?.length === NON_STANDARD_CODE_LENGTH &&
+ currency?.substring(0, 2) !== LP_TOKEN_IDENTIFIER
+ ? hexToString(currency)
+ : currency
+
+ let display = `${currencyCode}`
+
+ if (currencyCode === XRP && displaySymbol) {
+ display = `\uE900 ${display}`
+ }
+
+ if (issuer) {
+ display += '.'
+ display += shortenIssuer ? issuer.substring(0, 4) : issuer
+ }
+
+ content =
+ link && issuer ? (
+
+ {display}
+
+ ) : (
+ display
+ )
+ }
return {content}
}
diff --git a/src/containers/shared/components/MPTokenLink.tsx b/src/containers/shared/components/MPTokenLink.tsx
new file mode 100644
index 000000000..f1c2198bb
--- /dev/null
+++ b/src/containers/shared/components/MPTokenLink.tsx
@@ -0,0 +1,12 @@
+import { RouteLink } from '../routing'
+import { MPT_ROUTE } from '../../App/routes'
+
+export interface MPTokenLinkProps {
+ tokenID: string
+}
+
+export const MPTokenLink = ({ tokenID }: MPTokenLinkProps) => (
+
+ {tokenID}
+
+)
diff --git a/src/containers/shared/components/Tooltip/Tooltip.tsx b/src/containers/shared/components/Tooltip/Tooltip.tsx
index 7154eb8ca..673cd72de 100644
--- a/src/containers/shared/components/Tooltip/Tooltip.tsx
+++ b/src/containers/shared/components/Tooltip/Tooltip.tsx
@@ -91,6 +91,8 @@ export const Tooltip = ({ tooltip }: { tooltip?: TooltipInstance }) => {
>
)
+ const renderMPTId = () => {data.tokenId}
+
const { x, y, mode } = tooltip
const style: CSSProperties = { top: y + PADDING_Y, left: x }
const modeMap = {
@@ -100,6 +102,7 @@ export const Tooltip = ({ tooltip }: { tooltip?: TooltipInstance }) => {
missing: renderMissingValidators,
paystring: renderPayStringToolTip,
nftId: renderNFTId,
+ mptId: renderMPTId,
}
return modeMap[mode] ? (
diff --git a/src/containers/shared/components/Transaction/Clawback/Description.tsx b/src/containers/shared/components/Transaction/Clawback/Description.tsx
index ec59c0a96..c92397c35 100644
--- a/src/containers/shared/components/Transaction/Clawback/Description.tsx
+++ b/src/containers/shared/components/Transaction/Clawback/Description.tsx
@@ -6,8 +6,8 @@ import { formatAmount } from '../../../../../rippled/lib/txSummary/formatAmount'
export const Description = ({ data }: TransactionDescriptionProps) => {
const issuer = data.tx.Account
- const holder = data.tx.Amount.issuer
const amount = formatAmount(data.tx.Amount)
+ const holder = amount.isMPT ? data.tx.MPTokenHolder : data.tx.Amount.issuer
amount.issuer = issuer
return (
<>
diff --git a/src/containers/shared/components/Transaction/Clawback/parser.ts b/src/containers/shared/components/Transaction/Clawback/parser.ts
index 0da7126b7..f5ce6f6a0 100644
--- a/src/containers/shared/components/Transaction/Clawback/parser.ts
+++ b/src/containers/shared/components/Transaction/Clawback/parser.ts
@@ -1,7 +1,10 @@
import { Clawback, ClawbackInstructions } from './types'
import { TransactionParser } from '../types'
import { formatAmount } from '../../../../../rippled/lib/txSummary/formatAmount'
-import { computeBalanceChange } from '../../../utils'
+import {
+ computeRippleStateBalanceChange,
+ computeMPTokenBalanceChange,
+} from '../../../utils'
export const parser: TransactionParser = (
tx,
@@ -9,6 +12,37 @@ export const parser: TransactionParser = (
) => {
const account = tx.Account
const amount = formatAmount(tx.Amount)
+
+ if (amount.isMPT === true) {
+ const holder = tx.MPTokenHolder
+
+ const filteredMptNode = meta.AffectedNodes.filter(
+ (node: any) => node.ModifiedNode?.LedgerEntryType === 'MPToken',
+ )
+
+ // If no mpt is modified, it means the tx failed.
+ // We just return the amount that was attempted to claw.
+ if (!filteredMptNode || filteredMptNode.length !== 1)
+ return {
+ amount,
+ account,
+ holder,
+ }
+
+ const mptNode = filteredMptNode[0].ModifiedNode
+ const { change } = computeMPTokenBalanceChange(mptNode)
+ amount.amount =
+ BigInt(change) < 0
+ ? BigInt(-change).toString(10)
+ : BigInt(change).toString(10)
+
+ return {
+ account,
+ amount,
+ holder,
+ }
+ }
+
const holder = amount.issuer
amount.issuer = account
@@ -30,7 +64,7 @@ export const parser: TransactionParser = (
holder,
}
- const { change } = computeBalanceChange(
+ const { change } = computeRippleStateBalanceChange(
trustlineNode[0].ModifiedNode ?? trustlineNode[0].DeletedNode,
)
diff --git a/src/containers/shared/components/Transaction/Clawback/test/ClawbackSimple.test.tsx b/src/containers/shared/components/Transaction/Clawback/test/ClawbackSimple.test.tsx
index 6110b2b41..1a7e19ee7 100644
--- a/src/containers/shared/components/Transaction/Clawback/test/ClawbackSimple.test.tsx
+++ b/src/containers/shared/components/Transaction/Clawback/test/ClawbackSimple.test.tsx
@@ -1,7 +1,15 @@
+import { useQuery } from 'react-query'
import { createSimpleWrapperFactory, expectSimpleRowText } from '../../test'
import { Simple } from '../Simple'
import transaction from './mock_data/Clawback.json'
import transactionFailure from './mock_data/Clawback_Failure.json'
+import transactionMPT from './mock_data/ClawbackMPT.json'
+import transactionMPTFailure from './mock_data/ClawbackMPT_Failure.json'
+
+jest.mock('react-query', () => ({
+ ...jest.requireActual('react-query'),
+ useQuery: jest.fn(),
+}))
const createWrapper = createSimpleWrapperFactory(Simple)
@@ -17,6 +25,26 @@ describe('Clawback', () => {
wrapper.unmount()
})
+ it('handles MPT Clawback simple view ', () => {
+ const data = {
+ assetScale: 3,
+ }
+
+ // @ts-ignore
+ useQuery.mockImplementation(() => ({
+ data,
+ }))
+ const wrapper = createWrapper(transactionMPT)
+ expectSimpleRowText(wrapper, 'holder', 'rUZTPFN7MBJkjiZ48rak6q7MbhT4ur2kAD')
+ expectSimpleRowText(
+ wrapper,
+ 'amount',
+ '0.05 MPT (00000D668E702F54A27C42EF98C13B0787D1766CC9162A47)',
+ )
+
+ wrapper.unmount()
+ })
+
it('handles failed Clawback simple view ', () => {
const wrapper = createWrapper(transactionFailure)
expectSimpleRowText(wrapper, 'holder', 'rDZ713igKfedN4hhY6SjQse4Mv3ZrBxnn9')
@@ -27,4 +55,24 @@ describe('Clawback', () => {
)
wrapper.unmount()
})
+
+ it('handles failed MPT Clawback simple view ', () => {
+ const data = {
+ assetScale: 3,
+ }
+
+ // @ts-ignore
+ useQuery.mockImplementation(() => ({
+ data,
+ }))
+ const wrapper = createWrapper(transactionMPTFailure)
+
+ expectSimpleRowText(wrapper, 'holder', 'r9rAqX8Jjo4uACsimYDVsy5thHDPivujqf')
+ expectSimpleRowText(
+ wrapper,
+ 'amount',
+ '0.05 MPT (000010952ECE2AFC727F1C67EF568F360A2D92CB7C29FF7C)',
+ )
+ wrapper.unmount()
+ })
})
diff --git a/src/containers/shared/components/Transaction/Clawback/test/mock_data/ClawbackMPT.json b/src/containers/shared/components/Transaction/Clawback/test/mock_data/ClawbackMPT.json
new file mode 100644
index 000000000..2a7d4513a
--- /dev/null
+++ b/src/containers/shared/components/Transaction/Clawback/test/mock_data/ClawbackMPT.json
@@ -0,0 +1,85 @@
+{
+ "tx": {
+ "Account": "rDz9LyymZh4C1jJvFK6v6qXeeARLdYKuEW",
+ "Amount": {
+ "mpt_issuance_id": "00000D668E702F54A27C42EF98C13B0787D1766CC9162A47",
+ "value": "50"
+ },
+ "Fee": "10",
+ "Flags": 2147483648,
+ "MPTokenHolder": "rUZTPFN7MBJkjiZ48rak6q7MbhT4ur2kAD",
+ "Sequence": 3432,
+ "SigningPubKey": "ED0C1DE70A8762E6C98EC78CF13D278D6950ECDFE8E87BAD3732730845E2D9AB6A",
+ "TransactionType": "Clawback",
+ "TxnSignature": "8099CED925A463A2A24F55A496D2BB40108B75840770DFBA9796FBBD40AA482126EE9DAF2512D5D2E8268BCBFC277828E66A28CF3394702611290B45FBA88109",
+ "ctid": "C0000D6D00000000",
+ "date": 1728575981000
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rDz9LyymZh4C1jJvFK6v6qXeeARLdYKuEW",
+ "Balance": "99999970",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 3433
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "06A3654A4A8829FD0575ADDD068BD04F7483C407E027CB43F77C2A5CA575368B",
+ "PreviousFields": {
+ "Balance": "99999980",
+ "Sequence": 3432
+ },
+ "PreviousTxnID": "4B6D63C7AA15899EC1CB3D84C923B08A62D6643A75D28B254F7A0C082B2C0D75",
+ "PreviousTxnLgrSeq": 3436
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "AssetScale": 3,
+ "Flags": 98,
+ "Issuer": "rDz9LyymZh4C1jJvFK6v6qXeeARLdYKuEW",
+ "MPTokenMetadata": "7B226E616D65223A2255532054726561737572792042696C6C20546F6B656E222C2273796D626F6C223A225553544254222C22646563696D616C73223A322C22746F74616C537570706C79223A313030303030302C22697373756572223A225553205472656173757279222C22697373756544617465223A22323032342D30332D3235222C226D6174757269747944617465223A22323032352D30332D3235222C226661636556616C7565223A2231303030222C22696E74657265737452617465223A22322E35222C22696E7465726573744672657175656E6379223A22517561727465726C79222C22636F6C6C61746572616C223A22555320476F7665726E6D656E74222C226A7572697364696374696F6E223A22556E6974656420537461746573222C22726567756C61746F7279436F6D706C69616E6365223A2253454320526567756C6174696F6E73222C22736563757269747954797065223A2254726561737572792042696C6C222C2265787465726E616C5F75726C223A2268747470733A2F2F6578616D706C652E636F6D2F742D62696C6C2D746F6B656E2D6D657461646174612E6A736F6E227D",
+ "MaximumAmount": "9223372036854775807",
+ "OutstandingAmount": "50",
+ "OwnerNode": "0",
+ "Sequence": 3430
+ },
+ "LedgerEntryType": "MPTokenIssuance",
+ "LedgerIndex": "0EABDC95DBBC52F8A95A5F49C38211A30B916BC329774F46CC081D502F9E1895",
+ "PreviousFields": {
+ "OutstandingAmount": "100"
+ },
+ "PreviousTxnID": "4B6D63C7AA15899EC1CB3D84C923B08A62D6643A75D28B254F7A0C082B2C0D75",
+ "PreviousTxnLgrSeq": 3436
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rUZTPFN7MBJkjiZ48rak6q7MbhT4ur2kAD",
+ "Flags": 0,
+ "MPTAmount": "50",
+ "MPTokenIssuanceID": "00000D668E702F54A27C42EF98C13B0787D1766CC9162A47",
+ "OwnerNode": "0"
+ },
+ "LedgerEntryType": "MPToken",
+ "LedgerIndex": "DA40BA069F110465BD90BF5732163836F011E3E761CCF7B6949FAA24D97132F6",
+ "PreviousFields": {
+ "MPTAmount": "100"
+ },
+ "PreviousTxnID": "4B6D63C7AA15899EC1CB3D84C923B08A62D6643A75D28B254F7A0C082B2C0D75",
+ "PreviousTxnLgrSeq": 3436
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS"
+ },
+ "hash": "46B686335794B911B3B76C2F4B76AF424F9978C3E82B2F6488801C359AA71856",
+ "ledger_index": 3437,
+ "date": 1728575981000
+}
diff --git a/src/containers/shared/components/Transaction/Clawback/test/mock_data/ClawbackMPT_Failure.json b/src/containers/shared/components/Transaction/Clawback/test/mock_data/ClawbackMPT_Failure.json
new file mode 100644
index 000000000..b0c7d31c8
--- /dev/null
+++ b/src/containers/shared/components/Transaction/Clawback/test/mock_data/ClawbackMPT_Failure.json
@@ -0,0 +1,46 @@
+{
+ "tx": {
+ "Account": "rnGVhdnWv7g3fW8UNJyFHj6eyngsMdwA8c",
+ "Amount": {
+ "mpt_issuance_id": "000010952ECE2AFC727F1C67EF568F360A2D92CB7C29FF7C",
+ "value": "50"
+ },
+ "Fee": "10",
+ "Flags": 2147483648,
+ "MPTokenHolder": "r9rAqX8Jjo4uACsimYDVsy5thHDPivujqf",
+ "Sequence": 4246,
+ "SigningPubKey": "ED4F6FF2241860884D4DD6C5797BDA553155D194F07B1BFC67129F183322DA7DC3",
+ "TransactionType": "Clawback",
+ "TxnSignature": "C54F175F0AFD950507C059E0EA5E6FA0079E7CDE5DF62BB122B56DC34A351C2369E208B31F7C27B1E9D21753506E195B147500D033884C44373899C84A97680B",
+ "ctid": "C000109B00000000",
+ "date": 1728577343000
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rnGVhdnWv7g3fW8UNJyFHj6eyngsMdwA8c",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 4247
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "95A16157D164CD90D64BC94DE3EA7758AE3088391C9AC44AFCAC90C5153D83D5",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 4246
+ },
+ "PreviousTxnID": "8ACD0682CB1EDDCF6C61F15E6B9637D2719FDA2EC32EB384A68F36F0A0297D91",
+ "PreviousTxnLgrSeq": 4248
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tecINSUFFICIENT_FUNDS"
+ },
+ "hash": "26E6E7AEA4F78801EB0408D802FEBA11B962BFD680501DF0D0C58F30C6EA8951",
+ "ledger_index": 4251,
+ "date": 1728577343000
+}
diff --git a/src/containers/shared/components/Transaction/Clawback/types.ts b/src/containers/shared/components/Transaction/Clawback/types.ts
index 81930d4c2..bbfaa9067 100644
--- a/src/containers/shared/components/Transaction/Clawback/types.ts
+++ b/src/containers/shared/components/Transaction/Clawback/types.ts
@@ -3,6 +3,7 @@ import { Amount, ExplorerAmount } from '../../../types'
export interface Clawback extends TransactionCommonFields {
Amount: Amount
+ MPTokenHolder?: string
}
export interface ClawbackInstructions {
diff --git a/src/containers/shared/components/Transaction/EnableAmendment/Simple.tsx b/src/containers/shared/components/Transaction/EnableAmendment/Simple.tsx
index 184faee53..35f885c53 100644
--- a/src/containers/shared/components/Transaction/EnableAmendment/Simple.tsx
+++ b/src/containers/shared/components/Transaction/EnableAmendment/Simple.tsx
@@ -1,15 +1,13 @@
-import { useEffect, useState } from 'react'
+import { useContext, useEffect, useState } from 'react'
import { useLanguage } from '../../../hooks'
import { SimpleRow } from '../SimpleRow'
import { TransactionSimpleProps } from '../types'
import { EnableAmendment } from './types'
-import {
- getExpectedDate,
- getRippledVersion,
- nameOfAmendmentID,
-} from '../../../amendmentUtils'
+import { getExpectedDate, getRippledVersion } from '../../../amendmentUtils'
import { AMENDMENT_ROUTE } from '../../../../App/routes'
import { RouteLink } from '../../../routing'
+import SocketContext from '../../../SocketContext'
+import { getFeature } from '../../../../../rippled/lib/rippled'
const states = {
loading: 'Loading',
@@ -22,31 +20,21 @@ export const Simple = ({ data }: TransactionSimpleProps) => {
name: states.loading,
minRippledVersion: states.loading,
})
+ const rippledSocket = useContext(SocketContext)
useEffect(() => {
- nameOfAmendmentID(data.instructions.Amendment).then((name: string) => {
- if (name) {
- getRippledVersion(name).then((rippledVersion) => {
- if (rippledVersion) {
- setAmendmentDetails({
- name,
- minRippledVersion: rippledVersion,
- })
- } else {
- setAmendmentDetails({
- name,
- minRippledVersion: states.unknown,
- })
- }
- })
- } else {
+ const amendmentId = data.instructions.Amendment
+ getFeature(rippledSocket, amendmentId).then((feature) => {
+ const name =
+ feature && feature[amendmentId] ? feature[amendmentId].name : ''
+ getRippledVersion(name).then((rippledVersion) => {
setAmendmentDetails({
- name: states.unknown,
- minRippledVersion: states.unknown,
+ name: name || states.unknown,
+ minRippledVersion: rippledVersion || states.unknown,
})
- }
+ })
})
- }, [data.instructions.Amendment])
+ }, [data.instructions.Amendment, rippledSocket])
let amendmentStatus = states.unknown
let expectedDate: string | null = states.unknown
diff --git a/src/containers/shared/components/Transaction/EnableAmendment/test/EnableAmendmentSimple.test.tsx b/src/containers/shared/components/Transaction/EnableAmendment/test/EnableAmendmentSimple.test.tsx
index d04002da3..476931b4d 100644
--- a/src/containers/shared/components/Transaction/EnableAmendment/test/EnableAmendmentSimple.test.tsx
+++ b/src/containers/shared/components/Transaction/EnableAmendment/test/EnableAmendmentSimple.test.tsx
@@ -6,11 +6,11 @@ import { Simple } from '../Simple'
import mockEnableAmendmentWithEnabled from './mock_data/EnableAmendmentWithEnabled.json'
import mockEnableAmendmentWithMinority from './mock_data/EnableAmendmentWithMinority.json'
import mockEnableAmendmentWithMajority from './mock_data/EnableAmendmentWithMajority.json'
-import {
- getRippledVersion,
- nameOfAmendmentID,
-} from '../../../../amendmentUtils'
+import mockFeatureExpandedSignerList from './mock_data/FeatureExpandedSignerList.json'
+import mockFeatureNegativeUNL from './mock_data/FeatureNegativeUNL.json'
+import { getRippledVersion } from '../../../../amendmentUtils'
import { flushPromises } from '../../../../../test/utils'
+import { getFeature } from '../../../../../../rippled/lib/rippled'
const createWrapper = createSimpleWrapperFactory(Simple, i18n)
@@ -21,22 +21,35 @@ jest.mock('../../../../amendmentUtils', () => {
__esModule: true,
...originalModule,
getRippledVersion: jest.fn(),
- nameOfAmendmentID: jest.fn(),
+ }
+})
+
+jest.mock('../../../../../../rippled/lib/rippled', () => {
+ const originalModule = jest.requireActual(
+ '../../../../../../rippled/lib/rippled',
+ )
+
+ return {
+ __esModule: true,
+ ...originalModule,
+ getFeature: jest.fn(),
}
})
const mockedGetRippledVersion = getRippledVersion as jest.MockedFunction<
typeof getRippledVersion
>
-const mockedNameOfAmendmentID = nameOfAmendmentID as jest.MockedFunction<
- typeof nameOfAmendmentID
->
+
+const mockedGetFeature = getFeature as jest.MockedFunction
describe('EnableAmendment: Simple', () => {
+ afterEach(() => {
+ mockedGetFeature.mockReset()
+ })
it('renders tx that causes an amendment to loose majority', async () => {
mockedGetRippledVersion.mockImplementation(() => Promise.resolve('v1.9.1'))
- mockedNameOfAmendmentID.mockImplementation(() =>
- Promise.resolve('ExpandedSignerList'),
+ mockedGetFeature.mockImplementation(() =>
+ Promise.resolve(mockFeatureExpandedSignerList),
)
const wrapper = createWrapper(mockEnableAmendmentWithMinority)
expectSimpleRowLabel(wrapper, 'name', 'Amendment Name')
@@ -58,8 +71,8 @@ describe('EnableAmendment: Simple', () => {
it('renders tx that causes an amendment to gain majority', async () => {
mockedGetRippledVersion.mockImplementation(() => Promise.resolve('v1.9.1'))
- mockedNameOfAmendmentID.mockImplementation(() =>
- Promise.resolve('ExpandedSignerList'),
+ mockedGetFeature.mockImplementation(() =>
+ Promise.resolve(mockFeatureExpandedSignerList),
)
const wrapper = createWrapper(mockEnableAmendmentWithMajority)
expectSimpleRowLabel(wrapper, 'name', 'Amendment Name')
@@ -86,8 +99,8 @@ describe('EnableAmendment: Simple', () => {
it('renders tx that enables an amendment', async () => {
mockedGetRippledVersion.mockImplementation(() => Promise.resolve('v1.7.3'))
- mockedNameOfAmendmentID.mockImplementation(() =>
- Promise.resolve('NegativeUNL'),
+ mockedGetFeature.mockImplementation(() =>
+ Promise.resolve(mockFeatureNegativeUNL),
)
const wrapper = createWrapper(mockEnableAmendmentWithEnabled)
expectSimpleRowLabel(wrapper, 'name', 'Amendment Name')
@@ -108,7 +121,7 @@ describe('EnableAmendment: Simple', () => {
it('renders tx that cannot determine version or name', async () => {
mockedGetRippledVersion.mockImplementation(() => Promise.resolve(''))
- mockedNameOfAmendmentID.mockImplementation(() => Promise.resolve(''))
+ mockedGetFeature.mockImplementation(() => Promise.resolve(null))
const wrapper = createWrapper(mockEnableAmendmentWithEnabled)
expectSimpleRowLabel(wrapper, 'name', 'Amendment Name')
expectSimpleRowText(wrapper, 'name', 'Loading')
@@ -124,8 +137,8 @@ describe('EnableAmendment: Simple', () => {
it('renders tx that cannot determine version', async () => {
mockedGetRippledVersion.mockImplementation(() => Promise.resolve(''))
- mockedNameOfAmendmentID.mockImplementation(() =>
- Promise.resolve('NegativeUNL'),
+ mockedGetFeature.mockImplementation(() =>
+ Promise.resolve(mockFeatureNegativeUNL),
)
const wrapper = createWrapper(mockEnableAmendmentWithEnabled)
expectSimpleRowLabel(wrapper, 'name', 'Amendment Name')
@@ -142,7 +155,7 @@ describe('EnableAmendment: Simple', () => {
it('renders tx that cannot determine name', async () => {
mockedGetRippledVersion.mockImplementation(() => Promise.resolve('v1.7.3'))
- mockedNameOfAmendmentID.mockImplementation(() => Promise.resolve(''))
+ mockedGetFeature.mockImplementation(() => Promise.resolve(null))
const wrapper = createWrapper(mockEnableAmendmentWithEnabled)
expectSimpleRowLabel(wrapper, 'name', 'Amendment Name')
expectSimpleRowText(wrapper, 'name', 'Loading')
@@ -153,6 +166,6 @@ describe('EnableAmendment: Simple', () => {
wrapper.update()
expectSimpleRowText(wrapper, 'name', 'Unknown')
- expectSimpleRowText(wrapper, 'version', 'Unknown')
+ expectSimpleRowText(wrapper, 'version', 'v1.7.3')
})
})
diff --git a/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureExpandedSignerList.json b/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureExpandedSignerList.json
new file mode 100644
index 000000000..09635f407
--- /dev/null
+++ b/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureExpandedSignerList.json
@@ -0,0 +1,7 @@
+{
+ "B2A4DB846F0891BF2C76AB2F2ACC8F5B4EC64437135C6E56F3F859DE5FFD5856": {
+ "enabled": false,
+ "name": "ExpandedSignerList",
+ "supported": true
+ }
+}
diff --git a/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureNegativeUNL.json b/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureNegativeUNL.json
new file mode 100644
index 000000000..56341a552
--- /dev/null
+++ b/src/containers/shared/components/Transaction/EnableAmendment/test/mock_data/FeatureNegativeUNL.json
@@ -0,0 +1,7 @@
+{
+ "B4E4F5D2D6FB84DF7399960A732309C9FD530EAE5941838160042833625A6076": {
+ "enabled": false,
+ "name": "NegativeUNL",
+ "supported": true
+ }
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/Simple.tsx b/src/containers/shared/components/Transaction/MPTokenAuthorize/Simple.tsx
new file mode 100644
index 000000000..9823dfe4f
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/Simple.tsx
@@ -0,0 +1,26 @@
+import { useTranslation } from 'react-i18next'
+import { SimpleRow } from '../SimpleRow'
+import { TransactionSimpleComponent, TransactionSimpleProps } from '../types'
+import { MPTokenAuthorize } from './types'
+import { Account } from '../../Account'
+import { MPTokenLink } from '../../MPTokenLink'
+
+export const Simple: TransactionSimpleComponent = ({
+ data,
+}: TransactionSimpleProps) => {
+ const { MPTokenIssuanceID, MPTokenHolder } = data.instructions
+ const { t } = useTranslation()
+
+ return (
+ <>
+
+
+
+ {MPTokenHolder && (
+
+
+
+ )}
+ >
+ )
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/index.ts b/src/containers/shared/components/Transaction/MPTokenAuthorize/index.ts
new file mode 100644
index 000000000..707282fdf
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/index.ts
@@ -0,0 +1,13 @@
+import {
+ TransactionAction,
+ TransactionCategory,
+ TransactionMapping,
+} from '../types'
+
+import { Simple } from './Simple'
+
+export const MPTokenAuthorizeTransaction: TransactionMapping = {
+ Simple,
+ action: TransactionAction.MODIFY,
+ category: TransactionCategory.MPT,
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/test/MPTokenAuthorizeSimple.test.jsx b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/MPTokenAuthorizeSimple.test.jsx
new file mode 100644
index 000000000..932a18cc1
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/MPTokenAuthorizeSimple.test.jsx
@@ -0,0 +1,66 @@
+import { createSimpleWrapperFactory } from '../../test/createWrapperFactory'
+import { Simple } from '../Simple'
+import { expectSimpleRowText, expectSimpleRowNotToExist } from '../../test'
+import transactionSuccess from './mock_data/MPTokenAuthorize.json'
+import transactionFail from './mock_data/MPTokenAuthorize_Fail.json'
+import transactionWithHolder from './mock_data/MPTokenAuthorize_WithHolder.json'
+import transactionWithHolderFail from './mock_data/MPTokenAuthorize_WithHolderFail.json'
+
+const createWrapper = createSimpleWrapperFactory(Simple)
+
+describe('MPTokenAuthorize', () => {
+ it('handles MPTokenAuthorize w/o holder simple view ', () => {
+ const wrapper = createWrapper(transactionSuccess)
+
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '000005F398B624EBD06822198649C920C8B20ADB8EBE745E',
+ )
+ expectSimpleRowNotToExist(wrapper, 'mpt-holder')
+ wrapper.unmount()
+ })
+
+ it('handles MPTokenAuthorize view w/ holder simple view ', () => {
+ const wrapper = createWrapper(transactionWithHolder)
+
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '0000130B63FC523E33FDF4D1318D8D484B0D1111098CFD0B',
+ )
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-holder',
+ 'rK3bB9myvWoMaLbLnpksGx2Zz58BL225am',
+ )
+ wrapper.unmount()
+ })
+
+ it('handles failed MPTokenAuthorize view w/ holder simple view ', () => {
+ const wrapper = createWrapper(transactionWithHolderFail)
+
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '00000F76D46440EE21F74E5B2398315BC1CFEB9A7EB48A14',
+ )
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-holder',
+ 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
+ )
+ wrapper.unmount()
+ })
+
+ it('handles failed MPTokenAuthorize w/o holder simple view ', () => {
+ const wrapper = createWrapper(transactionFail)
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '0000098410531B842DEECCF4ABB1268C931EB71D9F6A1B64',
+ )
+ expectSimpleRowNotToExist(wrapper, 'mpt-holder')
+ wrapper.unmount()
+ })
+})
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize.json b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize.json
new file mode 100644
index 000000000..e41a47854
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize.json
@@ -0,0 +1,70 @@
+{
+ "tx": {
+ "Account": "rnLz9TWQAvaLpdyrtb1WbMgp7jZdNQ47Ny",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "MPTokenIssuanceID": "000005F398B624EBD06822198649C920C8B20ADB8EBE745E",
+ "Sequence": 1524,
+ "SigningPubKey": "ED97BAFB2D380AF67DA2C1968C3A1DC38797E9BA0653CE620F6BC97FFD66925EBB",
+ "TransactionType": "MPTokenAuthorize",
+ "TxnSignature": "28879892AF0D465063993BD1DDCA147C7CA0AB9C8429DAB3A0030AA4AC57AA80F725F295622913C07E4CAFB3160DEF7E8D0209429390B0FBD78F96B28E700A07",
+ "ctid": "C00005F600000000",
+ "date": 1711397033000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rnLz9TWQAvaLpdyrtb1WbMgp7jZdNQ47Ny",
+ "Balance": "99999990",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 1525
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "424AAE60FE8A4B7EF77DA492F9561AAFA1D09DB56BE5804B055235BCD662C9FE",
+ "PreviousFields": {
+ "Balance": "100000000",
+ "OwnerCount": 0,
+ "Sequence": 1524
+ },
+ "PreviousTxnID": "B6301327D79A93DC211043ABF66A60DC9C70BD2962FC42E0EAD0A829680ABAE8",
+ "PreviousTxnLgrSeq": 1524
+ }
+ },
+ {
+ "CreatedNode": {
+ "LedgerEntryType": "DirectoryNode",
+ "LedgerIndex": "65BCF554A41D30521B876D012D3DC167F9E886E02D88231E9DEBD2501A4A7BB5",
+ "NewFields": {
+ "Owner": "rnLz9TWQAvaLpdyrtb1WbMgp7jZdNQ47Ny",
+ "RootIndex": "65BCF554A41D30521B876D012D3DC167F9E886E02D88231E9DEBD2501A4A7BB5"
+ }
+ }
+ },
+ {
+ "CreatedNode": {
+ "LedgerEntryType": "MPToken",
+ "LedgerIndex": "91D261494BB3D64D5D3D12BD480EB58C5E2B21F3222B12FE442BC73276C27266",
+ "NewFields": {
+ "Account": "rnLz9TWQAvaLpdyrtb1WbMgp7jZdNQ47Ny",
+ "MPTokenIssuanceID": "000005F398B624EBD06822198649C920C8B20ADB8EBE745E"
+ }
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS"
+ },
+ "hash": "9592E76A725CF4A5A441024EE80596DFE8809D1AD1EC28A8D9DB2CEC2CB81EDC",
+ "ledger_index": 1526,
+ "date": 1711397033000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_Fail.json b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_Fail.json
new file mode 100644
index 000000000..8d305ae95
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_Fail.json
@@ -0,0 +1,49 @@
+{
+ "tx": {
+ "Account": "rJtok1j4okh4HkKxC3ArAvZbMD1vcDSteo",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "MPTokenIssuanceID": "0000098410531B842DEECCF4ABB1268C931EB71D9F6A1B64",
+ "Sequence": 2438,
+ "SigningPubKey": "EDE493F0B7846A102A5C6EF4FDD9E95D1A84B0BEB99DED06C4436C0D61E5FA0B67",
+ "TransactionType": "MPTokenAuthorize",
+ "TxnSignature": "C903E4BA935BA27424D05CA230385FBA392CB4B48C136E37EF5DABE07814A2A8FA0AAEE34FCAB994D1AED7A663EB4CA121FA07B83E3C5A74289ABA258AA45F00",
+ "ctid": "C000098800000000",
+ "date": 1711398512000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rJtok1j4okh4HkKxC3ArAvZbMD1vcDSteo",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 2439
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "FE496E6B5CBE4778460846E5B93648B41E15463E691311EA4CD7E578561CA20E",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 2438
+ },
+ "PreviousTxnID": "26BB8D3B11AA0C967470DBA5D6B09A10608B4D1DADE0408668A45C010F4B8DDC",
+ "PreviousTxnLgrSeq": 2439
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tecMPTOKEN_EXISTS"
+ },
+ "hash": "7B785C2D172D8FAE35DBBA66868D147747F32B5D6AA41F62D698E872643CE2B6",
+ "ledger_index": 2440,
+ "date": 1711398512000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_WithHolder.json b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_WithHolder.json
new file mode 100644
index 000000000..3cce1b4e2
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_WithHolder.json
@@ -0,0 +1,67 @@
+{
+ "tx": {
+ "Account": "rwfgw2dWqUAexB46z5QRq2dJcgTK9piw5w",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "MPTokenHolder": "rK3bB9myvWoMaLbLnpksGx2Zz58BL225am",
+ "MPTokenIssuanceID": "0000130B63FC523E33FDF4D1318D8D484B0D1111098CFD0B",
+ "Sequence": 4876,
+ "SigningPubKey": "ED936E848B8E37D20991C2E1C5C76ABAEC0625D693CEB85BA495B58E16712DA627",
+ "TransactionType": "MPTokenAuthorize",
+ "TxnSignature": "3F31AD3682B9261975E27895DFCB9F373C33C174A076445C33AE13A6713D7FC8C8305A4D05C4918979C9EAD0230A61CE9998B71BEE21653D6BFCCC65F599100E",
+ "ctid": "C000130F00000000",
+ "date": 1711400951000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rwfgw2dWqUAexB46z5QRq2dJcgTK9piw5w",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 4877
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "C688ECD4065B909634121581E792188424F29B48C062F1D1D4FED180DEAF3A23",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 4876
+ },
+ "PreviousTxnID": "D12E5ED52F495449A537DB9293174209CC132CDCD4EFBBACCEB7F8E8FC582BBC",
+ "PreviousTxnLgrSeq": 4877
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rK3bB9myvWoMaLbLnpksGx2Zz58BL225am",
+ "Flags": 2,
+ "MPTokenIssuanceID": "0000130B63FC523E33FDF4D1318D8D484B0D1111098CFD0B",
+ "OwnerNode": "0"
+ },
+ "LedgerEntryType": "MPToken",
+ "LedgerIndex": "E6BC3F027146E5A2A50C01C37E7C5320E608C9D1D5BE763F32748865DB6EF3DE",
+ "PreviousFields": {
+ "Flags": 0
+ },
+ "PreviousTxnID": "FA5F2B8CE18C33D09E0243A2D20319AB9AF9D6CF5F1C2568B0CC4764DEC31F7A",
+ "PreviousTxnLgrSeq": 4878
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS"
+ },
+ "hash": "5F92E78273BCF8A71E129F2CC9B8B0D5611E79D4CF81B530BF7B69892A579060",
+ "ledger_index": 4879,
+ "date": 1711400951000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_WithHolderFail.json b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_WithHolderFail.json
new file mode 100644
index 000000000..69b7eaa8c
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/test/mock_data/MPTokenAuthorize_WithHolderFail.json
@@ -0,0 +1,50 @@
+{
+ "tx": {
+ "Account": "rL4pMQAa3V7s9QNw1wEk2znnhjbfYo4GQC",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "MPTokenHolder": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
+ "MPTokenIssuanceID": "00000F76D46440EE21F74E5B2398315BC1CFEB9A7EB48A14",
+ "Sequence": 3959,
+ "SigningPubKey": "EDF7A3D93CE3AA46168649283C20C2D4FC36642FDD87449F1CCF068638BF17B10E",
+ "TransactionType": "MPTokenAuthorize",
+ "TxnSignature": "2795F1DC9C54493ADE475800A67FD5B3BC7B65F4E343CEEA0950E994F0FC10D0DAED13B4B0FD345E92BFD2B4F42A09A44906D5B2CD1D8FD7A4B3D28983F51806",
+ "ctid": "C0000F7900000000",
+ "date": 1711400033000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rL4pMQAa3V7s9QNw1wEk2znnhjbfYo4GQC",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 3960
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "00BFE84169F6CAA5D03348856B57D47788B6856ABA9FA6EC7A16E6DA1B99B9D7",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 3959
+ },
+ "PreviousTxnID": "E07D68B9728EE8954C66219FF713782933612A7D5EF44B50F5485557629DFE3D",
+ "PreviousTxnLgrSeq": 3960
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tecNO_AUTH"
+ },
+ "hash": "95AE2E382D6CFBFCECC012DDC52458E753FB9208A5040D2F441B5DE5BEA535CF",
+ "ledger_index": 3961,
+ "date": 1711400033000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenAuthorize/types.ts b/src/containers/shared/components/Transaction/MPTokenAuthorize/types.ts
new file mode 100644
index 000000000..878129a9f
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenAuthorize/types.ts
@@ -0,0 +1,6 @@
+import { TransactionCommonFields } from '../types'
+
+export interface MPTokenAuthorize extends TransactionCommonFields {
+ MPTokenIssuanceID: string
+ MPTokenHolder?: string
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/Simple.tsx b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/Simple.tsx
new file mode 100644
index 000000000..218ae93d2
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/Simple.tsx
@@ -0,0 +1,69 @@
+import { useTranslation } from 'react-i18next'
+import { SimpleRow } from '../SimpleRow'
+import { TransactionSimpleComponent, TransactionSimpleProps } from '../types'
+import { MPTokenIssuanceCreateInstructions } from './types'
+import { useLanguage } from '../../../hooks'
+import { isValidJsonString, localizeNumber } from '../../../utils'
+import { MPTokenLink } from '../../MPTokenLink'
+import { JsonView } from '../../JsonView'
+import './styles.scss'
+
+export const Simple: TransactionSimpleComponent = ({
+ data,
+}: TransactionSimpleProps) => {
+ const { issuanceID, metadata, assetScale, transferFee, maxAmount } =
+ data.instructions
+ const { t } = useTranslation()
+ const language = useLanguage()
+ const formattedFee =
+ transferFee &&
+ `${localizeNumber((transferFee / 1000).toPrecision(5), language, {
+ minimumFractionDigits: 3,
+ })}%`
+
+ return (
+ <>
+ {issuanceID && (
+
+
+
+ )}
+ {assetScale && (
+
+ {assetScale}
+
+ )}
+ {transferFee && (
+
+ {formattedFee}
+
+ )}
+ {maxAmount && (
+
+ {maxAmount}
+
+ )}
+ {metadata && (
+
+ {isValidJsonString(metadata) ? (
+
+ ) : (
+ metadata
+ )}
+
+ )}
+ >
+ )
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/index.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/index.ts
new file mode 100644
index 000000000..03275f8f7
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/index.ts
@@ -0,0 +1,15 @@
+import {
+ TransactionAction,
+ TransactionCategory,
+ TransactionMapping,
+} from '../types'
+
+import { Simple } from './Simple'
+import { parser } from './parser'
+
+export const MPTokenIssuanceCreateTransaction: TransactionMapping = {
+ Simple,
+ action: TransactionAction.CREATE,
+ category: TransactionCategory.MPT,
+ parser,
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/parser.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/parser.ts
new file mode 100644
index 000000000..d24770bc7
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/parser.ts
@@ -0,0 +1,21 @@
+import {
+ MPTokenIssuanceCreate,
+ MPTokenIssuanceCreateInstructions,
+} from './types'
+import { TransactionParser } from '../types'
+import { convertHexToString } from '../../../../../rippled/lib/utils'
+
+export const parser: TransactionParser<
+ MPTokenIssuanceCreate,
+ MPTokenIssuanceCreateInstructions
+> = (tx, meta) => ({
+ issuanceID: meta.mpt_issuance_id,
+ metadata: tx.MPTokenMetadata
+ ? convertHexToString(tx.MPTokenMetadata)
+ : undefined,
+ transferFee: tx.TransferFee,
+ assetScale: tx.AssetScale,
+ maxAmount: tx.MaximumAmount
+ ? BigInt(tx.MaximumAmount).toString(10)
+ : undefined,
+})
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/styles.scss b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/styles.scss
new file mode 100644
index 000000000..f7ce40841
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/styles.scss
@@ -0,0 +1,3 @@
+.jv-indent {
+ border-left: none;
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/test/MPTokenIssuanceCreateSimple.test.jsx b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/test/MPTokenIssuanceCreateSimple.test.jsx
new file mode 100644
index 000000000..0c82efab4
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/test/MPTokenIssuanceCreateSimple.test.jsx
@@ -0,0 +1,27 @@
+import { createSimpleWrapperFactory } from '../../test/createWrapperFactory'
+import { Simple } from '../Simple'
+import { expectSimpleRowText } from '../../test'
+import transactionSuccess from './mock_data/MPTokenIssuanceCreate.json'
+
+const createWrapper = createSimpleWrapperFactory(Simple)
+
+describe('MPTokenIssuanceCreate', () => {
+ it('handles MPTokenIssuanceCreate simple view ', () => {
+ const wrapper = createWrapper(transactionSuccess)
+
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '0000157844C3F3B57A8B579FEE1033CC8E8498729D063617',
+ )
+ expectSimpleRowText(wrapper, 'mpt-asset-scale', '2')
+ expectSimpleRowText(wrapper, 'mpt-max-amount', '9223372036854775807')
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-metadata',
+ 'https://ipfs.io/ipfs/QmZnjmB9Tk4xaA9E679ytrPXda3beWMLUnMB5RFj1eStLp',
+ )
+ expectSimpleRowText(wrapper, 'mpt-fee', '0.010%')
+ wrapper.unmount()
+ })
+})
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/test/mock_data/MPTokenIssuanceCreate.json b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/test/mock_data/MPTokenIssuanceCreate.json
new file mode 100644
index 000000000..2b5b91b7d
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/test/mock_data/MPTokenIssuanceCreate.json
@@ -0,0 +1,79 @@
+{
+ "tx": {
+ "Account": "rfGb6p2kWy3zQweWnYNxSFYoHeymcx7mhg",
+ "AssetScale": 2,
+ "Fee": "10",
+ "Flags": 34,
+ "MPTokenMetadata": "68747470733A2F2F697066732E696F2F697066732F516D5A6E6A6D4239546B34786141394536373979747250586461336265574D4C556E4D423552466A316553744C70",
+ "MaximumAmount": "9223372036854775807",
+ "Sequence": 5496,
+ "SigningPubKey": "EDAD408FAEE57EB4A347E6FE395B834DD47C6531C3C37B09ACC35528161CAD4B0E",
+ "TransactionType": "MPTokenIssuanceCreate",
+ "TransferFee": 10,
+ "TxnSignature": "F7AA8083EE7D7EFD10E11FF5A12B73D2D45A80094AEED4B41FBF2A90C9E03E5E4D162E91FA1EC156BF94E770E70E7633DF09665A5C2D5408178FC376BFC9B100",
+ "ctid": "C000157A00000000",
+ "date": 1710949602000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rfGb6p2kWy3zQweWnYNxSFYoHeymcx7mhg",
+ "Balance": "99999990",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 5497
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "1996F74D57092C1AC261F55CB16A45A63C785993691869A431D72A5BF8AF47A0",
+ "PreviousFields": {
+ "Balance": "100000000",
+ "OwnerCount": 0,
+ "Sequence": 5496
+ },
+ "PreviousTxnID": "531254FC0F1599CCAF9ABCDBE0854B6BFBBA225ADD7CA341D2897CBDC3E78E5E",
+ "PreviousTxnLgrSeq": 5496
+ }
+ },
+ {
+ "CreatedNode": {
+ "LedgerEntryType": "MPTokenIssuance",
+ "LedgerIndex": "265CEA78D8246B8B51D5CCC20AF4DB95569DE09E53115C888B176A3D1D05048A",
+ "NewFields": {
+ "AssetScale": 2,
+ "Flags": 34,
+ "Issuer": "rfGb6p2kWy3zQweWnYNxSFYoHeymcx7mhg",
+ "MPTokenMetadata": "68747470733A2F2F697066732E696F2F697066732F516D5A6E6A6D4239546B34786141394536373979747250586461336265574D4C556E4D423552466A316553744C70",
+ "MaximumAmount": "9223372036854775807",
+ "Sequence": 5496,
+ "TransferFee": 10
+ }
+ }
+ },
+ {
+ "CreatedNode": {
+ "LedgerEntryType": "DirectoryNode",
+ "LedgerIndex": "3A6F809498F6C0E3664BD8451BBAE5F972E45AF17537354D1C28F3A00B35BDFE",
+ "NewFields": {
+ "Owner": "rfGb6p2kWy3zQweWnYNxSFYoHeymcx7mhg",
+ "RootIndex": "3A6F809498F6C0E3664BD8451BBAE5F972E45AF17537354D1C28F3A00B35BDFE"
+ }
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS",
+ "mpt_issuance_id": "0000157844C3F3B57A8B579FEE1033CC8E8498729D063617"
+ },
+ "hash": "9686DA1322D2D8F9CD97C5848A8E3CADB9D3F73154DA59BB3A3CACC4CA43671C",
+ "ledger_index": 5498,
+ "date": 1710949602000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/types.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/types.ts
new file mode 100644
index 000000000..2cce5aa21
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceCreate/types.ts
@@ -0,0 +1,16 @@
+import { TransactionCommonFields } from '../types'
+
+export interface MPTokenIssuanceCreate extends TransactionCommonFields {
+ AssetScale?: number
+ MaximumAmount?: string
+ TransferFee?: number
+ MPTokenMetadata?: string
+}
+
+export interface MPTokenIssuanceCreateInstructions {
+ issuanceID?: string
+ metadata?: string
+ transferFee?: number
+ assetScale?: number
+ maxAmount?: string
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/Simple.tsx b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/Simple.tsx
new file mode 100644
index 000000000..2b9bb6676
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/Simple.tsx
@@ -0,0 +1,18 @@
+import { useTranslation } from 'react-i18next'
+import { SimpleRow } from '../SimpleRow'
+import { TransactionSimpleComponent, TransactionSimpleProps } from '../types'
+import { MPTokenIssuanceDestroy } from './types'
+import { MPTokenLink } from '../../MPTokenLink'
+
+export const Simple: TransactionSimpleComponent = ({
+ data,
+}: TransactionSimpleProps) => {
+ const { MPTokenIssuanceID } = data.instructions
+ const { t } = useTranslation()
+
+ return (
+
+
+
+ )
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/index.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/index.ts
new file mode 100644
index 000000000..52f730f68
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/index.ts
@@ -0,0 +1,13 @@
+import {
+ TransactionAction,
+ TransactionCategory,
+ TransactionMapping,
+} from '../types'
+
+import { Simple } from './Simple'
+
+export const MPTokenIssuanceDestroyTransaction: TransactionMapping = {
+ Simple,
+ action: TransactionAction.CANCEL,
+ category: TransactionCategory.MPT,
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/MPTokenIssuanceDestroySimple.test.jsx b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/MPTokenIssuanceDestroySimple.test.jsx
new file mode 100644
index 000000000..ee9a5b7c0
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/MPTokenIssuanceDestroySimple.test.jsx
@@ -0,0 +1,29 @@
+import { createSimpleWrapperFactory } from '../../test/createWrapperFactory'
+import { Simple } from '../Simple'
+import { expectSimpleRowText } from '../../test'
+import transactionSuccess from './mock_data/MPTokenIssuanceDestroy.json'
+import transactionFail from './mock_data/MPTokenIssuanceDestroy_Fail.json'
+
+const createWrapper = createSimpleWrapperFactory(Simple)
+
+describe('MPTokenIssuanceDestroy', () => {
+ it('handles MPTokenIssuanceDestroy simple view ', () => {
+ const wrapper = createWrapper(transactionSuccess)
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '0000071E15A457415B9A921957CA1958F0E3B8A049BE8627',
+ )
+ wrapper.unmount()
+ })
+
+ it('handles failed MPTokenIssuanceDestroy simple view ', () => {
+ const wrapper = createWrapper(transactionFail)
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '0000097E2ACB52C693EABBB156034140B2ED5E9522C4ACF4',
+ )
+ wrapper.unmount()
+ })
+})
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/mock_data/MPTokenIssuanceDestroy.json b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/mock_data/MPTokenIssuanceDestroy.json
new file mode 100644
index 000000000..3ac12863b
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/mock_data/MPTokenIssuanceDestroy.json
@@ -0,0 +1,76 @@
+{
+ "tx": {
+ "Account": "rpyShdZBMVC9p6tesouh97JEEWZgYGYTW1",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "MPTokenIssuanceID": "0000071E15A457415B9A921957CA1958F0E3B8A049BE8627",
+ "Sequence": 1823,
+ "SigningPubKey": "ED31ED6E308C928DA72935A03526C3C5422353EB686908D3ADAD9D573921DBDFB5",
+ "TransactionType": "MPTokenIssuanceDestroy",
+ "TxnSignature": "432417F93E86710B50619E4B9EAA43A7F636546A70D4A854E619AF01108A400519A4022B7BABE4807263CDD7EB43217B9ABB8F9745B988981B1A556D11C83200",
+ "ctid": "C000072100000000",
+ "date": 1710968140000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rpyShdZBMVC9p6tesouh97JEEWZgYGYTW1",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 0,
+ "Sequence": 1824
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "27F062565B9B9226F10C5AA25E1AD5C3E70A6A93FF2AB4851614A2C43D083850",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "OwnerCount": 1,
+ "Sequence": 1823
+ },
+ "PreviousTxnID": "E6DFD28EDD7213F43B7D0DB1296D09460458709583BEEA29F17C2F63B4DA9FC4",
+ "PreviousTxnLgrSeq": 1824
+ }
+ },
+ {
+ "DeletedNode": {
+ "FinalFields": {
+ "Flags": 0,
+ "Issuer": "rpyShdZBMVC9p6tesouh97JEEWZgYGYTW1",
+ "OutstandingAmount": "0",
+ "OwnerNode": "0",
+ "PreviousTxnID": "E6DFD28EDD7213F43B7D0DB1296D09460458709583BEEA29F17C2F63B4DA9FC4",
+ "PreviousTxnLgrSeq": 1824,
+ "Sequence": 1822
+ },
+ "LedgerEntryType": "MPTokenIssuance",
+ "LedgerIndex": "9C882DA4DF7B92FC968A0ADCA8BAFB7842264F98A5E147348C6E6077EAB24AA8"
+ }
+ },
+ {
+ "DeletedNode": {
+ "FinalFields": {
+ "Flags": 0,
+ "Owner": "rpyShdZBMVC9p6tesouh97JEEWZgYGYTW1",
+ "RootIndex": "B8124B2AD6A85560C73E1748E2C8B0E5C0871F4439F10B042DD7E4017D864287"
+ },
+ "LedgerEntryType": "DirectoryNode",
+ "LedgerIndex": "B8124B2AD6A85560C73E1748E2C8B0E5C0871F4439F10B042DD7E4017D864287"
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS"
+ },
+ "hash": "9EB556D18BFB67F31C8716C7F3CBBB070E1E7B120DEDDC423D25DFAD850BD93A",
+ "ledger_index": 1825,
+ "date": 1710968140000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/mock_data/MPTokenIssuanceDestroy_Fail.json b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/mock_data/MPTokenIssuanceDestroy_Fail.json
new file mode 100644
index 000000000..88b5cc651
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/test/mock_data/MPTokenIssuanceDestroy_Fail.json
@@ -0,0 +1,49 @@
+{
+ "tx": {
+ "Account": "rJbNXmT1uhmbghSQAFcgxAAN9yCRCu9y7g",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "MPTokenIssuanceID": "0000097E2ACB52C693EABBB156034140B2ED5E9522C4ACF4",
+ "Sequence": 2431,
+ "SigningPubKey": "EDF5A4F08EDD12BB89658B8DE56558600342AD92D42FEDFAD682F4DAD9647EF5AA",
+ "TransactionType": "MPTokenIssuanceDestroy",
+ "TxnSignature": "C3B5F7D9A21A5EF85663058790DE7F458EA903C0010F3C2E1FEA45647052C2D85231ED4C17EB324A97AC2D4DE9A71F46D277B8A0A7AFB42D30A3EDA42E8E4106",
+ "ctid": "C000098100000000",
+ "date": 1710970247000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rJbNXmT1uhmbghSQAFcgxAAN9yCRCu9y7g",
+ "Balance": "99999990",
+ "Flags": 0,
+ "OwnerCount": 0,
+ "Sequence": 2432
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "BA6DF5388FF6BF026D3F8C91893534890F87309A32D30749CC70BCB1C6F1BEF6",
+ "PreviousFields": {
+ "Balance": "100000000",
+ "Sequence": 2431
+ },
+ "PreviousTxnID": "07F74CD4BD3E54410E436F9895BC2EC98D35E05F7584CB95D6660AF0411E9283",
+ "PreviousTxnLgrSeq": 2431
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tecNO_PERMISSION"
+ },
+ "hash": "2F31C86C343B2D2DB3D8B01BDE84E5BFA0BCB86321365A16D93DF806B79B16FD",
+ "ledger_index": 2433,
+ "date": 1710970247000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/types.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/types.ts
new file mode 100644
index 000000000..db9b812f9
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceDestroy/types.ts
@@ -0,0 +1,5 @@
+import { TransactionCommonFields } from '../types'
+
+export interface MPTokenIssuanceDestroy extends TransactionCommonFields {
+ MPTokenIssuanceID: string
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/Simple.tsx b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/Simple.tsx
new file mode 100644
index 000000000..9f0b13ca8
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/Simple.tsx
@@ -0,0 +1,26 @@
+import { useTranslation } from 'react-i18next'
+import { SimpleRow } from '../SimpleRow'
+import { TransactionSimpleComponent, TransactionSimpleProps } from '../types'
+import { MPTokenIssuanceSet } from './types'
+import { Account } from '../../Account'
+import { MPTokenLink } from '../../MPTokenLink'
+
+export const Simple: TransactionSimpleComponent = ({
+ data,
+}: TransactionSimpleProps) => {
+ const { MPTokenIssuanceID, MPTokenHolder } = data.instructions
+ const { t } = useTranslation()
+
+ return (
+ <>
+
+
+
+ {MPTokenHolder && (
+
+
+
+ )}
+ >
+ )
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/index.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/index.ts
new file mode 100644
index 000000000..acbe8d389
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/index.ts
@@ -0,0 +1,13 @@
+import {
+ TransactionAction,
+ TransactionCategory,
+ TransactionMapping,
+} from '../types'
+
+import { Simple } from './Simple'
+
+export const MPTokenIssuanceSetTransaction: TransactionMapping = {
+ Simple,
+ action: TransactionAction.MODIFY,
+ category: TransactionCategory.MPT,
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/MPTokenIssuanceSetSimple.test.jsx b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/MPTokenIssuanceSetSimple.test.jsx
new file mode 100644
index 000000000..2083e158c
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/MPTokenIssuanceSetSimple.test.jsx
@@ -0,0 +1,53 @@
+import { createSimpleWrapperFactory } from '../../test/createWrapperFactory'
+import { Simple } from '../Simple'
+import { expectSimpleRowText, expectSimpleRowNotToExist } from '../../test'
+import transactionSuccess from './mock_data/MPTokenIssuanceSet.json'
+import transactionNoHolder from './mock_data/MPTokenIssuanceSet_NoHolder.json'
+import transactionFail from './mock_data/MPTokenIssuanceSet_Fail.json'
+
+const createWrapper = createSimpleWrapperFactory(Simple)
+
+describe('MPTokenIssuanceSet', () => {
+ it('handles MPTokenIssuanceSet simple view ', () => {
+ const wrapper = createWrapper(transactionSuccess)
+
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '00000BED9E4ADA3DCC1BE78683C4B623A74013818160590C',
+ )
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-holder',
+ 'r9hF4e3e6kLuxLobPwfQa2wzXZMDvBDeUg',
+ )
+ wrapper.unmount()
+ })
+
+ it('handles MPTokenIssuanceSet simple view w/o holder ', () => {
+ const wrapper = createWrapper(transactionNoHolder)
+
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '000002609BB39CEC721B5AB337B6BD862ACD2811CBBB5F18',
+ )
+ expectSimpleRowNotToExist(wrapper, 'mpt-holder')
+ wrapper.unmount()
+ })
+
+ it('handles failed MPTokenIssuanceSet simple view ', () => {
+ const wrapper = createWrapper(transactionFail)
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-issuance-id',
+ '00000F83146C83112AED215CD345F8E7327459BFCF6B8062',
+ )
+ expectSimpleRowText(
+ wrapper,
+ 'mpt-holder',
+ 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
+ )
+ wrapper.unmount()
+ })
+})
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet.json b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet.json
new file mode 100644
index 000000000..e78513a6c
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet.json
@@ -0,0 +1,67 @@
+{
+ "tx": {
+ "Account": "rERyS9qtwky94UMMjjmbku3uo5aQwAoJ58",
+ "Fee": "10",
+ "Flags": 1,
+ "MPTokenHolder": "r9hF4e3e6kLuxLobPwfQa2wzXZMDvBDeUg",
+ "MPTokenIssuanceID": "00000BED9E4ADA3DCC1BE78683C4B623A74013818160590C",
+ "Sequence": 3054,
+ "SigningPubKey": "EDF73A1C528F5BFBD6FF2B05D0C71760D7D2DF1DE3496935612E47BCB440F28040",
+ "TransactionType": "MPTokenIssuanceSet",
+ "TxnSignature": "5BC9ABE91A10F86440E301F17DFADD08D2E55E0699441372BE73D843B4481869BD6224ED461BC4F9894E97F1F99562D3CF2CD1A4E991BD7993DC24EDA63F5B05",
+ "ctid": "C0000BF100000000",
+ "date": 1711047580000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "r9hF4e3e6kLuxLobPwfQa2wzXZMDvBDeUg",
+ "Flags": 1,
+ "MPTokenIssuanceID": "00000BED9E4ADA3DCC1BE78683C4B623A74013818160590C",
+ "OwnerNode": "0"
+ },
+ "LedgerEntryType": "MPToken",
+ "LedgerIndex": "DDA698915F22D7CEA45896CB70DCC0DF803E1F573B92B6F0178F1688208EED04",
+ "PreviousFields": {
+ "Flags": 0
+ },
+ "PreviousTxnID": "39709CA66D9103354D09070234A14253EC779846BB73477EEB21C5A65144C844",
+ "PreviousTxnLgrSeq": 3056
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rERyS9qtwky94UMMjjmbku3uo5aQwAoJ58",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 3055
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "EDD52DA05DAB16BAF6A3B7D47CCB9FEAB7AAC2BDD9CB007F6A3B8E0DBCE50A45",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 3054
+ },
+ "PreviousTxnID": "41A99F7E107F813C132B105AB930FD8C6960530DDFA5D98FCEF5A5600DA39D38",
+ "PreviousTxnLgrSeq": 3055
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS"
+ },
+ "hash": "4993E5B875E0217ABC92EFC395805F1344D8A9A3D75437EEA457C05EDB3AB20B",
+ "ledger_index": 3057,
+ "date": 1711047580000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet_Fail.json b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet_Fail.json
new file mode 100644
index 000000000..60062ffc1
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet_Fail.json
@@ -0,0 +1,50 @@
+{
+ "tx": {
+ "Account": "rpizWPf4g8JLWFUT7143Zn9A1n2Dy9bnji",
+ "Fee": "10",
+ "Flags": 1,
+ "MPTokenHolder": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
+ "MPTokenIssuanceID": "00000F83146C83112AED215CD345F8E7327459BFCF6B8062",
+ "Sequence": 3972,
+ "SigningPubKey": "ED4EC06184C745D99AEAAA16526C900DC181C8546899F462C3D105C11A6677A65A",
+ "TransactionType": "MPTokenIssuanceSet",
+ "TxnSignature": "2A9D1795983016A162F05CEBBD35E65B955BD67AB96B8A2DB2E31027EA67DDF082B1C4D67F9219CBF9893520ACB0ACB80E2ED3CE96496AA4668BCCF4A46EAE0A",
+ "ctid": "C0000F8700000000",
+ "date": 1711048704000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rpizWPf4g8JLWFUT7143Zn9A1n2Dy9bnji",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 3973
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "F937A006AB86775D475946D43BA1612F3BC24A2C144550D4EC0266C2F08303D5",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 3972
+ },
+ "PreviousTxnID": "8D322CBF8A965E64FB903551C80A2E1DF9E2480A7AD0B1D98BE56574BC9FE215",
+ "PreviousTxnLgrSeq": 3973
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tecOBJECT_NOT_FOUND"
+ },
+ "hash": "E47D5242B7E210B9E1DAEFF90DC19DA9310C04CF4444C76F2C76A44533EEC48F",
+ "ledger_index": 3975,
+ "date": 1711048704000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet_NoHolder.json b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet_NoHolder.json
new file mode 100644
index 000000000..165596d2a
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/test/mock_data/MPTokenIssuanceSet_NoHolder.json
@@ -0,0 +1,67 @@
+{
+ "tx": {
+ "Account": "rEUGuTqrySk9o1rZSVx8seuvcsEZymeEYM",
+ "Fee": "10",
+ "Flags": 1,
+ "MPTokenIssuanceID": "000002609BB39CEC721B5AB337B6BD862ACD2811CBBB5F18",
+ "Sequence": 609,
+ "SigningPubKey": "ED92DDE49AA689EC63589623067968B85E4885A9874B3CAB89E07D192EBFA42FF9",
+ "TransactionType": "MPTokenIssuanceSet",
+ "TxnSignature": "6678BF017A62360DAA39156112960D934440467D2E4098958F053B6758A2D237DA82CD27FDA3B35BAB610D74669BF6C4DEB12D77984E48158B2D79C8BAEC3303",
+ "ctid": "C000026400000000",
+ "date": 1711133159000,
+ "hash": "undefined",
+ "inLedger": "undefined",
+ "ledger_index": "undefined",
+ "meta": "undefined",
+ "validated": "undefined",
+ "metaData": "undefined",
+ "status": "undefined"
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rEUGuTqrySk9o1rZSVx8seuvcsEZymeEYM",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 610
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "1D54D263727856612FC7C7A27D93532ED0C41B74FA651992C125AD19C900669D",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 609
+ },
+ "PreviousTxnID": "CF68A8D929F089F6F53B071250935DD7C3F52F6E175D82E53118265D776D4BF7",
+ "PreviousTxnLgrSeq": 610
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Flags": 35,
+ "Issuer": "rEUGuTqrySk9o1rZSVx8seuvcsEZymeEYM",
+ "OutstandingAmount": "0",
+ "OwnerNode": "0",
+ "Sequence": 608
+ },
+ "LedgerEntryType": "MPTokenIssuance",
+ "LedgerIndex": "E8DF796110B1FBD9086A4637491E49843D33D897FBC32F03A33140F76378EE86",
+ "PreviousFields": {
+ "Flags": 34
+ },
+ "PreviousTxnID": "CF68A8D929F089F6F53B071250935DD7C3F52F6E175D82E53118265D776D4BF7",
+ "PreviousTxnLgrSeq": 610
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS"
+ },
+ "hash": "73C21D8B5DFBF5DE03FCAF0D69C00E9E2918280561E9898C556A1C743A566D47",
+ "ledger_index": 612,
+ "date": 1711133159000
+}
diff --git a/src/containers/shared/components/Transaction/MPTokenIssuanceSet/types.ts b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/types.ts
new file mode 100644
index 000000000..5e15d49e8
--- /dev/null
+++ b/src/containers/shared/components/Transaction/MPTokenIssuanceSet/types.ts
@@ -0,0 +1,6 @@
+import { TransactionCommonFields } from '../types'
+
+export interface MPTokenIssuanceSet extends TransactionCommonFields {
+ MPTokenIssuanceID: string
+ MPTokenHolder?: string
+}
diff --git a/src/containers/shared/components/Transaction/NFTokenAcceptOffer/types.ts b/src/containers/shared/components/Transaction/NFTokenAcceptOffer/types.ts
index 249a9632d..5091a827e 100644
--- a/src/containers/shared/components/Transaction/NFTokenAcceptOffer/types.ts
+++ b/src/containers/shared/components/Transaction/NFTokenAcceptOffer/types.ts
@@ -1,6 +1,8 @@
+import { ExplorerAmount } from '../../../types'
+
export interface NFTokenAcceptOfferInstructions {
acceptedOfferIDs: string[]
- amount?: { currency: string; amount: number; issuer?: string }
+ amount?: ExplorerAmount
tokenID?: string
seller?: string
buyer?: string
diff --git a/src/containers/shared/components/Transaction/NFTokenCancelOffer/test/NFTokenCancelOfferSimple.test.jsx b/src/containers/shared/components/Transaction/NFTokenCancelOffer/test/NFTokenCancelOfferSimple.test.jsx
index b16576254..ec58e6280 100644
--- a/src/containers/shared/components/Transaction/NFTokenCancelOffer/test/NFTokenCancelOfferSimple.test.jsx
+++ b/src/containers/shared/components/Transaction/NFTokenCancelOffer/test/NFTokenCancelOfferSimple.test.jsx
@@ -1,21 +1,25 @@
import { BrowserRouter as Router } from 'react-router-dom'
import { mount } from 'enzyme'
import { I18nextProvider } from 'react-i18next'
+import { QueryClientProvider } from 'react-query'
import { Simple as NFTokenCancelOffer } from '../Simple'
import transaction from './mock_data/NFTokenCancelOffer.json'
import summarizeTransaction from '../../../../../../rippled/lib/txSummary'
import i18n from '../../../../../../i18n/testConfig'
+import { queryClient } from '../../../../QueryClient'
describe('NFTokenCancelOffer', () => {
it.only('handles NFTokenCancelOffer simple view ', () => {
const wrapper = mount(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
)
expect(wrapper.find('[data-test="token-id"] .value')).toHaveText(
'000800006203F49C21D5D6E022CB16DE3538F248662FC73C258BA1B200000018',
diff --git a/src/containers/shared/components/Transaction/NFTokenCreateOffer/types.ts b/src/containers/shared/components/Transaction/NFTokenCreateOffer/types.ts
index 624c2dc47..e25ed0359 100644
--- a/src/containers/shared/components/Transaction/NFTokenCreateOffer/types.ts
+++ b/src/containers/shared/components/Transaction/NFTokenCreateOffer/types.ts
@@ -1,6 +1,8 @@
+import { ExplorerAmount } from '../../../types'
+
export interface NFTokenCreateOfferInstructions {
account: string
- amount: { currency: string; amount: number; issuer?: string }
+ amount: ExplorerAmount
tokenID: string
isSellOffer: boolean
owner?: string
diff --git a/src/containers/shared/components/Transaction/OfferCreate/parser.ts b/src/containers/shared/components/Transaction/OfferCreate/parser.ts
index e895c2b5c..238bfd6a5 100644
--- a/src/containers/shared/components/Transaction/OfferCreate/parser.ts
+++ b/src/containers/shared/components/Transaction/OfferCreate/parser.ts
@@ -6,7 +6,7 @@ export function parser(tx: any) {
const base = tx.TakerGets.currency ? tx.TakerGets : { currency: 'XRP' }
const counter = tx.TakerPays.currency ? tx.TakerPays : { currency: 'XRP' }
const pays = formatAmount(tx.TakerPays)
- const price = pays.amount / gets.amount
+ const price = Number(pays.amount) / Number(gets.amount)
const invert =
CURRENCY_ORDER.indexOf(counter.currency) >
CURRENCY_ORDER.indexOf(base.currency)
diff --git a/src/containers/shared/components/Transaction/OracleSet/parser.ts b/src/containers/shared/components/Transaction/OracleSet/parser.ts
index bca503f5f..b27a3ba04 100644
--- a/src/containers/shared/components/Transaction/OracleSet/parser.ts
+++ b/src/containers/shared/components/Transaction/OracleSet/parser.ts
@@ -1,19 +1,6 @@
import { convertHexToString } from '../../../../../rippled/lib/utils'
import { OracleSet } from './types'
-
-// Convert scaled price (assetPrice) to original price using formula:
-// originalPrice = assetPrice / 10**scale
-// More details: https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-47d-PriceOracles
-export function convertScaledPrice(assetPrice: string, scale: number) {
- const scaledPriceInBigInt = BigInt(`0x${assetPrice}`)
- const divisor = BigInt(10 ** scale)
- const integerPart = scaledPriceInBigInt / divisor
- const remainder = scaledPriceInBigInt % divisor
- const fractionalPart = (remainder * BigInt(10 ** scale)) / divisor
- return fractionalPart > 0
- ? `${integerPart}.${fractionalPart.toString().padStart(scale, '0')}`
- : `${integerPart}`
-}
+import { convertScaledPrice } from '../../../utils'
export function parser(tx: OracleSet) {
const priceDataSeries = tx.PriceDataSeries.map((priceDataObj) => ({
diff --git a/src/containers/shared/components/Transaction/OracleSet/test/ConvertScalePrice.test.ts b/src/containers/shared/components/Transaction/OracleSet/test/ConvertScalePrice.test.ts
index 984c63e2b..2437a40c9 100644
--- a/src/containers/shared/components/Transaction/OracleSet/test/ConvertScalePrice.test.ts
+++ b/src/containers/shared/components/Transaction/OracleSet/test/ConvertScalePrice.test.ts
@@ -1,4 +1,4 @@
-import { convertScaledPrice } from '../parser'
+import { convertScaledPrice } from '../../../../utils'
const numberToHex = (number) => number.toString(16)
describe('convertScaledPrice', () => {
diff --git a/src/containers/shared/components/Transaction/Payment/parser.ts b/src/containers/shared/components/Transaction/Payment/parser.ts
index 743c05367..57dc16157 100644
--- a/src/containers/shared/components/Transaction/Payment/parser.ts
+++ b/src/containers/shared/components/Transaction/Payment/parser.ts
@@ -1,4 +1,4 @@
-import type { Payment } from 'xrpl'
+// import type { Payment } from 'xrpl'
import { formatAmount } from '../../../../../rippled/lib/txSummary/formatAmount'
import { PaymentInstructions } from './types'
import { Amount, ExplorerAmount } from '../../../types'
@@ -10,7 +10,8 @@ const formatFailedPartialAmount = (d: Amount): ExplorerAmount => ({
export const isPartialPayment = (flags: any) => 0x00020000 & flags
-export const parser = (tx: Payment, meta: any): PaymentInstructions => {
+// TODO: use MPTAmount type from xrpl.js
+export const parser = (tx: any, meta: any): PaymentInstructions => {
const max = tx.SendMax ? formatAmount(tx.SendMax) : undefined
const partial = !!isPartialPayment(tx.Flags)
const failedPartial = partial && meta.TransactionResult !== 'tesSUCCESS'
diff --git a/src/containers/shared/components/Transaction/Payment/test/PaymentSimple.test.tsx b/src/containers/shared/components/Transaction/Payment/test/PaymentSimple.test.tsx
index 60c9ab4b3..5166c919f 100644
--- a/src/containers/shared/components/Transaction/Payment/test/PaymentSimple.test.tsx
+++ b/src/containers/shared/components/Transaction/Payment/test/PaymentSimple.test.tsx
@@ -1,3 +1,4 @@
+import { useQuery } from 'react-query'
import {
createSimpleWrapperFactory,
expectSimpleRowLabel,
@@ -10,6 +11,12 @@ import mockPaymentDestinationTag from './mock_data/PaymentWithDestinationTag.jso
import mockPaymentPartial from './mock_data/PaymentWithPartial.json'
import mockPaymentSendMax from './mock_data/PaymentWithSendMax.json'
import mockPaymentSourceTag from './mock_data/PaymentWithSourceTag.json'
+import mockPaymentMPT from './mock_data/PaymentMPT.json'
+
+jest.mock('react-query', () => ({
+ ...jest.requireActual('react-query'),
+ useQuery: jest.fn(),
+}))
const createWrapper = createSimpleWrapperFactory(Simple)
@@ -114,4 +121,32 @@ describe('Payment: Simple', () => {
wrapper.unmount()
})
+
+ it('renders direct MPT payment', () => {
+ const data = {
+ assetScale: 3,
+ }
+
+ // @ts-ignore
+ useQuery.mockImplementation(() => ({
+ data,
+ }))
+
+ const wrapper = createWrapper(mockPaymentMPT)
+
+ expectSimpleRowText(
+ wrapper,
+ 'amount',
+ `0.1 MPT (000003C31D321B7DDA58324DC38CDF18934FAFFFCDF69D5F)`,
+ )
+ expectSimpleRowLabel(wrapper, 'amount', `send`)
+
+ expectSimpleRowText(
+ wrapper,
+ 'destination',
+ `rw6UtpfBFaGht6SiC1HpDPNw6Yt25pKvnu`,
+ )
+
+ wrapper.unmount()
+ })
})
diff --git a/src/containers/shared/components/Transaction/Payment/test/mock_data/PaymentMPT.json b/src/containers/shared/components/Transaction/Payment/test/mock_data/PaymentMPT.json
new file mode 100644
index 000000000..d35339b88
--- /dev/null
+++ b/src/containers/shared/components/Transaction/Payment/test/mock_data/PaymentMPT.json
@@ -0,0 +1,91 @@
+{
+ "tx": {
+ "Account": "rsC4dnxCb66FQT4XmCUeuQ7dYeqNio4rWg",
+ "Amount": {
+ "mpt_issuance_id": "000003C31D321B7DDA58324DC38CDF18934FAFFFCDF69D5F",
+ "value": "100"
+ },
+ "DeliverMax": {
+ "mpt_issuance_id": "000003C31D321B7DDA58324DC38CDF18934FAFFFCDF69D5F",
+ "value": "100"
+ },
+ "Destination": "rw6UtpfBFaGht6SiC1HpDPNw6Yt25pKvnu",
+ "Fee": "10",
+ "Flags": 2147483648,
+ "Sequence": 964,
+ "SigningPubKey": "ED35B07F41420220332C35B9F4D1F7AF26E67EBD5AD6C9E106D0F774DA15924169",
+ "TransactionType": "Payment",
+ "TxnSignature": "2C5CB9740457222F928667DC1196060EF7E61B4E3A8824727AE63ACCFDE35ED5CBEE69E982423592DF0464C57C2C445B4271573DE7A3346630024287844F2502",
+ "ctid": "C00003C900000000",
+ "date": 1727802036000
+ },
+ "meta": {
+ "AffectedNodes": [
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "AssetScale": 3,
+ "Flags": 34,
+ "Issuer": "rsC4dnxCb66FQT4XmCUeuQ7dYeqNio4rWg",
+ "MPTokenMetadata": "7B226E616D65223A2255532054726561737572792042696C6C20546F6B656E222C2273796D626F6C223A225553544254222C22646563696D616C73223A322C22746F74616C537570706C79223A313030303030302C22697373756572223A225553205472656173757279222C22697373756544617465223A22323032342D30332D3235222C226D6174757269747944617465223A22323032352D30332D3235222C226661636556616C7565223A2231303030222C22696E74657265737452617465223A22322E35222C22696E7465726573744672657175656E6379223A22517561727465726C79222C22636F6C6C61746572616C223A22555320476F7665726E6D656E74222C226A7572697364696374696F6E223A22556E6974656420537461746573222C22726567756C61746F7279436F6D706C69616E6365223A2253454320526567756C6174696F6E73222C22736563757269747954797065223A2254726561737572792042696C6C222C2265787465726E616C5F75726C223A2268747470733A2F2F6578616D706C652E636F6D2F742D62696C6C2D746F6B656E2D6D657461646174612E6A736F6E227D",
+ "MaximumAmount": "9223372036854775807",
+ "OutstandingAmount": "100",
+ "OwnerNode": "0",
+ "Sequence": 963
+ },
+ "LedgerEntryType": "MPTokenIssuance",
+ "LedgerIndex": "1CFF89335B544E0D6EEC35D74C0D26FF407DC02670F1C4E35A36CC875D34B1C3",
+ "PreviousFields": {
+ "OutstandingAmount": "0"
+ },
+ "PreviousTxnID": "6329586F264E4A6E2224318DCFC9B5F28048D84060B78A92CFFE65840DF8D970",
+ "PreviousTxnLgrSeq": 966
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rw6UtpfBFaGht6SiC1HpDPNw6Yt25pKvnu",
+ "Flags": 0,
+ "MPTAmount": "100",
+ "MPTokenIssuanceID": "000003C31D321B7DDA58324DC38CDF18934FAFFFCDF69D5F",
+ "OwnerNode": "0"
+ },
+ "LedgerEntryType": "MPToken",
+ "LedgerIndex": "3BAA73912496683A414494218D3CCA33D02F80D588F80C1257C691448E00E486",
+ "PreviousFields": {},
+ "PreviousTxnID": "60F99C8A23C4A366D19F43EA4BD43414AD4D4B7C21D0228FB7539D1C893E4A74",
+ "PreviousTxnLgrSeq": 967
+ }
+ },
+ {
+ "ModifiedNode": {
+ "FinalFields": {
+ "Account": "rsC4dnxCb66FQT4XmCUeuQ7dYeqNio4rWg",
+ "Balance": "99999980",
+ "Flags": 0,
+ "OwnerCount": 1,
+ "Sequence": 965
+ },
+ "LedgerEntryType": "AccountRoot",
+ "LedgerIndex": "77ECC02B8A7F16EB19A7BFBE8494E959497B4EC7734088583BD4F6B8C82878A5",
+ "PreviousFields": {
+ "Balance": "99999990",
+ "Sequence": 964
+ },
+ "PreviousTxnID": "6329586F264E4A6E2224318DCFC9B5F28048D84060B78A92CFFE65840DF8D970",
+ "PreviousTxnLgrSeq": 966
+ }
+ }
+ ],
+ "TransactionIndex": 0,
+ "TransactionResult": "tesSUCCESS",
+ "delivered_amount": {
+ "mpt_issuance_id": "000003C31D321B7DDA58324DC38CDF18934FAFFFCDF69D5F",
+ "value": "100"
+ }
+ },
+ "hash": "CD9EC015E68D3027919598E0466CFEF19950D0BC688A568DF8822A8BB0AFF98F",
+ "ledger_index": 11707,
+ "date": 1712072515000
+}
diff --git a/src/containers/shared/components/Transaction/UNLModify/test/UNLModifySimple.test.tsx b/src/containers/shared/components/Transaction/UNLModify/test/UNLModifySimple.test.tsx
index f3558c8ef..89cab8f69 100644
--- a/src/containers/shared/components/Transaction/UNLModify/test/UNLModifySimple.test.tsx
+++ b/src/containers/shared/components/Transaction/UNLModify/test/UNLModifySimple.test.tsx
@@ -44,7 +44,7 @@ describe('UNLModify: Simple', () => {
- {TestComponent}
- ,
+
+
+ {TestComponent}
+
+ ,
)
}
diff --git a/src/containers/shared/components/Transaction/types.ts b/src/containers/shared/components/Transaction/types.ts
index d1154be4a..722687b69 100644
--- a/src/containers/shared/components/Transaction/types.ts
+++ b/src/containers/shared/components/Transaction/types.ts
@@ -8,6 +8,7 @@ export enum TransactionCategory {
PAYMENT = 'PAYMENT',
NFT = 'NFT',
XCHAIN = 'XCHAIN',
+ MPT = 'MPT',
PSEUDO = 'PSEUDO',
UNKNOWN = 'UNKNOWN',
}
diff --git a/src/containers/shared/components/TransactionTable/test/TransactionTable.test.js b/src/containers/shared/components/TransactionTable/test/TransactionTable.test.js
index 54517c301..734e4f0d5 100644
--- a/src/containers/shared/components/TransactionTable/test/TransactionTable.test.js
+++ b/src/containers/shared/components/TransactionTable/test/TransactionTable.test.js
@@ -1,9 +1,11 @@
import { mount } from 'enzyme'
import { I18nextProvider } from 'react-i18next'
import { BrowserRouter } from 'react-router-dom'
+import { QueryClientProvider } from 'react-query'
import { TransactionTable } from '../TransactionTable'
import i18n from '../../../../../i18n/testConfig'
import mockTx from './mockTransactions.json'
+import { queryClient } from '../../../QueryClient'
const loadMore = jest.fn()
@@ -16,17 +18,19 @@ describe('Transaction Table container', () => {
hasAdditionalResults = false,
) =>
mount(
-
-
-
-
- ,
+
+
+
+
+
+
+ ,
)
it('renders without crashing', () => {
diff --git a/src/containers/shared/components/test/Amount.test.tsx b/src/containers/shared/components/test/Amount.test.tsx
index acbe14315..749f62033 100644
--- a/src/containers/shared/components/test/Amount.test.tsx
+++ b/src/containers/shared/components/test/Amount.test.tsx
@@ -1,9 +1,15 @@
import { I18nextProvider } from 'react-i18next'
import { BrowserRouter } from 'react-router-dom'
import { mount } from 'enzyme'
+import { useQuery } from 'react-query'
import { Amount } from '../Amount'
import i18n from '../../../../i18n/testConfig'
+jest.mock('react-query', () => ({
+ ...jest.requireActual('react-query'),
+ useQuery: jest.fn(),
+}))
+
describe('Amount', () => {
const createWrapper = (component: JSX.Element) =>
mount(
@@ -117,4 +123,34 @@ describe('Amount', () => {
expect(wrapper.find('.amount-localized').text()).toEqual('+\uE9000.009')
wrapper.unmount()
})
+
+ it('handles MPT amount', async () => {
+ const data = {
+ issuer: 'rL2LzUhsBJMqsaVCXVvzedPjePbjVzBCC',
+ assetScale: 3,
+ maxAmt: '100000000',
+ outstandingAmt: '1043001',
+ sequence: 2447,
+ metadata:
+ '{"name":"US Treasury Bill Token","symbol":"USTBT","decimals":2,"totalSupply":1000000,"issuer":"US Treasury","issueDate":"2024-03-25","maturityDate":"2025-03-25","faceValue":"1000","interestRate":"2.5","interestFrequency":"Quarterly","collateral":"US Government","jurisdiction":"United States","regulatoryCompliance":"SEC Regulations","securityType":"Treasury Bill","external_url":"https://example.com/t-bill-token-metadata.json"}',
+ flags: [],
+ }
+
+ // @ts-ignore
+ useQuery.mockImplementation(() => ({
+ data,
+ }))
+
+ const value = {
+ amount: '1043001',
+ currency: '0000098F03B3BCE934EE8CAA1DF25A42032388361B9E5A65',
+ isMPT: true,
+ }
+ const wrapper = createWrapper(
+ ,
+ )
+
+ expect(wrapper.find('.amount-localized').text()).toEqual('1,043.001')
+ wrapper.unmount()
+ })
})
diff --git a/src/containers/shared/components/test/Currency.test.tsx b/src/containers/shared/components/test/Currency.test.tsx
index e3d218090..8d7de259c 100644
--- a/src/containers/shared/components/test/Currency.test.tsx
+++ b/src/containers/shared/components/test/Currency.test.tsx
@@ -63,4 +63,26 @@ describe('Currency', () => {
expect(wrapper.find('.currency').text()).toEqual('\uE900 XRP')
wrapper.unmount()
})
+
+ it('handles MPT ID ', () => {
+ const wrapper = mount(
+
+
+ ,
+ )
+ const mpt = wrapper.find('.currency').at(0)
+
+ expect(mpt).toHaveText(
+ 'MPT (00000BDE5B4F868ECE457207E2C1750065987730B8839E0D)',
+ )
+ expect(mpt.find('a')).toHaveProp(
+ 'href',
+ '/mpt/00000BDE5B4F868ECE457207E2C1750065987730B8839E0D',
+ )
+ wrapper.unmount()
+ })
})
diff --git a/src/containers/shared/css/global.scss b/src/containers/shared/css/global.scss
index 327c74065..8d7bf9b0d 100644
--- a/src/containers/shared/css/global.scss
+++ b/src/containers/shared/css/global.scss
@@ -121,6 +121,7 @@ div.react-stockchart div {
@include transaction-category(XCHAIN, $yellow, $yellow-30, $yellow-90);
@include transaction-category(PSEUDO, $white, $white, $black-80);
@include transaction-category(UNKNOWN, $black-50, $black-30, $black-90);
+@include transaction-category(MPT, $blue, $blue-30, $blue-90);
.tx-result {
&.success {
diff --git a/src/containers/shared/css/variables.scss b/src/containers/shared/css/variables.scss
index a1987f372..91b259bed 100644
--- a/src/containers/shared/css/variables.scss
+++ b/src/containers/shared/css/variables.scss
@@ -120,6 +120,7 @@ $custom: $yellow-50;
// Feature Sets
$amm: $blue;
$nft: $blue-purple;
+$mpt: $blue;
// Currency colors
$CURRENCY_DEFAULT: #aedbf7;
diff --git a/src/containers/shared/test/amendmentUtils.test.ts b/src/containers/shared/test/amendmentUtils.test.ts
index f7f21e645..e4ef9d4e6 100644
--- a/src/containers/shared/test/amendmentUtils.test.ts
+++ b/src/containers/shared/test/amendmentUtils.test.ts
@@ -1,170 +1,4 @@
-import { getRippledVersion, nameOfAmendmentID } from '../amendmentUtils'
-
-const nameTable = [
- [
- '32A122F1352A4C7B3A6D790362CC34749C5E57FCE896377BFDC6CCD14F6CD627',
- 'NonFungibleTokensV1_1',
- ],
- [
- '4C97EBA926031A7CF7D7B36FDE3ED66DDA5421192D63DE53FFB46E43B9DC8373',
- 'MultiSign',
- ],
- [
- '6781F8368C4771B83E8B821D88F580202BCB4228075297B19E4FDC5233F1EFDC',
- 'TrustSetAuth',
- ],
- [
- '42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE',
- 'FeeEscalation',
- ],
- [
- '08DE7D96082187F6E6578530258C77FAABABE4C20474BDB82F04B021F1A68647',
- 'PayChan',
- ],
- ['740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11', 'Flow'],
- [
- '1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146',
- 'CryptoConditions',
- ],
- [
- '532651B4FD58DF8922A49BA101AB3E996E5BFBF95A913B3E392504863E63B164',
- 'TickSize',
- ],
- [
- 'E2E6F2866106419B88C50045ACE96368558C345566AC8F2BDF5A5B5587F0E6FA',
- 'fix1368',
- ],
- [
- '07D43DCE529B15A10827E5E04943B496762F9A88E3268269D69C44BE49E21104',
- 'Escrow',
- ],
- [
- '86E83A7D2ECE3AD5FA87AB2195AE015C950469ABF0B72EAACED318F74886AE90',
- 'CryptoConditionsSuite',
- ],
- [
- '42EEA5E28A97824821D4EF97081FE36A54E9593C6E4F20CBAE098C69D2E072DC',
- 'fix1373',
- ],
- [
- 'DC9CA96AEA1DCF83E527D1AFC916EFAF5D27388ECA4060A88817C1238CAEE0BF',
- 'EnforceInvariants',
- ],
- [
- '3012E8230864E95A58C60FD61430D7E1B4D3353195F2981DC12B0C7C0950FFAC',
- 'FlowCross',
- ],
- [
- 'CC5ABAE4F3EC92E94A59B1908C2BE82D2228B6485C00AFF8F22DF930D89C194E',
- 'SortedDirectories',
- ],
- [
- 'B4D44CC3111ADD964E846FC57760C8B50FFCD5A82C86A72756F6B058DDDF96AD',
- 'fix1201',
- ],
- [
- '6C92211186613F9647A89DFFBAB8F94C99D4C7E956D495270789128569177DA1',
- 'fix1512',
- ],
- [
- '67A34F2CF55BFC0F93AACD5B281413176FEE195269FA6D95219A2DF738671172',
- 'fix1513',
- ],
- [
- 'B9E739B8296B4A1BB29BE990B17D66E21B62A300A909F25AC55C22D6C72E1F9D',
- 'fix1523',
- ],
- [
- '1D3463A5891F9E589C5AE839FFAC4A917CE96197098A1EF22304E1BC5B98A454',
- 'fix1528',
- ],
- [
- 'F64E1EABBE79D55B3BB82020516CEC2C582A98A6BFE20FBE9BB6A0D233418064',
- 'DepositAuth',
- ],
- [
- '157D2D480E006395B76F948E3E07A45A05FE10230D88A7993C71F97AE4B1F2D1',
- 'Checks',
- ],
- [
- '7117E2EC2DBF119CA55181D69819F1999ECEE1A0225A7FD2B9ED47940968479C',
- 'fix1571',
- ],
- [
- 'CA7C02118BA27599528543DFE77BA6838D1B0F43B447D4D7F53523CE6A0E9AC2',
- 'fix1543',
- ],
- [
- '58BE9B5968C4DA7C59BA900961828B113E5490699B21877DEF9A31E9D0FE5D5F',
- 'fix1623',
- ],
- [
- '3CBC5C4E630A1B82380295CDA84B32B49DD066602E74E39B85EF64137FA65194',
- 'DepositPreauth',
- ],
- [
- '5D08145F0A4983F23AFFFF514E83FAD355C5ABFBB6CAB76FB5BC8519FF5F33BE',
- 'fix1515',
- ],
- [
- 'FBD513F1B893AC765B78F250E6FFA6A11B573209D1842ADC787C850696741288',
- 'fix1578',
- ],
- [
- '586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF1FC07EFE41D',
- 'MultiSignReserve',
- ],
- [
- '2CD5286D8D687E98B41102BDD797198E81EA41DF7BD104E6561FEB104EFF2561',
- 'fixTakerDryOfferRemoval',
- ],
- [
- 'C4483A1896170C66C098DEA5B0E024309C60DC960DE5F01CD7AF986AA3D9AD37',
- 'fixMasterKeyAsRegularKey',
- ],
- [
- '8F81B066ED20DAECA20DF57187767685EEF3980B228E0667A650BAF24426D3B4',
- 'fixCheckThreading',
- ],
- [
- '621A0B264970359869E3C0363A899909AAB7A887C8B73519E4ECF952D33258A8',
- 'fixPayChanRecipientOwnerDir',
- ],
- [
- '30CD365592B8EE40489BA01AE2F7555CAC9C983145871DC82A42A31CF5BAE7D9',
- 'DeletableAccounts',
- ],
- [
- '89308AF3B8B10B7192C4E613E1D2E4D9BA64B2EE2D5232402AE82A6A7220D953',
- 'fixQualityUpperBound',
- ],
- [
- '00C1FC4A53E60AB02C864641002B3172F38677E29C26C5406685179B37E1EDAC',
- 'RequireFullyCanonicalSig',
- ],
- [
- '25BA44241B3BD880770BFA4DA21C7180576831855368CBEC6A3154FDE4A7676E',
- 'fix1781',
- ],
- [
- '1F4AFA8FA1BC8827AD4C0F682C03A8B671DCDF6B5C4DE36D44243A684103EF88',
- 'HardenedValidations',
- ],
- [
- '4F46DF03559967AC60F2EB272FEFE3928A7594A45FF774B87A7E540DB0F8F068',
- 'fixAmendmentMajorityCalc',
- ],
-]
-
-describe('nameOfAmendmentID: ', () => {
- it.each(nameTable)(
- `should resolve amendment id "%s" to "%s"`,
- async (id, name) => {
- const retrievedName = await nameOfAmendmentID(id)
- return expect(retrievedName).toEqual(name)
- },
- )
-})
+import { getRippledVersion } from '../amendmentUtils'
const versionTable = [
['NonFungibleTokensV1_1', 'v1.9.2'],
diff --git a/src/containers/shared/transactionUtils.ts b/src/containers/shared/transactionUtils.ts
index e39c28ef1..0ccd26bd9 100644
--- a/src/containers/shared/transactionUtils.ts
+++ b/src/containers/shared/transactionUtils.ts
@@ -41,6 +41,21 @@ export const TX_FLAGS: Record> = {
0x00200000: 'tfOneAssetLPToken',
0x00400000: 'tfLimitLPToken',
},
+ MPTokenAuthorize: {
+ 0x00000001: 'tfMPTUnauthorize',
+ },
+ MPTokenIssuanceCreate: {
+ 0x00000002: 'tfMPTCanLock',
+ 0x00000004: 'tfMPTRequireAuth',
+ 0x00000008: 'tfMPTCanEscrow',
+ 0x00000010: 'tfMPTCanTrade',
+ 0x00000020: 'tfMPTCanTransfer',
+ 0x00000040: 'tfMPTCanClawback',
+ },
+ MPTokenIssuanceSet: {
+ 0x00000001: 'tfMPTLock',
+ 0x00000002: 'tfMPTUnlock',
+ },
NFTokenMint: {
0x00000001: 'tfBurnable',
0x00000002: 'tfOnlyXRP',
diff --git a/src/containers/shared/types.ts b/src/containers/shared/types.ts
index d219bc63e..3ae210e27 100644
--- a/src/containers/shared/types.ts
+++ b/src/containers/shared/types.ts
@@ -17,12 +17,18 @@ 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 type ExplorerAmount = {
issuer?: string
currency: string
- amount: number
+ amount: number | string
+ isMPT?: boolean
}
export interface Tx {
diff --git a/src/containers/shared/utils.js b/src/containers/shared/utils.js
index bdeae56e2..9046410e0 100644
--- a/src/containers/shared/utils.js
+++ b/src/containers/shared/utils.js
@@ -23,7 +23,8 @@ export const FETCH_INTERVAL_NODES_MILLIS = 60000
export const FETCH_INTERVAL_ERROR_MILLIS = 300
export const DECIMAL_REGEX = /^\d+$/
-export const HASH_REGEX = /[0-9A-Fa-f]{64}/i
+export const HASH256_REGEX = /[0-9A-Fa-f]{64}/i
+export const HASH192_REGEX = /[0-9A-Fa-f]{48}/i
export const CURRENCY_REGEX =
/^[a-zA-Z0-9]{3,}[.:+-]r[rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz]{27,35}$/
export const FULL_CURRENCY_REGEX =
@@ -128,15 +129,29 @@ export const isEarlierVersion = (source, target) => {
return false
}
+export const isValidJsonString = (str) => {
+ try {
+ JSON.parse(str)
+ return true
+ } catch (e) {
+ return false
+ }
+}
+
// Document: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/NumberFormat
-export const localizeNumber = (num, lang = 'en-US', options = {}) => {
+export const localizeNumber = (
+ num,
+ lang = 'en-US',
+ options = {},
+ isMPT = false,
+) => {
const number = Number.parseFloat(num)
const config = { ...NUMBER_DEFAULT_OPTIONS, ...options }
if (Number.isNaN(number)) {
return null
}
- if (config.style === 'currency') {
+ if (config.style === 'currency' && !isMPT) {
try {
const neg = number < 0 ? '-' : ''
const d = new Intl.NumberFormat(lang, config).format(number)
@@ -255,6 +270,8 @@ export const formatLargeNumber = (d = 0, digits = 4) => {
}
}
+export const convertHexToBigInt = (s) => BigInt(`0x${s}`)
+
export const durationToHuman = (s, decimal = 2) => {
const d = {}
const seconds = Math.abs(s)
@@ -301,7 +318,7 @@ export const formatTradingFee = (tradingFee) =>
})
: undefined
-export const computeBalanceChange = (node) => {
+export const computeRippleStateBalanceChange = (node) => {
const fields = node.FinalFields || node.NewFields
const prev = node.PreviousFields
const { currency } = fields.Balance
@@ -333,7 +350,50 @@ export const computeBalanceChange = (node) => {
}
}
+export const computeMPTokenBalanceChange = (node) => {
+ const final = node.FinalFields || node.NewFields
+ const prev = node.PreviousFields
+ const prevAmount = prev && prev.MPTAmount ? prev.MPTAmount : '0'
+ const finalAmount = final.MPTAmount ?? '0'
+
+ return {
+ previousBalance: BigInt(prevAmount),
+ finalBalance: BigInt(finalAmount),
+ account: final.Account,
+ change: BigInt(finalAmount) - BigInt(prevAmount),
+ }
+}
+
+export const computeMPTIssuanceBalanceChange = (node) => {
+ const final = node.FinalFields || node.NewFields
+ const prev = node.PreviousFields
+ const prevAmount =
+ prev && prev.OutstandingAmount ? prev.OutstandingAmount : '0'
+ const finalAmount = final.OutstandingAmount ?? '0'
+
+ return {
+ previousBalance: BigInt(prevAmount),
+ finalBalance: BigInt(finalAmount),
+ account: final.Issuer,
+ change: BigInt(finalAmount) - BigInt(prevAmount),
+ }
+}
+
export const renderXRP = (d, language) => {
const options = { ...CURRENCY_OPTIONS, currency: 'XRP' }
return localizeNumber(d, language, options)
}
+
+// Convert scaled price (assetPrice) in hex string to original price using formula:
+// originalPrice = assetPrice / 10**scale
+// More details: https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-47d-PriceOracles
+export function convertScaledPrice(assetPrice, scale) {
+ const scaledPriceInBigInt = BigInt(`0x${assetPrice}`)
+ const divisor = BigInt(10 ** scale)
+ const integerPart = scaledPriceInBigInt / divisor
+ const remainder = scaledPriceInBigInt % divisor
+ const fractionalPart = (remainder * BigInt(10 ** scale)) / divisor
+ return fractionalPart > 0
+ ? `${integerPart}.${fractionalPart.toString().padStart(scale, '0')}`
+ : `${integerPart}`
+}
diff --git a/src/index.html b/src/index.html
index 514732776..8109d44f4 100644
--- a/src/index.html
+++ b/src/index.html
@@ -15,8 +15,9 @@
-->
-
-
+
+
+
diff --git a/src/rippled/lib/rippled.js b/src/rippled/lib/rippled.js
index 723121ced..2591b0f95 100644
--- a/src/rippled/lib/rippled.js
+++ b/src/rippled/lib/rippled.js
@@ -1,4 +1,4 @@
-import { CTID_REGEX, HASH_REGEX } from '../../containers/shared/utils'
+import { CTID_REGEX, HASH256_REGEX } from '../../containers/shared/utils'
import { formatAmount } from './txSummary/formatAmount'
import { Error, XRP_BASE, convertRippleDate } from './utils'
@@ -144,7 +144,7 @@ const getTransaction = (rippledSocket, txId) => {
const params = {
command: 'tx',
}
- if (HASH_REGEX.test(txId)) {
+ if (HASH256_REGEX.test(txId)) {
params.transaction = txId
} else if (CTID_REGEX.test(txId)) {
params.ctid = txId
@@ -557,6 +557,69 @@ const getAMMInfo = (rippledSocket, asset, asset2) => {
})
}
+// get feature
+const getFeature = (rippledSocket, amendmentId) => {
+ const request = {
+ command: 'feature',
+ feature: amendmentId,
+ }
+ return query(rippledSocket, request).then((resp) => {
+ if (resp == null || resp.error_message) {
+ return null
+ }
+
+ return resp
+ })
+}
+
+const getMPTIssuance = (rippledSocket, tokenId) =>
+ query(rippledSocket, {
+ command: 'ledger_entry',
+ mpt_issuance: tokenId,
+ ledger_index: 'validated',
+ }).then((resp) => {
+ if (
+ resp.error === 'entryNotFound' ||
+ resp.error === 'lgrNotFound' ||
+ resp.error === 'objectNotFound'
+ ) {
+ throw new Error('MPT not found', 404)
+ }
+
+ if (resp.error_message) {
+ throw new Error(resp.error_message, 500)
+ }
+ return resp
+ })
+
+const getAccountMPTs = (
+ rippledSocket,
+ account,
+ marker = '',
+ ledgerIndex = 'validated',
+) =>
+ query(rippledSocket, {
+ command: 'account_objects',
+ account,
+ ledger_index: ledgerIndex,
+ type: 'mptoken',
+ marker: marker || undefined,
+ limit: 400,
+ }).then((resp) => {
+ if (resp.error === 'actNotFound') {
+ throw new Error('account not found', 404)
+ }
+ if (resp.error === 'invalidParams') {
+ return undefined
+ }
+
+ if (resp.error_message) {
+ throw new Error(resp.error_message, 500)
+ }
+
+ return resp
+ })
+
export {
getLedger,
getLedgerEntry,
@@ -576,4 +639,7 @@ export {
getSellNFToffers,
getNFTTransactions,
getAMMInfo,
+ getFeature,
+ getMPTIssuance,
+ getAccountMPTs,
}
diff --git a/src/rippled/lib/txSummary/formatAmount.ts b/src/rippled/lib/txSummary/formatAmount.ts
index 7eaf91239..c8cccd4d5 100644
--- a/src/rippled/lib/txSummary/formatAmount.ts
+++ b/src/rippled/lib/txSummary/formatAmount.ts
@@ -1,18 +1,34 @@
-import { Amount, ExplorerAmount } from '../../../containers/shared/types'
+import {
+ Amount,
+ ExplorerAmount,
+ MPTAmount,
+} from '../../../containers/shared/types'
import { XRP_BASE } from '../utils'
+export const isMPTAmount = (amount: Amount): amount is MPTAmount =>
+ (amount as MPTAmount).mpt_issuance_id !== undefined &&
+ (amount as MPTAmount).value !== undefined
+
export const formatAmount = (d: Amount | number): ExplorerAmount => {
if (d == null) {
return d
}
- return typeof d !== 'string' && typeof d !== 'number'
+
+ if (typeof d === 'string' || typeof d === 'number')
+ return {
+ currency: 'XRP',
+ amount: Number(d) / XRP_BASE,
+ }
+
+ return isMPTAmount(d)
? {
+ currency: d.mpt_issuance_id,
+ amount: d.value,
+ isMPT: true,
+ }
+ : {
currency: d.currency,
issuer: d.issuer,
amount: Number(d.value),
}
- : {
- currency: 'XRP',
- amount: Number(d) / XRP_BASE,
- }
}
diff --git a/src/rippled/lib/utils.js b/src/rippled/lib/utils.js
index a0a27261b..e49603078 100644
--- a/src/rippled/lib/utils.js
+++ b/src/rippled/lib/utils.js
@@ -1,4 +1,5 @@
-import { hexToString } from '@xrplf/isomorphic/utils'
+import { hexToString, hexToBytes } from '@xrplf/isomorphic/utils'
+import { encodeAccountID } from 'ripple-address-codec'
import { convertRippleDate } from './convertRippleDate'
import { formatSignerList } from './formatSignerList'
import { decodeHex } from '../../containers/shared/transactionUtils'
@@ -27,6 +28,19 @@ const NFT_FLAGS = {
0x00000002: 'lsfOnlyXRP',
0x00000008: 'lsfTransferable',
}
+const MPT_ISSUANCE_FLAGS = {
+ 0x00000001: 'lsfMPTLocked',
+ 0x00000002: 'lsfMPTCanLock',
+ 0x00000004: 'lsfMPTRequireAuth',
+ 0x00000008: 'lsfMPTCanEscrow',
+ 0x00000010: 'lsfMPTCanTrade',
+ 0x00000020: 'lsfMPTCanTransfer',
+ 0x00000040: 'lsfMPTCanClawback',
+}
+const MPTOKEN_FLAGS = {
+ 0x00000001: 'lsfMPTLocked',
+ 0x00000002: 'lsfMPTAuthorized',
+}
const hex32 = (d) => {
const int = d & 0xffffffff
const hex = int.toString(16).toUpperCase()
@@ -127,6 +141,33 @@ const formatNFTInfo = (info) => ({
warnings: info.warnings,
})
+const formatMPTIssuanceInfo = (info) => ({
+ issuer: info.node.Issuer,
+ assetScale: info.node.AssetScale,
+ maxAmt: info.node.MaximumAmount
+ ? BigInt(info.node.MaximumAmount).toString(10)
+ : undefined, // default is undefined because the default maxAmt is the largest 63-bit int
+ outstandingAmt: info.node.OutstandingAmount
+ ? BigInt(info.node.OutstandingAmount).toString(10)
+ : '0',
+ transferFee: info.node.TransferFee,
+ sequence: info.node.Sequence,
+ metadata: info.node.MPTokenMetadata
+ ? decodeHex(info.node.MPTokenMetadata)
+ : info.node.MPTokenMetadata,
+ flags: buildFlags(info.node.Flags, MPT_ISSUANCE_FLAGS),
+})
+
+const formatMPTokenInfo = (info) => ({
+ account: info.Account,
+ flags: buildFlags(info.Flags, MPTOKEN_FLAGS),
+ mptIssuanceID: info.MPTokenIssuanceID,
+ mptIssuer: encodeAccountID(
+ hexToBytes(info.MPTokenIssuanceID.substring(8, 48)),
+ ),
+ mptAmount: info.MPTAmount ? info.MPTAmount.toString(10) : '0',
+})
+
export {
XRP_BASE,
RippledError as Error,
@@ -136,4 +177,6 @@ export {
formatAccountInfo,
convertHexToString,
formatNFTInfo,
+ formatMPTIssuanceInfo,
+ formatMPTokenInfo,
}
diff --git a/src/rippled/transactions.js b/src/rippled/transactions.js
index ed2e6835b..0dc60f1a6 100644
--- a/src/rippled/transactions.js
+++ b/src/rippled/transactions.js
@@ -8,11 +8,14 @@ const log = logger({ name: 'transactions' })
const getTransaction = (transactionId, rippledSocket) => {
log.info(`get tx: ${transactionId}`)
return getRippledTransaction(rippledSocket, transactionId)
- .then((response) => formatTransaction(response))
- .then((data) => ({
- summary: summarizeTransaction(data, true).details,
- raw: data,
- }))
+ .then((data) => {
+ const formattedTransaction = formatTransaction(data)
+ return {
+ summary: summarizeTransaction(formattedTransaction, true).details,
+ processed: formattedTransaction,
+ raw: data,
+ }
+ })
.catch((error) => {
log.error(error.toString())
throw error