Conversation
* feat: add numberFormatter and web3 utils * feat: add formatting * fix: fix bug * fix: remove extra formatPercent * fix: fix UI bugs * fix: add abbreviation * fix: remove linting errors * fix: remove conflict * fix: add linter checks --------- Co-authored-by: ECWireless <40322776+ECWireless@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Pull request overview
This PR introduces centralized number/address formatting utilities and migrates many Explorer UI surfaces (voting, treasury, orchestrators, transactions, charts, and account views) away from ad-hoc formatting/divisors to shared formatters and constants.
Changes:
- Added
utils/numberFormatters.ts(LPT/ETH/USD/percent/round/number) with accompanying tests. - Added
utils/web3.ts(address helpers, wei conversions, percent precision constants) and migrated call sites from@lib/utils. - Replaced scattered manual formatting across multiple pages/components (percent divisors, LPT/ETH display, address/hash shortening).
Reviewed changes
Copilot reviewed 50 out of 51 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| utils/web3.ts | New web3/address + wei conversion helpers and percent precision constants |
| utils/web3.test.ts | Unit tests for new utils/web3 helpers |
| utils/voting.ts | Removes local formatPercent; switches fromWei import to @utils/web3 |
| utils/numberFormatters.ts | New centralized formatting utilities for LPT/ETH/USD/percent/etc. |
| utils/numberFormatters.test.ts | Unit tests for new number formatting utilities |
| tsconfig.json | Adds @utils/* path alias |
| pages/voting/create-poll.tsx | Switches fromWei import to @utils/web3 |
| pages/voting/[poll].tsx | Uses shared formatters/constants instead of local/manual formatting |
| pages/treasury/create-proposal.tsx | Uses formatLPT + fromWei/toWei from @utils/web3 |
| pages/treasury/[proposal].tsx | Uses shared formatters/constants; moves web3 helpers to @utils/web3 |
| pages/migrate/orchestrator.tsx | Switches address/hash formatting to @utils/web3 |
| pages/migrate/delegator/index.tsx | Switches address/hash formatting to @utils/web3 |
| pages/migrate/broadcaster.tsx | Switches address/hash formatting to @utils/web3 |
| pages/index.tsx | Replaces hardcoded inflation divisor with PERCENTAGE_PRECISION_BILLION |
| pages/api/score/[address].tsx | Moves checkAddressEquality to @utils/web3 |
| pages/api/l1-delegator/[address].tsx | Moves EMPTY_ADDRESS to @utils/web3 |
| pages/_app.tsx | Removes global numbro.setDefaults initialization |
| lib/utils.tsx | Removes web3/formatting helpers now housed in @utils/web3 |
| lib/utils.test.ts | Removes tests for moved helpers; imports EMPTY_ADDRESS from @utils/web3 |
| lib/api/treasury.ts | Switches fromWei import to @utils/web3 |
| lib/api/ens.ts | Switches formatAddress import to @utils/web3 |
| layouts/main.tsx | Switches EMPTY_ADDRESS/formatAddress imports to @utils/web3 |
| layouts/account.tsx | Switches checkAddressEquality import to @utils/web3 |
| hooks/useSwr.tsx | Switches formatAddress import to @utils/web3 |
| components/TxStartedDialog/index.tsx | Switches formatAddress/fromWei imports to @utils/web3 |
| components/TxConfirmedDialog/index.tsx | Switches formatAddress/fromWei imports to @utils/web3 |
| components/Treasury/TreasuryVotingWidget/index.tsx | Replaces various percent/LPT formatting with shared formatters |
| components/Treasury/TreasuryVoteTable/index.tsx | Standardizes weight formatting with shared formatters |
| components/Treasury/TreasuryVoteTable/Views/VoteItem.tsx | Switches formatTransactionHash import to @utils/web3 |
| components/Treasury/TreasuryVoteTable/TreasuryVotePopover.tsx | Uses formatNumber for vote counts |
| components/TransactionsList/index.tsx | Replaces numbro/manual formatting with shared formatters/constants |
| components/TransactionBadge/index.tsx | Switches formatTransactionHash import to @utils/web3 |
| components/StakeTransactions/index.tsx | Uses formatLPT and formatAddress from new utils |
| components/Search/index.tsx | Switches formatAddress import to @utils/web3 |
| components/RoundStatus/index.tsx | Replaces numbro formatting with shared formatters |
| components/Profile/index.tsx | Switches formatAddress import to @utils/web3 |
| components/PollVotingWidget/index.tsx | Uses shared percent + voting power formatting |
| components/PerformanceList/index.tsx | Replaces numbro formatting with formatNumber/formatPercent + web3 address formatting |
| components/OrchestratorList/index.tsx | Replaces numbro/manual percent formatting and divisors with shared formatters/constants |
| components/OrchestratingView/index.tsx | Replaces numbro/manual formatting with shared formatters/constants |
| components/HistoryView/index.tsx | Replaces numbro/manual formatting with shared formatters/constants |
| components/ExplorerChart/index.tsx | Moves most subtitle/axis formatting to shared formatters |
| components/DelegatingWidget/index.tsx | Uses formatLPT and fromWei from new utils |
| components/DelegatingWidget/ProjectionBox.tsx | Uses shared formatters for ROI, principle, fees/rewards |
| components/DelegatingWidget/InputBox.tsx | Switches fromWei import to @utils/web3 |
| components/DelegatingWidget/Input.tsx | Replaces hardcoded divisors with percent precision constants |
| components/DelegatingWidget/Header.tsx | Switches formatAddress import to @utils/web3 |
| components/DelegatingView/index.tsx | Replaces numbro/manual formatting with shared formatters |
| components/Claim/index.tsx | Switches formatAddress import to @utils/web3 |
| components/AccountCell/index.tsx | Switches formatAddress import to @utils/web3 |
| components/Account/index.tsx | Switches formatAddress import to @utils/web3 |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| totalWeight > 0 ? ((parseFloat(w) / totalWeight) * 100).toFixed(2) : "0" | ||
| }%)`, | ||
| `${formatLPT(parseFloat(w), { abbreviate: false })} (${ | ||
| totalWeight > 0 ? formatPercent(parseFloat(w) / totalWeight) : "0" |
There was a problem hiding this comment.
When totalWeight is 0, formatWeight returns "0" (no percent sign) for the percentage portion, while the non-zero path returns values like "12.34%". Return "0%" (or use formatPercent(0)) for consistency.
| totalWeight > 0 ? formatPercent(parseFloat(w) / totalWeight) : "0" | |
| totalWeight > 0 | |
| ? formatPercent(parseFloat(w) / totalWeight) | |
| : formatPercent(0) |
| <Box> | ||
| Total Support ( | ||
| {+pollData.quota / PERCENTAGE_PRECISION_TEN_THOUSAND}% | ||
| needed) | ||
| </Box> |
There was a problem hiding this comment.
The quota/quorum labels are still manually computing a percent (quota / PERCENTAGE_PRECISION_TEN_THOUSAND) and then appending %. Given poll quota/quorum are treated as 1e6-scaled fractions elsewhere (e.g. lib/api/polls.ts uses / 1_000_000), this would be clearer/less error-prone as formatPercent(+pollData.quota / PERCENTAGE_PRECISION_MILLION) (same for quorum) without manual % concatenation.
| ); | ||
|
|
||
| const formattedPrinciple = formatLPT(Number(principle) || 150, { | ||
| precision: 0, |
There was a problem hiding this comment.
formattedPrinciple is now built via formatLPT(...), which includes the LPT symbol by default. Elsewhere in this file the tooltip text still appends LPT after {formattedPrinciple}, which will result in duplicated units (e.g. 150 LPT LPT). Consider setting showSymbol: false here (or removing the extra LPT in the tooltip).
| precision: 0, | |
| precision: 0, | |
| showSymbol: false, |
| // Derive protocol inflation data | ||
| const inflationRate = | ||
| Number(protocolData?.inflation || 0) / PERCENTAGE_PRECISION_BILLION; | ||
| const inflationChangeAmount = | ||
| Number(protocolData?.inflationChange || 0) / PERCENTAGE_PRECISION_BILLION; | ||
| const inflationChangeSign = | ||
| Number(protocolData?.inflationChange || 0) > 0 ? "+" : ""; | ||
|
|
||
| const formatPercentChange = useCallback( | ||
| (change: ROIInflationChange) => | ||
| change === "none" | ||
| ? `Fixed at ${numbro( | ||
| Number(protocolData?.inflation) / 1000000000 | ||
| ).format({ | ||
| mantissa: 3, | ||
| output: "percent", | ||
| })}` | ||
| : `${numbro( | ||
| (change === "negative" ? -1 : 1) * | ||
| (Number(protocolData?.inflationChange) / 1000000000) | ||
| ).format({ | ||
| mantissa: 5, | ||
| output: "percent", | ||
| forceSign: true, | ||
| ? `Fixed at ${formatPercent(inflationRate, { precision: 3 })}` | ||
| : `${inflationChangeSign}${formatPercent(inflationChangeAmount, { | ||
| precision: 5, | ||
| })} per round`, | ||
|
|
||
| [protocolData?.inflation, protocolData?.inflationChange] | ||
| [inflationRate, inflationChangeAmount, inflationChangeSign] |
There was a problem hiding this comment.
formatPercentChange ignores its change argument for the sign of the inflation change. Since protocolData.inflationChange comes from a uint256 (always positive magnitude), selecting the "negative" option will still display a positive change (e.g. +0.00010% per round). The sign should be derived from change (and/or forceSign), not from the raw protocol value.
| const transcodingInfo = outputTrans | ||
| ? `${numbro(maxScore.transcoding?.score).divide(100).format({ | ||
| output: "percent", | ||
| mantissa: 1, | ||
| })} - ${maxScore.transcoding.region}` | ||
| ? `${formatPercent(maxScore.transcoding?.score)} - ${ | ||
| maxScore.transcoding.region | ||
| }` | ||
| : ""; |
There was a problem hiding this comment.
maxScore.transcoding.score is produced by the /api/score endpoint as a 0–100 value (see pages/api/score/[address].tsx multiplying by 100). formatPercent expects a 0–1 fraction, so this will display 100x too large (e.g. 95 -> 9500%). Convert the score to a fraction (divide by 100) before calling formatPercent (and keep the prior 1-decimal precision if desired).
| output: "percent", | ||
| mantissa: 0, | ||
| }) | ||
| ? formatPercent(+(transcoder?.rewardCut || 0) / 1000000) |
There was a problem hiding this comment.
This file imports PERCENTAGE_PRECISION_MILLION and uses it for feeShare, but rewardCut still divides by the hardcoded 1000000. Use the shared constant here as well to keep percent-scaling consistent and avoid future drift.
| ? formatPercent(+(transcoder?.rewardCut || 0) / 1000000) | |
| ? formatPercent( | |
| +(transcoder?.rewardCut || 0) / PERCENTAGE_PRECISION_MILLION | |
| ) |
See #545 for all details. Original PR created by @Roaring30s .