diff --git a/package-lock.json b/package-lock.json
index 9ea2d4cdc..bbdf5a7a9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -44,6 +44,7 @@
"recharts": "^2.15.3",
"ripple-address-codec": "^5.0.0",
"ripple-binary-codec": "^2.9.0",
+ "tldts": "^6.1.86",
"topojson-client": "^3.0.0",
"usehooks-ts": "^3.1.0",
"vite": "^7.3.2",
diff --git a/package.json b/package.json
index cc8d56238..5e4abf037 100644
--- a/package.json
+++ b/package.json
@@ -42,6 +42,7 @@
"recharts": "^2.15.3",
"ripple-address-codec": "^5.0.0",
"ripple-binary-codec": "^2.9.0",
+ "tldts": "^6.1.86",
"topojson-client": "^3.0.0",
"usehooks-ts": "^3.1.0",
"vite": "^7.3.2",
diff --git a/public/locales/ca-CA/translations.json b/public/locales/ca-CA/translations.json
index bc4553c11..cb84b3477 100644
--- a/public/locales/ca-CA/translations.json
+++ b/public/locales/ca-CA/translations.json
@@ -740,7 +740,6 @@
"account_page_asset_table_column_asset_class": null,
"account_page_asset_table_column_balance": null,
"account_page_asset_table_column_balance_usd": null,
- "account_page_asset_table_column_circulating_supply": null,
"account_page_asset_table_column_currency_code": null,
"account_page_asset_table_column_frozen": null,
"account_page_asset_table_column_highest_bid": null,
diff --git a/public/locales/en-US/translations.json b/public/locales/en-US/translations.json
index 8c55a6a7f..8bb5c5133 100644
--- a/public/locales/en-US/translations.json
+++ b/public/locales/en-US/translations.json
@@ -741,7 +741,6 @@
"account_page_asset_table_column_asset_class": "Asset Class",
"account_page_asset_table_column_balance": "Balance",
"account_page_asset_table_column_balance_usd": "Balance (USD)",
- "account_page_asset_table_column_circulating_supply": "Circ Supply",
"account_page_asset_table_column_confidential_balance": "Conf Balance",
"account_page_asset_table_column_currency_code": "Currency Code",
"account_page_asset_table_column_frozen": "Frozen",
diff --git a/public/locales/es-ES/translations.json b/public/locales/es-ES/translations.json
index 12e1a48f4..5e80854b4 100644
--- a/public/locales/es-ES/translations.json
+++ b/public/locales/es-ES/translations.json
@@ -741,7 +741,6 @@
"account_page_asset_table_column_asset_class": null,
"account_page_asset_table_column_balance": null,
"account_page_asset_table_column_balance_usd": null,
- "account_page_asset_table_column_circulating_supply": null,
"account_page_asset_table_column_currency_code": null,
"account_page_asset_table_column_frozen": null,
"account_page_asset_table_column_highest_bid": null,
diff --git a/public/locales/fr-FR/translations.json b/public/locales/fr-FR/translations.json
index 99bb71f9e..a2e67b4b5 100644
--- a/public/locales/fr-FR/translations.json
+++ b/public/locales/fr-FR/translations.json
@@ -741,7 +741,6 @@
"account_page_asset_table_column_asset_class": null,
"account_page_asset_table_column_balance": null,
"account_page_asset_table_column_balance_usd": null,
- "account_page_asset_table_column_circulating_supply": null,
"account_page_asset_table_column_currency_code": null,
"account_page_asset_table_column_frozen": null,
"account_page_asset_table_column_highest_bid": null,
diff --git a/public/locales/ja-JP/translations.json b/public/locales/ja-JP/translations.json
index 8229d9e98..f64660fdd 100644
--- a/public/locales/ja-JP/translations.json
+++ b/public/locales/ja-JP/translations.json
@@ -741,7 +741,6 @@
"account_page_asset_table_column_asset_class": null,
"account_page_asset_table_column_balance": null,
"account_page_asset_table_column_balance_usd": null,
- "account_page_asset_table_column_circulating_supply": null,
"account_page_asset_table_column_currency_code": null,
"account_page_asset_table_column_frozen": null,
"account_page_asset_table_column_highest_bid": null,
diff --git a/public/locales/ko-KR/translations.json b/public/locales/ko-KR/translations.json
index d02b1dd5c..cfeddbd31 100644
--- a/public/locales/ko-KR/translations.json
+++ b/public/locales/ko-KR/translations.json
@@ -741,7 +741,6 @@
"account_page_asset_table_column_asset_class": null,
"account_page_asset_table_column_balance": null,
"account_page_asset_table_column_balance_usd": null,
- "account_page_asset_table_column_circulating_supply": null,
"account_page_asset_table_column_currency_code": null,
"account_page_asset_table_column_frozen": null,
"account_page_asset_table_column_highest_bid": null,
diff --git a/public/locales/my-MM/translations.json b/public/locales/my-MM/translations.json
index c1ef818ab..b948a258d 100644
--- a/public/locales/my-MM/translations.json
+++ b/public/locales/my-MM/translations.json
@@ -741,7 +741,6 @@
"account_page_asset_table_column_asset_class": null,
"account_page_asset_table_column_balance": null,
"account_page_asset_table_column_balance_usd": null,
- "account_page_asset_table_column_circulating_supply": null,
"account_page_asset_table_column_currency_code": null,
"account_page_asset_table_column_frozen": null,
"account_page_asset_table_column_highest_bid": null,
diff --git a/src/containers/Accounts/AccountAsset/assetTables/IssuedMPTs.tsx b/src/containers/Accounts/AccountAsset/assetTables/IssuedMPTs.tsx
index 0b47cdd14..273837ab4 100644
--- a/src/containers/Accounts/AccountAsset/assetTables/IssuedMPTs.tsx
+++ b/src/containers/Accounts/AccountAsset/assetTables/IssuedMPTs.tsx
@@ -115,7 +115,7 @@ const IssuedMPTsContent = ({ accountId, onChange }: IssuedMPTsProps) => {
{t('account_page_asset_table_column_token_id')} |
{t('account_page_asset_table_column_ticker')} |
{t('account_page_asset_table_column_price_usd')} |
- {t('account_page_asset_table_column_circulating_supply')} |
+ {t('account_page_asset_table_column_supply')} |
{t('account_page_asset_table_column_confidential_balance')}{' '}
diff --git a/src/containers/Accounts/AccountAsset/assetTables/NFTTable.tsx b/src/containers/Accounts/AccountAsset/assetTables/NFTTable.tsx
index f3b3b37b7..1f19cd93e 100644
--- a/src/containers/Accounts/AccountAsset/assetTables/NFTTable.tsx
+++ b/src/containers/Accounts/AccountAsset/assetTables/NFTTable.tsx
@@ -16,9 +16,9 @@ import { XRP_BASE } from '../../../shared/transactionUtils'
import {
localizeNumber,
shortenAccount,
- shortenDomain,
shortenNFTTokenID,
} from '../../../shared/utils'
+import { shortenDomain } from '../../../shared/domainUtils'
import { XRP_SMALL_BALANCE_CURRENCY_OPTIONS } from '../../../shared/NumberFormattingUtils'
import { useLanguage } from '../../../shared/hooks'
import logger from '../../../../rippled/lib/logger'
diff --git a/src/containers/Accounts/AccountAsset/test/IssuedMPTs.test.tsx b/src/containers/Accounts/AccountAsset/test/IssuedMPTs.test.tsx
index c9190bc3e..b12ec04cf 100644
--- a/src/containers/Accounts/AccountAsset/test/IssuedMPTs.test.tsx
+++ b/src/containers/Accounts/AccountAsset/test/IssuedMPTs.test.tsx
@@ -90,7 +90,7 @@ describe('IssuedMPTs', () => {
})
expect(screen.getByText('Ticker')).toBeInTheDocument()
expect(screen.getByText('Price (USD)')).toBeInTheDocument()
- expect(screen.getByText('Circ Supply')).toBeInTheDocument()
+ expect(screen.getByText('Supply')).toBeInTheDocument()
expect(screen.getByText('Conf Balance')).toBeInTheDocument()
expect(screen.getByText('Asset Class')).toBeInTheDocument()
expect(screen.getByText('Transfer Fee')).toBeInTheDocument()
diff --git a/src/containers/Token/IOU/Header/index.tsx b/src/containers/Token/IOU/Header/index.tsx
index b826b5091..c2c883b7f 100644
--- a/src/containers/Token/IOU/Header/index.tsx
+++ b/src/containers/Token/IOU/Header/index.tsx
@@ -18,7 +18,11 @@ import {
parsePercent,
parsePrice,
} from '../../../shared/NumberFormattingUtils'
-import { shortenDomain, stripHttpProtocol } from '../../../shared/utils'
+import {
+ getRegistrableDomain,
+ shortenDomainFromLeft,
+} from '../../../shared/domainUtils'
+import { calculateIouCirculatingSupply } from '../../shared/utils/circulatingSupply'
interface HeaderProps {
currency: string
@@ -30,27 +34,6 @@ interface HeaderProps {
isAmmTvlLoading: boolean
}
-const calculateCirculatingSupply = (
- tokenData: LOSToken,
- holdersData: TokenHoldersData | undefined,
-): number => {
- if (tokenData.circ_supply) {
- return Number(tokenData.circ_supply)
- }
- let circSupply = Number(tokenData.supply) || holdersData?.totalSupply || 0
-
- // For stablecoins, don't subtract large percentage holders from circulating supply
- if (tokenData.asset_subclass !== 'stablecoin' && holdersData) {
- holdersData.holders.forEach((holder) => {
- if (holder.percent >= 20) {
- circSupply -= holder.balance
- }
- })
- }
-
- return circSupply
-}
-
export const Header = ({
currency,
tokenData,
@@ -61,7 +44,7 @@ export const Header = ({
isAmmTvlLoading,
}: HeaderProps) => {
const { t } = useTranslation()
- const circSupply = calculateCirculatingSupply(tokenData, holdersData)
+ const circSupply = calculateIouCirculatingSupply(tokenData, holdersData)
const xrpRate = Number(xrpUSDRate) || 0
// Memoized formatted overview data
@@ -93,11 +76,15 @@ export const Header = ({
const volume24hNum = Number(tokenData.daily_volume) || 0
const trades24hNum = Number(tokenData.daily_trades) || 0
- // Format supply values
- const formattedSupply = parseAmount(
- holdersData?.totalSupply?.toString() || tokenData.supply || '0',
- )
- const formattedCircSupply = parseAmount(circSupplyNum)
+ // Format supply values; a zero supply isn't meaningful, so show "--"
+ const supplyValue =
+ holdersData?.totalSupply?.toString() || tokenData.supply || '0'
+ const formattedSupply = Number(supplyValue)
+ ? parseAmount(supplyValue)
+ : '--'
+ const formattedCircSupply = circSupplyNum
+ ? parseAmount(circSupplyNum)
+ : '--'
// Calculate market cap
let marketCap: string | null = null
@@ -178,11 +165,10 @@ export const Header = ({
diff --git a/src/containers/Token/IOU/hooks/useMarketCalculations.ts b/src/containers/Token/IOU/hooks/useMarketCalculations.ts
deleted file mode 100644
index 4323830ca..000000000
--- a/src/containers/Token/IOU/hooks/useMarketCalculations.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { useMemo } from 'react'
-import { formatPrice } from '../../../shared/utils'
-import { TokenHoldersData } from '../api/holders'
-import { LOSToken } from '../../../shared/losTypes'
-import {
- calculateCirculatingSupply,
- formatCirculatingSupply,
-} from '../utils/tokenCalculations'
-
-interface MarketCalculations {
- marketCap: string | null
- circSupply: number
- formattedCircSupply: string
-}
-
-interface UseMarketCalculationsProps {
- holdersData: TokenHoldersData | undefined
- tokenData: LOSToken
- price: string
- xrpUSDRate: string
- isHoldersDataLoading: boolean
-}
-
-export const useMarketCalculations = ({
- holdersData,
- tokenData,
- price,
- xrpUSDRate,
- isHoldersDataLoading,
-}: UseMarketCalculationsProps): MarketCalculations =>
- useMemo(() => {
- const circSupply = calculateCirculatingSupply(holdersData, tokenData)
-
- const marketCap =
- !isHoldersDataLoading && circSupply && price && xrpUSDRate
- ? formatPrice(
- Number(circSupply) * Number(price) * Number(xrpUSDRate),
- ) || null
- : null
-
- const formattedCircSupply = formatCirculatingSupply(circSupply)
-
- return {
- marketCap,
- circSupply,
- formattedCircSupply,
- }
- }, [holdersData, tokenData, price, xrpUSDRate, isHoldersDataLoading])
diff --git a/src/containers/Token/IOU/test/Header/Header.test.tsx b/src/containers/Token/IOU/test/Header/Header.test.tsx
index 5f048d197..e18436bbb 100644
--- a/src/containers/Token/IOU/test/Header/Header.test.tsx
+++ b/src/containers/Token/IOU/test/Header/Header.test.tsx
@@ -12,8 +12,12 @@ jest.mock('../../../../shared/components/Currency', () => ({
default: ({ currency }: { currency: string }) => {currency} ,
}))
+const mockHeaderBoxesSpy = jest.fn()
jest.mock('../../components/HeaderBoxes', () => ({
- HeaderBoxes: () => HeaderBoxes ,
+ HeaderBoxes: (props: any) => {
+ mockHeaderBoxesSpy(props)
+ return HeaderBoxes
+ },
}))
jest.mock('../../../../shared/components/Account', () => ({
@@ -31,8 +35,6 @@ jest.mock('../../../../shared/utils', () => ({
return { num: d.toString(), unit: '' }
},
formatSmallNumber: (value: number) => value.toFixed(4),
- shortenDomain: (domain: string) => domain,
- stripHttpProtocol: (url: string) => url.replace(/^https?:\/\//, ''),
}))
const TestWrapper = ({ children }: { children: React.ReactNode }) => (
@@ -377,4 +379,21 @@ describe('Header Component', () => {
)
expect(screen.getByText('USD')).toBeInTheDocument()
})
+ it('shows -- instead of a formatted zero for supply and circ supply', () => {
+ mockHeaderBoxesSpy.mockClear()
+ render(
+
+
+ ,
+ )
+ const { marketData } = mockHeaderBoxesSpy.mock.calls.slice(-1)[0][0]
+ expect(marketData.supply).toBe('--')
+ expect(marketData.circ_supply).toBe('--')
+ })
})
diff --git a/src/containers/Token/IOU/test/hooks/useMarketCalculations.test.ts b/src/containers/Token/IOU/test/hooks/useMarketCalculations.test.ts
deleted file mode 100644
index 040ffdf52..000000000
--- a/src/containers/Token/IOU/test/hooks/useMarketCalculations.test.ts
+++ /dev/null
@@ -1,273 +0,0 @@
-import { renderHook } from '@testing-library/react'
-import { useMarketCalculations } from '../../hooks/useMarketCalculations'
-import { TokenHoldersData } from '../../api/holders'
-import { LOSToken } from '../../../../shared/losTypes'
-
-describe('useMarketCalculations', () => {
- const mockTokenData: LOSToken = {
- currency: 'USD',
- issuer_account: 'rIssuer',
- trustlines: 100,
- index: 0,
- }
-
- const mockHoldersData: TokenHoldersData = {
- totalSupply: 1000000,
- totalHolders: 100,
- holders: [],
- }
-
- it('returns market calculations with valid data', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '0.5',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- }),
- )
-
- expect(result.current.circSupply).toBe(1000000)
- expect(result.current.formattedCircSupply).toBeDefined()
- expect(result.current.marketCap).toBeDefined()
- })
-
- it('returns null marketCap when isHoldersDataLoading is true', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '0.5',
- xrpUSDRate: '2',
- isHoldersDataLoading: true,
- }),
- )
-
- expect(result.current.marketCap).toBeNull()
- })
-
- it('returns null marketCap when circSupply is 0', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: undefined,
- tokenData: mockTokenData,
- price: '0.5',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- }),
- )
-
- expect(result.current.marketCap).toBeNull()
- })
-
- it('returns null marketCap when price is empty', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- }),
- )
-
- expect(result.current.marketCap).toBeNull()
- })
-
- it('returns null marketCap when xrpUSDRate is empty', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '0.5',
- xrpUSDRate: '',
- isHoldersDataLoading: false,
- }),
- )
-
- expect(result.current.marketCap).toBeNull()
- })
-
- it('calculates marketCap correctly with valid inputs', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '1',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- }),
- )
-
- // 1000000 * 1 * 2 = 2000000
- expect(result.current.marketCap).toBeDefined()
- })
-
- it('handles zero price', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '0',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- }),
- )
-
- expect(result.current.marketCap).toBeNull()
- })
-
- it('handles zero xrpUSDRate', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '0.5',
- xrpUSDRate: '0',
- isHoldersDataLoading: false,
- }),
- )
-
- expect(result.current.marketCap).toBeNull()
- })
-
- it('handles very small price values', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '0.0001',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- }),
- )
-
- expect(result.current.marketCap).toBeDefined()
- })
-
- it('handles very large price values', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '1000000',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- }),
- )
-
- expect(result.current.marketCap).toBeDefined()
- })
-
- it('formats circulating supply', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '0.5',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- }),
- )
-
- expect(result.current.formattedCircSupply).toBeDefined()
- expect(typeof result.current.formattedCircSupply).toBe('string')
- })
-
- it('returns circSupply as number', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '0.5',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- }),
- )
-
- expect(typeof result.current.circSupply).toBe('number')
- })
-
- it('handles undefined holdersData', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: undefined,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '0.5',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- }),
- )
-
- expect(result.current.circSupply).toBe(1000000)
- })
-
- it('handles circ_supply in tokenData', () => {
- const { result } = renderHook(() =>
- useMarketCalculations({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, circ_supply: '500000' },
- price: '0.5',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- }),
- )
-
- expect(result.current.circSupply).toBe(500000)
- })
-
- it('memoizes results correctly', () => {
- const { result, rerender } = renderHook(
- (props) => useMarketCalculations(props),
- {
- initialProps: {
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '0.5',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- },
- },
- )
-
- const firstResult = result.current
-
- rerender({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '0.5',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- })
-
- expect(result.current).toEqual(firstResult)
- })
-
- it('updates when dependencies change', () => {
- const { result, rerender } = renderHook(
- (props) => useMarketCalculations(props),
- {
- initialProps: {
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '1000000' },
- price: '0.5',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- },
- },
- )
-
- const firstResult = result.current
-
- rerender({
- holdersData: mockHoldersData,
- tokenData: { ...mockTokenData, supply: '2000000' },
- price: '0.5',
- xrpUSDRate: '2',
- isHoldersDataLoading: false,
- })
-
- expect(result.current.circSupply).not.toEqual(firstResult.circSupply)
- })
-})
diff --git a/src/containers/Token/IOU/test/utils/tokenCalculations.test.ts b/src/containers/Token/IOU/test/utils/tokenCalculations.test.ts
deleted file mode 100644
index 0e4701ccc..000000000
--- a/src/containers/Token/IOU/test/utils/tokenCalculations.test.ts
+++ /dev/null
@@ -1,217 +0,0 @@
-import {
- calculateCirculatingSupply,
- formatCirculatingSupply,
-} from '../../utils/tokenCalculations'
-import { TokenHoldersData } from '../../api/holders'
-import { LOSToken } from '../../../../shared/losTypes'
-
-describe('tokenCalculations', () => {
- describe('calculateCirculatingSupply', () => {
- const mockTokenData: LOSToken = {
- currency: 'USD',
- issuer_account: 'rIssuer',
- trustlines: 100,
- index: 0,
- }
-
- it('returns circ_supply from tokenData if available', () => {
- const tokenData = {
- ...mockTokenData,
- circ_supply: '1000000',
- }
- const result = calculateCirculatingSupply(undefined, tokenData)
- expect(result).toBe(1000000)
- })
-
- it('returns supply from tokenData when circ_supply is not available', () => {
- const tokenData = {
- ...mockTokenData,
- supply: '5000000',
- }
- const result = calculateCirculatingSupply(undefined, tokenData)
- expect(result).toBe(5000000)
- })
-
- it('returns totalSupply from holdersData when tokenData supply is not available', () => {
- const holdersData: TokenHoldersData = {
- totalSupply: 3000000,
- totalHolders: 100,
- holders: [],
- }
- const tokenData = {
- ...mockTokenData,
- }
- const result = calculateCirculatingSupply(holdersData, tokenData)
- expect(result).toBe(3000000)
- })
-
- it('returns 0 when no supply data is available', () => {
- const tokenData = {
- ...mockTokenData,
- }
- const result = calculateCirculatingSupply(undefined, tokenData)
- expect(result).toBe(0)
- })
-
- it('subtracts large holders (>= 20%) for non-stablecoin tokens', () => {
- const holdersData: TokenHoldersData = {
- totalSupply: 1000000,
- totalHolders: 5,
- holders: [
- { account: 'rHolder1', balance: 300000, percent: 30 },
- { account: 'rHolder2', balance: 200000, percent: 20 },
- { account: 'rHolder3', balance: 100000, percent: 10 },
- ],
- }
- const tokenData = {
- ...mockTokenData,
- supply: '1000000',
- asset_subclass: 'token',
- }
- const result = calculateCirculatingSupply(holdersData, tokenData)
- // 1000000 - 300000 (30%) - 200000 (20%) = 500000
- expect(result).toBe(500000)
- })
-
- it('does not subtract large holders for stablecoin tokens', () => {
- const holdersData: TokenHoldersData = {
- totalSupply: 1000000,
- totalHolders: 5,
- holders: [
- { account: 'rHolder1', balance: 300000, percent: 30 },
- { account: 'rHolder2', balance: 200000, percent: 20 },
- { account: 'rHolder3', balance: 100000, percent: 10 },
- ],
- }
- const tokenData = {
- ...mockTokenData,
- supply: '1000000',
- asset_subclass: 'stablecoin',
- }
- const result = calculateCirculatingSupply(holdersData, tokenData)
- // For stablecoins, no subtraction: 1000000
- expect(result).toBe(1000000)
- })
-
- it('handles holders with exactly 20% threshold', () => {
- const holdersData: TokenHoldersData = {
- totalSupply: 1000000,
- totalHolders: 5,
- holders: [
- { account: 'rHolder1', balance: 200000, percent: 20 },
- { account: 'rHolder2', balance: 100000, percent: 10 },
- ],
- }
- const tokenData = {
- ...mockTokenData,
- supply: '1000000',
- asset_subclass: 'token',
- }
- const result = calculateCirculatingSupply(holdersData, tokenData)
- // 1000000 - 200000 (20%) = 800000
- expect(result).toBe(800000)
- })
-
- it('handles holders with less than 20% threshold', () => {
- const holdersData: TokenHoldersData = {
- totalSupply: 1000000,
- totalHolders: 5,
- holders: [
- { account: 'rHolder1', balance: 190000, percent: 19 },
- { account: 'rHolder2', balance: 100000, percent: 10 },
- ],
- }
- const tokenData = {
- ...mockTokenData,
- supply: '1000000',
- asset_subclass: 'token',
- }
- const result = calculateCirculatingSupply(holdersData, tokenData)
- // No subtraction: 1000000
- expect(result).toBe(1000000)
- })
-
- it('handles empty holders array', () => {
- const holdersData: TokenHoldersData = {
- totalSupply: 1000000,
- totalHolders: 0,
- holders: [],
- }
- const tokenData = {
- ...mockTokenData,
- supply: '1000000',
- asset_subclass: 'token',
- }
- const result = calculateCirculatingSupply(holdersData, tokenData)
- expect(result).toBe(1000000)
- })
-
- it('handles multiple large holders', () => {
- const holdersData: TokenHoldersData = {
- totalSupply: 1000000,
- totalHolders: 10,
- holders: [
- { account: 'rHolder1', balance: 250000, percent: 25 },
- { account: 'rHolder2', balance: 250000, percent: 25 },
- { account: 'rHolder3', balance: 250000, percent: 25 },
- { account: 'rHolder4', balance: 250000, percent: 25 },
- ],
- }
- const tokenData = {
- ...mockTokenData,
- supply: '1000000',
- asset_subclass: 'token',
- }
- const result = calculateCirculatingSupply(holdersData, tokenData)
- // 1000000 - (250000 * 4) = 0
- expect(result).toBe(0)
- })
-
- it('prioritizes circ_supply over supply', () => {
- const holdersData: TokenHoldersData = {
- totalSupply: 5000000,
- totalHolders: 100,
- holders: [],
- }
- const tokenData = {
- ...mockTokenData,
- circ_supply: '2000000',
- supply: '3000000',
- }
- const result = calculateCirculatingSupply(holdersData, tokenData)
- expect(result).toBe(2000000)
- })
- })
-
- describe('formatCirculatingSupply', () => {
- it('formats small numbers with full precision', () => {
- const result = formatCirculatingSupply(100)
- expect(result).toBe('100')
- })
-
- it('formats large numbers with abbreviation', () => {
- const result = formatCirculatingSupply(1000000)
- expect(result).toMatch(/1\.0M|1M/)
- })
-
- it('formats very large numbers with abbreviation', () => {
- const result = formatCirculatingSupply(1000000000)
- expect(result).toMatch(/1\.0B|1B/)
- })
-
- it('formats zero', () => {
- const result = formatCirculatingSupply(0)
- expect(result).toBe('0')
- })
-
- it('formats decimal numbers', () => {
- const result = formatCirculatingSupply(1234.5678)
- expect(result).toBeDefined()
- })
-
- it('handles very small numbers', () => {
- const result = formatCirculatingSupply(0.0001)
- expect(result).toBeDefined()
- })
- })
-})
diff --git a/src/containers/Token/IOU/utils/tokenCalculations.ts b/src/containers/Token/IOU/utils/tokenCalculations.ts
deleted file mode 100644
index 350b204eb..000000000
--- a/src/containers/Token/IOU/utils/tokenCalculations.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { parseIntegerAmount } from '../../../shared/NumberFormattingUtils'
-import { TokenHoldersData } from '../api/holders'
-import { LOSToken } from '../../../shared/losTypes'
-
-/**
- * Calculates the circulating supply for a token
- * @param holdersData - Token holders data
- * @param tokenData - Token data
- * @returns Calculated circulating supply
- */
-export const calculateCirculatingSupply = (
- holdersData: TokenHoldersData | undefined,
- tokenData: LOSToken,
-): number => {
- if (tokenData.circ_supply) {
- return Number(tokenData.circ_supply)
- }
- let circSupply = Number(tokenData.supply) || holdersData?.totalSupply || 0
-
- // For stablecoins, don't subtract large percentage holders from circulating supply
- if (tokenData.asset_subclass !== 'stablecoin' && holdersData) {
- holdersData.holders.forEach((holder) => {
- if (holder.percent >= 20) {
- circSupply -= holder.balance
- }
- })
- }
-
- return circSupply
-}
-
-/**
- * Formats the circulating supply for display
- * @param circSupply - The circulating supply number
- * @returns Formatted circulating supply string
- */
-export const formatCirculatingSupply = (circSupply: number): string =>
- parseIntegerAmount(circSupply)
diff --git a/src/containers/Token/MPT/Header/GeneralOverview.tsx b/src/containers/Token/MPT/Header/GeneralOverview.tsx
index aa3d8e242..a73e1df2b 100644
--- a/src/containers/Token/MPT/Header/GeneralOverview.tsx
+++ b/src/containers/Token/MPT/Header/GeneralOverview.tsx
@@ -36,10 +36,10 @@ export const GeneralOverview = ({
{t('token_page.issuer')}
-
@@ -60,7 +60,7 @@ export const GeneralOverview = ({
{t('token_page.transfer_fee')}
- {(transferFee && parsePercent(transferFee / 1000, 3)) ?? '--'}
+ {transferFee ? parsePercent(transferFee / 1000, 3) : '--'}
diff --git a/src/containers/Token/MPT/Header/MarketData.tsx b/src/containers/Token/MPT/Header/MarketData.tsx
index 8fe2c314e..45250cb9b 100644
--- a/src/containers/Token/MPT/Header/MarketData.tsx
+++ b/src/containers/Token/MPT/Header/MarketData.tsx
@@ -5,28 +5,41 @@ import { Tooltip, useTooltip } from '../../../shared/components/Tooltip'
import { ConfBalanceTooltipIcon } from '../../../shared/components/ConfBalanceTooltipIcon'
interface MarketDataProps {
- maxAmt?: string
outstandingAmt?: string
confidentialOutstandingAmt?: string
assetScale?: number
+ // Circulating supply (outstanding minus large holders), scaled decimal string.
+ circulatingSupply?: string
+ circulatingSupplyLoading?: boolean
}
export const MarketData = ({
- maxAmt,
outstandingAmt,
confidentialOutstandingAmt,
assetScale,
+ circulatingSupply,
+ circulatingSupplyLoading,
}: MarketDataProps): JSX.Element => {
const { t } = useTranslation()
const { tooltip } = useTooltip()
- const formattedSupply = parseAmount(
- convertScaledPrice(BigInt(maxAmt || '0'), assetScale ?? 0),
+ // Supply is the on-chain outstanding amount. A zero supply isn't a meaningful
+ // figure, so show "--" rather than "0.00".
+ const scaledOutstanding = convertScaledPrice(
+ BigInt(outstandingAmt || '0'),
+ assetScale ?? 0,
)
+ const formattedSupply = Number(scaledOutstanding)
+ ? parseAmount(scaledOutstanding)
+ : '--'
- const formattedCircSupply = parseAmount(
- convertScaledPrice(BigInt(outstandingAmt || '0'), assetScale ?? 0),
- )
+ // Circulating supply is derived from holder data, and is undefined when that
+ // is unavailable (e.g. the holders request failed) — "--" either way, never
+ // the unadjusted supply, which would look like a real circulating figure.
+ const formattedCircSupply =
+ circulatingSupply && Number(circulatingSupply)
+ ? parseAmount(circulatingSupply)
+ : '--'
const formattedConfidentialAmt = confidentialOutstandingAmt
? parseAmount(
@@ -45,7 +58,13 @@ export const MarketData = ({
{t('token_page.circulating_supply')}
- {formattedCircSupply}
+
+ {circulatingSupplyLoading ? (
+
+ ) : (
+ formattedCircSupply
+ )}
+
{formattedConfidentialAmt && (
diff --git a/src/containers/Token/MPT/Header/Metadata.tsx b/src/containers/Token/MPT/Header/Metadata.tsx
index 2182abb3b..f37e86af2 100644
--- a/src/containers/Token/MPT/Header/Metadata.tsx
+++ b/src/containers/Token/MPT/Header/Metadata.tsx
@@ -1,5 +1,8 @@
import { useTranslation } from 'react-i18next'
-import { JsonView } from '../../../shared/components/JsonView'
+import {
+ JsonView,
+ NO_STRING_COLLAPSE,
+} from '../../../shared/components/JsonView'
interface MetadataProps {
decodedMPTMetadata: Record | string
@@ -22,7 +25,12 @@ export const Metadata = ({
{isString ? (
{decodedMPTMetadata}
) : (
-
+
)}
diff --git a/src/containers/Token/MPT/Header/index.tsx b/src/containers/Token/MPT/Header/index.tsx
index 74bed94d9..00106edc7 100644
--- a/src/containers/Token/MPT/Header/index.tsx
+++ b/src/containers/Token/MPT/Header/index.tsx
@@ -5,12 +5,13 @@ import './styles.scss'
import {
BAD_REQUEST,
HASH192_REGEX,
- shortenDomain,
shortenMPTID,
- stripHttpProtocol,
convertToHttpURL,
- shortenAccount,
} from '../../../shared/utils'
+import {
+ getRegistrableDomain,
+ shortenDomainFromLeft,
+} from '../../../shared/domainUtils'
import { CopyableText } from '../../../shared/components/CopyableText'
import DomainLink from '../../../shared/components/DomainLink'
import { FormattedMPTIssuance } from '../../../shared/Interfaces'
@@ -37,6 +38,8 @@ interface Props {
setError: (error: number | null) => void
holdersCount?: number
holdersLoading?: boolean
+ circulatingSupply?: string
+ circulatingSupplyLoading?: boolean
}
export const Header = (props: Props) => {
@@ -48,6 +51,8 @@ export const Header = (props: Props) => {
setError,
holdersCount,
holdersLoading,
+ circulatingSupply,
+ circulatingSupplyLoading,
} = props
const [showURLDropdown, setShowURLDropdown] = useState(false)
const dropdownRef = useRef (null)
@@ -87,7 +92,6 @@ export const Header = (props: Props) => {
const {
issuer,
assetScale,
- maxAmt,
outstandingAmt,
confidentialOutstandingAmt,
transferFee,
@@ -162,13 +166,6 @@ export const Header = (props: Props) => {
/>
)}
- {/* Show issuer name if available */}
- {issuerName && (
-
- ({shortenAccount(issuerName)}
- )
-
- )}
{allUris.length > 0 && (
@@ -181,11 +178,10 @@ export const Header = (props: Props) => {
{allUris.length > 1 && (
|