A .NET Dinosaur in Web3 — Day 5: First dApp. Real UI. Real Blockchain
Alena .NET Dinosaur3 min read·Just now--
Honest disclaimer ⚠️Yes, this one is late. The dinosaur went travelling, then came back and started building something. Both count.
Day 5 was about wiring the WishList contract to a real frontend — React, TypeScript, ethers.js, MetaMask. But somewhere between the travel and the code, a project idea formed. Two-week sprint, MVP target. It stays under wraps for now — but the daily insights don’t stop.
Today: the dApp. Soon: something real.
The Goal
Take the WishList contract from Day 4 and make it usable in the real world. (If someone feels like fulfilling one of my wishes — I’m not stopping you).
The code is below:
Solidity: github.com/alena-dev-soft/solidity-learn/contracts/05day/
UI: https://github.com/alena-dev-soft/wishlist-dapp.git
What I Built
WishlistV2 — an upgraded contract plus a React dApp on top of it.
Contract additions:
createdAt: block.timestamp— creation timestamp. LikeDateTime.UtcNowin C# but in Unix seconds, written permanently on-chain.deleteWish(uint _index)— removes a wish. The trick: swap the target with the last element, thenpop(). Cheaper than shifting the entire array. This breaks the original order — acceptable here, but something to be aware of.
The frontend stack: Vite + React + TypeScript + ethers.js.
What Actually Clicked
ABI is just an interface.
To call a smart contract from JavaScript, you need its ABI — the list of function signatures. The mental model for .NET developers:
ABI = interface IWishlist in C#It describes what exists. Not how it works. The frontend doesn’t need the implementation — just the signatures.
provider vs signer — the key distinction.
provider reads from the blockchain — no permissions needed, no gas.
signer represents an account that can authorize transactions. If something changes state, it must be signed.
Blockchain is not a REST API.
In a normal React app — button click, data saves in 50ms, UI updates instantly. In Web3 — the transaction goes to network nodes, gets included in a block, block gets confirmed. On Sepolia that takes 10–15 seconds. await tx.wait() literally waits for the block. This is not a bug. This is the execution model.
isOwner flag — access control in the UI.
The contract enforces owner-only rules on-chain. But the UI should also reflect them — no point showing “Delete” to someone who can’t delete. Solution: load owner() from the contract, compare with the connected wallet, set an isOwner flag. Owner sees all controls. Everyone else sees a read-only list.
A Few Things That Can Waste Your Time
TypeScript doesn’t know about MetaMask out of the box.
window.ethereum isn’t part of the default typings. Quick fix: declare it as any. Good enough for now.
Vite creates a nested folder structure by default. If you run it inside an existing project directory, you end up one level too deep. Easy to miss, costs a few minutes.
Testnet latency is real. Sepolia blocks roughly every ~12 seconds. Even after tx.wait() resolves, the updated state might not be immediately visible.
A small delay or refetch avoids reading stale data.
The Project
The days off weren’t wasted. An idea formed — something that combines what I’m learning with a real use case. Two-week sprint, MVP target. The project stays under wraps for now, but the daily learning logs continue. The insights will keep coming. Just with a different backdrop.
Stage: Dinosaur 🦕 — first dApp live. Backend meets frontend. Something is forming.