
Alexander Karlsson
Ethereum, Render This: I Put an Entire React App Onchain
POIDHverse is a working React application stored across 24 Ethereum smart contracts. It needs no traditional hosting and will live for as long as Ethereum does.
Lately, with all this AI acceleration, I have felt quite numb. I have not really had that old itch to create something special or new.
Maybe it is because we have become so tunnel visioned on how to get the most out of AI. One week it is agent skills, the next it is a new MCP concept, then it is loop engineering. There is always another tool, technique, or workflow to understand.
Maybe AI is simply moving too fast. Maybe it is the crypto bear market.
Then one post on X left me completely mind blown.
Someone had put an entire trading platform, including the frontend, on Ethereum Mainnet. It did not need hosting. It did not really need anything. The application would be there for as long as Ethereum exists.
That scratched the old itch. The itch to try something most people would not.

So this is the story, and the deep dive, on how I put an entire React application onchain on Ethereum Mainnet.
POIDHverse is a working React application stored across 24 Ethereum smart contracts. It does not point to a website hosted somewhere else. The application itself lives on Ethereum.
First, what is POIDH?
POIDH stands for “pics or it didn’t happen”.
It is a platform where people put up bounties for others to complete. The person creating a bounty decides what they want someone to do and how large the prize should be. Other people can complete the task and submit a photo as proof. Depending on the bounty, the creator or a community vote decides which claim wins and receives the prize.
It is a simple idea that can create some strange bounties (to say the least).
POIDHverse itself started with POIDH bounty 1306. The request was:
Create a public app that showcases POIDH bounties in a creative and useful way.
I could have built another page with a front end skill that I found on X that puts the animation to the max where even the most hardcore designers would facepalm at it.
Instead, I turned the bounties into a universe.
From a bounty to a universe
Every bounty in POIDHverse becomes a star. Larger rewards create larger stars. Each chain has its own visual identity. New bounties pulse, unclaimed bounties glow more brightly, and voting or multiplayer bounties receive their own visual treatments.
A bounty’s position is derived from its chain ID and bounty ID. This means the same bounty appears in the same part of the universe every time instead of randomly moving whenever you open the application.
There are three ways to explore:
- Universe is the interactive Canvas view.
- Discover provides accessible search, filters, and sorting.
- Roulette chooses a random bounty from the currently active filters.
Selecting a star opens its description, reward, chain, issuer, status, and Discovery Score. Participating still happens on POIDH itself.
There is no wallet requirement, account system, or transaction interface. You open the application and immediately start exploring.
Hey, isn't this just a normal website?
That is exactly what it looks like.
You open a link. A universe appears. The stars move. You search, filter, open bounties, and press buttons. Nothing about it feels different from any other modern website.
But there is no traditional hosting behind POIDHverse.
The HTML, CSS, JavaScript, React runtime, and fallback bounty data are all stored inside 24 smart contracts on Ethereum Mainnet. There is no remote JavaScript bundle, external font, application server, database, CDN, or analytics service holding the application together.
Ethereum is storing the website.
Someone only needs a way to read Ethereum and call one function:
function html() external view returns (string memory);
The contract responds with the complete application. It returns the HTML document that your browser can open and run. You don't need a wallet, calling html() only reads from Ethereum, so a gateway or Ethereum node can retrieve the application without spending gas.
The deployed POIDHverse v0.1.0 release is immutable. It can no longer be edited or replaced, and it will continue returning exactly the same application for as long as Ethereum exists.
Normal things can still go wrong. My laptop can disappear. I could accidentally delete my repository. I could forget to renew a hosting account. None of that removes POIDHverse from Ethereum.
The application will still be reachable through its UniverseResolver contract:
0x4f6bdaaC679961aA0C8C6503CF204EA9F9A7A0aC
Even if that resolver later points to a newer release, v0.1.0 will remain available forever at its own version contract.
I can publish a newer version, but I cannot silently rewrite the old one.
But bounties keep changing
At this point there is an obvious question. If the application is permanent, how can it show a bounty that gets created tomorrow?
The application is permanent, but it is not frozen in time.
Fresh bounty information comes from POIDH’s live data feed. This means new bounties can appear without requiring me to deploy the entire application again.
POIDHverse uses three layers of data:
- Current data from POIDH
- The latest successful response saved in the browser
- An immutable snapshot stored with the application on Ethereum
When the POIDH feed is available, the universe shows the latest bounties. If the feed becomes unavailable, it uses the latest response saved in the browser. If neither is available, it falls back to 88 real bounties captured with v0.1.0.
The interface always tells you whether you are viewing live, saved, or snapshot data.
This is what I mean when I say the application is 100% onchain. It does not mean every new bounty must be permanently written into the application. It means the complete working release and a functional set of bounty data can be reconstructed from Ethereum without depending on me or any infrastructure.
The live feed keeps it alive. The onchain snapshot makes sure it survives.
Okay, this is the point where I am about to nerd out about the technical details, because this is the stuff I live for.
You have been warned.
If contract bytecode, EVM opcodes, and deployment architecture are not your thing, feel free to skip ahead to where I return to normal human language.
Building a React application as one file
POIDHverse was written in TypeScript and React.
React itself does not prevent an application from being onchain. React is simply the authoring tool. During the build, it becomes ordinary JavaScript that a browser can execute.
The production build creates one file:
release/universe.html
That document contains:
- the HTML structure;
- all CSS;
- the compiled React application;
- the React runtime;
- icons and visual elements;
- the fallback bounty data;
- the restrictive Content Security Policy.
There are no external scripts or stylesheets. The only allowed live connection is to POIDH.
The build process uses Vite to produce the JavaScript and CSS, then inlines both into the HTML document. It also creates a public copy that is identical at the byte level and records cryptographic hashes for the release.
The final v0.1.0 artifacts are:
| Artifact | Size |
|---|---|
| Complete application | 349,391 bytes |
| Standalone bounty snapshot | 111,260 bytes |
| Combined | 460,651 bytes |
The standalone snapshot contains 88 real POIDH bounties captured on 16 August 2026. A copy is bundled into the application for immediate fallback, while the exact source snapshot is also independently exposed through the contracts.
The first problem: Ethereum will not accept a 349 KB contract
Ethereum currently limits deployed contract runtime code to 24,576 bytes.
That limit comes from EIP-170, introduced to protect Ethereum nodes from the costs associated with loading and proving extremely large contracts. A proposal to increase the limit exists, but EIP-7907 is still a draft.
Our application is almost 14 times larger than the current limit. That is before adding the separate snapshot.
The solution is to divide the files into smaller pieces.
POIDHverse uses chunks of 22,500 bytes, leaving a comfortable margin below the EIP-170 ceiling.
This produces 21 immutable data contracts.
The application and snapshot are split across 21 storage contracts, then reconstructed in their original order.
Using contract bytecode as permanent file storage
A smart contract normally stores executable EVM instructions in its runtime bytecode.
But runtime bytecode is still just bytes.
POIDHverse takes a piece of the application and deploys those bytes as the runtime code of a tiny storage contract. Each stored payload starts with the STOP opcode:
0x00 || application bytes
The leading STOP makes accidental calls harmless. If someone calls one of these storage addresses, execution immediately stops instead of attempting to interpret HTML as EVM instructions.
The simplified storage container looks like this:
contract CodeStoreContainer {
constructor(bytes memory data) {
bytes memory runtime = bytes.concat(hex"00", data);
assembly ("memory-safe") {
return(add(runtime, 0x20), mload(runtime))
}
}
}
This constructor does something unusual: instead of returning normal compiled contract logic, it returns the supplied application data as the contract’s permanent runtime code.
Once deployed, that data cannot be modified.
UniverseVersion keeps the addresses of every data contract in two arrays:
address[] private _htmlPointers;
address[] private _snapshotPointers;
constructor(
string memory version_,
address[] memory htmlPointers_,
address[] memory snapshotPointers_,
bytes32 htmlHash_,
bytes32 snapshotHash_
) {
_htmlPointers = htmlPointers_;
_snapshotPointers = snapshotPointers_;
}
Each pointer leads to one tiny bytecode storage contract. That contract has:
- no callable functions;
- no normal Solidity runtime;
- no mutable storage;
- one
STOPbyte; - up to 22,500 bytes of HTML or JSON directly after it.
The first HTML chunk on Etherscan begins like this:
0x00 3c21646f63747970652068746d6c3e...
Breaking those first bytes apart gives us:
00 STOP
3c21646f63747970652068746d6c3e <!doctype html>
After skipping the first byte, the payload starts as completely normal HTML:
<!doctype html>
<html lang="en">
<head>
The first storage contract starts with a STOP byte followed directly by the opening bytes of the HTML document.
Reading the website back with EXTCODECOPY
Ethereum provides an opcode called EXTCODECOPY. It allows one contract to copy runtime bytecode from another contract into memory.
POIDHverse stores the chunk addresses in the correct order, measures each contract’s runtime length, and copies everything after the leading STOP byte:
assembly ("memory-safe") {
extcodecopy(
pointer,
add(add(data, 0x20), cursor),
1,
dataSize
)
}
The reader repeats this operation for all 16 HTML contracts and joins the results in order. The same process reconstructs the five snapshot chunks. No external indexer is needed because the version contract already knows the ordered list of addresses.
UniverseVersion: one immutable application release
The 21 storage contracts hold the bytes, but UniverseVersion turns those pieces into a coherent release.
Its constructor receives:
- the semantic version;
- the 16 HTML chunk addresses;
- the five snapshot chunk addresses;
- the expected HTML hash;
- the expected snapshot hash.
During deployment, the constructor reconstructs both artifacts and computes their Keccak 256 hashes. If either reconstructed hash differs from the build inputs, deployment reverts.
That means a partially deployed, incorrectly ordered, or corrupted release cannot be accepted accidentally.
The contract then exposes:
function version() external view returns (string memory);
function html() external view returns (string memory);
function snapshot() external view returns (bytes memory);
function htmlHash() external view returns (bytes32);
function snapshotHash() external view returns (bytes32);
function deployedAt() external view returns (uint256);
The important part is here:
function html() external view returns (string memory) {
return string(_htmlPointers.readAll());
}
Calling html() reads all 16 contracts, joins their data, and returns the original 349,391-byte document.
For v0.1.0, the committed HTML hash is:
0x831068194231788963b86dda60c1f7b0c072a63e3d27519e0a524a3cb829bc0c
Why there are exactly 24 contracts
The complete architecture is:
| Purpose | Contracts |
|---|---|
| HTML bytecode storage | 16 |
| Snapshot bytecode storage | 5 |
| Immutable application version | 1 |
| Permanent release history | 1 |
| Stable gateway resolver | 1 |
| Total | 24 |
The 21 storage contracts hold the actual files. UniverseVersion reconstructs and verifies one permanent release. UniverseRoot records the release history. UniverseResolver provides one stable address for gateways and the .wei name.
The browser reaches the onchain application through the resolver, then uses live, saved, or snapshot bounty data.
Immutable releases without freezing development forever
There is an obvious problem with making software immutable: what happens when something needs to change?
POIDHverse separates an immutable release from release discovery.
Every UniverseVersion is permanent. It has no update function, proxy, or storage mutation mechanism.
UniverseRoot is a registry that only allows new entries. The publisher can add a new generation, but it cannot edit or remove an older one.
Generation 1 → UniverseVersion v0.1.0
Generation 2 → UniverseVersion v0.2.0
Generation 3 → UniverseVersion v1.0.0
All three versions remain independently retrievable.
UniverseResolver provides a stable address that forwards html() and the other read functions to the currently approved version.
The first release activated immediately. Future releases must be staged for 72 hours before activation. Anyone can activate the staged release after the delay, so activation does not depend on me remaining available.
A bug fix means publishing another immutable application beside it. The old version remains exactly as deployed.
The interface that turns a contract into a website
POIDHverse adopts the core interface proposed in draft ERC-8244:
function html() external view returns (string memory);
The idea is simple: if a contract exposes html(), a compatible gateway, wallet, or browser can treat that contract as a web application.
The standard does not need to know how the HTML is stored. It could be one small contract or compressed data. In the case of POIDHverse, it is 21 bytecode storage contracts reconstructed by a version contract.
ERC-8244 is still a draft, so this might not be the final version how to deploy this.
The idea complements with standards such as ERC-4804, which describes translating web3:// URLs into EVM calls. POIDHverse currently uses WNS and an html() gateway rather than claiming complete ERC-4804 support.
Much of my inspiration came from zSwap and the fully onchain applications built by z0r0z.
If the website lives on Ethereum, how does my browser find it?
The application does not have a traditional domain or web server. Its permanent entry points are Ethereum addresses:
UniverseRoot
0x5DCbD2FCE275A49D233a46A8c337429b48B5A965
UniverseResolver
0x4f6bdaaC679961aA0C8C6503CF204EA9F9A7A0aC
UniverseVersion v0.1.0
0x941029fB4BA0823335001B31Cfa1f05CDc33B095
poidhverse.wei is a readable WNS name that resolves to the stable UniverseResolver contract address.
When somebody opens poidhverse.wei.limo, the gateway does the following:
- resolves
poidhverse.weito the resolver address; - reads
html()from Ethereum without making a transaction; - receives the complete HTML string;
- serves it to the browser as a webpage.
The gateway makes the application convenient for an ordinary browser, but it is not the official storage location.
There is no IPFS object containing the frontend. The .wei.limo gateway is a translator between normal web browsers and the application stored in Ethereum.
If that particular gateway disappears, the contracts and their bytes remain available. Another gateway can retrieve the same release. Freedom Browser is one example of a browser built to open websites directly from Ethereum.
Proving that the website really comes from Ethereum
The complete application can be retrieved from the resolver using Foundry’s cast tool:
cast call 0x4f6bdaaC679961aA0C8C6503CF204EA9F9A7A0aC \
"html()(string)" \
--rpc-url "$ETHEREUM_RPC_URL" \
--json |
jq -jr '.[0]' > retrieved-universe.html
The JSON decoding is important because the normal terminal representation adds quotes and escaped characters around returned strings. And you can inspect the retrieved-universe.html.
The resolver also returns the committed hash directly:
cast call 0x4f6bdaaC679961aA0C8C6503CF204EA9F9A7A0aC \
"htmlHash()(bytes32)" \
--rpc-url "$ETHEREUM_RPC_URL"
The actual Mainnet deployment
The Mainnet deployment happened in two stages.
First, UniverseRoot and UniverseResolver were deployed. Their permanent addresses were added to the application and the complete HTML was rebuilt.
The release deployment then created:
- 16 HTML storage contracts;
- five snapshot storage contracts;
- one
UniverseVersion; - one call publishing the version to
UniverseRoot; - one call activating it through
UniverseResolver.
Across both stages, the deployment required 26 transactions and created 24 contracts. It took approximately five to six minutes to deploy the whole thing.
The actual cost was:
| Stage | Cost |
|---|---|
| Root and resolver | 0.000039983663194056 ETH |
| Storage chunks, version, and publication | 0.006416181345199078 ETH |
| Total | 0.006456165008393134 ETH |
That was approximately $12 at the time of deployment. It's not that expensive for not needing to think about hosting bills.
The total gas used was 104,031,481 gas. The ETH cost remained relatively low because Ethereum Mainnet gas prices were unusually low during deployment.
Okay, back to normal human language
Putting an application into Ethereum contract bytecode is not the correct architecture for every website.
If you are creating a small static website, a permanent public artifact, or a simple application that rarely changes, this approach can make sense.
If your application depends on large images, video, or constant updates, storing it on Ethereum would quickly become expensive and impractical. Large eth_call responses can also exceed the limits of some RPC providers.
POIDHverse still uses .wei.limo as a gateway, WNS for its readable name, and POIDH for the freshest bounty data. None of them stores the v0.1.0 application.
A Different Kind of Forever
Smart contracts gave us programs that could continue operating without their original author.
But most people do not interact with raw smart contracts. They interact with websites.
That leaves an odd gap: the protocol may be permanent while its user interface remains only temporary.
POIDHverse is an experiment in closing that gap.
It is still a real React application with search, responsive design, touch gestures, Canvas rendering, and live data. The difference is that its final build is treated like an immutable public artifact.
That feels like a small glimpse of a different web. One where we can publish software that can stand the test of time.
Most of the spotlight right now is on AI, and rightfully so. It is an amazing technology that is changing nearly everything around us.
But seeing the new ERC drafts and how far Ethereum has come reminded me that there are still some ideas left to explore.
I think we have a pretty cool future ahead of us.