Building a dApp
Connecting an ordinary front end to a chain: wallets, pending transactions, and where the files actually live.
A Normal App With An Unusual Backend
Almost all of the difficulty is user experience. The user pays for writes, approves each one in a wallet, waits an unpredictable time for confirmation, and may be on the wrong network entirely. An interface that ignores any of that feels broken even when the contracts are perfect.
Front End Integration
Reads are ordinary asynchronous data-fetching and should be treated as such: cache them, revalidate them, show stale data rather than a spinner. Writes are their own thing, because a transaction has a lifecycle no HTTP request has.
| Stage | The interface should |
|---|---|
| Awaiting signature | Say the wallet is waiting — users miss the popup constantly |
| Broadcast, pending | Show a link to the explorer, keep the app usable |
| Mined and successful | Refresh the affected reads, confirm plainly |
| Reverted | Explain why in human terms — gas was still spent |
| Dropped or replaced | Handle it; it happens more than people expect |
Listening to events is usually better than polling state. They are cheap to emit, indexable, and give the front end a natural place to react — and an indexer over those logs is how any non-trivial dApp gets a queryable view of history.
Wallet Connection
Connection is a permission handshake: the site asks, the wallet shows the user which accounts would be exposed, and the user decides. Nothing is signed and no funds move.
| Must handle | Because |
|---|---|
| No wallet installed | Most visitors will not have one — explain, do not crash |
| Wrong network | Offer to switch; never let them transact on the wrong chain |
| Account switched | The wallet emits an event; your app must re-read everything |
| Disconnected | Clear cached state, do not keep showing a stale balance |
| Multiple wallets | Several extensions fight over the same injected object |
Use a connection library — RainbowKit, Web3Modal, ConnectKit — rather than talking to the injected provider by hand. They handle the matrix of browser extensions, mobile deep links and WalletConnect that you would otherwise rediscover one bug report at a time.
Decentralised Storage
On-chain storage costs thousands of dollars per megabyte, so almost nothing large lives there. The pattern is to store the content off-chain and its hash on-chain — the chain proves what the data is, somewhere else holds the bytes.
| Option | Model | Catch |
|---|---|---|
| IPFS | Content addressed by hash | Only stays available while someone pins it |
| Pinning service | Paid IPFS hosting | A centralised dependency again |
| Arweave | Pay once, stored permanently | Immutable — you cannot take it down |
| Ordinary cloud storage | Cheap and fast | Honest, but not decentralised |
Interview Questions
What makes dApp UX hard?
Writes cost money, need explicit approval, take unpredictable time and can revert or be dropped. The interface has to narrate a lifecycle that ordinary web apps never expose.
Why prefer events over polling state?
Events are cheap to emit, indexed and filterable, and they give the front end something to react to. An indexer over event logs is also how you get a queryable history.
What does connecting a wallet actually do?
It grants the site permission to see the account addresses. Nothing is signed, nothing moves — signing and transactions are separate approvals.
Why is signing a message a good login?
It is free, proves control of the address, and needs no password. The risk is that users learn to approve opaque requests, which is exactly what phishing exploits.
Why keep large data off-chain?
On-chain storage costs orders of magnitude more than any alternative. Storing a hash on-chain proves integrity while the bytes live somewhere cheap.
What is the weakness of IPFS for permanence?
Content addressing proves the file is unaltered but guarantees nothing about availability. If no node pins it, it is gone — which is why NFT images have vanished.