Skip to content

How BrowserChain works

Every browser tab is a validator

BrowserChain is a Stellar-like blockchain that runs entirely in browser tabs. Open the app and your tab starts a validator. It keeps its own copy of the ledger, votes with the other tabs in the Stellar Consensus Protocol (SCP), and closes a new ledger about every 5 seconds. There is no BrowserChain server, and the XLM on it is play money.

Opening the app starts a validator in that tab. To look around without one, open the demo with simulated data.

Ledgers
About every 5 s
Consensus
SCP, ported from stellar-core
Data
Real Stellar XDR
Money
Play XLM, no value

01 · What it is

What it is, precisely

BrowserChain reimplements two parts of Stellar in TypeScript: the Stellar Consensus Protocol and the classic ledger. Every tab runs both.

It is

  • A port of stellar-core’s SCP: nomination, the ballot protocol and the quorum checks. The function names follow the C++ code.
  • A classic Stellar ledger at protocol 23: accounts, sequence numbers, fees and reserves, signers and thresholds, trustlines, payments, the order-book DEX with path payments, claimable balances and clawback. Validity checks and result codes follow stellar-core.
  • Real Stellar XDR from @stellar/stellar-sdk. Transactions, SCP messages, quorum sets, ledger headers and results decode with any Stellar XDR tool.

It is not

  • stellar-core. No stellar-core code runs in your tab. The logic is ported to TypeScript, not compiled.
  • Connected to Stellar. BrowserChain nodes cannot talk to Stellar nodes. The peer protocol, the history format and the state hash are its own. Its ledger hashes differ from stellar-core’s.
  • Real money. It is a separate network with the passphrase BrowserChain Network ; October 2026. Its XLM has no value.

Each ported file names its stellar-core source in a header comment. The design, the data formats and every rule are in docs/ARCHITECTURE.md.

02 · Run a validator

Become a validator in one click

There is nothing to install and no account to create. Opening the page is the whole setup.

Browser on machine A Tab 1 Page UI · WebRTC Web Worker validator node Tab 2 Page UI · WebRTC Web Worker validator node Tab 3 Page UI · WebRTC Web Worker validator node BroadcastChannel Browser on machine B Tab 1 Page UI · WebRTC Web Worker validator node Tab 2 Page UI · WebRTC Web Worker validator node BroadcastChannel WebRTC peer to peer Nostr relays connection setup only

When you open the page

  1. Your tab claims an identity: the first free one of 64 slots in this browser, held with the Web Locks API. Each slot keeps its own key and chain in IndexedDB.
  2. The node starts in a Web Worker and looks for peers over BroadcastChannel and WebRTC.
  3. It waits up to 12 s for peers, because finding peers through Nostr can take several seconds. With ?transport=broadcast it waits 4 s. After the first peer appears, it waits 1 s more for others.
  4. If a peer is ahead, the node catches up first. If nobody shows up, it starts validating alone, and the Overview says it is the only validator.
  5. It joins the quorum. About every 5 s it proposes a transaction set, votes in SCP and closes the ledger with the others.
Every tab is a page plus a Web Worker, and the worker is the validator. Tabs of one browser talk over BroadcastChannel. Tabs on different machines talk over WebRTC: workers cannot open WebRTC connections, so the page runs them and passes the messages to its worker. One WebRTC link is drawn; each tab connects to every tab on the other machine.

The auto quorum

In SCP, every validator chooses whom it trusts. In auto mode, the default, BrowserChain chooses for you. Your node trusts itself and every active validator, and needs about two thirds of them to agree. The threshold is max(1, ceil(2n/3)) of n validators: with 4 validators, any 3 must agree.

A validator counts as active when all of these hold:

  • your node heard from it in the last 9 seconds;
  • it is synced, booting or stalled, on your chain, and at most one ledger away from you;
  • it takes part in SCP: it sent a signed SCP message for a recent ledger. A validator seen for the first time gets 5 seconds of grace.

A new validator joins the quorum at the next ledger. A tab that closes says bye and leaves the quorum at once. A tab that vanishes without a word stops counting as active after 9 seconds. It then leaves the quorum at the next ledger, or at once if the open ledger has made no progress for 4 seconds.

