Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
0b8367b
draft: first draft of the MPT-DEX feature support in Explorer
ckeshava Mar 23, 2026
cd9ff32
fix existing failing tests
ckeshava Mar 24, 2026
9453ff8
tests: OfferCreate transaction with MPT Amounts
ckeshava Mar 24, 2026
67626a2
test: Payment transaction Simple, Description and Detailed views with…
ckeshava Mar 24, 2026
13c81a6
test: AMMDelete transaction with MPT Amount
ckeshava Mar 24, 2026
54cd50a
test: AMMDeposit tests with MPT amounts
ckeshava Mar 24, 2026
5fc004f
test: AMMCreate transaction with two MPT assets
ckeshava Mar 24, 2026
5782f14
test: AMMClawback tests with MPT assets
ckeshava Mar 24, 2026
d31ee18
test: AMMWithdraw transaction with MPT asset/s
ckeshava Mar 24, 2026
8ac00cd
[trivial] linter updates
ckeshava Mar 24, 2026
ec33f9c
minor: strongly typed tx parameter in Payment.parser
ckeshava Mar 24, 2026
805e630
minor: do not use shortened MPT-ID values, except in Vault component
ckeshava Mar 24, 2026
7498ce0
fix: Ensure IOU and MPT Currencies are displayed in Green inside Tran…
ckeshava Mar 24, 2026
72a44ea
[trivial] linter complaints
ckeshava Mar 24, 2026
976236f
Merge branch 'main' into mpt-dex
ckeshava Apr 14, 2026
a3641a8
minor: refactor commonly used magic numbers/hashes
ckeshava Apr 14, 2026
a8f1f71
use Amount type instead of any type
ckeshava Apr 16, 2026
ad688b6
test: unit test file for useMPTIssuance hook
ckeshava Apr 16, 2026
416846f
minor: add unit tests for isMPTAsset and isMPTAmount methods
ckeshava Apr 16, 2026
ab4553c
Merge branch 'main' into mpt-dex
ckeshava Apr 16, 2026
d14cd92
revert unnecessary changes to transactionUtils file
ckeshava Apr 16, 2026
d749048
Merge branch 'mpt-dex' of https://github.com/ckeshava/explorer into m…
ckeshava Apr 16, 2026
599e3bc
minor: add unit test for formatAsset MPT case
ckeshava Apr 16, 2026
c82d0c3
feat: Use BigInt type to handle potentially large MPTAmount values
ckeshava Apr 16, 2026
d20a0c4
feat: remove formatAsset second def in utils.js file
ckeshava Apr 16, 2026
78c4164
minor: wrap tests with QueryClientProvider whenver Currency component…
ckeshava Apr 16, 2026
bcf8cff
Merge branch 'main' into mpt-dex
ckeshava May 4, 2026
ddf45fc
Merge branch 'main' into mpt-dex
ckeshava May 6, 2026
4a58b18
fix: resolve CI failures from React 18 / react-router 7 migration
ckeshava May 6, 2026
67ba204
feat: Use BigInt for MPTAmount subtraction in Offer meta renderer
ckeshava May 12, 2026
08e5675
feat: Preserve MPTAmount precision end-to-end in Amount rendering
ckeshava May 12, 2026
f18ce99
Merge branch 'main' into mpt-dex
ckeshava May 18, 2026
2a15173
feat: Forward isMPT flag to localizeNumber in Offer meta renderer
ckeshava May 18, 2026
b02b7c6
fix: Strip currency from BigInt branch in localizeNumber for MPT amounts
ckeshava May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 46 additions & 35 deletions src/containers/AMMPool/InfoCards/test/MarketDataCard.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { render, screen } from '@testing-library/react'
import { I18nextProvider } from 'react-i18next'
import { MemoryRouter } from 'react-router'
import { QueryClient, QueryClientProvider } from 'react-query'
import i18n from '../../../../i18n/testConfig'
import { MarketDataCard } from '../MarketDataCard'
import { TooltipProvider } from '../../../shared/components/Tooltip'
import { LOSAMMPoolData, FormattedBalance } from '../../types'

const queryClient = new QueryClient({
defaultOptions: { queries: { retry: 0 } },
})

