A lot has changed recently.
Institutional players are actively exploring DeFi. Risk frameworks are becoming table stakes. At Euler, we are moving toward institutional-grade infrastructure — and we are not alone. More and more projects across the ecosystem are getting serious.
At the same time, AI has fundamentally changed what is possible for small teams.
Writing code is no longer the bottleneck. Knowledge is.
Any team that takes its craft seriously can now achieve the kind of engineering rigor that was once reserved for big corporations.
With all of this in mind, the goal is clear:
We need to raise the status quo and build a frontend that sets the standard for any financial application.
This post is about one of the most underrated problems standing in the way.
Data Formatting and Error Propagation
This might not sound exciting, but hear me out.
The single most underrated problem in any DeFi frontend is data formatting and the propagation of errors and warnings to the UI.
The goal sounds simple:
We should always provide the user with correct and maximum information, with all errors and warnings shown explicitly, right where they are needed — zoomed in to the specific numbers that are affected.
In practice, this is incredibly hard.
DeFi apps fetch data from many different sources:
- Price APIs
- Third-party services
- Indexers
- Backends
- On-chain calls
- Random tokens
- Different vaults
- An ever-growing number of chains
Any data point can arrive as an unexpected type, a null, a stringified bigint, or simply be missing — while documentation promised to never return undefined.
We cannot trust external types. We always need to be paranoid.
And when something goes wrong?
Your whole page or section breaks.
That is better than a silent crash, sure. But it is not good enough.
If only one number on the entire page is problematic, only that number should show an error — not the whole table.
Common Mistakes
I have reviewed a lot of projects and codebases. These patterns show up everywhere, including in many well-known projects.
Formatting Into Strings Instead of Objects
We have all been there.
Your designer drops by:
"Hey, can we make the dollar sign gray and add 4px of spacing between it and the number?"
But you already formatted your number into a string like this:
"$2,423.00"
So what can you do now?
Once the value is flattened into a string, the UI loses flexibility.
You now need to split strings, use regex, or rewrite formatting logic just to style different parts of the value.
Trusting External Interfaces
Types say number, but reality sends:
undefinednull- A stringified number
- A malformed value
- A missing field
A classic example is the famous hardcoded fallback:
decimals || 18
It works — until it does not.
Silent coercion feels convenient, but in financial interfaces it can easily become dangerous.
Silent Errors Everywhere
This happens constantly:
-
0renders instead ofundefined - Missing data shows as an empty
<span> - Console errors are logged but never surfaced to the user
- Warnings are swallowed somewhere deep in the data layer
- The UI looks fine, but the value is wrong or incomplete
In DeFi, silent errors are especially dangerous because users often make financial decisions based on what the interface shows them.
All-or-Nothing Error Handling
One bad data point should not take down an entire section or page.
If one value is broken, that specific value should gracefully degrade.
Instead of this:
Entire table failed to render
We want this:
Only the problematic value shows an error or warning
The rest of the page should remain usable.
I have seen this pattern over and over.
And I think we can do much better.
How It Should Work
Instead of formatting numbers into flat strings, we should return structured objects that carry:
- The formatted value
- Metadata about the value
- Warnings encountered during formatting
- Errors encountered during formatting
For example:
export interface RobustFormattingResult<T> {
value: T | undefined
warnings: string[]
errors: string[]
}
The value itself should be a rich type like ViewNumber.
Not a string.
An object with everything the UI needs:
export type ViewNumber = {
belowMin?: boolean
aboveMax?: boolean
symbol?: string
originalValue?: string
compact?: string
viewValue?: string
sign?: string
decimals?: number
}
This gives the UI full control.
A component can render:
sign + symbol + viewValuesymbol + sign + viewValue- Compact notation
- Full notation
- Below-min values like
<$0.01 - Above-max values like
>$100 - Symbols with custom styling
- Warnings and errors next to the affected value
And it can do all of that without reformatting anything.
Why This Matters
When combined with warnings and errors, every number on the page can independently communicate its own trustworthiness.
For example:
Missing price from backend — unable to calculate this value.
Or:
Automatically converted type — please double check this information.
The user always gets maximum transparency.
Instead of hiding uncertainty, the interface shows exactly what happened and where.
That is the difference between a frontend that merely renders data and a frontend that users can trust.
Rendering It
Once you have a RobustFormattingResult, rendering becomes straightforward.
You format the value once:
const formattedResult = robustFormatBigIntToViewTokenAmount({
context: `${STORY_CONTEXT}.Playground`,
input: {
bigIntValue: 123456789n,
decimals: 6,
symbol: "USDC",
},
})
Then you pass the result directly into a display component:
return (
<h5 className="text-2xl font-semibold leading-none">
<DisplayTokenAmountField {...args} {...formattedResult} />
</h5>
)
Under the hood, everything renders as simple <span> elements:
<span class="inline-flex items-center">
<span class="inline tracking-tight">123.45</span>
<span class="inline items-center text-slate-300">USDC</span>
</span>
This is intentional.
Every part of the number is its own element:
- Value
- Symbol
- Sign
- Warnings
- Errors
- Loading state
- Additional metadata
This means you can style any piece independently.
You can set spacing between spans, change colors, inject custom components, or wrap the whole thing in a <Typography> component so everything inherits the correct text styles.
Even skeleton loaders can scale automatically with the surrounding typography.
No fighting with flat strings.
No regex.
No splitting formatted numbers apart after the fact.
See It in Action
Mock Vaults Demo
A live page simulating real-world data issues and showing how the UI handles each one transparently, with detailed component overviews.
You can also see the full fetch-mapper-hook architecture in action here:
Storybook — 30+ Cases
Storybook — DisplayTokenAmountField
This Storybook includes over 30 different cases for a single number component, including:
- Loading states
- Errors
- Warnings
- Edge cases
- Below-min values
- Above-max values
- Compact notation
- Full notation
Open-Source Libraries
The goal of this post is not to promote one specific implementation.
It is about changing how we think about the problem.
Once you start treating every number as a structured result with errors and warnings attached, the implementation follows naturally.
AI can help you build custom solutions tailored to your needs.
Or, if these libraries fit your use case, they are designed to support flexible UI implementations out of the box.
web3-robust-formatting
Formatters and mappers built around the structured result interface described above.
web3-display-components
React components with support for:
- Loading states
- Warnings
- Errors
- Compound values
- Tooltips
- Flexible rendering
- Everything rendered through
<span>elements for easy styling
AI Skills
The libraries also come with AI skills.
AI can initialize wrapper components for your project and learn when to use the libraries properly as you build.
CODEX_HOME="$(pwd)/.codex" npx web3-robust-formatting-codex-skill install --force
npx web3-display-components-codex-skill init-agents
npx web3-robust-formatting-codex-skill install
npx web3-robust-formatting-codex-skill init-agents
Conclusion
Knowledge is power.
Understanding how to think about data in financial systems is what separates a great frontend from a mediocre one.
The UI's main job is simple:
Do not break the user's trust.
Shifting things around and making things prettier will not attract that many users by itself.
But wrong values, missing warnings, silent failures, and pages that break?
That drives users away, frustrates them, and erodes trust — sometimes permanently.
Stop underestimating this problem.
Start treating every number in your UI as a promise to the user.
Let's make DeFi great together.


Top comments (0)