Keep the tab open

Your tab is the validator. Close it and the node stops. Each tab is a separate validator with its own key, so three tabs are three validators. When you open the page again, the new tab takes the first free identity slot. That is usually the slot you closed, so the same validator comes back, loads its stored chain and catches up.

Browsers slow down background tabs and sometimes freeze them. A frozen node stops voting, and the others drop it from their quorum. When the tab wakes up, the node checks that it is still in sync, catches up if it fell behind, and votes again.

Settings and switches

?fresh=1
A new identity and in-memory storage for this tab. The validator stores nothing, and each reload is a new validator. Use it when a private window blocks IndexedDB.
?name=alice
The validator name that peers see. You can also rename the node on the Validator page.
?network=friends
A separate network with its own passphrase and genesis. Everyone who wants to meet must use the same name.
?transport=broadcast
Tabs of this browser only, without WebRTC. A lone tab then waits 4 s instead of 12 s. The other values are webrtc and both, the default.
?mock=1
The interface on simulated data, with no node.
Validate switch
On the Validator page. Turn it off to watch consensus without voting.
Manual quorum
On the Validator page, switch the quorum set from Auto to Manual and pick the validators and the threshold yourself. Nested sets are allowed. A manual set never changes by itself, and SCP keeps its classic safety guarantees for it.
Resync
On the Validator page. It wipes the local chain and catches up from peers. The identity stays.

What you see

Overview

The last closed ledger: its hash, its transactions and how many validators agreed on it. A consensus card with each validator’s latest SCP statement for the open ledger. Then active validators, average close time, recent ledgers, a graph of the quorum and live transactions.

Validator

This node’s identity and state, the Validate switch and Resync. The quorum set with its threshold, fault tolerance and an intersection check. A slot inspector, the SCP message log, the peers with their transport, known validators, recent forks and the node log.

The Wallet makes test accounts that friendbot funds, sends payments, issues assets, trades on the DEX and creates claimable balances. The Explorer shows ledgers, transactions with decoded XDR, and accounts. The Lab decodes, builds, signs and submits XDR.

Try it with friends on other machines

  1. One person opens kaankacar.github.io/browserchain and keeps the tab in front. After up to 12 seconds it starts validating alone, and ledgers begin to close.
  2. Friends open the same link on their own machines. Everyone must use the same link, so that all of them run the same version on the same network.
  3. Each new tab finds the running network over WebRTC, catches up and starts to vote. Finding peers through Nostr can take several seconds. On the Validator page, the Peers tab lists them with the transport webrtc, and the Overview counts more active validators.
  4. If a tab started a chain of its own and other tabs joined it, there are now two groups, and they do not merge by themselves. A tab shows the alert “Another group of validators is on a different chain” when the other group is at least as large as its own. Press Resync in each tab that shows it.

Some networks block direct WebRTC connections. BrowserChain uses public STUN servers but no TURN relay, so two browsers behind strict firewalls may fail to connect. Tabs of one browser do not need any of this: they talk over BroadcastChannel.

03 · How the chain works

How the chain works, in plain words

BrowserChain follows Stellar’s design. There is no mining and no staking. Validators agree by federated voting, and every node applies the same transactions in the same order.

Federated voting

Each validator lists the validators it trusts and how many of them must agree. That list is its quorum set, for example “any 3 of these 4”. A group that satisfies it is a quorum slice. A quorum is a group of validators that contains a slice of each of its members.

SCP settles a statement in three steps:

  1. Vote. A node votes for a statement that it considers valid.
  2. Accept. It accepts the statement when a quorum that includes it voted for it or accepted it, or when a v-blocking set accepted it. A v-blocking set overlaps every slice of the node, so every quorum of the node includes at least one of its members.
  3. Confirm. It confirms the statement when a quorum accepted it. A confirmed statement is final.

This is safe as long as any two quorums share an honest validator. The Validator page checks that any two quorums intersect, when your node knows at most 16 validators.

Two phases for each ledger

Each ledger is one SCP slot. For each slot, SCP uses federated voting in two phases.

