solweb
sol:// · specificationdraft 3

The sol:// Protocol

Websites as content-addressed Solana accounts

This document also exists on solweb at sol://wduonnejmsmlbpuyhitkio4w7q7mtsvvtziulvsxz2w27273nn6a.devnet/.

We describe a URL scheme and a set of account layouts that let a website live entirely in Solana accounts, and a browser that treats the result as a security boundary. Files are stored in blobs addressed by their hash, a manifest maps paths to blobs, and a site account points at the current manifest. Because content lives in account state rather than in transaction history, a site persists for as long as Solana keeps its state, and every version it has ever published remains readable, verifiable and citable: a web archive that no one has to operate. Because the manifest also declares which programs a site may call, the browser's wallet can refuse to quietly sign anything else. This document is itself served this way.

Three stone slabs standing on bare ground, dithered to one bit
Fig. 1 Sites stand on their own. Nothing serves them; the chain keeps them.

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.

accountseedsholds
blob"blob", sha256(bytes)up to 10,168 bytes, a sealed flag and the writer
site"site", authority, slugauthority, frozen flag, version counter, manifest reference
version"version", site, nthe manifest reference published as version n
binding"sns", domainthe site a .sol name points to, and its owner when bound
SITEVERSION nMANIFESTBLOBS authorityslugversion: nfrozen? sha256lengthslotblob hashes /index.html/site.cssprogramsroutes addr =pda(sha256)sealed≤ 10 KB mutable, read from several RPCsimmutable, checked by hash
Fig. 2 Resolution walks from the one mutable account to bytes that can only be what their address says.

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.

A stack of thin plates, each slightly turned from the one below, dithered to one bit
Fig. 3 Versions only accumulate. Each plate is still there, under the one on top.

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

  1. 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.
  2. Read the site account, or for a pinned URL the version account.
  3. 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.
  4. 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.

directionencoding
requestsha256("solweb:handle")[0..8], u16 path length, path, u16 query length, query
responseu16 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

quantityvalue
rent deposit per stored byte5,080 lamports
bytes carried by a create / write transaction957 / 1,023
largest blob10,168 bytes
a full blob, deposit0.0527 SOL
site account / each version0.0027 / 0.0024 SOL
this paper, compressedabout 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.

  1. ERC-4804, Web3 URL to EVM Call Message Translation, 2022.
  2. ERC-6860, Web3 URL to EVM Call Message Translation (revised), 2023.
  3. RFC 4648, The Base16, Base32, and Base64 Data Encodings, section 6.
  4. Bonfida, Solana Name Service.