DEV Community

Cover image for A .NET Dinosaur in Web3 — Day 5: First dApp.
Alena
Alena

Posted on • Originally published at Medium

A .NET Dinosaur in Web3 — Day 5: First dApp.

⚠️ 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.)

Solidity: github.com/alena-dev-soft/solidity-learn/contracts/05day/
UI: github.com/alena-dev-soft/wishlist-dapp

What I Built

WishlistV2 — an upgraded contract plus a React dApp on top of it.

Contract additions:

  • createdAt: block.timestamp — creation timestamp. Like DateTime.UtcNow in C# but in Unix seconds, written permanently on-chain.
  • deleteWish(uint _index) — removes a wish. The trick: swap the target with the last element, then pop(). 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#
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (0)