interface RenderProps {
losData?: LOSAMMPoolData
balance1?: FormattedBalance | null
Expand Down Expand Up @@ -44,18 +49,20 @@ const renderComponent = ({
lpTokenBalance = '1000000',
}: RenderProps = {}) =>
render(
<I18nextProvider i18n={i18n}>
<MemoryRouter>
<TooltipProvider>
<MarketDataCard
losData={losData}
balance1={balance1}
balance2={balance2}
lpTokenBalance={lpTokenBalance}
/>
</TooltipProvider>
</MemoryRouter>
</I18nextProvider>,
<QueryClientProvider client={queryClient}>
<I18nextProvider i18n={i18n}>
<MemoryRouter>
<TooltipProvider>
<MarketDataCard
losData={losData}
balance1={balance1}
balance2={balance2}
lpTokenBalance={lpTokenBalance}
/>
</TooltipProvider>
</MemoryRouter>
</I18nextProvider>
</QueryClientProvider>,
)

describe('MarketDataCard', () => {
Expand Down Expand Up @@ -129,18 +136,20 @@ describe('MarketDataCard', () => {

it('does not render balance or LP rows when balances and LP are absent', () => {
render(
<I18nextProvider i18n={i18n}>
<MemoryRouter>
<TooltipProvider>
<MarketDataCard
losData={defaultLosData}
balance1={null}
balance2={null}
lpTokenBalance={undefined}
/>
</TooltipProvider>
</MemoryRouter>
</I18nextProvider>,
<QueryClientProvider client={queryClient}>
<I18nextProvider i18n={i18n}>
<MemoryRouter>
<TooltipProvider>
<MarketDataCard
losData={defaultLosData}
balance1={null}
balance2={null}
lpTokenBalance={undefined}
/>
</TooltipProvider>
</MemoryRouter>
</I18nextProvider>
</QueryClientProvider>,
)
const labels = document.querySelectorAll('.info-card-label')
const balanceLabels = Array.from(labels).filter((l) =>
Expand All @@ -152,17 +161,19 @@ describe('MarketDataCard', () => {

it('renders only balances and LP tokens when losData is undefined', () => {
const { container } = render(
<I18nextProvider i18n={i18n}>
<MemoryRouter>
<TooltipProvider>
<MarketDataCard
balance1={defaultBalance1}
balance2={defaultBalance2}
lpTokenBalance="1000000"
/>
</TooltipProvider>
</MemoryRouter>
</I18nextProvider>,
<QueryClientProvider client={queryClient}>
<I18nextProvider i18n={i18n}>
<MemoryRouter>
<TooltipProvider>
<MarketDataCard
balance1={defaultBalance1}
balance2={defaultBalance2}
lpTokenBalance="1000000"
/>
</TooltipProvider>
</MemoryRouter>
</I18nextProvider>
</QueryClientProvider>,
)
// LOS fields hidden
expect(screen.queryByText('tvl')).not.toBeInTheDocument()
Expand Down
17 changes: 12 additions & 5 deletions src/containers/AMMPool/test/AMMPoolHeader.test.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
import { render, screen } from '@testing-library/react'
import { MemoryRouter } from 'react-router'
import { I18nextProvider } from 'react-i18next'
import { QueryClient, QueryClientProvider } from 'react-query'
import i18n from '../../../i18n/testConfig'
import { AMMPoolHeader } from '../AMMPoolHeader'
import { FormattedBalance } from '../types'

const queryClient = new QueryClient({
defaultOptions: { queries: { retry: 0 } },
})

const renderComponent = (
asset1: FormattedBalance | null = null,
asset2: FormattedBalance | null = null,
) =>
render(
<I18nextProvider i18n={i18n}>
<MemoryRouter>
<AMMPoolHeader asset1={asset1} asset2={asset2} />
</MemoryRouter>
</I18nextProvider>,
<QueryClientProvider client={queryClient}>
<I18nextProvider i18n={i18n}>
<MemoryRouter>
<AMMPoolHeader asset1={asset1} asset2={asset2} />
</MemoryRouter>
</I18nextProvider>
</QueryClientProvider>,
)

describe('AMMPoolHeader', () => {
Expand Down
117 changes: 97 additions & 20 deletions src/containers/Transactions/DetailTab/Meta/Offer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,52 @@ import {
import { localizeNumber } from '../../../shared/utils'
import { Account } from '../../../shared/components/Account'
import Currency from '../../../shared/components/Currency'
import type { Amount } from '../../../shared/types'
import type { MetaRenderFunctionWithTx, MetaNode } from './types'

const normalize = (value: number | string, currency: string): string =>
currency === 'XRP' ? (Number(value) / XRP_BASE).toString() : String(value)
const isMPTOrIOUAmount = (
takerAmount: Amount | undefined,
): takerAmount is Exclude<Amount, string> =>
typeof takerAmount === 'object' && takerAmount !== null

const getCurrency = (takerAmount: Amount | undefined): string => {
if (!isMPTOrIOUAmount(takerAmount)) return 'XRP'
if ('mpt_issuance_id' in takerAmount) return takerAmount.mpt_issuance_id
return takerAmount.currency || 'XRP'
}

const getIsMPT = (takerAmount: Amount | undefined): boolean =>
isMPTOrIOUAmount(takerAmount) && 'mpt_issuance_id' in takerAmount

const getIssuer = (takerAmount: Amount | undefined): string | undefined => {
if (!isMPTOrIOUAmount(takerAmount)) return undefined
if ('mpt_issuance_id' in takerAmount) return undefined
return takerAmount.issuer
}

const normalize = (
value: number | string,
currency: string,
isMPT: boolean = false,
): string => {
if (isMPT) return String(value)
return currency === 'XRP'
? (Number(value) / XRP_BASE).toString()
: String(value)
}

// MPTAmount can be up to 2^63 - 1, beyond Number.MAX_SAFE_INTEGER,
// so subtract with BigInt to preserve precision.
const computeChange = (
prevValue: number | string | undefined,
finalValue: number | string | undefined,
isMPT: boolean,
): number | string => {
if (isMPT && prevValue != null && finalValue != null) {
return (BigInt(prevValue) - BigInt(finalValue)).toString()
}
return Number(prevValue) - Number(finalValue)
}

const renderChanges = (
_t: any,
Expand All @@ -22,14 +64,24 @@ const renderChanges = (
const meta: JSX.Element[] = []
const final = node.FinalFields
const prev = node?.PreviousFields
const paysCurrency = final.TakerPays.currency || 'XRP'
const getsCurrency = final.TakerGets.currency || 'XRP'
const paysCurrency = getCurrency(final.TakerPays)
const getsCurrency = getCurrency(final.TakerGets)
const paysIsMPT = getIsMPT(final.TakerPays)
const getsIsMPT = getIsMPT(final.TakerGets)
const finalPays = final.TakerPays.value || final.TakerPays
const finalGets = final.TakerGets.value || final.TakerGets
const prevPays = prev?.TakerPays?.value || prev?.TakerPays
const prevGets = prev?.TakerGets?.value || prev?.TakerGets
const changePays = normalize(prevPays - finalPays, paysCurrency)
const changeGets = normalize(prevGets - finalGets, getsCurrency)
const changePays = normalize(
computeChange(prevPays, finalPays, paysIsMPT),
paysCurrency,
paysIsMPT,
)
const changeGets = normalize(
computeChange(prevGets, finalGets, getsIsMPT),
getsCurrency,
getsIsMPT,
)

if (prevPays && finalPays) {
const options = { ...CURRENCY_OPTIONS, currency: paysCurrency }
Expand All @@ -39,7 +91,8 @@ const renderChanges = (
<b>
<Currency
currency={paysCurrency}
issuer={final.TakerPays.issuer}
issuer={getIssuer(final.TakerPays)}
isMPT={paysIsMPT}
displaySymbol={false}
/>
</b>{' '}
Expand All @@ -48,7 +101,12 @@ const renderChanges = (
<b>
{
{
change: localizeNumber(changePays, language, options),
change: localizeNumber(
changePays,
language,
options,
paysIsMPT,
),
} as any
}
</b>
Expand All @@ -57,9 +115,10 @@ const renderChanges = (
{
{
previous: localizeNumber(
normalize(prevPays, paysCurrency),
normalize(prevPays, paysCurrency, paysIsMPT),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

localizeNumber has a argument isMPT, should we set it in all of the localizeNumber calls here (e.g payIsMPT) so that BigInt will be execute?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, fixed in 2a15173

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

follow-up in b02b7c6: added regression tests in Meta.test.tsx covering all 6 localizeNumber calls, plus mixed MPT/XRP offers and the MPToken/MPTokenIssuance renderers. they surfaced a small bug where Intl.NumberFormat rejected the mpt_issuance_id as a currency code (it validates currency even with style: decimal) — fix is in the same commit.

language,
options,
paysIsMPT,
),
} as any
}
Expand All @@ -69,9 +128,10 @@ const renderChanges = (
{
{
final: localizeNumber(
normalize(finalPays, paysCurrency),
normalize(finalPays, paysCurrency, paysIsMPT),
language,
options,
paysIsMPT,
),
} as any
}
Expand All @@ -88,7 +148,8 @@ const renderChanges = (
<b>
<Currency
currency={getsCurrency}
issuer={final.TakerGets.issuer}
issuer={getIssuer(final.TakerGets)}
isMPT={getsIsMPT}
displaySymbol={false}
/>
</b>{' '}
Expand All @@ -97,7 +158,12 @@ const renderChanges = (
<b>
{
{
change: localizeNumber(changeGets, language, options),
change: localizeNumber(
changeGets,
language,
options,
getsIsMPT,
),
} as any
}
</b>
Expand All @@ -106,9 +172,10 @@ const renderChanges = (
{
{
previous: localizeNumber(
normalize(prevGets, getsCurrency),
normalize(prevGets, getsCurrency, getsIsMPT),
language,
options,
getsIsMPT,
),
} as any
}
Expand All @@ -118,9 +185,10 @@ const renderChanges = (
{
{
final: localizeNumber(
normalize(finalGets, getsCurrency),
normalize(finalGets, getsCurrency, getsIsMPT),
language,
options,
getsIsMPT,
),
} as any
}
Expand All @@ -143,11 +211,14 @@ const render: MetaRenderFunctionWithTx = (
) => {
const lines: JSX.Element[] = []
const fields = node.FinalFields || node.NewFields
const paysCurrency = fields.TakerPays.currency || 'XRP'
const getsCurrency = fields.TakerGets.currency || 'XRP'
const paysCurrency = getCurrency(fields.TakerPays)
const getsCurrency = getCurrency(fields.TakerGets)
const paysIsMPT = getIsMPT(fields.TakerPays)
const getsIsMPT = getIsMPT(fields.TakerGets)
const takerPaysValue = normalize(
fields.TakerPays.value || fields.TakerPays,
paysCurrency,
paysIsMPT,
)
const invert =
CURRENCY_ORDER.indexOf(getsCurrency) > CURRENCY_ORDER.indexOf(paysCurrency)
Expand Down Expand Up @@ -214,16 +285,22 @@ const render: MetaRenderFunctionWithTx = (
components={{
Currency: (
<Currency
currency={(invert ? getsCurrency : paysCurrency) || 'XRP'}
issuer={invert ? tx.TakerGets?.issuer : tx.TakerPays?.issuer}
currency={invert ? getsCurrency : paysCurrency}
issuer={
invert ? getIssuer(tx.TakerGets) : getIssuer(tx.TakerPays)
}
isMPT={invert ? getsIsMPT : paysIsMPT}
displaySymbol={false}
shortenIssuer
/>
),
Currency2: (
<Currency
currency={(invert ? paysCurrency : getsCurrency) || 'XRP'}
issuer={invert ? tx.TakerPays?.issuer : tx.TakerGets?.issuer}
currency={invert ? paysCurrency : getsCurrency}
issuer={
invert ? getIssuer(tx.TakerPays) : getIssuer(tx.TakerGets)
}
isMPT={invert ? paysIsMPT : getsIsMPT}
displaySymbol={false}
shortenIssuer
/>
Expand Down
8 changes: 8 additions & 0 deletions src/containers/Transactions/simpleTab.scss
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,14 @@ $subdued-color: $black-40;
}
}

a.currency {
color: $green-30;

&:hover {
color: $green-50;
}
}

&.list {
margin-bottom: 12px;

Expand Down
Loading
Loading