A .NET Dinosaur in Web3. Day 4
Alena .NET Dinosaur3 min read·Just now--
Writing My First Contract From Scratch
Three days of guided exercises. Today — no template, no “here’s the complete code, just copy and paste.” Just a brief: based on what you know — build your WishList contract.
The Task
A personal WishList where only the owner can fulfill a wish. Small enough to finish in one session. Not so small that the decisions made themselves.
Code: github.com/alena-dev-soft/solidity-learn/contracts/04day/
What Actually Clicked
The first thing I got tripped up on wasn’t syntax — it was initialization. In .NET, almost everything needs to be explicitly initialized, especially array-related types. In Solidity:
WishItem[] public wishes;That’s it. The array exists, it’s empty, push() works immediately. The EVM assigns default values at deployment — uint256 is 0, bool is false, address is the zero address.
Worth noting though: arrays in Solidity aren’t free the way they are in .NET. Every push() is a state change with a gas cost. And "EVM handles defaults" is not the same as "no initialization needed" — that distinction matters once you're working with memory arrays, nested structs, or upgradeable contracts.
The next decision was identification. Every wish needs some kind of handle — otherwise how do you reference the one you want to fulfill? My instinct was to reach for something explicit, maybe even overcomplicate it. The options in Solidity are straightforward:
- Array index — simplest, no overhead
- Manual counter — more control, slightly more code
mapping(uint256 => WishItem)— keyed table, most flexible
Went with array index. fulfillWish(uint _index) takes the position. For a contract this size, there's no reason to reach for more than you need.
⚠️ Fair warning: array index works as an identifier only as long as the array is append-only and items are never removed or reordered.
On Validation — Index Safety
Nothing in Solidity protects you unless you do it explicitly. Using an array index as an identifier is clean and simple — but only if you validate it first.
Calling fulfillWish(uint _index) without a boundary check is asking for trouble. At minimum:
require(_index < wishes.length, "Invalid index");Without this, you’re relying on implicit behavior and risking unexpected reverts. In .NET you often get guardrails for free. In Solidity — you build them yourself, or they don’t exist.
On Access Control — There Is No Default Security
The requirement was simple: only the owner can fulfill a wish. What clicked is that in Solidity this isn’t a built-in rule — it’s just code you have to write.
require(msg.sender == owner, "Not the owner");Without this line, anyone can call your function. Solidity doesn’t assume intent. It executes what’s written — nothing more, nothing less.
One Tooling Trap Worth Knowing
I use Edge, and I tend to keep tabs open for days. After a two-day gap — life happens — I came back to Remix with MetaMask still connected from before.
Here’s the gotcha: switching to Sepolia in the top network dropdown isn’t enough. The wallet panel at the bottom has its own network selector, and it needs to match. If they silently disagree, you get a gas estimation error with null revert data. Looks exactly like a contract bug. Isn’t one.
Web3 tooling fails loudly and explains itself poorly. File that under “features, probably.”
What’s Next
Solidity alone is a backend with no front door. The next step is wiring this contract to something a human can actually use — ethers.js, React, TypeScript. The blockchain as a data layer, with a real interface on top.
That’s Day 5.
Stage: Dinosaur 🦕 — first contract, no scaffolding.