Nomination decides what to vote on. A candidate value is a StellarValue: the hash of a transaction set, a close time, and the signature of the validator that built it. Nomination runs in rounds. Each round has leaders, chosen by a hash, and nodes vote for their leaders’ values. When candidates are confirmed, every node combines them the same way: the transaction set with the most operations wins, then the one with the highest fees, then the lowest hash.

The ballot protocol makes the choice final. Nodes put the combined value in a ballot and vote to prepare it, then to commit it. If a slot gets stuck, timers move it on: nomination adds leaders, and the ballot moves to a higher counter. Nomination round n, or ballot counter n, waits n seconds, up to 30 seconds.

One ledger, about every 5 seconds

SCP 5 s target per ledger 1Tx queue 2Tx set 3Nominate 4Prepare 5Confirm 6Externalize 7Close
  1. Tx queue. Every node keeps a queue of transactions. A new transaction is checked (signatures, sequence number, fee, balance) and sent on to all peers.
  2. Tx set. When the last ledger has closed and 5 s have passed since its previous tx set, each validator takes transactions from its queue by fee rate, up to 1,000 operations. It signs a value: the hash of that set and a close time.
  3. Nominate. Validators vote on these values until some are confirmed, then combine them into one.
  4. Prepare. The value goes into a ballot. Validators accept, then confirm, that the ballot is prepared.
  5. Confirm. Validators accept to commit the ballot. From here a node cannot change its value for this ledger.
  6. Externalize. Validators confirm the commit. The value is final.
  7. Close. Every node applies the same transaction set in the same order and gets the same ledger hash. It stores the ledger, and the cycle starts again.
The life of one ledger. Steps 3 to 6 are SCP. The Overview shows them live, and the Validator page logs every SCP message.

Fees, reserves and sequence numbers

  • Fees. Each operation costs the base fee, 100 stroops (1 XLM is 10,000,000 stroops). A higher fee bid only moves a transaction ahead when a ledger is full; the charge stays the base fee. There is no surge pricing.
  • Reserves. An account must keep (2 + subentries) × 0.5 XLM. Trustlines, offers, extra signers and data entries are subentries. A claimable balance adds 0.5 XLM per claimant to the reserve of the account that created it.
  • Sequence numbers. Each account has a sequence number, and a transaction must use the next one. So a transaction cannot be replayed or applied out of order. A node queues up to 16 transactions per account, and replaces a queued one only for 10 times its fee.
  • Order. At close, all fees are charged first. Then each transaction applies all of its operations or none, in an order that every node computes the same way.

Friendbot and the play money

The genesis ledger creates 100 billion play XLM. Sixteen friendbot accounts hold 1 billion each, and the root account holds the rest. Their keys come from the network passphrase, so anyone can derive them. Rules limit what those keys can do:

  • The root account is locked: nothing it signs is valid.
  • A friendbot account may sign one kind of transaction only: one operation that creates a new account with 10,000 XLM, or pays an existing account 10,000 XLM, at exactly the base fee.
  • A ledger holds at most 16 friendbot transactions.
  • When you press Create a test account on the Wallet page, your node builds the friendbot transaction itself, from a friendbot account assigned to it, and submits it like any other.

The DEX

The order book follows stellar-core at protocol 23. Offers buy and sell any pair of assets and cross by price, then by time, with stellar-core’s exchange math. Path payments cross several order books in one operation. One operation may cross at most 1,000 offers, and a ledger holds at most 200 operations from transactions that use the DEX.

Catching up

A node that falls behind catches up from its peers. There are no history archives: peers serve headers, ledgers and snapshots.

  • Replay. The node first checks that the ledger headers up to the target link to each other by hash. Then it downloads ledgers in batches of 64, applies each one itself, and checks that it gets the same header hash.
  • Snapshot. A new node with no chain starts from the newest snapshot, if there is one: a full copy of the state, taken every 64 ledgers. It checks the snapshot against the state hash in the ledger header, then replays the rest. A node that already has a chain uses a snapshot only when no peer can serve the ledgers and validators it knew before vouch for the snapshot.

Forks, and how they end

