Skip to main content
5 min read J.P.

TypeScript in the Web3 Stack: Why We Bet on It

Last month I spent two hours of my life hunting a bug that turned out to be a typo. The frontend code called a smart contract function, passed the parameters in the wrong order, and the transaction silently failed. No error in the console, no red squiggle in the editor — just nothing. The JavaScript code was syntactically correct. It was just semantically wrong, and nobody noticed until a user complained that their tokens had vanished. (They hadn't vanished — they were stuck in a contract because a uint256 parameter had received the value of an address.)

That incident was the final push for a decision we should have made long ago: our entire web3 stack now runs on TypeScript. And after working with it for nearly a year, I can say with conviction that it was one of the best decisions we've made.

The Problem with JavaScript in a Web3 Context

JavaScript is a great language — flexible, ubiquitous, with an enormous ecosystem. But that very flexibility becomes a liability in a web3 context. When you interact with smart contracts, you work with strictly typed data: uint256, address, bytes32, bool. A Solidity contract distinguishes sharply between a number and an address. JavaScript does not. For JavaScript, everything is either a string, a number, or an object — and conversion happens implicitly, often in surprising ways.

This leads to an entire class of bugs that are hard to find. You pass a string where a BigNumber is expected. You forget to convert a value to Wei. You mix up the order of parameters in a contract call. All of these are errors that occur at runtime — exactly when a real user is moving real money. In the best case, the transaction fails. In the worst case, it goes through but does something other than intended.

Type Safety for Contract ABIs

The biggest win from TypeScript in the web3 stack is type safety for contract interactions. Tools like TypeChain or viem's built-in types generate TypeScript interfaces directly from a smart contract's ABI. This means: if your contract has a function transfer(address to, uint256 amount), then your editor knows the exact parameter types. You cannot pass a string where an address is expected. You cannot swap the parameters. The compiler catches the error before you even think about a transaction.

In practice, this looks like: you import the generated types, and from that moment on, every contract call has autocomplete. You can see immediately which functions are available, what parameters they expect, and what return values they provide. This sounds like a small convenience gain, but it is actually a fundamental shift: instead of reading ABI documentation and hoping you type everything correctly, you let the compiler do the work.

DX Improvements in Daily Work

Beyond type safety, TypeScript improves the developer experience in ways that are hard to measure in pure numbers but enormously noticeable in daily work. Code navigation becomes trivial: "Show me all the places where this contract function is called" is one click. Refactoring becomes safe: if you rename a parameter, the compiler finds every place that needs updating. And onboarding new team members becomes faster because the types serve as living documentation.

The advantage is especially apparent when working with libraries like ethers.js v6 or viem. Both libraries are written in TypeScript from the ground up and offer excellent type definitions. When you make a provider call, for example, the types know that getBlock() returns a Block object with typed fields for timestamp, gasUsed, and so on. No more guessing, no more console.log to figure out what fields an object has.

Our Experience After One Year

Since we switched to TypeScript, the number of runtime errors in our dApp frontends has dropped significantly. Not to zero — TypeScript is no silver bullet — but the nature of errors has changed. We now catch the "dumb" mistakes (wrong parameter types, missing fields, invalid addresses) at compile time and can devote our debugging time to the genuinely hard problems: race conditions, network timeouts, unexpectedly high gas costs.

The transition was not painless. The initial tsconfig.json configuration for a web3 project requires a few decisions (particularly around BigInt support and target settings). And there are moments when TypeScript's type system gets in the way — especially with dynamic ABI calls or when working with poorly typed third-party libraries. In those cases, we occasionally reach for as unknown as T, but we try to keep that to a minimum.

Conclusion: Is the Switch Worth It?

If you are starting a new web3 project: yes, absolutely. If you are migrating an existing JavaScript project: it depends. The migration takes effort and does not deliver new features in the short term. But in the long run, it pays off — in fewer bugs, faster onboarding, and a codebase that remains maintainable as the team grows.

In web3, we move real money with code. Every avoided type error is potentially an avoided financial disaster. TypeScript does not make our code perfect, but it makes the most common mistakes impossible. And in an industry where a mistyped parameter can cost millions, that is not just a nice-to-have — it is an obligation.