Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
3 changes: 3 additions & 0 deletions public/locales/en-US/translations.json
Original file line number Diff line number Diff line change
Expand Up @@ -671,6 +671,8 @@
"account_clawbacks_from_vault": "<Account/> clawbacks<Amount/> from <Holder/>",
"account_clawbacks_from_vault_amount_omitted": "<Account/> clawbacks all remaining funds from <Holder/>",
"account_deletes_vault": "<Account/> deleted a vault with ID <VaultID/>",
"vault_delete_memo_data": "It sets the Vault deletion MemoData to <MemoData/>",
"memo_data": "Memo Data",
"vault_create_table_detail": "vault for <Asset/>",
"withdraws": "withdraws",
"deletes": "deletes",
Expand Down Expand Up @@ -825,6 +827,7 @@
"check_vault_id": "Please check the vault ID",
"get_vault_failed": "Unable to load vault information at this time. Please try again later.",
"private_vault": "Private Vault",
"deposits_allowed": "Deposits Allowed",

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.

This key is unused

"perm_domain_id": "Permissioned Domain ID",
"total_value_locked": "Total Value Locked (TVL)",
"shares": "Shares",
Expand Down
3 changes: 2 additions & 1 deletion src/containers/Transactions/simpleTab.scss
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ $subdued-color: $black-40;
&.condition,
&.fulfillment,
&.tx,
&.channel {
&.channel,
&.vault-id {
width: calc(100% - 120px);
color: $subdued-color;
font-size: 14px;
Expand Down
42 changes: 29 additions & 13 deletions src/containers/Vault/VaultHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,7 @@ import { MPT_ROUTE } from '../../App/routes'
import SocketContext from '../../shared/SocketContext'
import { getMPTIssuance } from '../../../rippled/lib/rippled'
import { parseVaultWebsite } from '../utils'
import {
shortenVaultID,
shortenAccount,
getCurrencySymbol,
isCurrencyExoticSymbol,
} from '../../shared/utils'
import { getCurrencySymbol, isCurrencyExoticSymbol } from '../../shared/utils'
import './styles.scss'
import { useAnalytics } from '../../shared/analytics'
import { parseAmount } from '../../shared/NumberFormattingUtils'
Expand Down Expand Up @@ -52,6 +47,8 @@ interface Props {
// Vault flags from XLS-65d spec
const VAULT_FLAGS = {
lsfVaultPrivate: 0x00010000,
lsfVaultDepositBlocked: 0x00020000,
Comment thread
pdp2121 marked this conversation as resolved.
Outdated
lsfVaultOwnerCanBlockDeposit: 0x00040000,
}

// Withdrawal policy values from XLS-65d spec
Expand Down Expand Up @@ -146,6 +143,11 @@ export const VaultHeader = ({ data, vaultId, displayCurrency }: Props) => {

const isPrivate =
flags !== undefined && (flags & VAULT_FLAGS.lsfVaultPrivate) !== 0
const canBlockDeposit =
flags !== undefined &&
(flags & VAULT_FLAGS.lsfVaultOwnerCanBlockDeposit) !== 0
const isDepositAllowed =
flags === undefined || (flags & VAULT_FLAGS.lsfVaultDepositBlocked) === 0

const decodedData = convertHexToString(vaultDataRaw)
const vaultWebsite = parseVaultWebsite(vaultDataRaw)
Expand Down Expand Up @@ -185,20 +187,15 @@ export const VaultHeader = ({ data, vaultId, displayCurrency }: Props) => {
value={
<CopyableText
text={vaultId}
displayText={`${shortenVaultID(vaultId)}`}
displayText={vaultId}
showCopyIcon
/>
}
/>
{owner && (
<TokenTableRow
label={t('owner')}
value={
<Account
account={owner}
displayText={`${shortenAccount(owner)}`}
/>
}
value={<Account account={owner} displayText={owner} />}
/>
)}
<TokenTableRow
Expand All @@ -218,6 +215,25 @@ export const VaultHeader = ({ data, vaultId, displayCurrency }: Props) => {
</div>
}
/>
{canBlockDeposit && (
<TokenTableRow
label={t('deposits_allowed')}
value={
<div className="private-vault-toggle">
<span
className={`toggle-pill ${isDepositAllowed ? 'active' : ''}`}
>
{t('yes')}
</span>
<span
className={`toggle-pill ${!isDepositAllowed ? 'active' : ''}`}
>
{t('no')}
</span>
</div>
}
/>
)}
{vaultCredential && (
<TokenTableRow
label={t('perm_domain_id')}
Expand Down
12 changes: 12 additions & 0 deletions src/containers/Vault/VaultHeader/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,19 @@
flex: 1;

.token-table {
width: 100%;
margin-top: 0;
table-layout: fixed;

// Values like Vault ID and Owner are long identifiers; let them use the
// full remaining width of the row instead of the shared table's fixed
// 230px column, wrapping only if the viewport is too narrow to fit
// them on one line.
.col2 {
display: block;
width: auto;
max-width: 100%;
}
}
}
}
Expand Down
95 changes: 84 additions & 11 deletions src/containers/Vault/VaultHeader/test/VaultHeader.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ describe('VaultHeader Component', () => {
).toBeInTheDocument()
})

it('displays the vault ID with truncation', () => {
it('displays the full vault ID, not a truncated form', () => {
const vaultId = 'ABC123DEF456GHI789JKL012'
const vaultData = {
Owner: 'rTestOwner123',
Expand All @@ -170,14 +170,12 @@ describe('VaultHeader Component', () => {
</TestWrapper>,
)

// Vault ID should be truncated for display: first 8 chars + "..." + last 6 chars
// This makes long IDs more readable while still being identifiable
const vaultIdLabel = screen.getByText('Vault ID')
expect(vaultIdLabel).toBeInTheDocument()

// Verify the truncated format: ABC123DE...KL012 (8 chars + ... + 6 chars)
const expectedTruncated = `${vaultId.substring(0, 8)}...${vaultId.substring(vaultId.length - 6)}`
expect(screen.getByText(expectedTruncated)).toBeInTheDocument()
// The full ID should be shown in the details table; there's plenty of
// width available and the value wraps rather than being truncated
expect(screen.getByText(vaultId)).toBeInTheDocument()
})

it('displays the owner as a clickable account link', () => {
Expand All @@ -200,11 +198,8 @@ describe('VaultHeader Component', () => {
// Owner label should be present
expect(screen.getByText('Owner')).toBeInTheDocument()

// Owner value should display the exact account address as link text
// The Account component displays the full address for short accounts
const ownerLink = screen.getByRole('link', {
name: `${owner.slice(0, 7)}...${owner.slice(-5)}`,
})
// Owner value should display the full, untruncated account address as link text
const ownerLink = screen.getByRole('link', { name: owner })
expect(ownerLink.getAttribute('href')).toBe(`/accounts/${owner}`)
})
})
Expand Down Expand Up @@ -324,6 +319,84 @@ describe('VaultHeader Component', () => {
})
})

/**
* =========================================
* SECTION 2b: Deposit Blocked Flag Tests
* =========================================
* When lsfVaultOwnerCanBlockDeposit (0x00040000) is set, the vault owner may
* block/unblock deposits, and the current state is reflected by
* lsfVaultDepositBlocked (0x00020000). The UI shows this as "Deposits
* Allowed" (the inverse of the raw flag) since "blocked" reads as if
* existing deposits are frozen, rather than new deposits being disallowed.
*/
describe('Deposit Allowed Flag', () => {
it('does not render the row when the owner cannot block deposits', () => {
const vaultData = {
Owner: 'rTestOwner',
Asset: { currency: 'XRP' },
Flags: 0,
}

render(
<TestWrapper>
<VaultHeader
data={vaultData}
vaultId="ABC123"
displayCurrency="XRP"
/>
</TestWrapper>,
)

expect(screen.queryByText('Deposits Allowed')).not.toBeInTheDocument()
})

it('displays YES as active when deposits are unblocked', () => {
const vaultData = {
Owner: 'rTestOwner',
Asset: { currency: 'XRP' },
Flags: 0x00040000, // lsfVaultOwnerCanBlockDeposit, deposits not blocked
}

render(
<TestWrapper>
<VaultHeader
data={vaultData}
vaultId="ABC123"
displayCurrency="XRP"
/>
</TestWrapper>,
)

const row = screen.getByText('Deposits Allowed').closest('tr')!
const togglePills = row.querySelectorAll('.toggle-pill')
expect(togglePills[0]).toHaveClass('active')
expect(togglePills[1]).not.toHaveClass('active')
})

it('displays NO as active when deposits are blocked', () => {
const vaultData = {
Owner: 'rTestOwner',
Asset: { currency: 'XRP' },
Flags: 0x00040000 | 0x00020000, // can block + currently blocked
}

render(
<TestWrapper>
<VaultHeader
data={vaultData}
vaultId="ABC123"
displayCurrency="XRP"
/>
</TestWrapper>,
)

const row = screen.getByText('Deposits Allowed').closest('tr')!
const togglePills = row.querySelectorAll('.toggle-pill')
expect(togglePills[0]).not.toHaveClass('active')
expect(togglePills[1]).toHaveClass('active')
})
})

/**
* =========================================
* SECTION 3: Asset Type Display Tests
Expand Down
3 changes: 3 additions & 0 deletions src/containers/shared/components/CopyableText/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,13 @@

.copyable-text-with-icon {
display: inline-flex;
min-width: 0;
max-width: 100%;
align-items: center;

.copyable-text-value {
color: $white;
overflow-wrap: anywhere;
}

.copy-icon-button {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ export const Simple: TransactionSimpleComponent = (
const { VaultID: vaultId, Holder: holder, Amount: amount } = data.instructions
return (
<>
<SimpleRow label={t('vault_id')} data-testid="vault_id">
<SimpleRow
label={t('vault_id')}
className="vault-id"
data-testid="vault_id"
>
{vaultId}
</SimpleRow>
<SimpleRow label={t('holder')} data-testid="holder">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,34 @@ import { Trans } from 'react-i18next'
import type { VaultDelete } from 'xrpl'
import { TransactionDescriptionProps } from '../types'
import { Account } from '../../Account'
import { decodeHex, hexMatch } from '../../../transactionUtils'

export const Description = ({
data,
}: TransactionDescriptionProps<VaultDelete>) => {
const { tx } = data
const { Account: account, VaultID: vaultId } = tx
// MemoData is not yet part of the xrpl package's VaultDelete type
const memoData = (tx as any).MemoData as string | undefined
return (
<Trans
i18nKey="account_deletes_vault"
components={{
Account: <Account account={account} />,
VaultID: <b>{vaultId}</b>,
}}
/>
<>
<Trans
i18nKey="account_deletes_vault"
components={{
Account: <Account account={account} />,
VaultID: <b>{vaultId}</b>,
}}
/>
{memoData && hexMatch.test(memoData) && (
<div data-testid="memo_data">
<Trans
i18nKey="vault_delete_memo_data"
components={{
MemoData: <b>{decodeHex(memoData)}</b>,
}}
/>
</div>
)}
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,34 @@ import { useTranslation } from 'react-i18next'
import type { VaultDelete } from 'xrpl'
import { TransactionSimpleComponent, TransactionSimpleProps } from '../types'
import { SimpleRow } from '../SimpleRow'
import { decodeHex, hexMatch } from '../../../transactionUtils'

export const Simple: TransactionSimpleComponent = (
props: TransactionSimpleProps<VaultDelete>,
) => {
const { t } = useTranslation()
const { data } = props
const { VaultID: vaultId } = data.instructions
// MemoData is not yet part of the xrpl package's VaultDelete type
const memoData = (data.instructions as any).MemoData as string | undefined
return (
<SimpleRow label={t('vault_id')} data-testid="vault_id">
{vaultId}
</SimpleRow>
<>
<SimpleRow
label={t('vault_id')}
className="vault-id"
data-testid="vault_id"
>
{vaultId}
</SimpleRow>
{memoData && hexMatch.test(memoData) && (
<SimpleRow
label={t('memo_data')}
className="dt"
data-testid="memo_data"
>
{decodeHex(memoData)}
</SimpleRow>
)}
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ describe('VaultDelete: Description', () => {
expect(container).toHaveTextContent(
'rLR12AgChXxLoQsuLCizNCgh5pt5jPheo1 deleted a vault with ID 2AA88C4CA646645E35E38B8D51CD2CA50BDE14A3F3FFE3838F2C8DCE95C2BABD',
)
expect(container).toHaveTextContent(
'It sets the Vault deletion MemoData to vault closed voluntarily',
)
unmount()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ describe('VaultDelete: Simple', () => {
'vault_id',
'2AA88C4CA646645E35E38B8D51CD2CA50BDE14A3F3FFE3838F2C8DCE95C2BABD',
)
expectSimpleRowText(container, 'memo_data', 'vault closed voluntarily')
unmount()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@
"TransactionType": "VaultDelete",
"TxnSignature": "D6A3F0AADD0ECEE2D51E051157EF582D8C1EFD84897A912557148FC963D9B93DFD725996F90ED2DF7F0922B34E47D28869E408EDD9F7CA2CEA681023EE57B70D",
"VaultID": "2AA88C4CA646645E35E38B8D51CD2CA50BDE14A3F3FFE3838F2C8DCE95C2BABD",
"MemoData": "7661756C7420636C6F73656420766F6C756E746172696C79",
"ctid": "C00626A700000C96",
"date": 807127481,
"ledger_index": 403111
Expand Down
Loading
Loading