The auto quorum keeps the network live while tabs come and go. The cost: when the network splits, both sides can keep closing ledgers. That is a fork. Stellar validators avoid forks with quorum sets chosen by hand, and a closed Stellar ledger is never rolled back. BrowserChain detects forks and resolves them:

  1. A node sees a peer with a different header for the same ledger. It walks back to the last ledger they share: the fork point.
  2. It counts vouchers for each branch. A voucher is a connected validator whose signed status puts it on that branch. Only validators that this node saw as active members at or before the fork point count. Keys first seen later count for nothing.
  3. The branch with more vouchers wins, and the node counts itself for its own branch. On a tie, the heavier branch wins: the one whose ledgers since the fork were confirmed by more validators.
  4. The losing node rolls back to the fork point, catches up from the winner, and puts its own transactions that are still valid back in the queue.

Why only validators known before the fork? In auto mode anyone can make new keys. If new keys could vote on forks, one person with many tabs could take over the chain. Two lone tabs that meet have no members to count, so the heavier branch decides.

In manual quorum mode, a node never rolls back by itself. It shows a fork alert, and you decide whether to resync.

Groups that never met

Two groups of tabs that started apart are strangers: no node in one group ever saw a node of the other as a member. When they meet, neither follows the other, because to each side the other group looks like a crowd of new keys. Each group keeps its chain.

A tab shows the alert “Another group of validators is on a different chain” when the other group is at least as large as its own. Press Resync in the alert. The tab wipes its chain, keeps its identity and catches up from the other group like a new node. Do this in every tab of that browser that shows the alert.

A tab that comes back after all the validators it knew have left can be stuck the same way. It cannot check the new chain, and voting alone would build a chain of its own. So it does not vote: the Overview says “This node is not voting”, gives the reason and offers Resync.

04 · Under the hood

Under the hood

BrowserChain is plain TypeScript in one repository. Each module has its own tests, and docs/ARCHITECTURE.md is the contract between the modules.

src/scp
SCP: quorum functions, nomination, the ballot protocol and slots. A port of stellar-core’s src/scp. It never touches the network, storage or the clock directly.
src/ledger
Ledger state, validity checks, operations, transaction sets and ledger close. src/ledger/dex holds the order book and path payments.
src/herder
The glue: the SCP driver, the transaction queue, transaction set building, pending messages, the quorum policy and the close pipeline.
src/overlay
Transports (BroadcastChannel, the WebRTC bridge, an in-memory network for tests), signed hellos, flooding, requests and rate limits.
src/history
Serves history to peers. Catch-up, fork detection and fork verdicts.
src/storage
IndexedDB and in-memory storage for ledgers, transactions, snapshots and settings.
src/node
Builds a node from the parts and runs it in the Web Worker. Identity slots, boot, and the RPC client that the interface uses.
src/sim
Many nodes on one virtual clock: partitions, crashes, forks and hostile peers, all repeatable from a seed.
src/ui
The React interface. It asks the node through RPC, and the wallet signs transactions in the page.
src/golden
Golden byte vectors for consensus data.

Signed hellos, bound to the WebRTC channel

When two nodes connect, each sends a hello signed with its node key. Over WebRTC the hello also covers a fresh nonce from the other side and the DTLS channel id: a hash of both peers’ DTLS certificate fingerprints, which each browser reads from its own connection. A hello made for one connection is useless on any other, so nobody can relay a captured hello to pass as another node. DTLS encrypts the rest of the channel. BroadcastChannel links never leave the browser.

Every consensus message is signed

Each SCP message is signed by its node over the network ID and the statement. Each nominated value carries the signature of the validator that built it, and status announcements are signed too. Nodes check every signature and every hash: transaction sets, quorum sets, ledger headers and snapshots. Buffers and requests have limits, for example 4 MB per message and 64 peers per node.

Golden byte vectors

src/golden freezes the bytes that consensus depends on: the genesis ledger, StrKeys, transaction envelopes, ledger closes, a signed value, SCP messages and hashes, overlay messages, and a 4-node simulation with its snapshot at ledger 64. Sixteen tests compare the code with them, so a refactor that changes these bytes fails a test. Every intended change is logged in src/golden/CHANGES.md.

Deterministic by design

