A lot has changed recently.
Institutional players are actively exploring DeFi. Risk frameworks are becoming table stakes. At Euler, we're moving toward institutional-grade infrastructure - and we're not alone. More and more projects across the ecosystem are getting serious.
At the same time, AI has fundamentally changed what's possible for small teams. Writing code is no longer the bottleneck - knowledge is. Any team that takes their 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 to you, 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're 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 — across random tokens, different vaults, and 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's better than a silent crash, sure. But it's 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've 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've 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 "$2,423.00" , so what can you do now?
Trusting external interfaces
Types say number, but reality sends undefined, a stringified number, or null.
The famous hardcoded || 18 when decimals are optional in TypeScript. Silent coercion that works — until it doesn't.
Silent errors everywhere
0 renders instead of undefined. Missing data shows as an empty <span> instead of a clear indicator. Console errors nobody reads. No warnings propagated to the UI.
All-or-nothing error handling
One bad data point takes down an entire section or page, instead of gracefully degrading at the individual number level.
I've 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 return structured objects that carry the value, its metadata, and any issues encountered along the way:
export interface RobustFormattingResult<T> {
value: T | undefined
warnings: string[]
errors: string[]
}
The value itself is a rich type like ViewNumber — not a string, but an object with everything the UI needs:
export type ViewNumber = {
belowMin?: boolean // render as "<$0.01"
aboveMax?: boolean // render as ">$100"
symbol?: string // "$" — never embedded in the value string
originalValue?: string
compact?: string // "1.23K", "4M"
viewValue?: string // "1,234.57" — the chosen display string
sign?: string // "-" kept separate for flexible rendering
decimals?: number
}
This means your component can render sign + symbol + viewValue or symbol + sign + viewValue, style the symbol independently, show compact or full notation, and display belowMin / aboveMax sentinels — all without reformatting anything.
When combined with the warnings and errors arrays, every number on your page independently communicates its own trustworthiness.
Missing price? Show ⚠️ with a tooltip: "Missing price from [source] — unable to calculate this value."
Auto-converted type? Show the number with a warning: "Automatically converted type — please double check this information."
The user always gets maximum transparency.
Rendering It
Once you have your RobustFormattingResult, rendering is straightforward — just spread it into a display component:
const formattedResult = robustFormatBigIntToViewTokenAmount({
context: `${STORY_CONTEXT}.Playground`,
input: {
bigIntValue: 123456789n,
decimals: 6,
symbol: "USDC",
},
})
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 — value, symbol, sign, warnings, and rest - is its own <span>. You can style any piece independently, set padding between spans, change colors, or even inject a custom component to replace any part of it!
No fighting with a flat string, no regex to split a formatted number apart after the fact. Wrap it with a <Typography> component and everything inherits text stylings — even the skeleton loader will automatically scale up or down to match.
See It in Action
Mock Vaults Demo — a live page simulating real-world data issues, showing how the UI handles each one transparently with detailed component overviews. Also see full fetch-mapper-hook architecture in action with this powerful formatting and rendering here!
Storybook — 30+ Cases — over 30 different cases on a single number component: loading, errors, warnings, edge cases, below-min, above-max, compact notation, and more.
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: don't break the user's trust. Shifting things around and making things prettier won't attract that many users. But wrong values, missing warnings, silent failures, and pages that break? That drives users away, frustrates them, and erodes trust — sometimes permanently.
The goal of this blog is not to promote any specific solution — it's about changing how we think about this 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 our open-source libraries fit your use case, they're designed to support any kind of UI:
- web3-robust-formatting — formatters and mappers with the structured result interface described above.
-
web3-display-components — React components with loading states, warnings, errors, compound values, tooltips out of the box, everything in
<span>elements for easy styling.
Comes with AI skills too - AI will be able to initalize wrapper components if you ask it to, and use libraries properly as you go!
CODEX_HOME="$(pwd)/.codex" npx web3-robust-formatting-codex-skill install --force
npx web3-display-components-codex-skill init-agents // teach AI when to use skill
npx web3-robust-formatting-codex-skill install
npx web3-robust-formatting-codex-skill init-agents // teach AI when to use skill
Stop underestimating this problem, and start treating every number in your UI as a promise to the user.
Let's make DeFi great together.




























