The Builder Era
/ 21 min read
The early 2020s were a strange time to become a blockchain engineer. The public story was JPEGs, Discords, tokens, and charts. The engineering story was more interesting: production systems were being built on runtimes where every write cost money, every user action needed a signature, and every piece of off-chain infrastructure had to agree with an on-chain source of truth it could not control.
How I got here
In 2021 I had just finished my bachelor’s at Ventspils University, in the computer science faculty. The thesis was a from-scratch rebuild of a Danish company’s IoT monitoring stack: roughly 800 modems watching for floods in buildings and problems on construction sites. Django, React, Docker, AWS, a few hundred unit tests, and enough production deployment work to make the thing real.
It shipped. The consultancy behind it could employ multiple people maintaining and extending the software. The thesis also got nominated as Latvia’s best CS bachelor thesis of 2021, which I am mentioning because this is the only time in the series I get to brag, and because “best thesis in a country of 1.8 million people” is exactly the right amount of ridiculous.
After that I was job-hunting and wanted hard problems. Not another CRUD surface around a database, but systems where the failure modes were new enough that I would have to think from first principles. A local Latvian blockchain company, with some of the sharpest engineers I have worked with, made me an offer.
I had zero blockchain background. That turned out to be fine. It was 2021. The industry was hiring anyone who could spell Merkle and look calm on a deploy call.
The vocabulary I wish I had first
Before getting into the projects, it helps to separate a few overloaded words. Most blockchain confusion comes from mixing up the ledger, the execution environment, and the services that orbit them.
The chain is not “the backend” in the usual sense. It is a replicated state machine. Users submit signed messages. Validators order those messages into blocks. The runtime executes them. Everything else — images, websites, indexers, signing services, alerting, admin tools — is infrastructure around that state machine.
If a normal backend has a bug, you can often patch data or redeploy. If a smart contract has a bug after launch, the answer depends on the upgrade pattern, the admin keys, the proxy, the governance rules, and whether the thing you broke is already part of public state. That changes how you design even small features.
Key concepts
Transaction
The atomic unit of state change on a blockchain. A signed message saying “do this”: transfer tokens, call a contract, deploy code. It is submitted by a user or service, propagated through the network, included in a block, and applied to state. It pays a fee in the chain’s native token and either fully succeeds or fully reverts.
EVM (Ethereum Virtual Machine)
The runtime that executes Ethereum smart contracts, and the de facto standard ported to many other chains. It is stack-based, gas-metered, uses 256-bit words, and exposes a common contract ABI and JSON-RPC surface. “EVM-compatible” means wallets, libraries, explorers, and Solidity contracts can often work with minimal changes.
NFT (Non-Fungible Token)
An on-chain token where each unit is unique: one token ID, one owner, one metadata pointer. The contract stores the ownership ledger. The media usually lives off-chain on IPFS or another storage system, referenced by hash. Mechanically, an NFT is closer to a mapping of tokenId -> owner than to the JPEG people argued about on Twitter.
Smart contract
Code deployed to a blockchain address. Users call it through transactions, and every validator executes the same code against the same prior state. A contract can hold funds, enforce ownership, emit events, and call other contracts. It is not a server process you can SSH into; it is deterministic code inside the chain’s state transition.
Merkle trees
A hash tree where each leaf is a piece of data, each internal node is a hash of its children, and the root commits to the entire set. Given the root and a short path of sibling hashes, a contract can verify that one item belongs to a large set without storing the whole set on-chain.
Project one: a provably-fair NFT game
The first project was a community-driven NFT game with a mystery reveal. Users could trade and merge cards, level them up, and eventually unmask Satoshi. Yes, that Satoshi. It was 2021; the industry was doing historical reenactment with metadata.
My part was the on-chain and protocol-adjacent plumbing. A few terms show up repeatedly in that work, so here is the local dictionary before the pieces start moving around:
Key concepts
EIP-712 signatures
Ethereum’s standard for typed, structured message signing. Instead of asking a user to sign an opaque hash, the wallet can show a readable payload such as { recipient, amount, deadline }. The signature is bound to a domain, usually contract plus chain, so a signature for one app cannot be replayed against another.
IPFS
InterPlanetary File System. Content-addressed peer-to-peer storage: you fetch a file by the hash of its contents, not by a server location. It became the standard place to put NFT assets. The caveat is availability. A hash proves what the file is, but someone still has to keep serving it, usually through a pinning service.
Multi-sig wallet
A smart-contract wallet that will not execute a transaction until N-of-M designated approvers have signed it. This is the default pattern for treasuries, admin keys, and protocol operations where one compromised laptop should not be enough to move funds or change production state.
Provable RNG
A random number whose output can be checked after the fact. It is usually built with commit-reveal, a VRF (Verifiable Random Function), or a public source of entropy. The point is not that every user will verify every draw. The point is that the project cannot secretly choose winners after seeing the inputs.
The project surface looked roughly like this:
| Component | Responsibility |
|---|---|
| Solidity contracts | Store token ownership, enforce game rules, verify signatures and proofs. |
| Rust signing service | Produce typed signatures for actions the contract should accept without trusting arbitrary client input. |
| IPFS assets | Store generated images and metadata by content hash. |
| Image generator | Render card states and reveal assets on demand. |
| Merkle proofs | Let the contract verify membership in a large set without storing every item. |
| Provable RNG | Make reveal outcomes auditable instead of “trust us, the backend rolled fairly.” |
The diagram below is not trying to say that every off-chain part was suspicious. It is showing which claims the contract could verify instead of trusting blindly: user intent, signed authorization, membership proofs, and content hashes.
The important boundary was not “on-chain good, off-chain bad.” That is too blunt to be useful. The important boundary was what the chain had to be able to verify.
For example, storing every allowlist entry or every precomputed reveal state directly on Ethereum would have been expensive. A Merkle root gave the contract a compact commitment to a much larger dataset. A user could later submit a Merkle proof showing that their item belonged to the committed set. The contract did not need the whole list. It only needed the root and the proof path.
The signing service had a similar shape. The contract should not accept arbitrary claims from the browser, but it could accept a signed message from a trusted signer if the message was domain-bound and structured. EIP-712 was the standard way to make those messages legible to wallets and unambiguous to contracts. In practice, that meant getting Solidity encoding, wallet encoding, and Rust encoding to agree byte-for-byte. When they did not, the signature was not “almost right.” It was invalid.
That was my first lesson in blockchain engineering: most bugs are boundary bugs. The wallet thinks it signed one thing. The backend thinks it encoded another. The contract verifies a third. Somewhere in that triangle, a user pays gas to discover your mental model was off by one hash.
The provable RNG had the same goal. Randomness in an NFT reveal is only meaningful if the party running the reveal cannot wait, inspect the inputs, and choose the output it likes. A verifiable construction does not make users read the math. Most do not. It makes manipulation detectable, which changes the trust model. That distinction mattered even when the Discord was mostly asking when reveal.
For a fixed-size collection, the reveal could be split into two questions: what distribution of assets should exist, and which unrevealed token ID gets which asset. The first question can be committed before randomness enters the system. The random seed is then used only to shuffle an already-committed list.
There was also the operational side. Some admin actions went through a multi-sig. If a launch window was aimed at a US audience, that meant somebody in Latvia could be awake at 4am signing a transaction so a feature flag flipped at the right time. Decentralization is beautiful until the calendar invite is in Pacific time.
Mainnet economics change your pulse
Key concepts
Gas
The EVM’s unit of computation cost. Every opcode consumes gas. Reading storage costs more than adding two numbers; writing storage costs much more. A transaction specifies a gas limit, and the sender pays for the work performed. Gas is how Ethereum prices CPU, memory, and especially persistent storage.
Nonce
A per-account transaction counter. Ethereum uses it to order transactions from the same sender and prevent replay. If your deployment script uses the wrong nonce, you may replace, block, or mis-sequence the transaction you intended to send.
Reorg
A chain reorganization. A block that looked accepted can be replaced by a competing branch if the network later agrees on a different canonical chain. Most application code waits for confirmations because “included in a block” and “very unlikely to disappear” are not the same statement.
Ethereum makes writes expensive on purpose. A public chain is a shared computer with adversarial users. If computation and storage were cheap, spam would eat the network. The fee market is the defense mechanism: when demand rises, transaction prices rise with it.
In the 2021 NFT season, demand rose a lot.
This is the shape of the pressure: the deployment steps were familiar, but each mainnet boundary made the blast radius bigger.
The live network also had behaviors testnets did not reproduce well. Testnets were less congested. Fees were calmer. Reorgs were rarer and less consequential. Mainnet had dropped transactions, replacement transactions, fee spikes, and users arriving all at once because marketing had scheduled a launch. The protocol might be deterministic, but the environment around it was not gentle.
Infrastructure the crates did not cover
The service layer around that NFT game was Rust: signing service, indexing code, image generation, internal tools. In 2026 that sounds normal. In 2021, for EVM-adjacent work, it was a fight.
The Rust ecosystem had excellent systems foundations, but many blockchain-specific crates were immature. The EIP-712 implementations did not always match wallet behavior. ABI helpers did not cover every Solidity encoding pattern. There was no polished equivalent of Solidity’s abi.encodePacked that I could casually cargo add, and, fun fact, cargo add itself was not even built into Cargo until 2022.
So I wrote pieces I would rather not write today:
| Primitive | Why it mattered |
|---|---|
| Merkle tree library | Build roots and proofs that Solidity could verify exactly. |
| EIP-712 encoder | Produce signatures wallets and contracts agreed on byte-for-byte. |
encodePacked equivalent | Match Solidity’s packed ABI encoding for hash preimages. |
This was not an aesthetic preference for Rust. It was an interoperability problem. If the backend signed a payload using one byte layout and the contract reconstructed another, the signature failed. If the Merkle proof was ordered differently than the Solidity verifier expected, the proof failed. If the image metadata hash did not match the pinned asset, the token pointed at the wrong thing forever.
The invariant was simple: every boundary had to agree on bytes. The work was making that true across Solidity, wallets, Rust, IPFS, and whatever JavaScript glue was sitting between them.
Renting without a return transaction
The next project was NFT renting. That sounds boring until you try to implement it on-chain.
In Web2, renting assumes an authority can force the return. A server can revoke access. A marketplace can update a database row. A landlord can eventually send somebody with a key, a clipboard, or a very tired expression.
On-chain, the chain only changes when someone submits a transaction. If a renter is supposed to return an NFT at the end of the rental period, what makes them do it? They may be offline. They may not want to pay gas. They may not have the native token. They may simply not care. You cannot repo the JPEG.
The design had to be rebuilt around that constraint.
| Web2 assumption | On-chain constraint | Protocol shape |
|---|---|---|
| The platform can force a return. | Only submitted transactions change state. | Do not require a renter-submitted return transaction. |
| Ownership is updated by a database write. | Ownership checks are contract logic. | Make access depend on time-aware contract state. |
| The user does not pay for internal bookkeeping. | Every state-changing transaction costs gas. | Avoid periodic cleanup transactions where possible. |
The core idea was to make expiry part of the ownership check. Instead of requiring a final “return” transaction, the contract could compare the rental’s expiry against block.timestamp. Before expiry, the renter had the relevant access. After expiry, the contract’s ownership or access logic treated the rental as over.
That changed the invariant from “a return transaction must happen” to “every access check must account for expiry.” The second invariant was much easier to enforce. It moved the burden from user behavior to contract logic.
That split is the point of the diagram: the contract can treat the rental as over because time has passed, while an indexer that only watches events may never see a new event telling it to update.
Key concepts
block.timestamp
The timestamp attached to the current block. Contracts use it for coarse time-based logic such as deadlines and expiries. It is not a precision clock, and block producers have some limited influence over it, but it is usually acceptable for rental windows, auctions, and timeouts where seconds-level precision is not the security boundary.
Event log
Data emitted by a contract during a transaction. Events are not contract state, but off-chain services use them to discover what happened: transfers, approvals, listings, deposits, and protocol-specific actions.
Indexer
An off-chain service that reads blocks and events, builds a queryable database, and powers app UIs, explorers, analytics, and notifications. Indexers make chain data usable, but they only know what they observe or derive. If a state change is implicit, the indexer has to understand the same rule the contract does.
Relayer
A service that submits a transaction on behalf of a user. The user signs an intent or message, and the relayer pays gas to put the corresponding transaction on-chain.
Paymaster
Infrastructure that sponsors or abstracts transaction fees for users, often used with account abstraction. The user can interact with an app without manually holding the chain’s native gas token for every action.
Account abstraction
A family of designs that make blockchain accounts more programmable. Instead of every user action being a simple externally-owned account transaction paid in the native token, account abstraction can support sponsored gas, session keys, batched actions, custom validation, and better recovery flows.
The design also exposed a caveat that every blockchain app eventually meets: on-chain truth and off-chain views are not the same thing.
External tools often track NFT ownership by reading Transfer events. A normal transfer emits an event, indexers ingest it, and marketplaces update their databases. But a rental expiry based on block.timestamp does not necessarily emit anything at the moment it expires. No transaction happened. No event was written. The contract’s access logic knew the rental had ended, but a third-party indexer that only watched transfers could still show the renter as the apparent owner.
That is not a cosmetic detail. A lot of the user experience in crypto is built from off-chain interpretations of on-chain data. Explorers, wallets, marketplace UIs, portfolio trackers, bots, and analytics systems all build their own picture of the world. If your protocol uses implicit state transitions, those systems need either explicit events, custom indexing logic, or enough documentation to derive the same state.
In hindsight, the industry drifted toward a more Web2-shaped answer for a lot of this: relayers, paymasters, and account abstraction. Instead of asking every normal user to understand gas, hold native tokens, and submit every maintenance transaction themselves, apps increasingly sponsor or abstract that work away. The on-chain primitives are powerful. The UX of making a user buy gas to return a rented game item is not.
The AMM I wanted to finish
The next project was the one I loved most: Hypersea, an AMM that tried to manage its own liquidity concentration automatically.
To understand why that was interesting, you need the AMM tradeoff.
Key concepts
DEX (decentralized exchange)
An on-chain venue for swapping tokens, with no central operator and no order book in the traditional sense. Most run on the AMM model: a pool of liquidity, a pricing curve, and a contract that lets anyone trade against it. Settlement is just a transaction; there is no exchange to log into and no withdrawal queue to wait on.
AMM (Automated Market Maker)
A market maker implemented as a smart contract. Instead of matching buyers and sellers in an order book, it prices swaps with a formula over the pool’s reserves. Liquidity providers deposit assets into the pool and earn fees when traders swap against it.
Concentrated liquidity
Liquidity placed inside a selected price range instead of across all possible prices. This improves capital efficiency because the same assets support more trading near the current price. The cost is active management: if the market leaves your range, your position stops earning fees and your exposure changes.
Uniswap V3
The 2021 Uniswap release that made concentrated liquidity mainstream. LPs choose price ranges, represented internally by ticks, and receive position NFTs. It made pools much more capital efficient, but it also made liquidity provision an active strategy rather than a passive deposit.
Impermanent loss
The opportunity cost LPs take when pool prices move relative to simply holding the assets. It is called “impermanent” because it can shrink if prices return, but the loss is real if the LP exits while the pool composition is worse than holding.
Oracle
A source of external data used by on-chain contracts, usually prices. Oracles can be on-chain TWAPs, specialized oracle networks, or protocol-specific feeds. They are powerful because contracts cannot natively see the outside world; they are risky because bad oracle data can become bad protocol behavior.
Classic constant-product AMMs such as early Uniswap are simple and passive. LPs deposit both sides of a pair, and the curve handles trades. The downside is capital efficiency: most of the liquidity is spread across prices the market may never touch.
Uniswap V3 improved that by letting LPs concentrate capital in specific price ranges. If ETH/USDC is trading near 3,000, an LP can provide liquidity around that range instead of from zero to infinity. Same capital, more depth near the active price, more fees per dollar when the market stays inside the range.
The tradeoff is that the LP now has a job. They need to choose ranges, monitor volatility, rebalance when price moves, and manage impermanent loss. Concentrated liquidity made AMMs more efficient, but it also made passive LPs less passive.
The useful way to read the next diagram is as a shift in who chooses the active price range: the pool formula, the LP, or the protocol itself.
| Design | Strength | Cost |
|---|---|---|
| Constant-product AMM | Simple, passive liquidity. | Capital spread across unused price space. |
| Stable-swap AMM | Efficient for assets expected to trade near parity. | Works best for tightly correlated pairs. |
| Uniswap V3-style concentration | High capital efficiency near selected ranges. | LPs actively manage ranges and exposure. |
| Hypersea direction | Protocol manages concentration based on market conditions. | More complex curve logic, oracle dependence, and rebalancing rules. |
Hypersea’s idea was to automate the part that V3 pushed onto LPs. The protocol would observe market conditions, including volatility and oracle inputs, then feed those inputs into deterministic reshape rules. Those rules changed the effective curve the pool exposed to traders. When the market looked calm, the pool could concentrate more tightly. When volatility rose, it could spread liquidity wider to reduce the chance that positions fell out of range too quickly.
That sounds like “the protocol did the moving,” which is the cute version. The technical version is that the pool needed rules for when and how its effective curve changed, what data those rules trusted, and how LP accounting stayed fair while the curve adapted.
The failure modes were easy to imagine and hard to dismiss. If the oracle lagged behind the real market, traders could swap against a stale shape. If the reshape rule reacted too eagerly, the pool could churn between ranges and burn value on adjustment instead of market making. If LP shares were accounted incorrectly across a reshape, two users who deposited into the same pool at different times could end up with subtly different claims on its assets for reasons neither of them chose.
The invariant was not “make LPs rich.” Protocols do not get that invariant, despite the number of pitch decks that have bravely attempted it. The invariant was that liquidity movement had to be deterministic, explainable, and fair to LPs who were not actively managing positions themselves.
I left before seeing that project through. I still think the problem was one of the more interesting ones from that era: how much active strategy can a protocol absorb before it stops being a neutral market maker and becomes an automated trading system with extra steps?
The pattern underneath
Looking back, the through-line was not NFTs, renting, or AMMs. It was learning where blockchain systems put responsibility.
An NFT project puts ownership on-chain, but the art, metadata, reveal pipeline, signing service, and marketplace views live around it. A rental protocol can make expiry part of contract logic, but indexers and wallets still need to understand the implicit state. An AMM can encode a pricing rule in a contract, but oracle design and LP accounting decide whether that rule behaves well under real market conditions.
The chain gives you a shared execution layer. It does not remove architecture. It makes the boundaries more expensive to misunderstand.
That was the builder era for me: hand-rolled primitives, expensive deploys, awkward UX, and a growing respect for byte-level agreement across systems that did not care about my intent.
The next thing on the desk was a cross-chain protocol that, on paper, did not fit. Literally. The math was over budget by 2.2x before any payload, before any encoding overhead, before anything.
That’s Part 2.