Closing a ledger is a pure function of the state, the transaction set and the agreed value. It reads no clock and no random numbers. The rest of the code gets time from an injected clock. So the same node code runs in your browser and in a simulation on a virtual clock, where many nodes run fast and every run can be repeated.

05 · How it was built

How it was built

The idea came from the viral “X running in your browser” projects. Many of them compile existing game code to WebAssembly, and several use WebRTC for multiplayer. Doing the same with stellar-core, a large C++ program, looked like months of work. BrowserChain takes another path: it reimplements Stellar’s consensus and classic ledger in TypeScript, and ports the logic and the tests from stellar-core.

It was built with Claude Code, Anthropic’s coding agent, in multi-agent workflows, in this order:

  1. An architecture contract. docs/ARCHITECTURE.md fixed the module boundaries, the shared types, the data formats and the rules before the modules were written.
  2. Parallel module agents. Separate agents built SCP, the ledger, the DEX, overlay and storage, the UI, the herder, and history with the simulation. Each wrote unit tests, ported from stellar-core’s tests where possible.
  3. Integration into a Web Worker runtime: one node per tab, an RPC client for the interface, and a bridge that carries WebRTC from the page into the worker.
  4. End-to-end tests in real browsers. Playwright opens several tabs as several validators and checks consensus, restarts, crashes, frozen tabs, forks and the wallet. Two opt-in specs connect two browsers over real WebRTC through the public relays.
  5. A migration to @stellar/stellar-sdk. The XDR code moved from the deprecated @stellar/stellar-base package to the SDK. Golden byte vectors guarded the move.
  6. Adversarial review. Review agents attacked the code through separate lenses: SCP, ledger, herder, runtime and UI. After each review, an independent verifier re-ran the evidence for every finding on the code from before and after the fix, and fixed what did not hold.
  7. Targeted fixes. Later rounds fixed specific problems, with regression tests. For example: the genesis root is locked, friendbot transactions follow strict rules and a cap per ledger, fork verdicts count only validators known before the fork, and hellos are bound to the WebRTC channel.

By the numbers

Vitest tests (unit, simulation and golden) in 108 files, all passing
1,324
SCP test cases ported from stellar-core’s SCP tests
333
Playwright tests in 20 spec files; 3 are opt-in: 2 over real WebRTC, 1 with a large state
36
golden tests over 9 groups of frozen consensus bytes
16
lines of TypeScript in the app and the node
~39,000
lines of tests and test helpers: about 28,900 for Vitest and 2,100 for Playwright
~31,000

The SCP simulations run networks of 4 to 12 nodes, some with nested quorum sets, with random message delays, crashed nodes, lost messages, and Byzantine nodes that send conflicting or forged messages. A typical scenario runs 20 random seeds of 20 slots each and checks that no two nodes ever decide different values. The node simulation in src/sim adds partitions, forks, departures and hostile peers.

Counted on October 19, 2026: npx vitest run for the tests, the files in e2e/ for Playwright, and wc -l for the lines. Test kits and the simulation harness count as test helpers.

Sources

Only public sources went in:

  • stellar-core source and tests (Apache-2.0), for SCP, the ledger, the DEX and the herder.
  • The SCP Internet-Draft, draft-mazieres-dinrg-scp-05, to cross-check the SCP port.
  • npm packages, among them @stellar/stellar-sdk for XDR and keys, Trystero for WebRTC over Nostr, idb for IndexedDB, React, Tailwind CSS, Vite, Vitest and Playwright.
  • Public documentation and web pages.

06 · Limits

Limits, and what is simulated

BrowserChain is a learning and demo network. Keep these points in mind.

What is simulated

  • The network, not the consensus. Validators are browser tabs that come and go. The SCP messages, ledgers and signatures between them are real.
  • Auto quorum and fork resolution are BrowserChain additions. Stellar validators choose their quorum sets by hand, and a closed Stellar ledger is never rolled back.
  • The money. XLM here has no value, and anyone can derive the friendbot keys.
  • The demo (?mock=1) runs no consensus. A script plays the validators, makes up traffic and applies your transactions to a simplified ledger.

