feat: Add basic ledger entry page#1146
Conversation
## High Level Overview of Change Fix typo for website in english translation ### Context of Change Bug introduced in #1056 ### Type of Change - [x] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Refactor (non-breaking change that only restructures code) - [ ] Tests (You added tests for code that already exists, or your new feature included in this PR) - [ ] Documentation Updates - [ ] Translation Updates - [ ] Release ### TypeScript/Hooks Update - [ ] Updated files to React Hooks - [ ] Updated files to TypeScript
| await getLedgerEntry(rippledContext, id) | ||
| return 'entry' |
There was a problem hiding this comment.
Is there any way we can cache the result of this method invocation? It feels wasteful to invoke this API only for the purpose of checking if the hash pertains to a transaction, ledger-entry (or) an NFT.
There was a problem hiding this comment.
That change should probably be made in a separate PR
| if (resp.error === 'lgrNotFound') { | ||
| throw new Error('invalid ledger index/hash', 400) | ||
| } |
There was a problem hiding this comment.
This RPC command always fetches the last validated ledger. This error cannot possibly occur.
There was a problem hiding this comment.
Better to have an extra error handler and not need it - no need to delete it IMO
|
@mvadari Thanks for sending me some feedback. Unfortunately, I hit an error while trying to use the custom Copilot setup steps configured for this repository. The error I am seeing is: Once you or someone with the necessary access fixes the problem, please let me know in a comment and I'll try again. Thanks! |
There was a problem hiding this comment.
Pull request overview
Adds a new “Entry” page/route for XRPL ledger_entry objects and wires it into existing navigation (search + tx meta) so users can jump from affected ledger nodes / object IDs to a dedicated object view.
Changes:
- Introduces
/entry/:id/:tab?route and newEntrycontainer with “Simple” + “Raw” tabs, plus redirects for certain entry types (e.g.,AccountRoot). - Updates transaction meta renderers and some transaction/NFT UI to deep-link ledger object indices (Offer/PayChannel/RippleState/etc.) to the new Entry page.
- Updates rippled client
getLedgerEntryAPI (signature + optional deleted-entry support) and extends header search routing to detect ledger entry hashes.
Reviewed changes
Copilot reviewed 27 out of 27 changed files in this pull request and generated 15 comments.
Show a summary per file
| File | Description |
|---|---|
| src/rippled/lib/rippled.ts | Updates getLedgerEntry signature and adds include_deleted support for ledger_entry calls. |
| src/containers/Transactions/index.tsx | Minor formatting-only change. |
| src/containers/Transactions/DetailTab/Meta/RippleState.tsx | Adds Entry-page link for RippleState affected nodes; adjusts rendered currency display. |
| src/containers/Transactions/DetailTab/Meta/PayChannel.tsx | Adds Entry-page link for PayChannel affected nodes. |
| src/containers/Transactions/DetailTab/Meta/Offer.tsx | Adds Entry-page link placeholder via i18n component mapping for Offer affected nodes. |
| src/containers/Transactions/DetailTab/Meta/MPTokenIssuance.tsx | Adds Entry-page link for MPTokenIssuance affected nodes. |
| src/containers/Transactions/DetailTab/Meta/MPToken.tsx | Adds Entry-page link for MPToken affected nodes. |
| src/containers/Transactions/DetailTab/Meta/index.tsx | Enhances default meta line with an i18n-driven Entry-page link. |
| src/containers/Transactions/DetailTab/Meta/DirectoryNode.tsx | Enhances directory node meta lines with an i18n-driven Entry-page link. |
| src/containers/TokenNonMain/TokenHeader/index.tsx | Avoids rendering a transaction link row when previousTxn is missing. |
| src/containers/shared/components/Transaction/PaymentChannelCreate/Simple.tsx | Links created channel IDs to the Entry page. |
| src/containers/shared/components/Transaction/NFTokenCreateOffer/Simple.tsx | Links offer index to the Entry page. |
| src/containers/shared/components/Transaction/NFTokenCancelOffer/Simple.tsx | Links offer index to the Entry page. |
| src/containers/shared/components/Transaction/NFTokenAcceptOffer/Simple.tsx | Links offer index to the Entry page. |
| src/containers/NFT/NFTTabs/test/Offers.test.js | Updates anchor-count assertion due to new Entry links. |
| src/containers/NFT/NFTTabs/Offers.tsx | Links offer index values to the Entry page. |
| src/containers/Header/Search.tsx | Extends hash-type detection/routing to include Entry-page navigation. |
| src/containers/Entry/SimpleTab.tsx | New Simple tab layout for entries (summary + index/prev pointers). |
| src/containers/Entry/simpleTab.scss | Styling for Entry simple tab layout. |
| src/containers/Entry/Simple/index.tsx | Selects a “Simple” renderer for entries (currently reuses transaction mapping + fallback). |
| src/containers/Entry/Simple/DefaultSimple.tsx | Default Entry simple renderer that displays arbitrary ledger entry fields. |
| src/containers/Entry/index.tsx | New Entry container: fetches ledger entries, handles errors, and redirects for specific types. |
| src/containers/Entry/entry.scss | Styling for Entry page shell/summary. |
| src/containers/App/routes.ts | Makes accounts/transactions identifiers required in paths and adds new ENTRY_ROUTE. |
| src/containers/App/index.tsx | Registers new Entry route and adds explicit /accounts and /transactions routes. |
| src/containers/Accounts/AMM/AMMAccounts/index.tsx | Updates call site for new getLedgerEntry signature. |
| public/locales/en-US/translations.json | Adds/updates i18n strings to support Entry links and Entry page UI text. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| command: 'ledger_entry', | ||
| index, | ||
| ledger_index: 'validated', | ||
| include_deleted: includeDeleted, |
|
|
||
| if (resp.error_message === 'invalidParams') { | ||
| if (resp.error === 'invalidParams') { | ||
| throw new Error('invalidParams for ledger_entry', 404) |
| if (resp.error) { | ||
| throw new Error(resp.error, 500) |
| export const ACCOUNT_ROUTE: RouteDefinition<{ | ||
| id?: string | ||
| tab?: 'assets' | 'transactions' | ||
| assetType?: 'issued' | 'nfts' | 'mpts' | ||
| }> = { | ||
| path: '/accounts/:id?/:tab?/:assetType?', | ||
| path: '/accounts/:id/:tab?/:assetType?', | ||
| } |
| <SimpleRow label={t('owner')} data-test="owner"> | ||
| <Account account={owner} /> | ||
| </SimpleRow> | ||
| )} | ||
| <SimpleRow label={t('prev_ledger_index')} data-test="prev-ledger-index"> | ||
| <RouteLink to={LEDGER_ROUTE} params={{ identifier: prevLedgerIndex }}> | ||
| {prevLedgerIndex} | ||
| </RouteLink> | ||
| </SimpleRow> | ||
| <SimpleRow label={t('prev_tx_id')} data-test="prev-tx"> |
| import { ErrorBoundary } from 'react-error-boundary' | ||
| import { useTranslation } from 'react-i18next' | ||
| import { transactionTypes } from '../../shared/components/Transaction' | ||
| import { DefaultSimple } from './DefaultSimple' | ||
|
|
||
| export const Simple: FC<{ | ||
| data: any | ||
| type: string | ||
| }> = ({ data, type }) => { | ||
| // Locate the component for the left side of the simple tab that is unique per TransactionType. | ||
| const { t } = useTranslation() | ||
| const SimpleComponent = transactionTypes[type]?.Simple | ||
| if (SimpleComponent) { | ||
| return ( | ||
| <ErrorBoundary | ||
| fallback={ | ||
| <div className="error"> | ||
| <div>{t('component_error')}</div> | ||
| <div>{t('try_detailed_raw')}</div> | ||
| </div> | ||
| } | ||
| > | ||
| <SimpleComponent data={data} /> | ||
| </ErrorBoundary> | ||
| ) | ||
| } |
| const { isLoading, data, error, isError } = useQuery(['entry', id], () => { | ||
| if (id === '') { | ||
| return undefined | ||
| } | ||
| if (HASH256_REGEX.test(id)) { | ||
| return getLedgerEntry(rippledSocket, id, true).catch( | ||
| (ledgerEntryRequestError) => { | ||
| const status = ledgerEntryRequestError.code | ||
| trackException( | ||
| `entry ${id} --- ${JSON.stringify( | ||
| ledgerEntryRequestError.message, | ||
| )}`, | ||
| ) | ||
|
|
||
| return Promise.reject(status) | ||
| }, | ||
| ) | ||
| } | ||
|
|
||
| return Promise.reject(BAD_REQUEST) | ||
| }) | ||
| const { width } = useWindowSize() | ||
|
|
||
| useEffect(() => { | ||
| trackScreenLoaded() | ||
|
|
||
| return () => { | ||
| window.scrollTo(0, 0) | ||
| } | ||
| }, [tab, trackScreenLoaded]) | ||
|
|
||
| useEffect(() => { | ||
| if (id != null && data?.index != null) { | ||
| if (data.node.LedgerEntryType === 'AccountRoot') { | ||
| const path = buildPath(ACCOUNT_ROUTE, { id: data.node.Account }) | ||
| navigate(path) | ||
| } else if (data.node.LedgerEntryType === 'MPTokenIssuance') { | ||
| const path = buildPath(MPT_ROUTE, { id: data.node.mpt_issuance_id }) | ||
| navigate(path) | ||
| } else if (data.node.LedgerEntryType === 'AMM') { | ||
| const path = buildPath(ACCOUNT_ROUTE, { id: data.node.Account }) | ||
| navigate(path) | ||
| } | ||
| } | ||
| }, [id, data, navigate]) |
| "prev_tx_id": "Previous Modifying Transaction", | ||
| "entry_not_found": "Entry not found", | ||
| "entry_empty_title": "No entry hash supplied", | ||
| "entry_empty_hint": "Enter a entry hash in the search box", |
| <Trans i18nKey="transaction_balance_line_one"> | ||
| It {action} a{' '} | ||
| <b> | ||
| <Currency currency={currency} /> | ||
| </b> | ||
| RippleState node between | ||
| It {action} a <b>{currency}</b> | ||
| RippleState{' '} | ||
| <RouteLink to={ENTRY_ROUTE} params={{ id: node.LedgerIndex }}> | ||
| node | ||
| </RouteLink>{' '} |
| const determineHashType = async ( | ||
| id: string, | ||
| rippledContext: ExplorerXrplClient, | ||
| ) => { | ||
| try { | ||
| await getTransaction(rippledContext, id) | ||
| return 'transactions' | ||
| } catch (e) { | ||
| return 'nft' | ||
| try { | ||
| await getLedgerEntry(rippledContext, id) | ||
| return 'entry' | ||
| } catch (e2) { | ||
| // TODO: better error message here | ||
| return 'nft' | ||
| } |
High Level Overview of Change
This PR adds a new type of page for ledger entries/objects. For now it just has a default page that shows all the fields of an object, but over time we can add special ones.
It also:
Detailedtab metadata breakdownAccountpage if the object is an accountContext of Change
Type of Change
TypeScript/Hooks Update
Before / After
Test Plan