I reviewed this standard and I think it has a lot of potential. I also think there may be room to push the idea further.
If we think of html() not only as a way to serve one complete HTML document, but as a way for a contract to expose the UI for its own domain, then each contract can become a self-contained service.
For example:
AccountManagementContract
├── html() => account UI
└── state/actions => account state and logic
In this model, the HTML returned by the contract is not just static content. It can read from and write to the contract that hosts it, or interact with other contracts through the injected provider. This makes the contract behave more like a complete web service without requiring a traditional backend.
This can be extended further with a higher-level registry/factory contract that acts as the composition root for a larger application system.
For example:
RegistryContract
├── AccountManagementContract
│ ├── html() => account UI
│ └── state/actions => account state and logic
│
├── TradeManagementContract
│ ├── html() => trading UI
│ └── state/actions => trading state and logic
│
├── GovernanceManagementContract
│ ├── html() => governance UI
│ └── state/actions => voting/proposal state and logic
│
└── TreasuryManagementContract
├── html() => treasury UI
└── state/actions => treasury state and logic
This is different from simply splitting one large HTML file into chunks. Instead, each contract represents a domain or service, and each service hosts its own UI through html().
The registry contract can then provide service discovery for the whole system. A client can query the registry, discover the available service contracts, fetch their HTML interfaces, and render or route between them.
At the client layer, the renderer can also use the registry as a routing table. For example, the registry can map paths or route identifiers to service contracts. The client reads the registry, resolves the requested path, fetches the corresponding service contract’s html(), and renders it.
/account => AccountManagementContract.html()
/trade => TradeManagementContract.html()
/governance => GovernanceManagementContract.html()
/treasury => TreasuryManagementContract.html()
This makes the registry not only a service discovery layer, but also an on-chain routing layer for contract-hosted applications.
The registry can also support lazy loading. A client does not need to fetch every service UI upfront. The registry can map page IDs or paths to service contracts.
For example:
// page 0 => homepage
// page 1 => vote
// page 2 => treasury
mapping(uint256 => address) public pages;
Then the client can choose its own loading strategy.
Full load:
- read all registered pages
- fetch all html()
- render the full application shell
Lazy load:
- read only the requested page ID or path
- fetch only that contract's html()
- render it when needed
This means both partial loading and full loading are possible. The registry only defines the application topology, while the client decides how much of the application to load at a time.
This gives a few benefits:
- It reduces practical HTML size limitations because the application is separated across multiple domain contracts.
- It makes large contract-hosted applications easier to organize and reason about.
- It allows each domain contract to own both its state/actions and its own UI.
- It creates a single source of trust through the registry contract.
- If the registry is made immutable, the application topology can also become immutable.
- It allows clients to implement routing based on registry-defined paths, making larger contract-hosted applications feel closer to normal multi-page web applications.
- It supports lazy loading, so clients can load only the page or service needed instead of fetching the whole application upfront.
So the base html() interface can stay simple, while a registry-based abstraction could support larger contract-hosted application systems, almost like on-chain microservices or contract-hosted micro-frontends.