Simplified compared with Stellar

  • No Soroban smart contracts, no liquidity pools, no sponsorship operations, no inflation and no surge pricing.
  • Only v1 transaction envelopes. Fee-bump and v0 envelopes, and the newer v2 preconditions (minimum sequence age or ledger gap, extra signers), are not supported. There are no protocol upgrades.
  • A flat state hash replaces the BucketList, and the ledger skip list is not kept. So ledger hashes differ from stellar-core’s.
  • History comes from peers, not from history archives. Checkpoints are every 64 ledgers.
  • No message authentication code on every message, as stellar-core’s overlay has. A WebRTC link is checked once with a signed hello bound to its DTLS channel, and DTLS protects the rest. Announcements and consensus messages are signed end to end.
  • Auto quorum favors liveness over safety when the network splits. Manual mode keeps SCP’s classic guarantees for the quorum you set.

Known limits

  • Open membership. In auto mode any key can join as a validator. Only validators known before a fork can decide it, so newcomers cannot. But colluding validators that outnumber the honest ones before a fork can. Groups that formed apart do not merge on their own; Resync moves a tab to the other chain.
  • Public friendbot keys. Anyone can sign for the friendbot accounts. The rules cap the drain at 160,000 XLM per ledger, so the faucet’s 16 billion XLM could be emptied in about 100,000 ledgers, about 6 days. Anyone can also fill the 16 friendbot places in each ledger. A node still puts its own requests first in the transaction sets it proposes, so they get in when one of its sets wins.
  • Stalls after departures. When validators leave and one of the remaining validators misbehaves, nomination can stall in some runs.
  • Background tabs. Browsers throttle or freeze tabs in the background. A frozen tab does not vote until it wakes up.
  • Strict networks. There is no TURN relay. Two browsers behind strict NATs or firewalls may not connect over WebRTC.

07 · Questions

Questions

Is this real Stellar?

No. It reimplements Stellar’s consensus protocol and classic ledger in TypeScript, on real Stellar XDR, with logic and tests ported from stellar-core. But it is a separate network. It cannot talk to Stellar nodes, its ledger hashes differ, and its XLM has no value.

Can I use my real keys?

No. Never paste a real Stellar secret key into BrowserChain. Wallet keys are stored unencrypted in your browser’s local storage, and this is a play-money test network. Use Create a test account on the Wallet page instead: it makes a new key, and friendbot funds it with 10,000 play XLM.

What happens when I close the tab?

Your validator stops. It says bye to its peers, and they drop it from their quorum. Its key and chain stay in IndexedDB. When you open the page again, the new tab takes the first free identity slot, which is usually the one you closed, and catches up.

If every tab everywhere closes, the network pauses: there is no server that keeps it running. The next tab picks up from the chain it stored.

Does it work on phones?

The interface is built to work at phone width. Phones are not tested, though: the automated tests run in desktop Chromium. Phone browsers also pause tabs in the background and when the screen locks, and a paused validator does not vote.

Is there a server?

There is no BrowserChain server. GitHub Pages serves the static files, and everything else runs in the tabs. To connect browsers on different machines, BrowserChain uses public services it does not run: six public Nostr relays carry the WebRTC connection setup, and public STUN servers from Google and Cloudflare (Trystero’s defaults) help the browsers find a direct path. As with any WebRTC app, peers and relays can see your IP address.

What data stays in my browser?

  • IndexedDB, one database per network and identity slot: your validator’s secret key and name, its settings, its ledgers, transactions and snapshots, its latest SCP messages, and the validators it has seen.
  • Local storage: wallet keys (unencrypted), wallet activity, the theme and a few view choices. Session storage keeps the XDR you type in the Lab.
  • With ?fresh=1 the validator keeps everything in memory. The wallet still uses local storage.

What leaves your browser is what a validator shares with its peers: its name and signed statuses, SCP messages, transactions and transaction sets, and ledgers and snapshots for peers that catch up.

Can I run it myself?

Yes. Clone the repository, run npm install and npm run dev, and open http://localhost:5173 in two or three tabs. To reach other machines, run npm run build and serve dist/ from the root of any static host. npm test runs the unit, simulation and golden tests, and npm run e2e runs Playwright.