Introduction
A decentralised application is usually a set of programs on a chain and a website somewhere else. The programs are public, audited and often immutable; the website is served by whoever controls a domain, a CDN account and a deploy key. Users approve transactions on the strength of the website, so the least defended component decides what they sign.
Ethereum's web3:// scheme1,2 addresses part of this by translating URLs into read-only contract calls. This protocol is its Solana counterpart. It differs in three ways: storage is content-addressed and shared between sites, every version is retained and can be pinned by URL, and a site declares the programs it is permitted to use.
Names and URLs
sol-url = "sol://" [ pin "." ] target [ "." cluster ] path [ "?" query ]
pin = "v" 1*DIGIT
target = label ".sol" / site-address
cluster = "mainnet" / "devnet" / "localnet" ; default: mainnet
site-address = 52( %x61-7A / "2"-"7" ) ; RFC 4648 base32, lowercase
Hosts in standard URL schemes are case-insensitive, and browsers lowercase them, so a base58 address cannot survive in the host. Canonical URLs therefore spell site addresses in lowercase base323; clients accept base58 from people and convert it. A .sol name is looked up in the Solana Name Service4.
A pin such as sol://v3.alice.sol/ selects an immutable version. Because the version is part of the origin, relative and absolute links inside a pinned site stay pinned.
Accounts
All accounts are program-derived addresses of a single program.
| account | seeds | holds |
|---|---|---|
| blob | "blob", sha256(bytes) | up to 10,168 bytes, a sealed flag and the writer |
| site | "site", authority, slug | authority, frozen flag, version counter, manifest reference |
| version | "version", site, n | the manifest reference published as version n |
| binding | "sns", domain | the site a .sol name points to, and its owner when bound |
A manifest reference is the manifest's SHA-256, its length, the slot it was published at, and the hashes of up to eight blobs that contain it.
Publishing
A publisher creates each blob at the address derived from its hash, writes it in transaction-sized pieces, and seals it. The program refuses to seal a blob whose contents do not hash to its address; a sealed blob can never be written or closed. Identical bytes always have the same address, so content uploaded by any site is free for every other.
Publishing a version is a single instruction. The program checks that every manifest blob is sealed and that, concatenated, they hash to the declared manifest hash. It then writes a new version account and advances the site. A site's authority can transfer control or freeze the site, after which no version can be added.
Permanence and the archive
A site does not live in Solana's transaction history, which validators prune and only specialised archives retain. It lives in account state, which every validator and every RPC node holds in full. Reading a site therefore needs no archive node, no indexer and no host of any kind, and the bytes outlast every particular machine that serves them: they persist for as long as Solana keeps its state.
Nothing in the program deletes history. Sealed blobs cannot be closed, version accounts are never removed, and a site can only append. Every version a site has ever published stays retrievable at a pinned URL and verifiable against the hash recorded when it was published, in the slot it was published. The result is a web archive that comes for free. Unlike a crawler's copy, it is the publisher's own record: complete, ordered, timestamped by the chain and tamper-evident.

This changes what a citation can promise. A reference of the form sol://v3.…/#section cannot rot and cannot be silently edited: it resolves to the same bytes it resolved to on the day it was written, or to nothing at all, but never to something else. The essay in this collection is frozen, so even its unpinned address is such a reference.
Manifests
{ "solweb": 1, "title": "…", "notFound": "/404.html",
"programs": ["11111111111111111111111111111111"],
"routes": [{ "path": "/api/clock", "program": "dyn…", "accounts": [] }],
"files": { "/index.html": { "type": "text/html; charset=utf-8",
"encoding": "br", "size": 2939, "sha256": "b620…", "blobs": ["c41f…"] } } }
Files are compressed with brotli when that saves space and may span any number of blobs. A request is matched against the exact path, then /index.html for directories, then .html, then the notFound page.
Resolution and verification
- Derive the SNS domain account for the name and the binding account for that domain. Read both; reject the binding if the domain's owner has changed since it was written.
- Read the site account, or for a pinned URL the version account.
- Read the manifest blobs. Require that each is owned by the program, sealed, stored at the address its hash derives, and that its bytes hash to that address. Require that their concatenation hashes to the manifest reference.
- Read the file's blobs under the same conditions, decompress, and compare length and SHA-256 with the manifest entry.
The RPC node is not trusted with content: it can only withhold blobs, never alter them undetected. It is trusted with the mutable accounts in steps 1 and 2, which is why a client may read them from several independent endpoints and require identical answers.
Every response carries a content security policy that confines a page to its own origin, the cluster's RPC endpoints and any origins its manifest lists. All code a page can run is therefore code that was hash-checked.
Dynamic routes
A route hands a path to a program. The client builds a transaction calling that program with the path and query, simulates it without a signature, and decodes the return data as the response.
| direction | encoding |
|---|---|
| request | sha256("solweb:handle")[0..8], u16 path length, path, u16 query length, query |
| response | u16 status, u8 type length, content type, body |
Responses are limited to the 1,024 bytes of return data a transaction may produce. Accounts for the simulation come from the route, either fixed addresses or named query parameters.
The transaction firewall
A solweb browser provides a wallet to the pages it renders. When a page asks for a signature, the browser decodes the transaction, simulates it with inner instructions, and shows the change in the user's balance. Every program the transaction invokes directly is compared with the programs the manifest declares; any program not declared is reported prominently and the confirmation becomes sign anyway. Programs reached through cross-program calls are listed too, but not judged: they are chosen by the declared programs, not by the page.
The effect is that a compromised frontend cannot quietly add an instruction. To do so it would have to publish a new manifest declaring the program, which is an authority-signed, publicly recorded change, and impossible for a frozen site.
Costs
| quantity | value |
|---|---|
| rent deposit per stored byte | 5,080 lamports |
| bytes carried by a create / write transaction | 957 / 1,023 |
| largest blob | 10,168 bytes |
| a full blob, deposit | 0.0527 SOL |
| site account / each version | 0.0027 / 0.0024 SOL |
| this paper, compressed | about 7 KB |
Figures are read from the devnet rent parameters when this page is published. Deposits are refundable for unsealed blobs only; a sealed blob is permanent so that sites sharing it cannot break one another, and so that the archive described above cannot be dismantled.
Limitations
Names currently require the Solana Name Service on the cluster in question. The trust placed in RPC endpoints for mutable accounts is reduced by quorum but not eliminated; a light client would remove it. Dynamic responses are small. Large media belong elsewhere and should be referenced by hash. Permanence is bounded by the chain: it holds while the accounts remain in Solana's state, a future change that expired state would weaken it, and on devnet it lasts as long as devnet does.
Changes
Draft 3. Added the section on permanence and the archive. The firewall now judges only programs a transaction invokes directly.
Draft 2. Added the transaction firewall and costs sections and revised the abstract.
Every earlier draft remains readable at its pinned address, for example sol://v1.… for draft 1.
- ERC-4804, Web3 URL to EVM Call Message Translation, 2022.
- ERC-6860, Web3 URL to EVM Call Message Translation (revised), 2023.
- RFC 4648, The Base16, Base32, and Base64 Data Encodings, section 6.
- Bonfida, Solana Name Service.
