# XE Testnet — full site as Markdown

> Public testnet for XE, an open-source Layer 1 for decentralized compute and networking: per-account block lattice, ~3s finality, zero fees, signed P2P chat, multisig governance and compute leasing, with a JSON HTTP API on every node.

Every page of https://test.network, generated from the same content the HTML renders from. The index with when-to-use guidance is at https://test.network/llms.txt; the API contract is at https://test.network/openapi.json.

Contents:

- XE Testnet — https://test.network/
- About — XE Testnet — https://test.network/about
- Bug Bounty — XE Testnet — https://test.network/bounty
- Contact — XE Testnet — https://test.network/contact
- Developers — XE Testnet API, OpenAPI spec, CLI and SDK docs — https://test.network/developers
- XE Network Documentation — https://test.network/docs
- Accounts & Keys — https://test.network/docs/accounts
- API Reference — https://test.network/docs/api
- Architecture — https://test.network/docs/architecture
- Assets (XE & XUSD) — https://test.network/docs/assets
- Block Lattice — https://test.network/docs/block-lattice
- CLI Reference — https://test.network/docs/cli
- Compute Leasing — https://test.network/docs/compute
- Consensus — https://test.network/docs/consensus
- System Constants — https://test.network/docs/constants
- Cryptography — https://test.network/docs/cryptography
- Deployment — https://test.network/docs/deployment
- Binary Encoding — https://test.network/docs/encoding
- Explorer & Web UI — https://test.network/docs/explorer
- Getting Started — https://test.network/docs/getting-started
- Networking — https://test.network/docs/networking
- SDK — https://test.network/docs/sdk
- State Chain — https://test.network/docs/state-chain
- Proof of Uptime — https://test.network/docs/uptime
- Web Wallet — https://test.network/docs/wallet
- Explorer — XE Testnet — https://test.network/explorer
- Privacy — XE Testnet — https://test.network/privacy
- XE Testnet — https://test.network/wallet

# XE is a decentralized compute & networking platform.

`Ledger` · `Compute` · `Messaging` · `Governance` · `Identity (soon)`

An account chain each, transactions final in seconds with zero fees, XE carrying the vote weight, signed P2P chat and multisig governance — with leasing built on top, so a VM can be rented from a stranger and settled on-chain. Built for people and machines. **This is a testnet under active development, and not all of it works yet** — the status below is honest about which parts. Come stress-test it.

- [Open Web Wallet](https://ldn.test.network/wallet/)
- [Get Started](https://test.network/docs/getting-started)
- [Source on GitHub](https://github.com/xeprotocol/xe)
- [Open Explorer](https://test.network/explorer)

```sh
git clone https://github.com/xeprotocol/xe
cd xe && make build
./xe wallet create
```

- network: testnet-0005
- finality: ~3s
- fees: 0
- providers: 2

## Status

XE is pre-1.0 and this is a testnet, so some of what is described on this site is built and running, and some of it is not. Here is the split, as it actually stands today.

### Working today

- Build the binary and run a node that joins testnet-0005
- Join the P2P XE network
- Wallets, addresses and keys
- Get 1,000 XE per account per day from the faucet
- Send, receive and check balances in XE
- Burn XE
- Explorer and wallet UI, embedded in the node
- REST API and SSE event streams
- Signed peer-to-peer chat
- Run a provider node and earn XE when leases settle
- Compute leasing end to end: escrow, timekeeper-attested acceptance and settlement, renewal, cancellation
- SSH into a leased VM through the testnet SSH gateway
- XUSD-priced leases, settled on-chain (XUSD is operator-minted — testers can read the market but cannot fund a lease yet)

### On the roadmap, not testable yet

- GPU compute
- Proof of uptime for providers (design stage)
- Identity
- A public way for testers to obtain XUSD

## We want this testnet hammered

- Make transactions, run a node, and try to make the ledger disagree with itself
- Try to spam it, grief it, or find consensus bugs
- Feed a node malformed blocks and see what falls over
- Report anything weird → [open an issue on GitHub](https://github.com/xeprotocol/xe/issues)

Top bug hunters & heavy users will be **recognized on mainnet**. The bug bounty is one open programme with 2% of supply behind it — up to 50,000 XE per finding, for serious attacks and for volume that shows where the network degrades. [Bug Bounty](https://test.network/bounty)

- Finality: ~3s
- Fees: 0
- Bootstrap Nodes: 3
- Providers Online: 2
- XE Supply: 42M

## Quick Start

Two ways in, depending on what you are here to do. Both talk to the same live network; neither needs permission.

### Build on it

_I want to build an app or agent_

The TypeScript SDK builds, signs and proves blocks in your process and submits them to a public node — you hold the keys and never run a node. It is pre-release and **not on npm yet**: clone [xeprotocol/sdk](https://github.com/xeprotocol/sdk), run `npm install` (which builds it), then `npm install /path/to/sdk` in your project. Node 20+ or a browser.

```ts
import { Wallet, Xe, fromMicro, toMicro } from '@xeprotocol/sdk'

const wallet = Wallet.create()   // or Wallet.fromSeedHex(process.env.SEED)
console.log(wallet.address)      // hand this out to receive funds

const xe = new Xe({ client: 'https://ldn.core.test.network', wallet })

// Anything sent to you arrives as PENDING until you claim it.
await xe.receiveAll()
console.log(fromMicro(await xe.balance('XE')), 'XE')

await xe.send({ to: someAddress, amount: toMicro('1.5'), memo: 'thanks' })
```

- [SDK Guide](https://test.network/docs/sdk)
- [HTTP API](https://test.network/docs/api)

### Run it

_I want to run a node or hunt bugs_

Build the `xe` binary, make two wallets, draw from the faucet, move XE between them, then join the network as a node or a provider. The terminal here is a simulation of the real CLI — type in it; it shows you what each command does without touching a wallet.

- [Get Started](https://test.network/docs/getting-started)
- [Bug Bounty](https://test.network/bounty)

## Tooling

- **[xeprotocol/xe](https://github.com/xeprotocol/xe)** — The node source, GPL-3 — clone it, build it, run it
- **[@xeprotocol/sdk](https://test.network/docs/sdk)** — TypeScript client — keys stay local, no node to run; build from source
- **[xe](https://test.network/docs/cli)** — Wallet, lease, SSH, send/receive, offline signing
- **[HTTP API](https://test.network/docs/api)** — REST :8080 — accounts, blocks, leases, chat, VMs
- **[Explorer](https://test.network/explorer)** — Embedded web UI — accounts, blocks, state chain
- **[Web Wallet](https://ldn.test.network/wallet/)** — Client-side keys, multi-wallet, chat, DAO signing

## Capabilities

- **Block Lattice** — Every account has its own chain. Transactions are parallel, non-blocking, and final in seconds. No miners, no mempool.
- **P2P Networking** — Peer discovery and data propagation over libp2p. Nodes find each other automatically and sync directly, peer to peer.
- **Fast Consensus** — Every block finalizes through representative voting: converge, commit-lock, then an irrevocable final vote at ≥67% of delegated weight — in parallel across account chains.
- **XE Votes, XUSD Pays** — XE is the native asset: it carries voting weight and is what providers earn when a lease settles. XUSD is the stablecoin that prices compute; on the testnet only the operators' minter accounts issue it — a mintable asset must never mint consensus power, so only XE ever votes. Zero transaction fees.
- **Signed Messaging** — Send ed25519-signed messages between any two accounts, in real time, delivered over libp2p streams encrypted with Noise/TLS 1.3 and streamed to clients over SSE.
- **Earn by Providing** — Run a provider node, offer your spare compute, earn XE emissions every time a lease settles. Bare metal or cloud — your choice. Providers are live on the testnet and settling leases, and there is room for more.
- **Compute Leasing** — Lease a real VM from a provider, SSH in through the testnet gateway, renew a minute at a time, settle on-chain. Escrow, timekeeper-attested timing, renewal, cancellation and force-settle are live. Leases are priced in XUSD, which only the operators can mint today — so you can read the market and run a provider, but funding a lease yourself is not open to testers yet.
- **Multisig Governance** — Network parameters evolve through state-chain op blocks with M-of-N multisig signing. No hard forks.
- **Provable Uptime (upcoming)** — Design stage — providers will prove they were online and serving via cryptographic uptime proofs. Not yet implemented.

## What can you build?

- **Decentralized VPS** — Lease VMs from providers. SSH in, run workloads, settle on-chain.
- **CI/CD Runners** — Ephemeral compute for build pipelines. Lease, test, settle.
- **Inference Swarms** — Lease VMs across providers. Deploy models in parallel.
- **Agent Payments** — Finality in seconds, zero fees, own chain per agent.
- **Signed Chat** — libp2p streams. ed25519 signed. SSE delivery. DHT discovery.
- **Governance Tools** — State-chain op blocks. M-of-N multisig signing. Live tip updates.
- **Block Explorers** — REST API analytics. Accounts, leases, conflicts.
- **Provider Nodes** — Advertise resources, provision VMs, earn XE emissions.
- **Uptime Proofs** — Design stage. Heartbeat chains, merkle epochs planned.
- **Wallet Apps** — tweetnacl + blakejs. AES-GCM seeds. PBKDF2 derivation.
- **Multisig Treasuries** — M-of-N accounts. Hash-derived addresses. Keyset rotation.
- **Directories** — Account-to-peer mapping. Signed registrations, TTL expiry.

## Start Building

The node source is public under the GPL-3 — clone it, build it, run it, break it. Findings accepted by the bug bounty are worth up to 50,000 XE.

- [Get Started](https://test.network/docs/getting-started)
- [Source on GitHub](https://github.com/xeprotocol/xe)
- [Documentation](https://test.network/docs)
- [Bug Bounty](https://test.network/bounty)

---

Canonical HTML: https://test.network/ · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# About XE Testnet

test.network is the public testnet site for XE, an open-source Layer 1 protocol for decentralized compute and networking. It is built and operated by **XE L1 Ltd.**, a UK company (registration number 17245674), and everything on this site — the node, the CLI, the wallet, the explorer and these docs — is published under the GPL-3.

- [Read the Docs](https://test.network/docs)
- [Source on GitHub](https://github.com/xeprotocol/xe)
- [Contact](https://test.network/contact)

## What XE is

XE gives every account its own block chain — a block lattice rather than a single global chain — so transactions are parallel, settle in about three seconds and carry no fee. XE is the native asset and the only one that carries voting weight; XUSD, the stablecoin that prices compute, is minted only by the operators' sys.minter accounts. On top of the ledger sit signed peer-to-peer chat over libp2p, a multisig-governed state chain for network parameters, and a compute market in which a provider leases a real VM to a consumer and the lease is escrowed, attested and settled on-chain.

The protocol is designed for people and machines alike: an autonomous agent can hold an account, draw from the faucet, pay another agent and, once leasing opens, rent compute — all through the same JSON HTTP API that every node exposes and that this site documents in full, including an OpenAPI 3.1 description at /openapi.json.

## Where the project stands

XE is pre-1.0 and this is a testnet under active development, so the site is deliberately honest about what works. Today you can build the node, join testnet-0005, create wallets, receive 1,000 XE per account per day from the faucet, send, receive and burn XE, read everything through the REST API and SSE streams, and chat between accounts with signed messages. Compute leasing works end to end when a provider is online, but leases are paid in XUSD, which only the operators' minter accounts issue and testers cannot obtain yet; GPU compute, end-to-end encrypted messaging and dispute arbitration are designed but not yet testable. The testnet may be wiped at any time, and XE on it has no monetary value.

The purpose of the testnet is to be broken: a standing bug bounty with 2% of the XE supply behind it pays up to 50,000 XE per finding in native XE at mainnet launch, for serious attacks and for volume testing alike, and top bug hunters and heavy users are recognised on mainnet. The mainnet project itself lives at xe.network; this site covers the testnet only.

## Resources

- **[Documentation](https://test.network/docs)** — Getting started, architecture, protocol and reference
- **[API Reference](https://test.network/docs/api)** — Every node endpoint, executable, with an OpenAPI spec
- **[xeprotocol/xe](https://github.com/xeprotocol/xe)** — The node source, GPL-3 — clone it, build it, run it
- **[Bug Bounty](https://test.network/bounty)** — 2% of supply, up to 50,000 XE per finding, whole system in scope
- **[llms.txt](https://test.network/llms.txt)** — A map of the site for agents, with when-to-use guidance

---

Canonical HTML: https://test.network/about · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Find a bug. Earn XE.

Help harden the protocol before genesis. One standing programme, the whole system in scope, open now. We have set aside **2% of the XE supply** — for it, and we want two things: **novel, serious attacks** on the ledger, consensus, leasing and networking, and **sustained volume** that shows where the network degrades. Report on GitHub and earn up to **50,000 XE** per finding, paid in native XE at mainnet launch. Rewards are discretionary and contingent on launch; amounts are provisional until the pool is finalized, and XE carries no guaranteed monetary value.

- [Submit a Report](https://github.com/xeprotocol/xe/issues/new)
- [Scope](https://test.network/bounty#scope)
- [Reward Tiers](https://test.network/bounty#tiers)
- [Critical? Email first](mailto:security@xe.network)

- Of Supply Allocated: 2%
- Top Reward (XE): 50,000
- Severity Tiers: 5
- Submission Channel: GitHub
- Payout Date: Genesis

## What We Want

This is a standing programme, not an event, and nothing is held back for later. It pays for two kinds of work, judged on the same tiers: findings that break the protocol, and findings that show what sustained volume does to it. Heavy, honest use is part of the job — the network is meant to be hammered.

### Track 1 — Security findings

- Ledger: double spends, balance inflation, unauthorized mint or burn, micro-unit overflow
- Consensus: conflicting blocks finalized, vote weight forged or misapplied, finality reversed
- Leasing: escrow taken without service, settlement or force-settle abused, attestations forged, VM access with the wrong key
- Cryptography and keys: forgery, key recovery, address or identity confusion
- Nodes: crashes or corruption from crafted input, unsafe defaults, admin surfaces reachable
- Governance and chat: unsigned state-chain changes, keyset rotation abuse, message spoofing

### Track 2 — Volume & resilience

- Sustained transaction volume that drops blocks, stalls finality or evicts peers
- Spam and rate-limit abuse that degrades service for other clients
- Load that exhausts a node's memory, disk or file handles
- Chat, SSE and API starvation under many concurrent clients
- Provider and lease churn that breaks settlement or accounting
- Reproducible measurements: what you sent, at what rate, and what the network did

Volume findings are rewarded by their impact on the same tiers — a finality stall you can reproduce is Severe, a measurable slowdown is Medium. Sustained heavy users are recognised on mainnet alongside the top researchers.

## Scope

Everything that ships with the XE node, everything the public testnet runs, and everything on this site. If it is documented here and it can be broken, it is in scope.

### In Scope

- Block lattice and every block type — send, receive, burn, mint, the lease family, multisig
- Consensus, finality and representative voting
- XE and XUSD accounting in micro-units, emission and settlement maths
- Compute leasing end to end: escrow, timekeeper attestation, renewal, cancellation, force-settle, provider VMs and the SSH gateway
- P2P networking, peer discovery and sync
- Node lifecycle — config, storage, restart and recovery — and crash resistance against crafted input
- State chain and multisig governance
- Signed chat, SSE streams and the account directory
- Wallet, CLI, web wallet and SDK key handling — keygen, signing, addresses, storage
- The HTTP API, its rate limits and the test.network proxy
- The explorer, the web wallet, this website and the docs (including header, XSS and content issues)
- Third-party dependencies where the weakness is reachable through XE
- Spam, rate-limit and load abuse — see Track 2

### Out of Scope

- Volumetric DDoS against the hosting layer — nginx, DNS, the website or faucet service — rather than the protocol
- Social engineering of XE staff or users
- Physical attacks on hardware

## Reward Tiers

Five tiers, one programme. Severity is assigned by the XE core team based on impact, exploitability and report quality. Critical findings earn up to 50,000 XE. Amounts are targeted ceilings — exceptional findings may exceed them — and are provisional until the bounty pool is finalized ahead of mainnet launch.

| Tier | Reward | Description |
| --- | --- | --- |
| Critical | 50,000 XE | Catastrophic protocol breaks. Unauthorized mint, double-spend, escrow theft, key recovery, consensus that finalizes conflicting histories, or lattice compromise. |
| Severe | 25,000 XE | Serious breaks short of catastrophe. Signature forgery, validation bypass, node compromise, finality that stalls network-wide under load — exploitable and damaging at scale. |
| High | 10,000 XE | Targeted DoS, race conditions, replay attacks, privilege escalation, sustained degradation a single client can cause. |
| Medium | 2,500 XE | Validation gaps, accounting mismatches, non-sensitive disclosure, inconsistent API responses, measurable slowdowns under volume. |
| Minor | 650 XE | UI bugs, typos, broken explorer views, misleading log messages, documentation errors. |

## Bug Classes

Illustrative examples per tier across the whole system. If you find something impactful that doesn't fit below, report it anyway.

| Severity | Class | Examples | Reward |
| --- | --- | --- | --- |
| Critical | Supply & ledger integrity | unauthorized mint · double spend · balance inflation · micro-unit overflow · escrow drained without service | 50,000 XE |
| Critical | Cryptographic & consensus compromise | key recovery · signature forgery · identity hijack · conflicting blocks both finalized · vote weight forged | 50,000 XE |
| Severe | Validation bypass | malformed block accepted · send/receive/burn/lease rule bypass · unsigned state-chain change · attestation forged | 25,000 XE |
| Severe | Node compromise, ledger loss & network stalls | crash from crafted input · storage corruption · unrecoverable restart · finality stalled network-wide by load | 25,000 XE |
| High | Races, replays & state transitions | race condition · replay attack · state inconsistency · TOCTOU · lease renewed or settled out of order | 10,000 XE |
| High | Keys, access & privilege | key material exposure · unsafe file permissions · admin endpoint reachable · VM access with the wrong key | 10,000 XE |
| High | Degradation one client can cause | dropped blocks under sustained volume · peer eviction · chat or SSE starvation · node OOM from a single source | 10,000 XE |
| Medium | Validation, accounting & load edge cases | accounting mismatch · validation gap · API inconsistency · rounding · measurable slowdown under volume | 2,500 XE |
| Medium | Information disclosure | metadata leak · verbose error · debug exposure · missing security headers with impact | 2,500 XE |
| Minor | UI/UX, docs & cosmetic | layout · responsive · a11y · typo · broken link · log noise · self-XSS | 650 XE |

## Leaderboard

Ranked by total XE awarded across the programme. Updated when the site is redeployed after reports are triaged.

**No reports accepted yet.** File a finding to claim the top spot.

Updated manually when the site is redeployed as reports are accepted and paid.

## How to Report

Reports are filed as public issues on [github.com/xeprotocol/xe](https://github.com/xeprotocol/xe/issues/new). Critical/Severe findings go privately by email first — we coordinate disclosure and open the public issue once a patch has shipped.

1. **Reproduce against testnet** Verify on `test.network`. Capture tx hashes, block heights, exact reproduction steps. For volume findings, capture the rate you sent, for how long, from how many sources, and what the network did.
2. **File a GitHub issue** Open an issue at [github.com/xeprotocol/xe/issues/new](https://github.com/xeprotocol/xe/issues/new) with a suggested severity tier and the area it lands in. Public by default — for Critical/Severe findings, see step 4 instead.
3. **Include a clear PoC** Minimal reproduction script or test case. Impact analysis: who's affected, worst case. For load, the script that generates it.
4. **Critical/Severe: email first** Findings that risk funds or the network go privately to `security@xe.network`— not a public issue. Ask for an encryption key first; we'll reply with one before you send details, then with a tracking ID. A public issue goes up once a patch ships.
5. **Triage & acceptance** Core team confirms, assigns severity, and replies on the issue (or by email with a tracking ID for Critical/Severe) — we apply tracking labels ourselves on triage.
6. **Payout at genesis** Accepted bounties pay in native XE at mainnet launch, if and when it happens. Provide an XE address (or a designated mainnet address) in your report.

## Rules

- Everything documented on this site is in scope now — there are no phases and nothing is held back for later
- Critical and Severe findings go by email first, not a public issue — we'll reply with a tracking ID, and a public issue goes up once a patch ships
- Test only on testnet (test.network) — never on mainnet once live
- Volume testing against the public testnet nodes is authorized; open an issue before a sustained campaign so we can watch it and attribute the results to you
- Don't pivot to attack other users' funds, keys, or workloads, and don't target the hosting layer
- Good-faith research that follows these rules is authorized; we won't pursue legal action over it
- One report per bug. Chained findings can be split across reports
- Duplicate reports go to the first verifiable submission
- Severity, eligibility, and reward at discretion of XE core team
- Payouts in native XE at mainnet genesis from the 2%-of-supply allocation, contingent on launch. No cash equivalent, no guaranteed monetary value
- XE team members and direct contractors are not eligible

## Ready to break things?

Spin up accounts on testnet, hammer the ledger, the network and the compute market — with clever attacks or with sheer volume — and tell us what falls over.

- [Submit a Report](https://github.com/xeprotocol/xe/issues/new)
- [How to Report](https://test.network/bounty#how)
- [Critical? Email first](mailto:security@xe.network)

---

Canonical HTML: https://test.network/bounty · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Get in touch.

The XE testnet is run by XE L1 Ltd. Most conversations happen in the open on GitHub; general enquiries go to [`hello@xe.network`](mailto:hello@xe.network), and anything security-sensitive goes to the security address first. There is no support desk and no ticketing system — the channels below are the real ones, and they are all read.

- [Open an Issue](https://github.com/xeprotocol/xe/issues/new)
- [Email Us](mailto:hello@xe.network)
- [Email Security](mailto:security@xe.network)
- [Bug Bounty](https://test.network/bounty)

## Channels

1. **Security vulnerabilities** Email [`security@xe.network`](mailto:security@xe.network). Critical and Severe findings must go by email rather than a public issue: we reply with a tracking ID, coordinate disclosure, and open the public issue once a patch has shipped. Findings accepted by the bug bounty are rewarded in XE at mainnet launch — up to 50,000 XE.
2. **Bugs, questions and feature requests** Open an issue at [github.com/xeprotocol/xe/issues](https://github.com/xeprotocol/xe/issues). Include the node version, the network id (testnet-0005), what you did and what you expected — the more reproducible, the faster it gets fixed. Documentation errors on this site are bugs too and count for the bounty's Minor tier.
3. **General, business, press and legal** Email [`hello@xe.network`](mailto:hello@xe.network) for anything that is not a bug or a vulnerability — partnerships, press, legal and privacy requests about this site. Postal correspondence goes to the registered office of XE L1 Ltd. (company number 17245674): 128 City Road, London, EC1V 2NX, United Kingdom.
4. **Agents and automated integrations** No human is needed to integrate: the API needs no key for reads, the contract is at [`/openapi.json`](https://test.network/openapi.json), and [`/llms.txt`](https://test.network/llms.txt) explains when XE is the right tool. If something in those files is wrong, that is a GitHub issue.

Response times are best effort. Security reports are acknowledged first; everything else is triaged on GitHub in the order it arrives.

---

Canonical HTML: https://test.network/contact · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Build on XE Testnet.

Everything needed to integrate with the XE testnet from code or from an AI agent, on one page: the JSON HTTP API every node exposes, its [OpenAPI 3.1 description](https://test.network/openapi.json), how it is versioned, how it is rate limited, how it reports errors, and where the CLI, wallet and source live. Reads need no API key. Amounts are integer micro-units (1 XE = 1,000,000).

- [API Reference](https://test.network/docs/api)
- [OpenAPI spec](https://test.network/openapi.json)
- [Getting Started](https://test.network/docs/getting-started)
- [llms.txt](https://test.network/llms.txt)

## Resources

- **[API Reference](https://test.network/docs/api)** — Every endpoint, executable against the live node
- **[OpenAPI 3.1](https://test.network/openapi.json)** — operationIds, schemas, security schemes, headers
- **[API discovery](https://test.network/api/v1)** — JSON index of endpoints, servers, auth and limits
- **[xe CLI](https://test.network/docs/cli)** — Wallet, faucet, send/receive, node, offline signing
- **[@xeprotocol/sdk](https://test.network/docs/sdk)** — TypeScript client — pre-release, not on npm, build from source
- **[xeprotocol/xe](https://github.com/xeprotocol/xe)** — Node, CLI, wallet and explorer source, GPL-3
- **[llms.txt](https://test.network/llms.txt)** — Site map for agents with when-to-use guidance

## Base URLs and versioning

The API is versioned in the URL path. The current major version is **v1** at [`https://test.network/api/v1`](https://test.network/api/v1). Pin that in anything that must not move. The unversioned [`https://test.network/api`](https://test.network/api) is an alias for the current major version and follows it when it changes. Every response carries `X-API-Version: 1`. The node itself, `https://ldn.core.test.network`, is unversioned and tracks the node release; it does not add the headers or JSON error wrapping described here.

- Within a major version changes are additive only: new endpoints, new optional fields, new enum values. Ignore fields you do not recognise.
- Removing or renaming a field, changing a type or an endpoint's meaning happens only in a new major version, and the previous major keeps working for at least 180 days after the new one ships.
- Deprecated endpoints and versions are marked deprecated: true in the OpenAPI document and answer with a Deprecation header (RFC 9745, Deprecation: @<unix-seconds>), a Sunset header (RFC 8594, the HTTP-date of removal) and a Link rel="deprecation" to the migration notes. Sunset is never less than 90 days after the Deprecation date.
- Testnet wipes reset ledger state, not the API. Identifiers change; the contract does not.

The same policy is published machine-readably as x-versioning in /openapi.json and as versioning in the /api/v1 discovery document.

## Authentication

Most reads are open: no key, no sign-up. Four endpoints are operator-only and take `Authorization: Bearer <XE_API_ADMIN_TOKEN>` (`POST /lease/request`, `POST /lease/{hash}/renew`, and the unfiltered `GET /chat/events` and `GET /chat/contacts` listings); the public node does not hand that token out. Reading a specific account's chat needs an ownership proof: fetch a challenge from `GET /chat/auth/challenge`, sign sha256("xe/chat-read-auth/v1\0" || challenge) with the account key, and pass account, pub_key, challenge and sig as query parameters. Writes are signed blocks you construct and sign yourself — the node never signs on your behalf. The security schemes are declared in the OpenAPI document.

## Rate limits

Limits are per client IP and per request class, matching the node's own: **reads** (GET, HEAD) 1,000 requests per 5 seconds, **writes** (POST, PUT, PATCH, DELETE) 50 requests per 5 seconds — 200/s and 10/s sustained. Every response from test.network/api tells you where you stand, so you can throttle before being refused:

- RateLimit-Policy: "read";q=1000;w=5 — the policy in force (quota q per window w seconds), per draft-ietf-httpapi-ratelimit-headers.
- RateLimit: "read";r=998;t=4 — requests remaining (r) and seconds until the window resets (t).
- RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset — the same three numbers in the older header form.
- Retry-After — on a 429 only: how many seconds to wait. The body is the JSON Error shape with code rate_limited.

Streaming endpoints (SSE) count once when opened. The direct node endpoint enforces the same limits but does not send the headers.

## Errors

Every 4xx and 5xx from test.network/api is JSON. The body is the `Error` schema from the OpenAPI document: `error.code` (machine-readable, snake_case: bad_request, unauthorized, forbidden, not_found, method_not_allowed, rate_limited, upstream_error, upstream_unavailable, upstream_timeout), `error.status`, `error.message` (what happened), `error.hint` (what to do next) and `error.docs` / `error.openapi` links. When the node itself answered with a non-JSON error, its status and body are kept under `error.upstream`. The node's own JSON errors pass through unchanged. Each operation in the OpenAPI document lists its error responses, all typed against the same schema.

## For AI agents

Every page on this site returns Markdown for `Accept: text/markdown` at its own URL. [`/llms.txt`](https://test.network/llms.txt) maps the site and says when XE is the right tool; [`/llms-full.txt`](https://test.network/llms-full.txt) is the whole site in one file. The OpenAPI document has a unique operationId, a description and typed parameters on every operation, so it can be loaded directly as a tool definition.

---

Canonical HTML: https://test.network/developers · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# XE Network Documentation

> Block-lattice cryptocurrency with compute leasing, DAO governance, and P2P messaging.

XE is a decentralized compute and networking platform. Consumers pay **XUSD** to lease real VMs from providers; providers earn newly minted **XE** when a lease settles. Underneath sits a lattice ledger where every account maintains its own chain, with cross-chain references forming a directed acyclic graph — parallel transactions, no miners, no global bottleneck — extended with DAO governance, peer-to-peer messaging and an account directory.

> \[!WARNING] Everything here is a work in progress
> XE is pre-1.0 and runs on a **testnet only**. These docs describe the system as designed and as built, and the two are not always the same thing: **not every command or endpoint documented here works today**, and parts that do work go down without notice. Where something is known to be unavailable, the page says so — start with [Getting Started](/docs/getting-started), which lists what is currently broken.
>
> There is no backward compatibility. Protocol changes ship as a testnet wipe: the network is re-bootstrapped under a new network ID and every account, balance and block on it is discarded. Testnet coins have no value.
>
> The source is public at [github.com/xeprotocol/xe](https://github.com/xeprotocol/xe) under the GPL-3.0, and bugs are worth money — see the [bug bounty](/bounty).

## Core Properties [#core-properties]

* **Account-chain architecture.** Each account has its own chain of blocks. Sends debit the sender's chain; receives credit the recipient's chain. No miners, no global ordering.
* **Dual assets.** XE is emitted as a reward for compute providers and confers voting weight for consensus. XUSD is used to pay for compute leases; it carries no consensus weight, because XUSD is mintable by authorized minters and minting must never mint voting power.
* **Compute leasing.** Consumers lease virtual machines from providers and can extend them a minute at a time. Resource costs are denominated in XUSD, billed per minute; providers earn XE emissions upon settlement.
* **DAO governance.** A deterministic state chain allows the network to evolve parameters through signed proposals without hard forks.
* **P2P messaging and directory.** Nodes exchange messages and register in a decentralised account directory.

## Components [#components]

| Component           | Stack                                                           | Description                                                               |
| ------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------- |
| **Core node**       | Go, libp2p, BadgerDB                                            | Block lattice, consensus, networking, API                                 |
| **Embedded Web UI** | Plain HTML + ES modules + Web Crypto, embedded via `//go:embed` | Explorer, wallet, DAO console, state inspector — served by `xe node --ui` |
| **Proof of Uptime** | Design proposal                                                 | Verifiable uptime proofs for compute providers (not yet implemented)      |

## Documentation Sections [#documentation-sections]

### Overview [#overview]

* [Getting Started](/docs/getting-started) — build, run, and connect to the testnet
* [Architecture](/docs/architecture) — package breakdown and startup sequence

### Concepts [#concepts]

* [Block Lattice](/docs/block-lattice) — the DAG structure and cross-chain references
* [Accounts & Keys](/docs/accounts) — ed25519 key pairs, addresses, multisig
* [Assets](/docs/assets) — the dual-asset model (XE and XUSD)

### Protocol [#protocol]

* [Consensus](/docs/consensus) — delegation, conflict detection, voting, quorum
* [Networking](/docs/networking) — libp2p, gossip, frontier sync, DHT, messaging
* [Compute Leasing](/docs/compute) — lifecycle, cost model, attestations, VM management
* [State Chain](/docs/state-chain) — DAO governance via deterministic state machine
* [Proof of Uptime](/docs/uptime) — heartbeat chains, merkle epochs (design stage — not yet implemented)

### Interfaces [#interfaces]

* [API Reference](/docs/api) — HTTP REST API for accounts, blocks, leases, chat, and more
* [CLI Reference](/docs/cli) — unified CLI for node and client operations
* [Explorer & Web UI](/docs/explorer) — embedded web UI served by `xe node --ui`
* [Web Wallet](/docs/wallet) — client-side wallet with Web Crypto, embedded in binary

### Reference [#reference]

* [Binary Encoding](/docs/encoding) — block and vote wire formats
* [Cryptography](/docs/cryptography) — ed25519, SHA-256, signing contexts
* [Constants](/docs/constants) — all system constants
* [Deployment](/docs/deployment) — configuration, production setup

---

Canonical HTML: https://test.network/docs · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Accounts & Keys

> Ed25519 key pairs, addresses, signing, multisig, and delegation.

Every single-key XE account is an ed25519 key pair, but the **address is not the public key**. Both are 32 bytes / 64 lowercase hex, so nothing about the wire shape gives it away — the values differ, and substituting one for the other fails validation:

```
address = sha256("xe/account/v1" ‖ pubkey_32)
```

The address is the identity: it goes in a block's `account`, `destination` and `representative`, in `/accounts/{address}/*` paths, and in `provider` / `consumer` on leases. The public key is the credential: it appears as `pub_key` on a chain's **first** block, on directory registrations and on chat envelopes, and as `signatures[].public_key` on multisig blocks. Because SHA-256 is one-way, a key cannot be recovered from an address — anything that must verify a signature is handed the key alongside it. Committing to the key instead of being it is what allows a key to be rotated later without the account changing. No prefixes, no checksums.

## Key Generation [#key-generation]

```go
kp, err := core.GenerateKeyPair()        // random
kp := core.KeyPairFromSeed(seed)         // deterministic from 32-byte seed
addr := kp.Address()                     // sha256("xe/account/v1" ‖ pubkey); core.DeriveAddress(pubKeyHex) for a bare key
```

`xe wallet create` prints both values and a 24-word recovery phrase (`xe wallet phrase` shows it again; `xe wallet restore` rebuilds the seed file from it).

## Block Signing [#block-signing]

1. Canonical encoding via `MarshalBlockCanonical()`
2. `SHA-256(networkID ‖ canonical ‖ aux)` → `b.Hash` — the aux tail (`MarshalBlockAux`) frames the opening block's `pub_key` (tag `xe/block/pubkey/v1`, present only when `previous == "0"`) and, on lease-family blocks, the certificate hash plus the timekeeper attestations; it is empty for every other block. Genesis is hashed with the network-ID prefix cleared
3. `ed25519.Sign(privateKey, hashBytes)` → `b.Signature`

The first block of a single-key chain **must** declare `pub_key` (the node checks it derives `account`); every later block **must not** — the key is already on the chain, and accepting a redeclaration would be a silent credential swap.

## Multisig Accounts [#multisig-accounts]

* Address = `sha256(canonical(keyset))` (hash-derived)
* Opened with `multisig_open` block
* Rotated via `multisig_update` blocks
* Spending: M-of-N threshold; Receiving: 1-of-N

## Delegation [#delegation]

Each block includes an optional `Representative` field. Empty means keep current delegation. Delegation weight is the account's **XE** balance in micro-XE — XUSD is mintable by authorized minters and must not mint consensus weight, so it contributes nothing.

---

Canonical HTML: https://test.network/docs/accounts · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# API Reference

> Executable HTTP API playground for accounts, blocks, leases, state chain, directory, chat, and node operations.

Every XE node exposes the same JSON HTTP API. By default it listens on
`http://127.0.0.1:8080` (`--api` is on by default; change the port and bind
address with `--api-port` and `--api-bind`). The playground below targets the
live London testnet node at `https://ldn.core.test.network` through a local docs
proxy, so requests work from this site without browser CORS issues.

## Conventions [#conventions]

* **Amounts are micro-units.** 1 XE = 1 XUSD = 1,000,000 micro-units, and every
  amount, balance, and weight in the API is an integer micro-unit count. A
  balance of `100000000` is 100 XUSD.
* **The API is self-describing.** `GET /` returns a manifest of every route
  with its method and a one-line description — the same list the node uses to
  register its handlers.
* **Authentication** is per-endpoint. Most reads are open. Four endpoints are
  operator-only, gated behind an admin bearer token
  (`Authorization: Bearer <XE_API_ADMIN_TOKEN>`; `403` when no token is
  configured, `401` when it is wrong): `POST /lease/request`,
  `POST /lease/{hash}/renew`, the parameterless `GET /chat/events` firehose and
  the parameterless node-wide `GET /chat/contacts` list. Reading a specific
  account's chat (`/chat/messages`, `/chat/contacts?account=`,
  `/chat/events?account=`) requires an ownership proof: fetch a challenge from
  `GET /chat/auth/challenge` (valid 120 s, single-use, bound to the issuing
  node), sign `sha256("xe/chat-read-auth/v1\0" || challenge)` with the account
  key, and pass `account`, `pub_key`, `challenge` and `sig` as query
  parameters. `pub_key` is required — an address is
  `sha256("xe/account/v1" || pubkey)` and so cannot verify its own signature.
* **Retryable errors.** A rejected block returns `{"error":"block rejected: …","retryable":<bool>}`:
  `400` with `retryable: false` for a deterministically invalid block (bad
  signature or PoW, structural or value validation), `503` with
  `retryable: true` for a transient failure (a dependency this node has not
  synced yet, a frontier race). Branch on the flag, not the prose.
* **Rate limits** on the node are per-IP with three classes: reads (GET) at
  200 requests/second (burst 1,000), writes (POST) at 10 requests/second
  (burst 50), and probes (`/health`, `/ready`) at 20 requests/second (burst
  100\) in a bucket of their own. Exceeding a bucket returns `429`. Through
  `https://test.network/api` every response also carries `RateLimit-Policy`
  and `RateLimit` headers (plus the older
  `RateLimit-Limit`/`-Remaining`/`-Reset`) so a client can throttle itself,
  and a `429` adds `Retry-After` — see [rate limits](/developers#rate-limits).
  Bodies are capped at 1 MiB; lists take `?offset=N&limit=M` (default 100, max
  1,000).
* **Versioning.** The proxied API is versioned in the URL path: pin
  `https://test.network/api/v1`; `https://test.network/api` is an alias of the
  current major and every response carries `X-API-Version`. Changes within a
  major are additive only, and deprecations are announced with `Deprecation`
  and `Sunset` headers at least 90 days ahead — see
  [versioning](/developers#versioning). The machine-readable contract is the
  [OpenAPI 3.1 document](/openapi.json).

## Endpoints

Base URL: `https://test.network/api/v1` (versioned proxy; `https://test.network/api` is an alias of the current major, `https://ldn.core.test.network` is the node itself). Machine-readable: [OpenAPI 3.1](https://test.network/openapi.json). Each entry gives the operationId used there.

### Accounts

- `GET /accounts` — **List all accounts** (`listAccounts`). Returns accounts known by the node with balances and frontier hashes. Balances are micro-units (1 XE = 1 XUSD = 1,000,000 micro-units).
  - Response: JSON `AccountList` — Every account the node knows.
- `GET /accounts/{address}/balance` — **Get account balance** (`getAccountBalance`). Returns per-asset balances for one account, in micro-units. `balances` includes unfinalized inflows; `spendable` counts only finalized funds and is the settlement figure.
  - Path `address`: Account address, 64 hex characters.
  - Response: JSON `Balances` — Balances, spendable balances and the finalized height.
- `GET /accounts/{address}/chain` — **Get account chain** (`getAccountChain`). Returns the account block chain from oldest to newest, paginated, with the full chain length in `total`. Each block carries a `finalized` flag; only the first block carries `pub_key`.
  - Path `address`: Account address, 64 hex characters.
  - Query `offset` (optional, integer): Number of entries to skip.
  - Query `limit` (optional, integer): Maximum number of entries to return.
  - Response: JSON `AccountChain` — Blocks, oldest first.
- `GET /accounts/{address}/keyset` — **Get multisig keyset** (`getAccountKeyset`). Returns the multisig keyset for an account when present (404 for a single-key account). `keys` are ed25519 public keys, not addresses.
  - Path `address`: Account address, 64 hex characters.
  - Response: JSON `Keyset` — The account's current keyset.
- `GET /accounts/{address}/reputation` — **Get account reputation** (`getAccountReputation`). Returns the reputation aggregate for one account, built from on-chain lease activity.
  - Path `address`: Account address, 64 hex characters.
  - Response: JSON `Reputation` — Reputation aggregate.
- `GET /reputation` — **All reputations** (`listReputations`). Returns reputation aggregates for every known account, keyed by address. Paginated.
  - Query `offset` (optional, integer): Number of entries to skip.
  - Query `limit` (optional, integer): Maximum number of entries to return.
  - Response: JSON `ReputationList` — Address → aggregate.

### Blocks

- `POST /blocks/send` — **Submit send block** (`submitSendBlock`). Submits a signed send block. Requires a valid signature, hash, post-block balance, and PoW nonce; the destination must differ from the sending account. Success is 201 echoing the stored block; a rejection carries a `retryable` flag (400 false / 503 true).
  - Body: JSON `SendBlock` — see `#/components/schemas/SendBlock` in the OpenAPI document.
  - Response: JSON `SubmitResult` — Block accepted.
- `POST /blocks/receive` — **Submit receive block** (`submitReceiveBlock`). Submits a signed receive block. 'source' is the hash of the pending send block being received. An account's first block must also declare `pub_key`; every later block must not.
  - Body: JSON `ReceiveBlock` — see `#/components/schemas/ReceiveBlock` in the OpenAPI document.
  - Response: JSON `SubmitResult` — Block accepted.
- `POST /blocks/lease` — **Submit lease block** (`submitLeaseBlock`). Submits a signed compute lease block: XUSD escrow to a provider plus the requested vcpus/memory_mb/disk_gb/duration, an access public key and the hash of the provider's current certificate. `amount` must equal the per-minute cost formula exactly.
  - Body: JSON `LeaseBlock` — see `#/components/schemas/LeaseBlock` in the OpenAPI document.
  - Response: JSON `SubmitResult` — Block accepted.
- `POST /blocks/lease_accept` — **Submit lease accept** (`submitLeaseAcceptBlock`). Submits a provider lease acceptance block referencing the lease block via 'source', pinning the certificate and locking the emission parameters, with timekeeper attestations.
  - Body: JSON `LeaseAcceptBlock` — see `#/components/schemas/LeaseAcceptBlock` in the OpenAPI document.
  - Response: JSON `SubmitResult` — Block accepted.
- `POST /blocks/lease_renew` — **Submit lease renew** (`submitLeaseRenewBlock`). Submits a consumer renewal block: additional XUSD escrow and duration for an accepted lease, attested before its effective expiry. The SDK's holdLease submits one of these a minute at a time.
  - Body: JSON `LeaseRenewBlock` — see `#/components/schemas/LeaseRenewBlock` in the OpenAPI document.
  - Response: JSON `SubmitResult` — Block accepted.
- `POST /blocks/lease_settle` — **Submit lease settle** (`submitLeaseSettleBlock`). Submits a provider lease settlement block referencing the lease block via 'source'. Asset is XE and `amount` is the emission summed over the base term and every renewal; the XUSD escrow is burnt.
  - Body: JSON `LeaseSettleBlock` — see `#/components/schemas/LeaseSettleBlock` in the OpenAPI document.
  - Response: JSON `SubmitResult` — Block accepted.
- `POST /blocks/lease_cancel` — **Submit lease cancel** (`submitLeaseCancelBlock`). Submits a consumer lease cancellation block. Only the consumer can cancel, and only before the lease is accepted.
  - Body: JSON `LeaseCancelBlock` — see `#/components/schemas/LeaseCancelBlock` in the OpenAPI document.
  - Response: JSON `SubmitResult` — Block accepted.
- `POST /blocks/lease_force_settle` — **Submit lease force settle** (`submitLeaseForceSettleBlock`). Submits a consumer force-settle block to recover the full escrow from a lease the provider never settled, once expiry + settle grace + force-settle gap has passed (GET /node → lease_timing) and before the escrow expiry closes the refund window.
  - Body: JSON `LeaseForceSettleBlock` — see `#/components/schemas/LeaseForceSettleBlock` in the OpenAPI document.
  - Response: JSON `SubmitResult` — Block accepted.
- `POST /blocks/multisig_open` — **Open multisig account** (`submitMultisigOpenBlock`). Submits a multisig account open block with a keyset {keys, threshold} (ed25519 public keys) and a signatures array in place of the single signature. The account address is the SHA-256 of the canonical keyset, so the block declares no `pub_key`.
  - Body: JSON `MultisigOpenBlock` — see `#/components/schemas/MultisigOpenBlock` in the OpenAPI document.
  - Response: JSON `SubmitResult` — Block accepted.
- `POST /blocks/multisig_update` — **Update multisig keyset** (`submitMultisigUpdateBlock`). Submits a multisig keyset update block signed by the current keyset's threshold.
  - Body: JSON `MultisigUpdateBlock` — see `#/components/schemas/MultisigUpdateBlock` in the OpenAPI document.
  - Response: JSON `SubmitResult` — Block accepted.
- `POST /blocks/burn` — **Submit burn block** (`submitBurnBlock`). Submits a signed burn block, permanently destroying XE (XE only).
  - Body: JSON `BurnBlock` — see `#/components/schemas/BurnBlock` in the OpenAPI document.
  - Response: JSON `SubmitResult` — Block accepted.
- `POST /blocks/mint` — **Submit mint block** (`submitMintBlock`). Submits a signed XUSD mint block. Only accounts listed in the sys.minter state key are authorized; on the testnet those are operator accounts, and the faucet hands out XE, not XUSD.
  - Body: JSON `MintBlock` — see `#/components/schemas/MintBlock` in the OpenAPI document.
  - Response: JSON `SubmitResult` — Block accepted.
- `GET /blocks/{hash}` — **Get block by hash** (`getBlock`). Returns a block by its hash, plus a `finalized` flag.
  - Path `hash`: Block hash, 64 hex characters.
  - Response: JSON `Block` — The block.
- `GET /blocks/recent` — **Recent blocks** (`listRecentBlocks`). Returns the newest account-chain blocks across the lattice, newest first. Optional ?limit (default 100, max 1000) and ?since (unix nanoseconds, strictly newer). Served from a snapshot rebuilt at most once per second.
  - Query `limit` (optional, integer): Maximum number of blocks to return.
  - Query `since` (optional, integer): Only blocks newer than this unix-nanosecond timestamp.
  - Response: JSON `BlockList` — Newest blocks first.

### Leases

- `GET /leases` — **List leases** (`listLeases`). Returns known compute leases, including every renewal applied. Filter with ?state=created|accepted|settled|cancelled|unfulfilled|expired; paginated.
  - Query `state` (optional, one of `created`, `accepted`, `settled`, `cancelled`, `unfulfilled`, `expired`): Only leases in this lifecycle state.
  - Query `offset` (optional, integer): Number of entries to skip.
  - Query `limit` (optional, integer): Maximum number of entries to return.
  - Response: JSON `LeaseList` — Leases known to the node; [] when there are none.
- `GET /leases/{hash}` — **Get lease** (`getLease`). Returns one lease by lease hash. Needs a real lease hash — list current ones with GET /leases (optionally ?state=accepted).
  - Path `hash`: Lease block hash.
  - Response: JSON `Lease` — The lease.
- `GET /providers` — **List providers** (`listProviders`). Returns advertised compute providers and their current utilization. `account` is the provider address to put in a lease block's `destination` and to look up with GET /certificate/{provider}. A lease still in `created` reserves nothing; capacity is decided at accept time.
  - Response: JSON `ProviderList` — Providers currently advertising; [] when none are online.
- `GET /certificate` — **Node perf certificate** (`getNodeCertificate`). Returns this node's performance certificate (provider mode only; 404 otherwise).
  - Response: JSON `Certificate` — This node's certificate.
- `GET /certificate/{provider}` — **Provider perf certificate** (`getProviderCertificate`). Returns a provider's current, unexpired performance certificate — what a lease is priced and written against. Expired certificates are never returned here; use GET /certificate/hash/{hash} for those.
  - Path `provider`: Provider account address (from GET /providers).
  - Response: JSON `Certificate` — The provider's certificate.
- `GET /certificate/hash/{hash}` — **Certificate by hash** (`getCertificateByHash`). Returns a retained performance certificate by its own hash, including expired ones — historical lease blocks pin the certificate that was current when they were written, so nodes keep serving them.
  - Path `hash`: Performance certificate hash, 64 hex characters (the `hash` field of GET /certificate/{provider}).
  - Response: JSON `Certificate` — The certificate.
- `GET /certificate/nonces/{hash}` — **Certificate CPU witness** (`getCertificateNonces`). Returns the solving nonces behind a certificate's chained proof-of-work phase (one per puzzle, ~8 KB). Only the node that issued the certificate holds them; other nodes answer 404. Verify with perf.VerifyCPUChain(seed, nonces, puzzles) against `cpu_hash`.
  - Path `hash`: Performance certificate hash, 64 hex characters (the `hash` field of GET /certificate/{provider}).
  - Response: JSON `CertificateNonces` — The witness.
- `GET /certificate/attestations/{hash}` — **Certificate attestations** (`getCertificateAttestations`). Returns the full start and end timekeeper attestations behind a certificate's merkle roots, so a verifier can recompute the roots and check each signature. Held only by the issuing node.
  - Path `hash`: Performance certificate hash, 64 hex characters (the `hash` field of GET /certificate/{provider}).
  - Response: JSON `CertificateAttestations` — Start and end attestations.
- `POST /lease/request` — **Request lease** (`requestLease`). Asks the node to build, sign and PoW a lease block from its own wallet. At least one of vcpus/memory_mb/disk_gb must be ≥ 1; duration is required. Responds 201 with the new lease hash.
  - Auth: operator-only, `Authorization: Bearer <XE_API_ADMIN_TOKEN>`.
  - Body: JSON `LeaseRequest` — see `#/components/schemas/LeaseRequest` in the OpenAPI document.
  - Response: JSON `LeaseRequestResult` — The hash of the lease the node created.
  - Note: Operator-only: requires Authorization: Bearer <XE_API_ADMIN_TOKEN>; signs and escrows from the node operator's own wallet. Shown for reference.
- `POST /lease/{hash}/renew` — **Renew lease (operator)** (`renewLease`). Asks the node to extend an accepted lease in place from its own wallet: it prices the extra duration at the provider's current certificate, gathers attestations, signs and PoWs the lease_renew block. `xe lease renew <hash> <secs>` calls this.
  - Auth: operator-only, `Authorization: Bearer <XE_API_ADMIN_TOKEN>`.
  - Path `hash`: Lease block hash.
  - Body: JSON `LeaseRenewRequest` — see `#/components/schemas/LeaseRenewRequest` in the OpenAPI document.
  - Response: JSON `LeaseRenewResult` — The renewal block hash, cost and duration.
  - Note: Operator-only: requires Authorization: Bearer <XE_API_ADMIN_TOKEN> and the node's own wallet must be the lease's consumer. Shown for reference.
- `POST /attestation/request` — **Request attestation** (`requestAttestation`). Asks this node, as a timekeeper, to attest the current time for a lease: ed25519 over sha256(lease_hash || timestamp). Rate limited to one per lease per client IP every 15 s (a rejected request does not consume the window). Needs a real lease hash — list current ones with GET /leases (optionally ?state=accepted).
  - Body: JSON `AttestationRequest` — see `#/components/schemas/AttestationRequest` in the OpenAPI document.
  - Response: JSON `Attestation` — The attestation.
- `GET /vms` — **List VMs** (`listVms`). Returns VM records known to the provider.
  - Response: JSON `VmList` — VM records.
- `GET /vms/{lease}` — **Get VM** (`getVm`). Returns VM details for a lease. Needs a real lease hash — list current ones with GET /leases (optionally ?state=accepted).
  - Path `lease`: Lease block hash.
  - Response: JSON `Vm` — The VM record.
- `POST /tunnel/{leaseHash}/tcp` — **TCP tunnel** (`openTcpTunnel`). Opens a raw TCP tunnel into a leased VM. Takes no JSON body: authentication is an X-Signature header (ed25519 signature over the 32 decoded lease-hash bytes with the lease's access key), after which the HTTP connection is hijacked into a byte stream.
  - Auth: `X-Signature` header signed with the lease access key.
  - Path `leaseHash`: Lease block hash.
  - Response: `application/octet-stream` — The connection is hijacked into a raw TCP byte stream.
  - Note: Not runnable from the browser: the response is a hijacked raw TCP stream, and the required X-Signature header needs the lease access key. Use the xe CLI or curl instead. Needs a real lease hash — list current ones with GET /leases (optionally ?state=accepted).

### State Chain

- `GET /statechain/tip` — **State chain tip** (`getStateChainTip`). Returns the latest state chain block.
  - Response: JSON `StateBlock` — The newest state block.
- `GET /statechain/blocks/{index}` — **State block by index** (`getStateBlock`). Returns a state chain block by index.
  - Path `index`: Zero-based block index.
  - Response: JSON `StateBlock` — The state block.
- `GET /statechain/blocks` — **State blocks** (`listStateBlocks`). Returns state chain blocks and block count. Optional ?start and ?limit.
  - Query `start` (optional, integer): First block index to return.
  - Query `limit` (optional, integer): Maximum number of blocks to return.
  - Response: JSON `StateBlockPage` — A page of state blocks.
- `GET /statechain/kv/{key}` — **State value** (`getStateValue`). Returns one state chain key-value entry by full key (dots and slashes allowed).
  - Path `key`: State key, e.g. sys.network_id, sys.minter, epoch.0.
  - Response: JSON `StateValue` — The entry.
- `GET /statechain/kv` — **All state values** (`listStateValues`). Returns state chain key-value entries. Optional ?prefix filter plus ?offset/?limit pagination.
  - Query `prefix` (optional, string): Only keys starting with this prefix.
  - Query `offset` (optional, integer): Number of entries to skip.
  - Query `limit` (optional, integer): Maximum number of entries to return.
  - Response: JSON `StateValueList` — Entries.
- `GET /statechain/keyset` — **DAO keyset** (`getDaoKeyset`). Returns the current DAO keyset (`sys.dao_keyset`): M-of-N ed25519 public keys that sign state-chain blocks.
  - Response: JSON `Keyset` — The DAO keyset.
- `POST /statechain/blocks` — **Submit state block** (`submitStateBlock`). Submits a state chain block signed by the DAO keyset threshold.
  - Body: JSON `StateBlockSubmission` — see `#/components/schemas/StateBlockSubmission` in the OpenAPI document.
  - Response: JSON `StateBlock` — The appended state block.

### Directory

- `POST /directory/register` — **Register directory entry** (`registerDirectoryEntry`). Registers an account-to-peer mapping, signed by the account key over the registration payload.
  - Body: JSON `DirectoryRegistration` — see `#/components/schemas/DirectoryRegistration` in the OpenAPI document.
  - Response: JSON `DirectoryEntry` — The stored registration.
- `GET /directory` — **List directory** (`listDirectory`). Returns account-to-peer registrations, paginated.
  - Query `offset` (optional, integer): Number of entries to skip.
  - Query `limit` (optional, integer): Maximum number of entries to return.
  - Response: JSON `DirectoryEntryList` — Registrations.
- `GET /directory/{account}` — **Lookup directory entry** (`getDirectoryEntry`). Returns the registration for one account (404 if the account has not registered or its entry expired).
  - Path `account`: Account address, 64 hex characters.
  - Response: JSON `DirectoryEntry` — The registration.

### Chat

- `POST /chat/send` — **Send chat message** (`sendChatMessage`). Sends a peer-to-peer chat message. Requires a full pre-signed envelope (from, pub_key, signature over the envelope id) — the node never signs on behalf of callers. Timestamps must be within ±5 minutes of node time; messages are capped at 8 KB. Delivery is over encrypted libp2p transport; the payload itself is not end-to-end encrypted.
  - Body: JSON `ChatMessage` — see `#/components/schemas/ChatMessage` in the OpenAPI document.
  - Response: JSON `ChatSendResult` — { ok: true } once the envelope is stored and relayed.
- `GET /chat/auth/challenge` — **Chat auth challenge** (`getChatAuthChallenge`). Issues a single-use challenge valid for 120 seconds. Sign it with your account key (ed25519 over the raw challenge bytes) and pass account, challenge, and sig to the chat read endpoints as ownership proof.
  - Response: JSON `ChatChallenge` — A fresh challenge.
- `GET /chat/messages` — **Chat messages** (`listChatMessages`). Returns stored chat messages for an account. Requires an ownership proof: get a challenge from /chat/auth/challenge, sign it, and pass account, challenge, and sig. Optional ?since (unix nanoseconds).
  - Auth: ownership proof (`account`, `pub_key`, `challenge`, `sig`).
  - Query `account` (required, string): Account address, 64 hex characters.
  - Query `pub_key` (required, string): Hex ed25519 public key of `account`. Required: an address is a hash of the key and cannot verify a signature on its own.
  - Query `challenge` (required, string): Single-use challenge from GET /chat/auth/challenge.
  - Query `sig` (required, string): ed25519 signature over sha256("xe/chat-read-auth/v1\0" || challenge) by the account key, hex.
  - Query `since` (optional, integer): Only messages newer than this unix-nanosecond timestamp.
  - Response: JSON `ChatMessageList` — Stored messages.
- `GET /chat/contacts` — **Chat contacts** (`listChatContacts`). Returns the addresses an account has exchanged messages with. A named account requires an ownership proof (account, challenge, sig); with no account it lists every account with stored chat history, which is operator-only and returns 403 without the admin token.
  - Auth: ownership proof (`account`, `pub_key`, `challenge`, `sig`).
  - Query `account` (optional, string): Account address, 64 hex characters.
  - Query `pub_key` (optional, string): Hex ed25519 public key of `account`. Required: an address is a hash of the key and cannot verify a signature on its own.
  - Query `challenge` (optional, string): Single-use challenge from GET /chat/auth/challenge.
  - Query `sig` (optional, string): ed25519 signature over sha256("xe/chat-read-auth/v1\0" || challenge) by the account key, hex.
  - Response: JSON `ChatContactList` — Contact addresses.
- `GET /chat/events` — **Chat event stream** (`streamChatEvents`). SSE stream of new messages. A named account requires an ownership proof (account, challenge, sig); with no account it is the unfiltered firehose, which is operator-only and returns 403 without the admin token.
  - Auth: ownership proof (`account`, `pub_key`, `challenge`, `sig`).
  - Query `account` (optional, string): Account address, 64 hex characters.
  - Query `pub_key` (optional, string): Hex ed25519 public key of `account`. Required: an address is a hash of the key and cannot verify a signature on its own.
  - Query `challenge` (optional, string): Single-use challenge from GET /chat/auth/challenge.
  - Query `sig` (optional, string): ed25519 signature over sha256("xe/chat-read-auth/v1\0" || challenge) by the account key, hex.
  - Response: `text/event-stream` — Server-sent events, one `ChatMessage` JSON object per event.

### Node

- `GET /node` — **Node info** (`getNodeInfo`). Returns node identity, build version, network id, peer count, genesis hashes, the active lease timing windows (`lease_timing`) and, in provider mode, the current performance certificate summary.
  - Response: JSON `NodeInfo` — Node info.
- `GET /health` — **Health (liveness)** (`getHealth`). Liveness: the process is up and its internal loops (vote, sync, discovery) are running. Says nothing about consensus. Returns 503 with `ok: false` when a loop has stalled.
  - Response: JSON `HealthReport` — Liveness report.
- `GET /ready` — **Ready (readiness)** (`getReadiness`). Readiness: synced, peered (≥ --ready-min-peers, default 1), delegated vote weight present, quorum reachable and finality advancing (stall threshold --ready-finality-stall, default 180 s). Returns 503 with the failing checks when not ready — the probe load balancers and systemd should use.
  - Response: JSON `ReadyReport` — Readiness report.
- `GET /supply` — **Supply** (`getSupply`). Aggregate supply per asset in micro-units and the conservation identity the node checks: balances + in_flight + locked_stake + burned + escrow_burned + stake_forfeited == genesis + minted + emitted. `genesis_supply` is the authority for the 42,000,000 XE figure; treat `stable: false` as retry.
  - Response: JSON `SupplyReport` — Supply per asset.
- `GET /network/activations` — **Feature activations** (`getNetworkActivations`). `sys.activations` as of the state-chain tip (activated features and protocol version), plus the feature set this build implements. Features listed as active but not implemented mean the node must upgrade.
  - Response: JSON `ActivationsReport` — Activation state.
- `GET /network/readiness` — **Upgrade readiness** (`getNetworkReadiness`). Delegated vote weight grouped by advertised node version and supported feature — the gate the DAO reads before signing a `sys.activations` update.
  - Response: JSON `NetworkReadiness` — Weight by version and feature.
- `GET /pending/{address}` — **Pending for account** (`listPendingForAccount`). Returns finalized sends addressed to an account that it has not yet received — what the account's next receive blocks should reference as `source`.
  - Path `address`: Account address, 64 hex characters.
  - Response: JSON `PendingForAccount` — Unreceived sends to the account.
- `GET /pending` — **All pending sends** (`listPending`). Returns every unreceived send known to the node, across all accounts.
  - Response: JSON `PendingSendList` — All unreceived sends.
- `GET /frontiers` — **Frontiers** (`listFrontiers`). Returns the frontier (head block) of every known account chain — hash, block type, timestamp, block count and representative — sorted by address and paginated.
  - Query `offset` (optional, integer): Number of entries to skip.
  - Query `limit` (optional, integer): Maximum number of entries to return.
  - Response: JSON `Frontiers` — Frontier per account.
- `GET /delegation` — **Delegation weights** (`getDelegation`). Returns representative vote weights in micro-XE (final XE balances summed per representative), the quorum total, and any weight held by representatives excluded by a `sys.representatives` allowlist.
  - Response: JSON `Delegation` — Representative → weight.
- `GET /conflicts` — **Conflicts** (`listConflicts`). Returns open account-chain conflicts: two or more blocks claiming the same `previous`, with the vote weight snapshot each has received so far. Paginated.
  - Query `offset` (optional, integer): Number of entries to skip.
  - Query `limit` (optional, integer): Maximum number of entries to return.
  - Response: JSON `ConflictList` — Conflicts.
- `GET /conflicts/{account}` — **Conflicts for account** (`listConflictsForAccount`). Returns conflicts scoped to one account.
  - Path `account`: Account address, 64 hex characters.
  - Response: JSON `ConflictList` — Conflicts on that account chain.

### Versioning

URL-path versioning; the current major is v1 at `https://test.network/api/v1` (pin this). `https://test.network/api` is an alias of the current major and every response carries `X-API-Version`. Changes within a major are additive only; a new major keeps the previous one for 180 days; deprecations are flagged with `deprecated: true` in the OpenAPI document and `Deprecation` (RFC 9745) + `Sunset` (RFC 8594) headers at least 90 days ahead. Full policy: https://test.network/developers#versioning.

### Rate limits

Per client IP and request class: read 1000 per 5s, write 50 per 5s. Every response carries `RateLimit-Policy` and `RateLimit` (draft-ietf-httpapi-ratelimit-headers, e.g. `"read";q=1000;w=5` and `"read";r=998;t=4`) plus `RateLimit-Limit`/`RateLimit-Remaining`/`RateLimit-Reset`; a `429` adds `Retry-After`. Details: https://test.network/developers#rate-limits.

### Errors

Every 4xx/5xx from the `https://test.network/api` proxy is JSON: `{"error": {"code", "status", "message", "hint", "docs", "openapi"}}` (schema `Error` in the OpenAPI document, referenced by each operation's error responses). Codes: bad_request, unauthorized, forbidden, not_found, method_not_allowed, rate_limited, upstream_error, upstream_unavailable, upstream_timeout. The node's own JSON errors pass through unchanged.

## Notes [#notes]

* Most `GET` endpoints run immediately. Chat reads need the ownership proof
  described above, and lease-scoped lookups need a real lease hash — the
  samples come from `GET /leases` on the live network and go stale when it is
  wiped, at which point those entries return 404 until they are refreshed.
* `POST` endpoints include editable starter JSON showing the exact shape the
  node decodes. Block submissions must be fully formed: valid signature, block
  hash, post-block `balance`, and proof-of-work nonce. The node does not sign
  or complete blocks for you.
* Entries marked as reference-only (`/lease/request`, the TCP tunnel) cannot
  run from the browser; the panel explains what each needs instead.
* Sample account and block identifiers are refreshed from the live network
  with `node scripts/refresh-playground-samples.mjs` — rerun it after a
  testnet wipe.

---

Canonical HTML: https://test.network/docs/api · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Architecture

> Core node package map, block validation pipeline, and startup sequence.

The core node is written in Go. It uses libp2p for peer-to-peer networking, BadgerDB for persistent storage, blake2b for proof-of-work, and ed25519 for signatures.

## Package Map [#package-map]

```
xe/
├── cmd/
│   └── xe/         Single binary — node daemon, wallet, send/receive, leases, ssh, chat
├── core/           Domain logic — ledger, crypto, encoding, PoW, voting, quorum, supply
├── store/          Pluggable storage — MemStore (testing), BadgerStore (production)
├── net/            libp2p networking — gossip, sync, DHT discovery, netcheck, messaging, tunnel
├── node/           Orchestration — ties all packages together into a running node
├── api/            HTTP REST + SSE API — handler, routes, CORS, rate limits, /health /ready
├── statechain/     Deterministic state machine — DAO governance, KV store, activations, sync
├── vm/             VM abstraction — Lima and QEMU managers, mock, credentials
├── perf/           Performance certificates — PoW-chain + memory benchmark, price multiplier
├── web/            Embedded web UI — HTML + ES modules + CSS, served by xe node --ui
├── client/         HTTP client shared by CLI subcommands
├── directory/      P2P account directory — registration, verification, gossip
├── chat/           P2P messaging — envelope format, PoW, chat store
├── logging/        Levelled, optionally JSON, size-rotated logs
├── metrics/        Prometheus registry for the operator listener
├── genesis/        Published genesis bundles, one directory per network
├── deploy/         systemd unit, pm2 ecosystem, Dockerfile, monitoring stack
└── scripts/ci/     Build and release helpers
```

This is the source the testnet runs, with the development test suites and internal operational tooling removed; development happens in a private repository and this tree is its published release.

## Core Package Files [#core-package-files]

| File                 | Purpose                                                                                                                          |
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `types.go`           | Block, Vote, Conflict, Lease (+ renewal segments), PendingSend structs; BlockType and LeaseState constants                       |
| `ledger.go`          | Ledger struct — validates/adds blocks, per-account locking, delegation tracking, lease cost formula, genesis-driven lease timing |
| `lease_renew.go`     | `lease_renew` validation — consumer-only, attested before effective expiry, cumulative duration cap                              |
| `address.go`         | `sha256("xe/account/v1" ‖ pubkey)` address derivation and payload-key checks                                                     |
| `amount.go`          | Per-asset decimal precision; micro-unit amount parsing and formatting                                                            |
| `crypto.go`          | KeyPair, GenerateKeyPair, HashBlock (SHA-256), SignBlock, VerifyBlock (ed25519), network-ID binding                              |
| `encoding.go`        | MarshalBlockCanonical (binary encoding), MarshalBlockAux, MarshalBlock, UnmarshalBlock; vote encoding                            |
| `pow.go`             | blake2b PoW — ComputePoW, ComputePoWConcurrent, ComputePoWWithContext, ValidatePoW                                               |
| `vote.go`            | VoteManager — casts and validates votes for conflict resolution                                                                  |
| `quorum.go`          | QuorumManager — tallies votes, finalizes/rejects blocks at 67% weight                                                            |
| `finalization.go`    | Two-phase finalization voting — the central consensus mechanism                                                                  |
| `conflict.go`        | Conflict detection — equivocation checks                                                                                         |
| `cascade_commit.go`  | Conflict-promotion overlay — atomically commits a winning fork's cascade                                                         |
| `rollback.go`        | Cascading cross-account rollback of rejected forks                                                                               |
| `spendable.go`       | Spendable-balance computation — settlement surfaces honor finalization                                                           |
| `supply.go`          | Per-asset supply auditor behind `GET /supply` — both sides of the conservation identity                                          |
| `genesis.go`         | Genesis loading and validation — embedded placeholder or `--genesis-dir` bundle                                                  |
| `activations.go`     | `sys.activations` feature registry and block-type gating                                                                         |
| `repeligibility.go`  | `sys.representatives` allowlist — which delegated weight counts toward quorum                                                    |
| `mint.go`            | Authorized XUSD mint validation — sys.minter accounts only                                                                       |
| `memo.go`            | On-chain memo size and validation rules                                                                                          |
| `multisig.go`        | Multisig address derivation, keyset validation, threshold signatures                                                             |
| `recovery_phrase.go` | 24-word recovery phrases for wallet seeds                                                                                        |
| `reputation.go`      | Deterministic per-account reputation from on-chain lease activity                                                                |
| `retryable.go`       | Classifies errors that may resolve on retry (missing dependencies)                                                               |
| `attestation.go`     | Timekeeper attestation validation for lease blocks                                                                               |
| `store.go`           | Store interface and optional interfaces                                                                                          |

## Block Validation Pipeline [#block-validation-pipeline]

```
AddBlock(b *Block)
  │
  ├── 1. Normalize hex fields (lowercase)
  ├── 2. VerifyBlock — recompute hash + check ed25519 signature
  ├── 3. ValidatePoW — blake2b(nonce || hash) >= difficulty
  ├── 4. Timestamp check — within ±1 hour of local time
  ├── 5. Duplicate check — block hash not already in store
  ├── 6. Conflict detection — check if Previous hash is shared
  │     ├── No conflict → continue on main chain
  │     └── Conflict → stage block, fire callback, return
  │
  ├── 6b. Opening-key check — previous == "0" requires pub_key deriving the account; any later block must omit it
  │
  ├── 7. Type-specific validation (per-account lock held)
  │     ├── send     → balance sufficient, frontier matches, amount > 0
  │     ├── receive  → pending send exists, destination matches
  │     ├── mint     → XUSD only, account in sys.minter, no source/destination/memo, amount > 0
  │     ├── burn     → XE only, no source/destination, amount > 0, balance sufficient
  │     ├── lease    → XUSD only, cost formula correct against the provider certificate, balance sufficient
  │     ├── lease_accept → lease exists, valid certificate, emission params locked, attestations valid (no stake)
  │     ├── lease_renew  → consumer only, lease accepted, attested before effective expiry, cost at current certificate
  │     ├── lease_settle → lease expired within settle grace, XE emission formula summed over segments
  │     ├── lease_cancel → consumer only, source lease exists and is cancellable
  │     ├── lease_force_settle → consumer only, after grace + gap, before escrow expiry
  │     └── multisig_open/update → keyset valid; open derives the account address, update rotates the keyset
  │
  ├── 8. Update in-memory state (asset balances, delegation weights)
  └── 9. Write to store (atomic commit via AtomicBlockStore)
```

## Startup Sequence [#startup-sequence]

1. Load the genesis pair (`--genesis-dir` bundle or the embedded placeholder), set the network ID and apply its lease timing
2. Open or create key pair (loads `{dataDir}/node.key` or generates new); refuse a data directory that belongs to another network
3. Open store (BadgerStore at `{dataDir}/ledger`)
4. Create libp2p host (TCP, noise encryption, yamux, per-IP connection caps)
5. Setup pubsub (GossipSub)
6. Create gossip layers (block, vote, marketplace, directory, state chain, certificates)
7. Setup mDNS (unless `--disable-mdns`)
8. Create ledger (wraps store with validation)
9. Wire voting (VoteManager + QuorumManager)
10. Setup frontier sync
11. Setup DHT (Kademlia) and ambient discovery (unless `--no-discovery`)
12. Create messenger
13. Initialize state chain
14. Wire timekeeper config
15. Register gossip handlers and the netcheck admission handshake
16. Dial bootstrap peers
17. Start the API listener, the operator listener (`/metrics`, `/health`, `/ready`) and background goroutines

---

Canonical HTML: https://test.network/docs/architecture · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Assets (XE & XUSD)

> Dual-asset model — XE for consensus weight and provider emission, XUSD for compute payment.

XE supports two native assets with distinct economic roles.

## Units [#units]

Both assets use 6 decimal places (`core.Decimals = 6`). Every on-chain amount — balances, send amounts, lease costs, stakes — is an unsigned 64-bit integer in **micro-units**:

```
1 XE   = 1,000,000 µXE
1 XUSD = 1,000,000 µXUSD
```

A balance of `1000000` is one whole token, not a million. Where integer division on an amount is unavoidable, the protocol always rounds up.

## XE [#xe]

The native asset. Supply comes from the genesis block (42,000,000 XE) and from lease settlement, which mints XE to the provider in proportion to the lease's XUSD cost:

```
emission_µXE = max(1, ceil(cost_µXUSD × R_capped / 1000))
R_capped     = CapR(LockedR, LockedPayoutCap, LockedTWAP)
```

The emission parameters are locked onto the lease when the provider accepts it, so every node computes the same emission. The live `epoch.0` publishes `r_effective: 2000`, so providers currently earn 2× the lease cost.

**XE confers voting weight** — an account's delegation weight is its XE balance in micro-XE.

## XUSD [#xusd]

> \[!NOTE] XUSD is operator-issued on the testnet
> The faucet hands out XE from a pre-funded wallet; XUSD enters circulation only through `mint` blocks from the accounts under the state chain's `sys.minter` key, which the operators control. On `testnet-0005` XUSD exists and settles leases (`GET /supply` shows the minted total), but unless you have been granted some, the lease pricing below describes what the protocol charges rather than something you can pay for yourself.

* **Lease payment** — consumers pay XUSD for compute
* **No voting weight** — XUSD is mintable by authorized minters, so letting it carry consensus weight would let a minter mint governance power along with the balance; only XE counts
* **Issuance** — XUSD enters circulation only via minter `mint` blocks from accounts in `sys.minter` (claim logic was removed from the ledger). On the testnet that key holds a single operator account; the faucet sends XE and holds no minter key. `sys.minter` is designed for a bridge, and none is deployed.
* **Destruction** — XUSD leaves circulation when a lease's escrow is burnt at settlement, or at escrow expiry when neither party acted. Cancellation and force-settle refund the escrow instead.

## Asset Encoding [#asset-encoding]

8-byte field, left-aligned UTF-8, zero-padded:

```
"XE"   → 58 45 00 00 00 00 00 00
"XUSD" → 58 55 53 44 00 00 00 00
```

## Cost Formula [#cost-formula]

Lease pricing is deterministic, in micro-XUSD:

```
perHourMicro = vCPUs × 20_000 + ceil(memoryMB / 1024) × 10_000 + diskGB × 1_000
minutes      = ceil(duration / 60)
cost         = max(1, ceil(ceil(perHourMicro × minutes / 60) × multiplierMilli / 1000))
```

Rates are quoted per hour but **billing granularity is one minute**: the duration is rounded up to whole minutes and the hourly rate divided down by 60, so a whole-hour lease prices exactly as before and a sub-hour lease pays only for the minutes it uses. `multiplierMilli` is the provider's price multiplier scaled ×1000 (1000 = 1.000×), read from the performance certificate the lease references. Every validator recomputes this from the lease block's own fields (`core.LeaseCost`) and rejects a mismatch, so the amount is not negotiable.

| Resource | Rate                 |
| -------- | -------------------- |
| vCPU     | 20,000 µXUSD/hour    |
| Memory   | 10,000 µXUSD/GB/hour |
| Disk     | 1,000 µXUSD/GB/hour  |

Worked examples at 1.000×:

* 1 vCPU, 1 GB memory, 10 GB disk, 1 day → 24 h × (20,000 + 10,000 + 10,000) = **960,000 µXUSD** (0.96 XUSD)
* 1 vCPU, 1 GB memory, 1 GB disk, 5 minutes → ceil(31,000 × 5 / 60) = **2,584 µXUSD**; emission at R = 2.000 is 5,168 µXE

There is **no provider stake** on the current network (`LeaseStakeDivisor` is 0, so `stake` on every lease record is `0`). XE emission at settlement = `ceil(cost × R_capped / 1000)` µXE (minimum 1), summed over the base term and every renewal segment at the R each locked.

---

Canonical HTML: https://test.network/docs/assets · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Block Lattice

> Per-account chains, cross-chain links, and the thirteen block types.

Every account address corresponds to a chain of blocks. Each block's `Previous` field points to the hash of the preceding block. The first block has `Previous` set to `"0"`.

## Cross-Chain Links [#cross-chain-links]

Transfers require two blocks:

1. A **send block** on the sender's chain debits the sender (creates a pending send)
2. A **receive block** on the recipient's chain credits the recipient (consumes the pending send)

The sender and receiver do not need to be online at the same time.

Account chains open with a crediting block — `receive`, `mint`, `multisig_open`, or `genesis`:

```
Alice            Bob              Carol
┌─────────┐      ┌────────┐      ┌─────────┐
│ recv    │      │ recv   │      │ recv    │
│ bal: 10 │      │ bal: 5 │      │ bal: 10 │
└───┬─────┘      └───┬────┘      └───┬─────┘
    │                │               │
┌───▼─────┐      ┌───▼────┐      ┌───▼─────┐
│ send    │─────▶│ recv   │      │ send    │
│ to: Bob │      │ src:   │      │ to: Bob │
│ amt: 5  │      │ bal: 10│      │ amt: 3  │
│ bal: 5  │      └───┬────┘      │ bal: 7  │
└─────────┘          │           └─────────┘
                 ┌───▼────┐
                 │ recv   │◀── from Carol
                 │ bal: 13│
                 └────────┘
```

## Properties [#properties]

* No contention between accounts (parallel processing)
* Instant finality for non-conflicting transactions

## XE Extensions [#xe-extensions]

* Dual assets (XE and XUSD)
* Additional block types (lease, lease\_accept, lease\_renew, lease\_settle, …)
* State chain for governance
* blake2b PoW as anti-spam
* Voting weight from delegated XE balances (XUSD is mintable and confers none)

## Block Types [#block-types]

Thirteen block types are defined:

| Type                 | Byte | Asset      | Debits                              | Credits                                       |
| -------------------- | ---- | ---------- | ----------------------------------- | --------------------------------------------- |
| `send`               | 0x01 | XE or XUSD | sender                              | —                                             |
| `receive`            | 0x02 | XE or XUSD | —                                   | recipient                                     |
| `lease`              | 0x04 | XUSD       | consumer (escrow)                   | —                                             |
| `lease_accept`       | 0x05 | XUSD       | — (no stake on the current network) | —                                             |
| `lease_settle`       | 0x06 | XE         | —                                   | provider (emission); the XUSD escrow is burnt |
| `genesis`            | 0x07 | XE         | —                                   | treasury (42,000,000 XE)                      |
| `multisig_open`      | 0x08 | XE or XUSD | —                                   | —                                             |
| `multisig_update`    | 0x09 | XE or XUSD | —                                   | —                                             |
| `lease_cancel`       | 0x0A | XUSD       | —                                   | consumer (refund)                             |
| `burn`               | 0x0B | XE         | self                                | —                                             |
| `lease_force_settle` | 0x0C | XUSD       | —                                   | consumer (refund)                             |
| `mint`               | 0x0D | XUSD       | —                                   | minter (new supply)                           |
| `lease_renew`        | 0x0E | XUSD       | consumer (additional escrow)        | —                                             |

`0x03` is a retired, unallocated gap: the permissionless `claim` type was removed — XUSD now enters circulation only via `mint` blocks on accounts registered under the state chain's `sys.minter` key. Genesis is XE-only and its balance must equal `GenesisSupply` (42,000,000 XE).

---

Canonical HTML: https://test.network/docs/block-lattice · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# CLI Reference

> The xe binary — unified CLI for node operation and client interaction.

The `xe` binary is a unified CLI for both node operation and client interaction. `xe node` runs the daemon; every other subcommand is a client that talks to a node's HTTP API (`XE_NODE`, default `https://ldn.core.test.network`). `./xe --help` prints the same list.

_The HTML version of this page (https://test.network/docs/cli) embeds an interactive simulation of the xe CLI; the commands it accepts are documented below._

## Commands [#commands]

```
Node:
  node [flags]              Start a node daemon (flags below)

Wallet:
  wallet create             Create a new wallet and show its 24-word recovery phrase
  wallet restore            Restore a wallet from its recovery phrase, read from stdin
                            (prompt or pipe, never an argument); refuses to overwrite
  wallet phrase             Show the recovery phrase of the current wallet
  wallet balance            Show wallet balance

Transactions:
  send <addr> <amount> [--asset XE|XUSD] [--memo "text"]
                            Send funds; --memo attaches up to 64 bytes of UTF-8
  receive                   Receive all pending sends
  faucet                    Request testnet XE from the faucet service (1,000 XE per
                            account per rolling 24 h; arrives pending — run receive)
  mint <amount>             Mint XUSD — authorized sys.minter wallet only (ops/bootstrap)
  burn <amount> [--yes] [--memo "text"]
                            Permanently destroy XE from your wallet (irreversible)

Compute:
  providers                 List compute providers
  lease [--vcpus N] [--memory MB] [--disk GB] [--duration SECS] [--provider ADDR]
                            Create a lease (defaults 1 vCPU, 1024 MB, 1 GB, 300 s)
  lease status <hash>       Check lease status
  lease renew <hash> <additional-seconds>
                            Operator only: renew from the node's wallet (XE_API_ADMIN_TOKEN)
  vm <hash>                 Get VM info
  ssh <hash>                SSH into a leased VM through the gateway

Messaging (off-chain, funds-free):
  directory register [--watch]
                            Register this wallet's account against the node so others
                            can message it; --watch stays resident and re-registers
                            every 20 minutes (entries expire at 30)
  chat send <addr> <message>
                            Send a signed, PoW-solved chat message; prints its id
  chat read [--since <unix-ns>] [--follow] [--json]
                            Read this wallet's messages (ownership-proofed); --follow
                            polls for new ones, --json prints one envelope per line

Reputation:
  reputation <addr>         Show reputation aggregate for an account

Tools:
  keygen                    Generate an ed25519 SSH keypair for lease access
  verify-genesis [flags]    Print the network_id and genesis hashes this binary runs
                            with; --genesis-dir/--genesis/--statechain-genesis verify a
                            supplied bundle instead; --json; --expect-network-id,
                            --expect-ledger-hash, --expect-statechain-hash exit non-zero
                            on mismatch
  sign-block                Sign a block from stdin; seed from XE_SEED
  version                   Print version
  help                      Print usage
```

`xe faucet` talks to the standalone faucet service (`XE_FAUCET`, default
`https://faucet.test.network`). It sends 1,000 testnet XE per account per
rolling 24 hours from a pre-funded wallet; the grant lands as a pending send, so
claim it with `xe receive`.

`xe sign-block` takes no seed argument — the binary rejects one because a
positional seed leaks via `ps`, `/proc`, and shell history. Set `XE_SEED`
instead. `wallet restore` reads the phrase from stdin for the same reason.

## Environment Variables [#environment-variables]

| Variable             | Default                         | Description                                                                                               |
| -------------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `XE_NODE`            | `https://ldn.core.test.network` | Node API URL for every client command                                                                     |
| `XE_WALLET`          | `~/.xe/wallet.seed`             | Wallet seed file                                                                                          |
| `XE_SEED`            | (none)                          | Hex seed for `sign-block` (required — the command takes no seed argument)                                 |
| `XE_FAUCET`          | `https://faucet.test.network`   | Faucet service URL                                                                                        |
| `XE_API_ADMIN_TOKEN` | (none)                          | Node: enables the operator-only API endpoints when set. Client: sent as the bearer token by `lease renew` |
| `XE_SSH_HOST`        | `ldn.test.network`              | SSH gateway hostname                                                                                      |
| `XE_SSH_PORT`        | `2222`                          | SSH gateway port                                                                                          |
| `XE_VM_MOCK`         | (unset)                         | Node: use the mock VM manager instead of Lima/QEMU (development only)                                     |

## Node Flags [#node-flags]

Flags accept either `-name` or `--name`.

### Network and genesis [#network-and-genesis]

| Flag                                 | Default    | Description                                                                                                 |
| ------------------------------------ | ---------- | ----------------------------------------------------------------------------------------------------------- |
| `--port`                             | 9000       | libp2p TCP port                                                                                             |
| `--dial`                             | (none)     | Comma-separated bootstrap peer multiaddrs                                                                   |
| `--data`                             | ./data     | Data directory (bound to one network)                                                                       |
| `--genesis-dir`                      | (embedded) | Directory holding `ledger-genesis.json` and `statechain-genesis.json`; joins that network without a rebuild |
| `--genesis` / `--statechain-genesis` | (embedded) | The two genesis files individually; must be used together                                                   |
| `--max-conns-per-ip`                 | 8          | Max inbound connections per source IP                                                                       |
| `--max-inbound-conns`                | 0 (= 256)  | Max simultaneous inbound connections; negative = no split                                                   |
| `--no-discovery`                     | false      | Disable ambient DHT peer discovery; peer only with the `--dial` list                                        |
| `--discovery-peers`                  | 0 (= 24)   | Peer count at which ambient discovery stops dialling                                                        |
| `--disable-mdns`                     | false      | Disable mDNS LAN discovery                                                                                  |

### API, UI and operator listeners [#api-ui-and-operator-listeners]

| Flag                     | Default        | Description                                                                                   |
| ------------------------ | -------------- | --------------------------------------------------------------------------------------------- |
| `--api`                  | true           | Enable/disable HTTP API server                                                                |
| `--api-port`             | 8080           | HTTP API port                                                                                 |
| `--api-bind`             | 127.0.0.1      | API bind address                                                                              |
| `--cors-origin`          | (none)         | Allowed CORS origin                                                                           |
| `--ui`                   | false          | Enable embedded web UI (requires `--api`)                                                     |
| `--ui-port`              | 8000           | Web UI port                                                                                   |
| `--ui-bind`              | 127.0.0.1      | Web UI bind address                                                                           |
| `--ui-dir`               | (embedded)     | Dev override: serve UI from filesystem                                                        |
| `--ui-faucet`            | (none)         | Base URL of a faucet service; the UI proxies `/faucet/*` to it (empty = faucet button hidden) |
| `--wallet`               | true           | Expose `/wallet/` in the embedded UI; `--wallet=false` serves 404                             |
| `--metrics-addr`         | 127.0.0.1:9095 | Operator listener for `/metrics`, `/health`, `/ready`; empty disables it                      |
| `--ready-min-peers`      | 1              | Connected-peer floor below which `/ready` reports not ready (0 declares a standalone node)    |
| `--ready-finality-stall` | 90s            | How long the final-height watermark may stay flat, with a backlog, before `/ready` fails      |

### Provider mode [#provider-mode]

| Flag                      | Default | Description                                                                                                                   |
| ------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `--provide`               | false   | Enable provider mode (requires KVM; auto-accepts matching leases)                                                             |
| `--vcpus`                 | 2       | vCPUs to offer                                                                                                                |
| `--memory`                | 2048    | Memory in MB                                                                                                                  |
| `--disk`                  | 20      | Disk in GB                                                                                                                    |
| `--price-multiplier`      | 1000    | Provider price multiplier ×1000 (1000 = baseline); non-default values are flagged as simulation-only pending anti-cheat gates |
| `--ssh-port`              | 0       | SSH gateway port (0 = disabled)                                                                                               |
| `--limactl-path`          | (PATH)  | Path to limactl                                                                                                               |
| `--min-lease-duration`    | (none)  | Policy: minimum lease duration as a Go duration (e.g. `1h`); empty = no limit                                                 |
| `--max-lease-duration`    | (none)  | Policy: maximum lease duration (e.g. `720h`); empty = no limit                                                                |
| `--min-lease-cost`        | 0       | Policy: minimum cost in whole XUSD (0 = no min)                                                                               |
| `--max-lease-cost`        | 0       | Policy: maximum cost in whole XUSD (0 = no max)                                                                               |
| `--max-concurrent-leases` | 0       | Policy: max active leases (0 = capacity-bound only)                                                                           |

The lease-duration flags are duration strings, not numbers — passing `0` fails
validation and stops the node at startup. Leave them unset for no limit.

### Logging [#logging]

| Flag                                 | Default        | Description                                             |
| ------------------------------------ | -------------- | ------------------------------------------------------- |
| `--log-level`                        | info           | `debug`, `info`, `warn` or `error`                      |
| `--log-file`                         | (stderr)       | Write to a size-bounded rotating file instead of stderr |
| `--log-max-size` / `--log-max-files` | (see `--help`) | Rotation size in MB and number of rotated files kept    |
| `--log-json`                         | false          | Emit logs as JSON                                       |

---

Canonical HTML: https://test.network/docs/cli · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Compute Leasing

> Lease lifecycle, per-minute cost model, renewal, attestations, VM management, and provider economics.

XE extends the block lattice with an on-chain compute marketplace. Consumers lease virtual machines from providers, paying XUSD. Providers earn XE emission rewards upon settlement.

> \[!NOTE] Live on testnet-0005 — with two caveats
> Providers are online (`GET /providers` lists them) and leases are being accepted, renewed and settled on the live network. Two things stop a newcomer from exercising it: leases are priced in **XUSD**, which only the operators' `sys.minter` accounts issue and which the faucet does not hand out; and provider availability is not guaranteed — check the live list before assuming a counterparty exists. GPU leasing is not implemented at all.

## Lifecycle [#lifecycle]

1. **Consumer creates `lease` block** — escrows XUSD for the full cost, specifying vCPUs, memory, disk, and duration
2. **Provider creates `lease_accept` block** — locks the emission rate, provisions the VM, gets timekeeper attestations
3. **Consumer optionally creates `lease_renew` blocks** — each one escrows more XUSD at the provider's current rate and extends the same lease in place; the SDK does this a minute at a time
4. **Provider creates `lease_settle` block** — mints XE emission, burns the escrow, tears down the VM

Two blocks cover the paths where that does not happen — the consumer's escrow is recoverable on both:

* **`lease_cancel`** — the consumer withdraws a lease no provider accepted; the escrow is refunded in full
* **`lease_settle` never arrives*&#x2A; — once the provider has abandoned the lease, the consumer submits &#x2A;*`lease_force_settle`**: full escrow refund

A lease ends in one of six states: `created`, `accepted`, `settled`, `cancelled`, `unfulfilled` (force-settled), or `expired` (neither party acted; escrow burned after the refund window closes).

### Timing windows are a network parameter [#timing-windows-are-a-network-parameter]

The settle and force-settle windows are set in the ledger genesis and reported by every node under `GET /node` → `lease_timing`, so read them from the network rather than assuming the binary defaults. Relative to the lease's effective expiry (start + base duration + every renewal):

| Window                    | Binary default | `testnet-0005` | Meaning                                                                                     |
| ------------------------- | -------------- | -------------- | ------------------------------------------------------------------------------------------- |
| `min_duration_secs`       | 60 s           | **5 s**        | Smallest legal lease or renewal                                                             |
| `settle_grace_ns`         | 1 h            | **120 s**      | Provider may `lease_settle` until expiry + grace                                            |
| `force_settle_gap_ns`     | 25 min         | **300 s**      | Dead zone after the grace; consumer may `lease_force_settle` from expiry + grace + gap      |
| `escrow_expiry_ns`        | 365 d          | **1 h**        | Refund window closes at expiry + escrow expiry; the escrow is then burnt                    |
| `archive_gap_ns`          | 1 h            | **10 min**     | After expiry + escrow expiry + this gap, the node archives the lease out of its working set |
| `max_attestation_skew_ns` | 10 min         | **60 s**       | Tolerance between timekeeper timestamps on one block                                        |

## Cost Model [#cost-model]

All amounts are micro-units. Rates are quoted per hour; **billing granularity is one minute**:

```
perHourMicro = vCPUs × 20_000 + ceil(memMB / 1024) × 10_000 + diskGB × 1_000
minutes      = ceil(duration / 60)
cost         = max(1, ceil(ceil(perHourMicro × minutes / 60) × multiplierMilli / 1000))
XE emission  = max(1, ceil(cost × R_capped / 1000))  // µXE, rate locked at accept (and per renewal)
```

Every validator re-derives `cost` from the block's own fields (`core.LeaseCost`) and hard-rejects a mismatch. Provider `PriceMultiplierMilli` range: 500 (0.5×) to 10000 (10×), default 1000 (1×) — the binary flags non-default values as a simulation feature pending anti-cheat gates. Duration limits: `min_duration_secs` (see above) to 31,536,000 s (365 days), cumulative across renewals. Resource caps per lease: 4,096 vCPUs, 64 TiB memory, 1 PiB disk.

**There is no provider stake** on the current network: `LeaseStakeDivisor` is 0, `LeaseStake()` returns 0, and every lease record reports `"stake": 0`. Force-settle refunds the consumer; there is nothing of the provider's to burn.

## Renewal [#renewal]

`lease_renew` is consumer-signed and references the lease by hash in `source`. It carries `amount` (the additional XUSD, priced by the same formula at the provider's current certificate), `duration` (additional seconds), `certificate_hash`, the emission parameters locked from the epoch at the attested renewal time, and timekeeper `attestations` proving the renewal happened while the lease was still live. Renewals grow the single escrow in place; settlement sums emission over the base term plus every renewal segment, each at its own locked R. Lease records expose them under `renewals[]`.

The renewal must be attested before the lease's effective expiry, so a client renewing "a minute at a time" needs to gather attestations ahead of each boundary — this is what the SDK's `holdLease` does. Operators can also renew from the node's own wallet with `POST /lease/{hash}/renew` (admin token required).

## Attestations [#attestations]

* Signed timestamps from trusted timekeeper nodes
* Timekeeper keys stored in state chain under `sys.timekeepers`
* SHA-256(leaseHash || timestamp) signed with ed25519
* Max skew between attestations: `max_attestation_skew_ns` (60 s on `testnet-0005`)
* Median of valid timestamps used as canonical time
* Max 20 attestations per block
* Rate limited: one attestation per lease per client (IP over HTTP, peer ID over p2p) every 15 s; a request rejected by validation does not consume the window

## VM Management [#vm-management]

* Lima (QEMU-based) VMs with KVM acceleration; a QEMU-direct manager also exists for hosts without Lima
* Ubuntu 24.04 cloud images (`ubuntu-24.04-server-cloudimg-amd64.img`, downloaded on first use)
* Cloud-init for SSH key injection
* VMs named `xe-{leaseHash[:12]}`
* Manager interface: Provision, Teardown, DialSSH, Get, List
* VM access is SSH-only, through the tunnel — there is no exec API

## SSH Gateway & Tunnel [#ssh-gateway--tunnel]

* Protocol: `/xe/tunnel/2.0.0`
* SSH gateway authenticates via lease's `AccessPubKey`
* ProxyJump for end-to-end encryption
* HTTP tunnel endpoint: `POST /tunnel/{leaseHash}/tcp` (header `X-Signature`: ed25519 signature of the lease hash by the access key)
* Max 100 concurrent SSH connections
* Public gateway: `ldn.test.network:2222` (`XE_SSH_HOST` / `XE_SSH_PORT`)

## Economics [#economics]

* XUSD is **escrowed** at lease creation (and grown by each renewal), burned at settle, and refunded in full on cancel or force-settle
* XE is **inflationary** — minted on lease settlement: emission = ceil(cost × R\_capped / 1000) µXE per segment, with the rate locked at accept and at each renewal (live `epoch.0`: `r_effective` 2000 ⇒ 2× cost)
* Provider price multiplier range: 500 (0.5×) to 10000 (10×), default 1000 (1×)
* No stake: an abandoned lease is recoverable via `lease_force_settle`, which refunds the consumer's escrow
* `GET /supply` reports both sides of the conservation identity per asset, including escrow burnt and emission minted

## Provider Policy [#provider-policy]

Providers can filter incoming leases before expensive attestation/VM-provision gates run. Five flags:

* `--min-lease-duration` — Go duration string (e.g. `5m`)
* `--max-lease-duration` — Go duration string (e.g. `720h`)
* `--min-lease-cost` — minimum cost in whole XUSD (uint64)
* `--max-lease-cost` — maximum cost in whole XUSD (uint64)
* `--max-concurrent-leases` — max active leases (uint64)

All default to zero/empty (fully permissive). The gate runs in `autoAcceptLease` immediately after the idempotency check. Defined in `node/policy.go`. The live providers advertise `max_concurrent_leases: 5`.

## Performance Certificates [#performance-certificates]

* Benchmark on startup, workload version **4**
* Phase 1 (CPU): 1,024 chained proof-of-work puzzles at 19 difficulty bits, each seeded by the last — the solving nonces are the witness served by `GET /certificate/nonces/{hash}`
* Phase 2 (memory): 256 MB table (8,388,608 × 32-byte entries) + 10,000,000 random reads
* Score = 1.0 / elapsed\_seconds
* 7-day validity; expired certificates stay retrievable by hash (`GET /certificate/hash/{hash}`) because historical lease blocks pin them
* Required for `lease_accept` and `lease_renew` blocks; `GET /node` → `certificate.valid` says whether this node can currently be leased from
* Broadcast via `xe/certificates` gossip topic

---

Canonical HTML: https://test.network/docs/compute · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Consensus

> Two-phase ORV finality — every block finalizes at ≥67% of delegated XE weight.

Every block reaches a finalized state via representative voting — not just blocks that fork. Each chain **position** (account + `Previous` hash) runs an election; a fork just means the election has more than one candidate.

## Two-Phase Voting [#two-phase-voting]

| Phase       | Vote kind       | Behaviour                                                                                                                                                                                                                   |
| ----------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1. Converge | `Final = false` | **Mutable.** A representative votes for its preferred candidate (lowest hash among candidates it holds) and may re-vote as its preference shifts. Converge votes establish a leader; they never finalize anything.          |
| 2. Final    | `Final = true`  | **Irrevocable.** Once a candidate has a visible ≥67% converge supermajority, the representative writes a **write-once commit-lock** for the position and casts a final vote for the locked hash. Only final votes finalize. |

A block finalizes when representatives holding **≥67% of delegated XE weight** have final-voted it:

```
blockWeight * 100 >= totalWeight * 67
```

Uses `big.Int` arithmetic. Deterministic preference: lexicographically lowest hash.

The commit-lock is never deleted — not on resolution, rollback, or restart — so a representative is structurally unable to final-vote two hashes at one position. Adversarial timing can delay finalization, never double it. The 67% quorum lock path additionally waits out a **3-second candidate-stability window** after the last new candidate appears, so a transient supermajority on a higher-hash sibling cannot grab the lock.

## Delegation [#delegation]

* Voting weight is the delegated **XE** balance, in micro-XE. XUSD confers **no** weight: XUSD is mintable (faucet/bridge), and mintable supply must not mint consensus weight.
* Weight snapshots frozen at conflict detection time
* Persistent via DelegationStore interface
* `sync.RWMutex` for concurrency

## Dependency Gating [#dependency-gating]

A representative withholds its own vote for a block whose dependencies are not yet finalized locally — its `Previous`, plus the cross-account `Source` for receives and lease lifecycle blocks. It still tallies incoming votes, so a lagging node never deadlocks an election.

## The Finality Wall [#the-finality-wall]

* Each account has a `final_height` watermark — the height of its highest finalized block. The ledger refuses to replace any block at or below it.
* **Spendable balance** counts only finalized inflows; an account with nothing finalized has no spendable balance.

## Fallback Resolution [#fallback-resolution]

If 67% is unreachable (non-voting reps inflate the total), then after 10 seconds only the **lowest-hash** final-voted candidate may finalize, and only if it holds a &#x2A;*strict majority (>50%)** of total delegated weight in final votes. Otherwise the node withholds rather than risk divergent finalization — since each representative is commit-locked to one hash per position, at most one candidate can ever hold a strict majority.

## Conflict Detection [#conflict-detection]

* A conflict = two blocks with the same `Previous` hash (equivocation)
* Max 10 block hashes per conflict
* Conflicting blocks placed in staging
* Conflict callback fires on detection **and** whenever a new sibling body is staged for an existing conflict; only gossip re-deliveries of an already-staged body are suppressed

## Voting [#voting]

* Per-conflict mutex prevents double-voting
* Vote buffering for votes arriving before local conflict detection (max 10 per conflict)
* ±5 minute timestamp window
* Vote wire format: 204 bytes (138 signing bytes + 2 length + 64 signature) — see [Binary Encoding](/docs/encoding)

## Quorum Manager [#quorum-manager]

* Stale conflict sweep every 15 seconds
* Block status: Pending (0), Finalized (1), Rejected (2)
* Block swap mechanism for staged winners

---

Canonical HTML: https://test.network/docs/consensus · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# System Constants

> Difficulty, timing, size limits, lease economics, performance certificates — and which of them are network parameters rather than binary constants.

Most values below are compiled into the `xe` binary. The **lease timing** block is not: it is set in the ledger genesis of each network and published by every node under `GET /node` → `lease_timing`, so the live network can differ from the defaults, and does.

## Proof of Work [#proof-of-work]

* DefaultDifficulty (blocks): `0xfffff80000000000`
* DefaultPoWDifficulty (chat envelopes): `0xffffc00000000000`
* TestDifficulty: `0x0000000000000002`

Both live difficulties are advertised in `GET /node` (`pow_difficulty`, `chat_pow_difficulty`).

## Timing [#timing]

| Constant                   | Value                                                           |
| -------------------------- | --------------------------------------------------------------- |
| Block timestamp tolerance  | ±1 hour                                                         |
| Vote window                | ±5 minutes                                                      |
| Re-vote backoff            | 15 seconds                                                      |
| Chat envelope freshness    | ±5 minutes                                                      |
| Chat read challenge TTL    | 120 seconds                                                     |
| Attestation rate limit     | 15 seconds per lease per client                                 |
| Quorum fallback            | 10 seconds                                                      |
| Lock stability window      | 3 seconds                                                       |
| Stale conflict sweep       | 15 seconds                                                      |
| Periodic sync              | 10 seconds                                                      |
| Sync rate limit            | 5 seconds/peer                                                  |
| Statechain sync rate limit | 30 seconds/peer                                                 |
| Bootstrap re-dial watchdog | 30 seconds (10 s dial timeout)                                  |
| Ambient discovery round    | 60 seconds (5 min backoff, target 24 peers)                     |
| Directory TTL              | 30 minutes (`xe directory register --watch` refreshes every 20) |
| Wallet unlock              | 30 minutes, hard                                                |

## Lease Timing (network parameter) [#lease-timing-network-parameter]

| Parameter                 | Binary default | `testnet-0005` genesis |
| ------------------------- | -------------- | ---------------------- |
| `min_duration_secs`       | 60             | **5**                  |
| `settle_grace_ns`         | 1 h            | **120 s**              |
| `force_settle_gap_ns`     | 25 min         | **300 s**              |
| `escrow_expiry_ns`        | 365 d          | **1 h**                |
| `archive_gap_ns`          | 1 h            | **10 min**             |
| `max_attestation_skew_ns` | 10 min         | **60 s**               |

Read them live: `curl -s https://ldn.core.test.network/node | jq .lease_timing`.

## Size Limits [#size-limits]

| Limit                    | Value                          |
| ------------------------ | ------------------------------ |
| Conflict hashes          | 10 max                         |
| Pending votes/conflict   | 10 max                         |
| Attestations/block       | 20 max                         |
| Memo                     | 64 bytes UTF-8                 |
| POST body                | 1 MiB                          |
| Gossip message           | 256 KB                         |
| Sync request             | 1 MiB                          |
| Sync response            | 10 MiB                         |
| Sync blocks/session      | 10,000                         |
| Sync page                | 64 default, 256 max            |
| Frontiers/request        | 10,000                         |
| State key                | 128 bytes                      |
| State value              | 64 KB                          |
| State block              | 256 KB                         |
| Message request/response | 64 KB                          |
| Netcheck handshake       | 1 KB, ≤ 8 advertised features  |
| List pagination          | `limit` default 100, max 1,000 |

## Lease Economics [#lease-economics]

| Parameter           | Value                                                                                    |
| ------------------- | ---------------------------------------------------------------------------------------- |
| Min duration        | `min_duration_secs` (network parameter, 5 s on `testnet-0005`)                           |
| Max duration        | 31,536,000 s (365 days), cumulative across renewals                                      |
| Max per lease       | 4,096 vCPUs, 67,108,864 MB memory, 1,048,576 GB disk                                     |
| Billing granularity | 1 minute (duration rounded up to whole minutes)                                          |
| vCPU rate           | 20,000 µXUSD/hour                                                                        |
| Memory rate         | 10,000 µXUSD/GB/hour                                                                     |
| Disk rate           | 1,000 µXUSD/GB/hour                                                                      |
| Provider stake      | none (`LeaseStakeDivisor` = 0)                                                           |
| XE emission         | ceil(cost × R\_capped / 1000) µXE per segment (min 1, R locked at accept / each renewal) |
| R fallback          | 2000 (2.000×) when no epoch is published                                                 |

## Performance Certificates [#performance-certificates]

| Parameter            | Value                       |
| -------------------- | --------------------------- |
| WorkloadVersion      | 4                           |
| PoWPuzzles           | 1,024 chained puzzles       |
| PoWDifficultyBits    | 19                          |
| MemoryTableSize      | 8,388,608 entries (256 MB)  |
| MemoryReads          | 10,000,000                  |
| CertificateValidity  | 7 days                      |
| PriceMultiplierMilli | 500 – 10,000, default 1,000 |

## Consensus & Network [#consensus--network]

| Parameter                                   | Value                                                        |
| ------------------------------------------- | ------------------------------------------------------------ |
| Quorum threshold                            | 67% of delegated XE weight                                   |
| Fallback finalization                       | strict majority (>50%) of lowest-hash final votes after 10 s |
| Connection manager low                      | 100                                                          |
| Connection manager high                     | 400                                                          |
| Connection grace                            | 1 minute                                                     |
| Max inbound connections per IP              | 8 (`--max-conns-per-ip`)                                     |
| Max inbound connections                     | 256 (`--max-inbound-conns`)                                  |
| Netcheck mismatch ban                       | 10 minutes (network id), 1 hour (protocol version)           |
| API rate limit (reads, GET)                 | 200 req/s, burst 1,000                                       |
| API rate limit (writes, POST)               | 10 req/s, burst 50                                           |
| API rate limit (probes, `/health` `/ready`) | 20 req/s, burst 100                                          |
| Rate limiter idle TTL                       | 5 minutes (max 100,000 tracked IPs)                          |
| Default API port                            | 8080 (bind 127.0.0.1)                                        |
| Default UI port                             | 8000 (bind 127.0.0.1)                                        |
| Default metrics/ops port                    | 9095 (bind 127.0.0.1; `/metrics`, `/health`, `/ready`)       |
| Default libp2p port                         | 9000                                                         |
| Readiness defaults                          | ≥ 1 peer, sync within 180 s, finality stall ≤ 90 s           |

---

Canonical HTML: https://test.network/docs/constants · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Cryptography

> ed25519, SHA-256, blake2b PoW. Cross-implementation compatibility (Go ↔ JS).

XE uses standard cryptographic primitives with cross-implementation compatibility between Go and JavaScript (tweetnacl/blakejs).

## Key Generation [#key-generation]

Ed25519 via `crypto/rand` or deterministic from 32-byte seed.

## Signing Contexts [#signing-contexts]

* **Account address** — `sha256("xe/account/v1" ‖ pubkey)`. The address commits to the key rather than being it, so a credential can be rotated without the identity changing. Multisig addresses are the SHA-256 of the canonical keyset instead.
* **Block hashing** — `SHA-256(networkID ‖ canonical bytes ‖ aux bytes)`. The aux tail is a sequence of 8-byte-big-endian-length-prefixed sections: the opening block's `pub_key` under the tag `xe/block/pubkey/v1` (ASCII hex, present only when `previous == "0"`), then, on lease-family blocks, the certificate hash and the timekeeper attestations sorted by public key. It is empty for any other block, so a plain non-opening send hashes exactly as it did before keys were split from addresses. Genesis blocks are hashed with the network-ID prefix cleared — they are created before the network ID is known.
* **Block signing** — `ed25519.Sign(privateKey, hash)`
* **Vote signing** — ed25519 over canonical vote encoding
* **Attestation signing** — ed25519 over `SHA-256(leaseHash || timestamp)`
* **Chat signing** — ed25519 over the envelope `id = sha256(f(from) ‖ f(pub_key) ‖ f(to) ‖ f(message) ‖ u64be(timestamp))` where `f(x)` is a 4-byte big-endian length followed by the UTF-8 bytes; the same `id` is the PoW target
* **Chat read proof** — `ed25519.Sign(priv, SHA-256("xe/chat-read-auth/v1\0" ‖ challenge_bytes))` over a single-use, 120-second challenge from `GET /chat/auth/challenge`
* **Directory signing** — `ed25519.Sign(priv, SHA-256("xe/directory-registration/v1\x00" ‖ len‖networkID ‖ len‖account ‖ len‖pubKey ‖ len‖nodePeer ‖ len‖timestamp))`, where each `len` is an 8-byte big-endian length prefix framing the field that follows; the node checks `pubKey` derives `account` before verifying

## Proof of Work [#proof-of-work]

Anti-spam only, not consensus. Always computed client-side.

```
result = blake2b_8(nonce_LE || blockHash)
valid  = result >= difficulty
```

* Block DefaultDifficulty: `0xfffff80000000000` (\~2²¹ attempts, \~1s)
* Chat DefaultPoWDifficulty: `0xffffc00000000000` (\~2¹⁸ attempts — chat spam pricing is tuned independently of block mining)
* TestDifficulty: `0x0000000000000002` (instant)
* Nonce: little-endian; result compared as big-endian
* Both difficulties are advertised in `GET /node`

Functions: `ComputePoW`, `ComputePoWConcurrent`, `ComputePoWWithContext`, `ValidatePoW`

---

Canonical HTML: https://test.network/docs/cryptography · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Deployment

> Single-binary deployment with embedded UI, the published packaging (systemd, Docker, pm2, monitoring), bootstrap peers and provider requirements.

A single `xe` binary serves everything: the block lattice, the HTTP API, the operator listener and the web UI (embedded via `//go:embed`, enabled with `--ui`). The repository's [`deploy/`](https://github.com/xeprotocol/xe/tree/master/deploy) directory and [`docs/run-a-node.md`](https://github.com/xeprotocol/xe/blob/master/docs/run-a-node.md) are the canonical operator references; this page summarises them and adds what is specific to the public testnet hosts.

## Listeners [#listeners]

| Listener    | Default          | Flags                                                       | Serves                                                             |
| ----------- | ---------------- | ----------------------------------------------------------- | ------------------------------------------------------------------ |
| HTTP API    | `127.0.0.1:8080` | `--api`, `--api-port`, `--api-bind`                         | REST + SSE API, `GET /` manifest                                   |
| Web UI      | `127.0.0.1:8000` | `--ui`, `--ui-port`, `--ui-bind`, `--wallet`, `--ui-faucet` | explorer, wallet, DAO console; reverse-proxies `/api/*` to the API |
| Operator    | `127.0.0.1:9095` | `--metrics-addr` (empty disables)                           | `/metrics` (Prometheus), `/health`, `/ready`                       |
| libp2p      | `0.0.0.0:9000`   | `--port`                                                    | gossip, sync, netcheck, tunnel                                     |
| SSH gateway | off              | `--ssh-port`                                                | SSH into leased VMs (provider mode)                                |

Only the libp2p port needs to be reachable from the internet. Everything else binds to loopback by default, and exposing it publicly is an explicit choice that should go through a reverse proxy. IPv6 is not supported — the node listens on `/ip4/0.0.0.0/tcp/<port>` only.

## Embedded UI (Recommended) [#embedded-ui-recommended]

```bash
xe node --ui \
  --genesis-dir /etc/xe/genesis \
  --dial /ip4/45.77.226.208/tcp/9000/p2p/12D3KooW... \
  --api-bind 0.0.0.0 \
  --ssh-port 2222
```

(Replace `12D3KooW...` with a current peer ID — see [Bootstrap Peers](#bootstrap-peers) — and put the published `ledger-genesis.json` and `statechain-genesis.json` for the network in the genesis directory.)

> \[!WARNING] Provider mode requires KVM
> `--provide` is deliberately absent from the example above — it is opt-in, and the live bootstrap nodes do not run it. It enables QEMU/Lima VM provisioning, which hard-requires KVM (`/dev/kvm`) on the host, and a provider node **auto-accepts** matching leases as soon as the flag is set. Only add `--provide` on bare metal or a VPS with nested virtualization enabled, and set the [provider policy flags](/docs/compute#provider-policy) to bound what it will accept.

## Packaging [#packaging]

| Artifact     | Path                                           | Notes                                                                                                                                                                                                          |
| ------------ | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| systemd unit | `deploy/xe-node.service` (+ `deploy/systemd/`) | Hardened: `ProtectSystem=strict`, `ReadWritePaths=/var/lib/xe`; reads `/etc/xe/xe-node.env` (`deploy/xe-node.env.example` lists every knob). Providers add a `kvm.conf` drop-in with `SupplementaryGroups=kvm` |
| Docker       | `deploy/Dockerfile`                            | Static, non-root, distroless image; mount the genesis at `/genesis` and pass `--genesis-dir /genesis`                                                                                                          |
| pm2          | `deploy/pm2/ecosystem.config.js`               | What the public bootstrap hosts use, behind Caddy                                                                                                                                                              |
| Monitoring   | `deploy/monitoring/`                           | Prometheus scrape config, alert rules, Alertmanager, Grafana dashboard, compose file — all against the operator listener                                                                                       |

Builds are reproducible (`make build`, `make dist`, `make verify-repro`; pinned toolchain in `.go-version`, `-trimpath`, `-buildvcs=false`, `CGO_ENABLED=0`). No release has been tagged yet, so `docs/run-a-node.md`'s download-and-verify recipe (SHA256SUMS, Sigstore, `gh attestation verify`) describes the intended flow; build from source today.

## Public Hosts (Caddy + pm2) [#public-hosts-caddy--pm2]

On the three bootstrap hosts, Caddy terminates TLS and proxies to the node's two listeners; both processes are managed by pm2.

```
        ┌─────────────┐
        │   Caddy     │ :80/:443 (TLS termination)
        └──────┬──────┘
       ┌───────┴────────┐
       ▼                ▼
 xe-node UI server  xe-node HTTP API
  127.0.0.1:8000     127.0.0.1:8080
   (--ui, --ui-port)  (--api-port)
       │                ▲
       └─ /api/* proxy ─┘
```

`*.core.test.network` is the API listener; `ldn.test.network` is the UI listener (wallet and explorer). The explorer and wallet are not separate applications and there are no static UI assets to deploy. Because external clients behind Caddy share an egress bucket in the node's per-IP rate limiter, a plain sequential sweep of the public URLs can trip `429` — back off rather than retry hot.

## Data Directory [#data-directory]

| Path              | Content                   |
| ----------------- | ------------------------- |
| `ledger/`         | BadgerDB database         |
| `host.key`        | libp2p identity (peer ID) |
| `node.key`        | Node account key          |
| `ssh_host_key`    | SSH gateway host key      |
| `lima/`           | Lima VM state             |
| `lima-templates/` | Lima YAML templates       |
| `images/`         | VM base images            |

The database lives in `ledger/` — wiping any other directory does not clear the chain state. A data directory is bound to one network: after a testnet wipe, start with a fresh `--data` directory alongside the new genesis bundle, or the node stops with `genesis mismatch: this data dir belongs to a different network`.

## Bootstrap Peers [#bootstrap-peers]

| Node | Location  | IP              | API                             |
| ---- | --------- | --------------- | ------------------------------- |
| ldn  | London    | 45.77.226.208   | `https://ldn.core.test.network` |
| ffm  | Frankfurt | 192.248.176.245 | `https://ffm.core.test.network` |
| nyc  | New York  | 144.202.4.117   | `https://nyc.core.test.network` |

Dial multiaddrs take the form `/ip4/<ip>/tcp/9000/p2p/<peer-id>`; the current set is listed in [Getting Started](/docs/getting-started#connecting-to-the-testnet). Peer IDs are derived from each node's `host.key` and change if that key is regenerated, so confirm them against the API:

```bash
curl -s https://ldn.core.test.network/node | jq -r .id
curl -s https://ffm.core.test.network/node | jq -r .id
curl -s https://nyc.core.test.network/node | jq -r .id
```

## Providers [#providers]

Providers participate over libp2p only; they have no public DNS or HTTP API of their own, and they advertise themselves on the `xe/marketplace` gossip topic. Query the live list rather than trusting any static table:

```bash
curl -s https://ldn.core.test.network/providers | jq
```

Two providers are registered at the time of writing (4 vCPU / 8 GB / 50 GB each, up to 5 concurrent leases). Requirements for running one: Ubuntu 24.04 or similar, `/dev/kvm`, QEMU and Lima (`--limactl-path` if not on `PATH`), a reachable libp2p port, and a valid performance certificate — the node benchmarks itself on startup and `GET /node` → `certificate.valid` must be `true` before it can be leased from. Advertise 60–70% of physical resources.

## Health, Readiness and Metrics [#health-readiness-and-metrics]

`/health` (liveness: process up, sampler running, store readable) and `/ready` (synced, peered, delegated weight present, quorum reachable, finality advancing) are served on both the API and the operator listener; `/metrics` only on the operator listener. Wire supervisors to `/health` only — during a network-wide quorum incident every node reports not-ready, and restarting them all makes it worse. Load balancers can drain on `/ready`. `xe_delegated_weight_micro_xe` at zero and `xe_finality_advances_total` flat are the two alerts that matter.

## Release Process [#release-process]

Development happens in a private repository; the public tree is its published release, cut per network (`release: testnet-0005 …`). Deploys to the bootstrap hosts are gated on the binary's genesis matching the live network's `network_id`, and protocol changes ship as a testnet wipe: new genesis bundle under `genesis/<network-id>/`, fresh data directories, new network ID. The documentation sites are separate Next.js applications with their own deploy workflows.

---

Canonical HTML: https://test.network/docs/deployment · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Binary Encoding

> Version 2 (0x02) — deterministic block and vote wire formats.

Version 2 (`0x02`). Deterministic layout for hashing and signing.

## Block Canonical Encoding [#block-canonical-encoding]

| Offset     | Size | Field                                                                                                                                                                                                               |
| ---------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 0          | 1    | Version byte (0x02)                                                                                                                                                                                                 |
| 1          | 1    | Type byte                                                                                                                                                                                                           |
| 2          | 8    | Asset (left-aligned UTF-8, zero-padded)                                                                                                                                                                             |
| 10         | 32   | Account (hex-decoded)                                                                                                                                                                                               |
| 42         | 32   | Previous (hex-decoded; "0" → 32 zero bytes)                                                                                                                                                                         |
| 74         | 8    | Balance (big-endian uint64)                                                                                                                                                                                         |
| 82         | 8    | Timestamp (big-endian int64)                                                                                                                                                                                        |
| 90+        | var  | Type-specific tail                                                                                                                                                                                                  |
| after tail | 32   | Representative (32 zero bytes if empty)                                                                                                                                                                             |
| after rep  | 48   | Genesis lease-timing tail — genesis only, present iff any timing field is non-zero: six big-endian uint64s (min\_duration, settle\_grace, force\_settle\_gap, escrow\_expiry, archive\_gap, max\_attestation\_skew) |
| trailer    | 1+N  | Send/burn only: `memo_len` (1 byte, 0–64) + memo bytes — **always present on send and burn, even when the memo is empty**, so a missing memo and an empty memo encode identically                                   |

## Memos [#memos]

Send and burn blocks may carry a UTF-8 memo of up to 64 bytes (`MaxMemoBytes`, byte-counted). The memo is part of the canonical encoding, so it is covered by the block hash and signature.

## Type Bytes [#type-bytes]

Send=0x01, Receive=0x02, Lease=0x04, LeaseAccept=0x05, LeaseSettle=0x06, Genesis=0x07, MultisigOpen=0x08, MultisigUpdate=0x09, LeaseCancel=0x0A, Burn=0x0B, LeaseForceSettle=0x0C, Mint=0x0D, LeaseRenew=0x0E

`0x03` is a retired, unallocated gap (the removed permissionless claim type).

## Type-Specific Tails [#type-specific-tails]

| Type                                 | Tail (all integers big-endian)                                                                                                        |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| `send`                               | destination (32) ‖ amount (8)                                                                                                         |
| `receive`                            | source (32)                                                                                                                           |
| `lease`                              | destination (32) ‖ amount (8) ‖ vcpus (8) ‖ memory\_mb (8) ‖ disk\_gb (8) ‖ duration (8) ‖ access\_pub\_key (32, zero bytes if unset) |
| `lease_accept`                       | source (32) ‖ amount (8) ‖ locked\_r (8) ‖ locked\_payout\_cap (8) ‖ locked\_twap\_milli (8)                                          |
| `lease_settle`                       | source (32) ‖ amount (8)                                                                                                              |
| `lease_renew`                        | source (32) ‖ amount (8) ‖ duration (8) ‖ locked\_r (8) ‖ locked\_payout\_cap (8) ‖ locked\_twap\_milli (8)                           |
| `lease_cancel`, `lease_force_settle` | source (32)                                                                                                                           |
| `multisig_open`, `multisig_update`   | canonical keyset bytes                                                                                                                |
| `burn`, `mint`                       | amount (8)                                                                                                                            |
| `genesis`                            | —                                                                                                                                     |

A declared `pub_key` is **not** part of the canonical bytes: it is bound into the hash through the aux input (see [Cryptography](/docs/cryptography)), which is why blocks travel and are stored as JSON rather than in this binary form.

## Full Block Encoding [#full-block-encoding]

```
[canonical bytes] [8 bytes PoW nonce (little-endian uint64)]
```

## Vote Encoding [#vote-encoding]

| Offset | Size | Field                                |
| ------ | ---- | ------------------------------------ |
| 0      | 1    | Version (0x02)                       |
| 1      | 32   | RepPubKey                            |
| 33     | 32   | BlockHash                            |
| 65     | 32   | ConflictAccount                      |
| 97     | 32   | ConflictPrev                         |
| 129    | 8    | Timestamp (big-endian)               |
| 137    | 1    | Final flag (0 = converge, 1 = final) |
| 138    | 2    | Signature length                     |
| 140    | N    | Signature bytes                      |

Signing bytes are offsets 0–137 (138 bytes); the final flag is signed so a converge vote cannot be flipped into a final vote. Total wire size with a 64-byte ed25519 signature: 204 bytes. `DecodeVote` rejects any version byte other than 0x02.

PoW nonce = little-endian; all other numeric fields = big-endian.

---

Canonical HTML: https://test.network/docs/encoding · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Explorer & Web UI

> Embedded web UI shipped inside the xe binary via //go:embed. No JS deps.

The embedded web UI ships inside the `xe` binary via `//go:embed`. No JavaScript dependencies, no build step, no separate deployment. Enable with `xe node --ui`.

## Technology [#technology]

* Plain HTML + ES modules + Web Crypto
* No framework, no bundler, zero JS dependencies
* Embedded in the Go binary via `//go:embed`
* Served by a separate HTTP server on `--ui-port` (default 8000, bound to 127.0.0.1) that reverse-proxies `/api/*` to the API server

## Enabling [#enabling]

```bash
xe node --ui                     # serve UI on 127.0.0.1:8000 (default)
xe node --ui --ui-port 8081      # serve UI on a different port
xe node --ui --wallet=false      # disable wallet pages (on by default)
```

`--ui` requires `--api` — the node exits at startup otherwise, because the UI
proxies `/api/*` to the API server. `--ui-bind` defaults to 127.0.0.1, so a
remote box needs a reverse proxy in front of the UI port.

## Pages [#pages]

* **Dashboard (/)** — node identity, stat tiles with sparklines, block-rate chart, recent blocks
* **Accounts (/accounts)** — sortable table of all accounts
* **Blocks (/blocks)** — recent blocks with asset, amount, balance
* **Leases (/leases)** — all compute leases
* **Providers (/providers)** — capacity metrics (capacity, used, active, total leases, last update)
* **Provider (/provider)** — own provider status and stats
* **Wallet (/wallet)** — send and receive (served by default; `--wallet=false` disables)
* **Wallets (/wallet/wallets)** — create, import, rename, remove wallets; reveal seed
* **DAO (/dao)** — draft, sign, submit state chain blocks
* **State Chain (/statechain)** — block history and KV browser
* **Conflicts (/conflicts)** — active/resolved conflicts
* **Peers (/peers)** — network connectivity
* **Pending (/pending)** — unreceived sends
* **Frontiers (/frontiers)** — all account frontiers
* **Chat (/chat)** — P2P messaging interface

## Development Override [#development-override]

Use `--ui-dir ./web/` to serve from filesystem instead of embedded assets (hot reload during development).

## Legacy [#legacy]

The standalone explorer and web wallet apps — including the bundled React explorer this site still serves at its own `/explorer` route — are deprecated. The embedded UI (`xe node --ui`) replaces both.

---

Canonical HTML: https://test.network/docs/explorer · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Getting Started

> Build the XE CLI, create a wallet, request XE from the testnet faucet, and make your first transactions.

This guide covers building the XE CLI from source, creating a wallet, funding it from the testnet faucet, and making your first transactions.

> \[!WARNING] Work in progress — not everything here works today
> XE is pre-1.0 and this is a testnet. Commands are added, changed and broken as development goes, and parts of the network go down. Two things to know before leasing:
>
> * **The faucet hands out XE, not XUSD.** Leases are priced in XUSD, which is minted only by the operators' `sys.minter` accounts, so unless you have been granted XUSD you can read the compute market but not pay for a lease.
> * **Provider availability is not guaranteed.** `xe providers` shows who is online right now (two providers at the time of writing); when it is empty, `xe lease` has nobody to accept a lease and `xe ssh` has nothing to connect to. A provider advertisement alone does not prove that acceptance, provisioning and SSH work end to end.

## Prerequisites [#prerequisites]

* Go 1.25+ (the repository pins the exact release in `.go-version`; `export GOTOOLCHAIN="go$(cat .go-version)"` selects it without a version manager)
* Git and curl
* `make` — it drives the build and is not present on a clean Ubuntu image, so
  install it first (`sudo apt-get install -y make`) or the first build command below fails

## Building from Source [#building-from-source]

The source is public at [github.com/xeprotocol/xe](https://github.com/xeprotocol/xe), licensed under the [GPL-3.0](https://github.com/xeprotocol/xe/blob/master/LICENSE) — use it, modify it, redistribute it, provided you pass on the same freedoms and publish the source of anything you distribute. No release binary has been tagged yet, so building is the way to get one.

```bash
git clone https://github.com/xeprotocol/xe.git
cd xe
make build          # produces ./xe
```

`make` is the single definition of how the binary is built. Builds are reproducible — pinned toolchain (`.go-version`), `-trimpath`, `-buildvcs=false`, `CGO_ENABLED=0` — so the same commit produces the same bytes on any machine. `make verify-repro` proves it locally.

One binary does everything: `xe node` runs the daemon, and every other subcommand is a client that talks to a node's HTTP API.

## Running a Node [#running-a-node]

Start a node with default settings: libp2p on port 9000, HTTP API on port 8080, data in `./data`.

```bash
./xe node
```

## Connecting to the Testnet [#connecting-to-the-testnet]

A node's identity is its **genesis, not its binary**. A stock build embeds a placeholder genesis that no live network uses, and peers ban a node whose network ID does not match theirs. Point the node at the published genesis bundle instead, and it joins the live network without a rebuild. Verify the bundle first, then start:

```bash
./xe verify-genesis --genesis-dir ./genesis/testnet-0005 \
  --expect-network-id testnet-0005

./xe node \
  --data "$HOME/.xe/testnet-0005" \
  --genesis-dir ./genesis/testnet-0005 \
  --dial /ip4/45.77.226.208/tcp/9000/p2p/12D3KooWJg4PQYGSfNCupBWZdEWbKj7pgdp5MmmUXbPdBcp6YDtT,/ip4/144.202.4.117/tcp/9000/p2p/12D3KooWEqv1BRZkSntgcgbrJh7bobFRSkBdRupubvNZLEx8hZLA \
  --port 9000 --api --api-port 8080
```

`--data` holds the node's persistent state and identity; after a testnet wipe use the new genesis bundle *and* a fresh data directory, or the node refuses to start with `genesis mismatch: this data dir belongs to a different network`.

The current network is &#x2A;*`testnet-0005`**, ledger genesis `3ff640410d78…d1e150`, statechain genesis `d01aabc1a29d…8f296d6` (confirm both against `GET /node` and `GET /statechain/blocks/0` on a bootstrap node; the genesis bundle is published in the repository's [`genesis/testnet-0005/`](https://github.com/xeprotocol/xe/tree/master/genesis/testnet-0005) directory). There are three bootstrap nodes:

| Node      | API                             | p2p                                                                                      |
| --------- | ------------------------------- | ---------------------------------------------------------------------------------------- |
| London    | `https://ldn.core.test.network` | `/ip4/45.77.226.208/tcp/9000/p2p/12D3KooWJg4PQYGSfNCupBWZdEWbKj7pgdp5MmmUXbPdBcp6YDtT`   |
| Frankfurt | `https://ffm.core.test.network` | `/ip4/192.248.176.245/tcp/9000/p2p/12D3KooWEbQ5zDvSz6kKE5ppzwBXKRFsnZbHNjRx94QZFaPeGA4e` |
| New York  | `https://nyc.core.test.network` | `/ip4/144.202.4.117/tcp/9000/p2p/12D3KooWEqv1BRZkSntgcgbrJh7bobFRSkBdRupubvNZLEx8hZLA`   |

Peer IDs are derived from each node's key and change if that key is regenerated. Fetch the current one from the node's API:

```bash
curl -s https://ldn.core.test.network/node | jq -r .id
```

The bootstrap list you configure is your node's first view of the network, so use more than one. Once connected, the node also advertises itself in the DHT under `xe/discovery/1/<network_id>` and dials peers it finds there (up to 24, `--discovery-peers`; `--no-discovery` turns this off), and mDNS finds peers on your LAN (`--disable-mdns`). Check that it worked:

```bash
curl -s localhost:8080/node | jq '{network_id, peers: (.peers | length)}'
```

Confirm `network_id` is `testnet-0005` and `peer_count` becomes greater than zero.

> \[!NOTE] A testnet wipe retires these values
> `testnet-0005`, the genesis hashes and the bundle above are all discarded when the protocol changes and the network is re-bootstrapped. Re-check this page after a wipe, or ask the network itself: `curl -s https://ldn.core.test.network/statechain/kv/sys.network_id`.

## Using the CLI [#using-the-cli]

Point the CLI at a testnet node and interact without running your own:

```bash
export XE_NODE=https://ldn.core.test.network
```

### Hello world — two wallets and a transfer [#hello-world--two-wallets-and-a-transfer]

The shortest useful thing you can do: create two wallets, fund one, send to the other. Keep each wallet in its own file with `XE_WALLET`.

```bash
XE_WALLET=~/.xe/alice.seed xe wallet create
XE_WALLET=~/.xe/bob.seed   xe wallet create
```

```
Wallet created!
  Address:    7d27d0a34cc2a5cd08f65905a983fabec1a517baf6d3cdab0a921256ecb9af57
  Public key: 665b50f96f8a4a86e1940386cce7fa1c0592c8eba9524fe9d579254fc341f02b
  File:       /home/you/.xe/alice.seed
```

The **address** is what you hand out, and it is not the public key — it is `sha256("xe/account/v1" ‖ pubkey)`, so identity and credential stay separate. The seed file *is* the account: back it up, and treat anyone who has it as the owner of the funds.

Fund Alice, then claim the grant:

```bash
XE_WALLET=~/.xe/alice.seed xe faucet
XE_WALLET=~/.xe/alice.seed xe receive
XE_WALLET=~/.xe/alice.seed xe wallet balance
```

Send Bob 25 XE, using the address printed for Bob above, and let Bob claim it:

```bash
XE_WALLET=~/.xe/alice.seed xe send <bob-address> 25 --asset XE --memo "hello world"
XE_WALLET=~/.xe/bob.seed   xe receive
XE_WALLET=~/.xe/bob.seed   xe wallet balance
```

Every transfer is two blocks — a `send` on the sender's chain and a `receive` on the recipient's — so funds sit as **pending** until the recipient signs for them. Nothing lands in an account without a block signed by its own key. Both sides settle in a few seconds, and neither pays a fee.

Any account is public, so you can watch the same thing from outside:

```bash
curl -s $XE_NODE/accounts/<address>/balance
curl -s $XE_NODE/accounts/<address>/chain
```

> \[!NOTE] How the faucet works
> `xe faucet` asks the faucet service (`XE_FAUCET`, default `https://faucet.test.network`) for a grant — a bare HTTP POST, no proof-of-work. It sends **1,000 XE per account per day** from a pre-funded wallet; it mints nothing and holds no minter key. A repeat request inside that window returns `429` with a `retry_after_seconds`. The grant arrives as a pending send, so follow it with `xe receive`.

### Messaging [#messaging]

Chat is off-chain and free. The recipient registers so the network knows which node they listen on; registrations lapse after 30 minutes, and `--watch` keeps one alive:

```bash
XE_WALLET=~/.xe/bob.seed   xe directory register
XE_WALLET=~/.xe/alice.seed xe chat send <bob-address> "hello bob"
XE_WALLET=~/.xe/bob.seed   xe chat read              # --follow --json streams new messages
```

### Compute [#compute]

```bash
xe providers                                      # List compute providers
xe lease --vcpus 1 --memory 1024 --duration 300   # Create a lease (needs XUSD)
xe lease status <hash>                            # Watch it get accepted and settled
xe ssh <hash>                                     # SSH into the leased VM
```

> \[!NOTE] Leasing needs XUSD and a provider
> Two providers are online on `testnet-0005` and leases are being accepted, renewed and settled — but they are priced in XUSD, which only the operators' minter accounts issue, so a fresh wallet cannot fund one yet. Always check `xe providers` first: provider availability changes independently of releases, and an empty list means a lease has nobody to accept it.

## Docker Deployment [#docker-deployment]

The repository ships a Dockerfile, a systemd unit, a pm2 ecosystem file and a Prometheus/Grafana/Alertmanager bundle in [`deploy/`](https://github.com/xeprotocol/xe/tree/master/deploy), and [`docs/run-a-node.md`](https://github.com/xeprotocol/xe/blob/master/docs/run-a-node.md) is the full operator guide — use those rather than the sketch below if you are running a node for real. A minimal equivalent:

```dockerfile
FROM golang:1.25-alpine AS build
ARG VERSION=dev
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags "-X main.version=${VERSION}" -o /xe ./cmd/xe/

FROM alpine:3.20
COPY --from=build /xe /usr/local/bin/xe
EXPOSE 8080 9000
ENTRYPOINT ["xe", "node"]
```

## Web-Based Quick Start [#web-based-quick-start]

If you prefer a browser-based experience, use the hosted web wallet at [ldn.test.network/wallet](https://ldn.test.network/wallet/):

1. Open the wallet and create a new wallet (your seed is encrypted client-side with AES-GCM)
2. Request testnet XE from the faucet — 1,000 XE per account per day
3. Explore the network via the [Explorer](/explorer)
4. Send transactions or use chat. Leasing compute is CLI-only and needs XUSD, which testers cannot obtain yet

## Environment Variables [#environment-variables]

| Variable      | Default                         | Description          |
| ------------- | ------------------------------- | -------------------- |
| `XE_NODE`     | `https://ldn.core.test.network` | Node API URL         |
| `XE_WALLET`   | `~/.xe/wallet.seed`             | Wallet seed file     |
| `XE_FAUCET`   | `https://faucet.test.network`   | Faucet service URL   |
| `XE_SSH_HOST` | `ldn.test.network`              | SSH gateway hostname |
| `XE_SSH_PORT` | `2222`                          | SSH gateway port     |

---

Canonical HTML: https://test.network/docs/getting-started · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Networking

> libp2p host, peer admission, GossipSub topics, frontier sync, DHT, and request-response messaging.

Fully peer-to-peer using libp2p. No central servers.

## Components [#components]

| Component       | Purpose                                                     | Protocol                                              |
| --------------- | ----------------------------------------------------------- | ----------------------------------------------------- |
| Host            | TCP transport, connection management                        | libp2p core                                           |
| Netcheck        | Peer admission — network-ID + version handshake             | `/xe/netcheck/1`                                      |
| GossipSub       | Broadcast blocks, votes, marketplace, statechain, directory | libp2p GossipSub                                      |
| Sync            | Frontier-based block synchronization                        | `/xe/sync/1.0.0`                                      |
| Statechain sync | State-chain block synchronization                           | `/xe/statechain-sync/1.0.0`                           |
| Messaging       | Request-response over streams                               | `/xe/msg/1.0.0`                                       |
| Tunnel          | SSH-over-network tunnels to leased VMs                      | `/xe/tunnel/2.0.0`                                    |
| DHT             | Kademlia routing + rendezvous discovery                     | `/xe` prefix; namespace `xe/discovery/1/<network_id>` |

## Discovery Mechanisms [#discovery-mechanisms]

1. **Bootstrap peers** — explicit via `--dial`; a watchdog re-dials disconnected bootstraps every 30s (10s timeout per dial). This is the only way a fresh node learns of the network, so configure more than one.
2. **Ambient DHT discovery** — once connected, the node advertises itself under `xe/discovery/1/<network_id>` and, every 60s while below the target peer count (default 24, `--discovery-peers`), looks up and dials up to 8 peers found there (15s dial timeout, 5 min backoff). `--no-discovery` disables it, leaving the node peered only with its `--dial` list.
3. **mDNS** — LAN discovery; `--disable-mdns` turns it off on shared networks.

## GossipSub Topics [#gossipsub-topics]

| Topic             | Data Type       |
| ----------------- | --------------- |
| `xe/blocks`       | Block           |
| `xe/votes`        | Vote            |
| `xe/marketplace`  | MarketplaceMsg  |
| `xe/statechain`   | StateChainBlock |
| `xe/directory`    | Registration    |
| `xe/certificates` | Certificate     |

Max gossip message size: 256 KB.

## Sync Protocol [#sync-protocol]

* Frontier-based: exchange `account → latest block hash`
* Paginated responses (default 64 blocks/page, max 256)
* Max 10,000 blocks per sync session
* 5-second per-peer cooldown
* Periodic re-sync every 10 seconds (with dirty flag optimization)
* Cross-account dependency retry passes, with early exit when a pass makes no progress
* Block quarantine for permanently invalid blocks

## Messaging Protocol [#messaging-protocol]

* Request-response semantics
* 30-second stream deadline
* 64 KB max request/response
* Message types: `vm_credentials`, `vm_status`, `account_chat`, `attest_timestamp`, `block_request`, `vote_request`, `cert_request`
* `block_request`, `vote_request`, and `cert_request` are targeted sync-repair RPCs — a node missing a block body, an election's votes, or a lease certificate asks a peer for it directly
* DHT-based peer discovery fallback

## Security [#security]

* **Peer admission**: on connect, peers exchange network ID, protocol version and advertised features over `/xe/netcheck/1` (1 KB handshake, 5s timeout); a network-ID mismatch disconnects the peer with a 10-minute ban, a protocol-version mismatch (beyond two minor versions, or below `sys.min_protocol_version`) with a 1-hour ban. After a testnet wipe, this is why a node started with the old genesis logs `CANNOT JOIN NETWORK` rather than syncing.
* Inbound connections are capped per source IP (default 8, `--max-conns-per-ip`) and in total (default 256, `--max-inbound-conns`), reserving the rest of the connection budget for peers this node dials
* Transport encryption (Noise or TLS 1.3)
* Persistent Ed25519 identity
* GossipSub pre-validates field lengths
* Sync rate limiting (5s per peer)
* Max message sizes enforced

## Host Configuration [#host-configuration]

* TCP on all interfaces, configurable port
* Connection manager: low=100, high=400, grace=1min
* Persistent identity at `{dataDir}/host.key`
* Optional relay + hole-punching support

---

Canonical HTML: https://test.network/docs/networking · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# SDK

> The TypeScript client for XE — hold your own keys, move value and rent compute without running a node. Pre-release, built from source.

`@xeprotocol/sdk` is the official client library for XE. It builds, signs and
proof-of-works blocks **in your own process** and submits them to any node's
HTTP API, so your keys never leave your machine and you never have to run a
node. It runs in Node.js 20+ and in the browser on the same code path, and it
is written in TypeScript with `bigint` throughout, so micro-unit amounts and
nanosecond timestamps are never rounded.

The source of truth is the repository at
[github.com/xeprotocol/sdk](https://github.com/xeprotocol/sdk); this page
mirrors its README so the SDK is discoverable alongside the
[HTTP API](/docs/api) and the [CLI](/docs/cli). When they disagree, the
repository wins.

> \[!WARNING] Pre-release — not on npm
>
> * **Not published to a registry yet.** `npm install @xeprotocol/sdk` fails
>   today; build it from the repository as described below.
> * XE is pre-1.0 and runs on a public testnet only. Protocol changes land by
>   wiping the testnet; balances, accounts and history are discarded when that
>   happens, without warning.
> * **There is no backward compatibility**, and there will be none before 1.0.
>   The API in the SDK will change.
> * XE on the testnet has no monetary value.

## Install from source [#install-from-source]

```sh
git clone https://github.com/xeprotocol/sdk.git
cd sdk
npm install        # runs the prepare script, which compiles src/ to dist/
npm test           # unit tests, no network needed
```

Then use it from your own project either by path or straight from git — both
run the build for you:

```sh
npm install /path/to/sdk
# or
npm install github:xeprotocol/sdk
```

`npm link` works too during development. The package exposes two entry points:
`@xeprotocol/sdk` (Node and browser) and `@xeprotocol/sdk/jobs` (Node only, for
running code on leased machines).

## Quick start [#quick-start]

```ts

const wallet = Wallet.create()          // or Wallet.fromSeedHex(process.env.SEED)
console.log(wallet.address)             // hand this out to receive funds

const xe = new Xe({ client: 'https://ldn.core.test.network', wallet })

// Anything sent to you arrives as PENDING and is yours only once you claim it.
await xe.receiveAll()

console.log(fromMicro(await xe.balance('XE')), 'XE')

await xe.send({
  to: someAddress,
  amount: toMicro('1.5'),
  memo: 'thanks',
})
```

Point `client` at any node. `https://ldn.core.test.network` is the public
London testnet node; the other bootstrap nodes are listed in
[Getting Started](/docs/getting-started). Fund a fresh wallet with
`xe faucet` from the [CLI](/docs/cli) or the faucet tutorial below — the faucet
sends 1,000 XE per account per day as a pending transfer, which
`xe.receiveAll()` claims.

## Things the SDK gets right for you [#things-the-sdk-gets-right-for-you]

### Address is not public key [#address-is-not-public-key]

An address is derived from a public key, and the two are different values:

```
address = sha256("xe/account/v1" || pubkey)
```

The **address** is identity — it goes in a block's `account` and `destination`,
and it is what you paste to receive funds. The **public key** is a credential.
Keeping them apart is what lets a key be rotated without the account changing,
so the SDK types them distinctly (`Address`, `PublicKey`) and will not let you
pass one where the other belongs.

### Amounts are integers [#amounts-are-integers]

Both assets carry six decimal places and every amount on the wire is an integer
count of micro-units; timestamps are unix **nanoseconds**. Both exceed what a
JavaScript `number` can hold exactly, so the SDK uses `bigint` throughout and
parses responses losslessly — a rounded timestamp produces a block the network
rejects, and a rounded balance is simply wrong.

```ts
toMicro('1.5')        // 1500000n
fromMicro(1500000n)   // '1.500000'
```

### Errors tell you whether to retry [#errors-tell-you-whether-to-retry]

The node distinguishes a failure worth retrying (a block that arrived before its
dependency) from a terminal one (a bad signature). The SDK carries that
distinction through, so you never guess from a message:

```ts

try {
  await xe.send({ to, amount })
} catch (err) {
  if (isRetryable(err)) { /* back off and try again */ }
}
```

A non-2xx response always throws. An empty list from the SDK means the account
genuinely has nothing — never that the request failed.

## Renting a machine [#renting-a-machine]

A lease escrows XUSD with a provider for a fixed term. To pay only for the time
you use, keep the term short and renew it a minute at a time: nothing beyond the
current minute is ever escrowed, so if you stop — or your process dies — the
machine is released within a minute.

```ts

const xe = new Xe({
  client: 'https://ldn.core.test.network',
  wallet: Wallet.fromSeedHex(process.env.SEED),
  // Renewals are timed by the network's timekeepers, so the SDK needs to
  // reach a threshold of them.
  timekeepers: [
    'https://ldn.core.test.network',
    'https://ffm.core.test.network',
    'https://nyc.core.test.network',
  ],
})

const sshKey = Wallet.create() // the key the machine will accept
const lease = await xe.openLease({
  provider,                      // an address from xe.client.providers()
  vcpus: 1, memoryMb: 1024, diskGb: 10,
  durationSecs: 60,
  accessPubKey: sshKey.publicKey,
})

// Renew a minute at a time until the term reaches ten minutes.
await holdLease(xe, lease.hash, { renewSecs: 60, totalSecs: 600 })
```

`openLease` prices the lease from the provider's current performance
certificate, exactly as the ledger will. `renewLease` gathers timekeeper
attestations, checks them locally and locks the current oracle epoch's emission
parameters. `holdLease` renews ahead of each expiry and confirms every extension
on the ledger before scheduling the next. `cancelLease` withdraws a lease the
provider has not accepted; `forceSettleLease` reclaims the escrow of one the
provider never settled.

**Prices.** You sign the exact amount of every lease and renewal, so nobody can
charge you more than you signed for. On top of that the SDK refuses to sign a
price you did not agree to: a price ceiling (`maxPricePerMinute`), renewals held
at the price the lease opened at, and a total `budget` passed to `holdLease`.
When a limit is hit the SDK stops renewing; the machine runs to the end of the
minute already paid for and is then released. The full model is in
[docs/budgets.md](https://github.com/xeprotocol/sdk/blob/master/docs/budgets.md).

> \[!NOTE] Paying for a lease needs XUSD
> Leases are priced in XUSD, which only the operators' `sys.minter` accounts
> issue and which the faucet does not hand out, so testers can read the compute
> market (`xe.client.providers()`, `GET /leases`) and run a provider, but cannot
> fund a lease themselves yet. See [Assets](/docs/assets).

## Running a job [#running-a-job]

`@xeprotocol/sdk/jobs` (Node only) runs your code on a rented machine and brings
the results home, holding the lease a minute at a time while it runs:

```ts

const job = await runJob(xe, {
  machine: { vcpus: 1, memoryMb: 1024, diskGb: 1 },
  files: { 'main.py': 'print("hello")' },
  run: 'python3 main.py',
  budget: toMicro('0.01'),
})
console.log(job.status, job.stdout, fromMicro(job.paid))
```

Five complete programs, including every way a job can fail, are in
[`examples/jobs/`](https://github.com/xeprotocol/sdk/tree/master/examples/jobs).

## Tutorials [#tutorials]

Step-by-step, in TypeScript and JavaScript, each ending in a complete program
you can run against the testnet:
[`examples/`](https://github.com/xeprotocol/sdk/tree/master/examples).

| # | Tutorial              | You will                                                           |
| - | --------------------- | ------------------------------------------------------------------ |
| 1 | Your first wallet     | install the SDK, create and keep a wallet, connect, read a balance |
| 2 | Getting testnet funds | use the faucet, claim pending transfers, wait for finality         |
| 3 | Sending XE            | send with a memo, claim it on the other side, look up a block      |
| 4 | Errors and retries    | tell temporary failures from final ones and retry safely           |
| 5 | Renting a machine     | find a provider, price a machine, rent it                          |
| 6 | Holding a lease open  | keep a machine as long as you need it, paying a minute at a time   |

Tutorials 1–4 need nothing but Node.js; 5–6 need XUSD.

## What works today [#what-works-today]

| Area                                                                    | Status                                     |
| ----------------------------------------------------------------------- | ------------------------------------------ |
| Wallets, addresses, signing                                             | ✅                                          |
| Canonical block encoding, hashing, proof of work                        | ✅ verified byte-for-byte against the node  |
| Send, receive, burn                                                     | ✅                                          |
| Balances, pending, chains, blocks, supply                               | ✅                                          |
| Providers, leases, state chain (read)                                   | ✅                                          |
| Leasing a machine: open, renew, cancel, force-settle, hold a lease open | ✅                                          |
| Price ceiling, renewal price lock, budgets, quotes                      | ✅                                          |
| Jobs: run code on a leased machine and collect results (Node)           | ✅ through the testnet SSH gateway          |
| Published package                                                       | 🚧 not yet                                 |
| Messaging and the account directory                                     | 🚧 not yet — use the [HTTP API](/docs/api) |
| Other languages                                                         | 🚧 later — TypeScript first                |

## Lower-level exports [#lower-level-exports]

Everything the high-level `Xe` class uses is exported, so you can build blocks
yourself when you need to: `XeClient` (typed HTTP calls), `Wallet`,
`deriveAddress`, `verifySignature`, `hashBlock`, `signBlock`,
`marshalCanonical`, `solvePow` / `validatePow`, `toMicro` / `fromMicro`,
`toAddress` / `toPublicKey` / `toHash`, and lossless JSON helpers
(`parseLossless`, `stringifyWithBigInts`). The wire format they implement is
documented under [Encoding](/docs/encoding) and [Cryptography](/docs/cryptography).

## Development [#development]

```sh
npm install
npm run typecheck
npm run lint
npm test                    # unit tests, no network needed
npm run build
npm run test:integration    # against a public node; skips with a reason when none is reachable
```

---

Canonical HTML: https://test.network/docs/sdk · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# State Chain

> Linear multisig chain — a deterministic state machine for DAO governance and emission.

A linear chain of multisig blocks forming a deterministic state machine. It carries DAO governance and drives emission: the oracle, tokenomics parameters, and network phase all live here.

## Properties [#properties]

* **Linear** — one chain, one tip
* **Multisig** — threshold signatures from DAOKeyset
* **Self-referential** — keyset stored in own KV store at `sys.dao_keyset`
* **Deterministic** — replay all blocks → same KV state

## Block Structure [#block-structure]

```go
type Block struct {
    Index      uint64
    PrevHash   string
    Ops        []Op            // [{action: "set"/"delete", key, value}]
    Signatures []BlockSignature
    Hash       string          // SHA-256 of the canonical encoding (below)
    Timestamp  int64
}
```

The hash covers the canonical encoding `index(8 BE) ‖ prev_hash(32) ‖ num_ops(4 BE) ‖ ops ‖ timestamp(8 BE)` — signatures excluded, so signing does not change the hash.

## System Keys [#system-keys]

Nine validated `sys.*` keys:

* `sys.dao_keyset` — DAO signer quorum (min threshold: 2)
* `sys.timekeepers` — trusted timekeeper **public keys** and threshold (identity is a key, not an address)
* `sys.minter` — authorized XUSD issuers, as account **addresses** matched against a `mint` block's `account`
* `sys.oracle` — oracle keys, threshold and the `epoch.` prefix it may write
* `sys.phase` — network phase, `1` or `2` (transitions are one-directional)
* `sys.tokenomics` — emission R-curve parameters and Hermite cap
* `sys.activations` — feature activation registry: each feature names an `at_index` and/or `not_before_ns` trigger, evaluated against the converged tip; `GET /network/activations` renders it
* `sys.min_protocol_version` — minimum protocol version peers must advertise to pass admission
* `sys.representatives` — `{"mode":"open"}` (default, every representative counts) or `{"mode":"allowlist","addresses":[…]}` restricting whose delegated weight forms the quorum denominator; `GET /delegation` reports the split

`sys.network_id` is read-only — set at genesis, never writable over the chain. `sys.*` keys can never be deleted, which is why `sys.representatives` has an explicit `open` mode: it is the only way to lift an allowlist once published.

## Operations [#operations]

* `set` — create/update key with JSON value
* `delete` — remove key (cannot delete `sys.*` keys)
* Key format: `^[a-z0-9_.-]+$`, max 128 chars
* Value max: 64 KB, must be valid JSON

## Sync [#sync]

* Protocol: `/xe/statechain-sync/1.0.0`
* Gossip topic: `xe/statechain`
* Page size: 64 blocks
* 30-second per-peer cooldown
* Genesis never sent over sync (configured locally)

---

Canonical HTML: https://test.network/docs/state-chain · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Proof of Uptime

> Design proposal for verifiable uptime proofs — not yet implemented.

> \[!WARNING] Design stage — not implemented
> Proof of uptime is a **design**, not shipped behaviour. Nothing on this page runs in an XE node today: the protocol implementation contains no heartbeat, epoch, or claim code, and no block type carries an uptime proof. The constructions described here were explored as standalone prototypes in the separate `proof-of-uptime` repository, and will change before they ship.
>
> What *is* implemented for leases today is timekeeper attestation on `lease_accept` and `lease_settle` blocks (see [Compute Leasing](/docs/compute)). It does not prove continuous uptime.

The proposal: verifiable uptime proofs for compute providers, using a three-layer architecture.

## Layer 1: Heartbeat Chain [#layer-1-heartbeat-chain]

* \~60 second interval, dual-signed (provider + consumer)
* Each heartbeat would chain to the previous via SHA-256
* \~250 bytes per heartbeat

## Layer 2: Merkle Epochs [#layer-2-merkle-epochs]

* 60 heartbeats → merkle tree → 32-byte epoch root
* Epochs would chain via `prevEpochHash`
* Selective disclosure via merkle proofs

## Layer 3: Chained Claims [#layer-3-chained-claims]

* 24 epoch roots → merkle tree → claim root
* \~200 bytes on-chain per claim
* \~1,800x compression vs raw heartbeat data (24h at \~250 bytes / \~60 s; a PoC-4 demo run in the prototype repo measured 3,681x)

## Design Decision: No VDF [#design-decision-no-vdf]

Economics would solve collusion: emission rate \< lease cost, so collusion is a net loss.

## Threat Analysis [#threat-analysis]

Status reflects the design on paper — none of these mitigations are implemented.

| Threat                 | Severity | Status                              |
| ---------------------- | -------- | ----------------------------------- |
| Ghost Node Attack      | Critical | Addressed in design (multi-layer)   |
| Collusion              | High     | Addressed in design (economics)     |
| Key Compromise         | Critical | Open — must solve before production |
| Timestamp Manipulation | Medium   | Open — must solve before production |

---

Canonical HTML: https://test.network/docs/uptime · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Web Wallet

> Client-side wallet using Web Crypto. Private keys never leave the browser.

Client-side wallet embedded in the `xe` binary, served as part of the embedded web UI (`xe node --ui` — wallet pages are on by default). Private keys never leave the browser. Zero JavaScript dependencies.

## Security [#security]

* Seeds encrypted with AES-GCM via Web Crypto API
* Keys derived via PBKDF2 (600,000 iterations, SHA-256)
* Fresh random IV per encryption
* Unlock lasts a hard 30 minutes from unlock (checked lazily on use) — there is no idle timer; lock explicitly or close the tab to end it sooner
* Seeds stored in localStorage as encrypted ciphertext only
* Private keys never leave the browser
* No JS dependencies — uses native Web Crypto

> **Warning:** after unlock, the raw passphrase is cached in `sessionStorage`
> under `xe.session.unlock` for up to 30 minutes so page navigations don't
> re-prompt. Any code running in the same origin can read it — roughly
> equivalent in risk to holding the decrypted seed in JS memory. The vault on
> disk stays encrypted regardless. Use the lock button on shared machines.

## Features [#features]

* **Multi-wallet management** — create, import, rename, delete wallets
* **Send/Receive** — transfer XE between accounts; pending sends are claimed with a per-item receive button (there is no auto-receive polling)
* **Faucet** — request testnet XE; the button appears when the node is started with a faucet target
* **P2P Chat** — real-time delivery, ed25519 signed envelopes
* **Provider dashboard** — read-only view of providers; creating leases and VM/SSH access are CLI-only
* **DAO governance** — draft, sign, submit state chain blocks
* **State inspector** — browse state chain blocks and KV entries
* **Client-side signing** — blocks are signed in the browser, but not offline: the node supplies the frontier, balance, network ID, and PoW parameters, and the same call submits the block

## Enabling [#enabling]

```bash
xe node --ui                     # embedded UI, wallet pages included (default)
xe node --ui --wallet=false      # UI without wallet (explorer only)
```

Wallet pages are enabled by default whenever the UI is on; `--wallet=false`
makes the node serve 404 for `/wallet/*`. Because keys stay client-side this is
a containment convenience, not the security boundary — the UI binds to
`--ui-bind` (default 127.0.0.1), so exposing it publicly is an explicit choice
that should go through a reverse proxy.

## Technology [#technology]

Plain HTML + ES modules + Web Crypto. Embedded in the Go binary via `//go:embed`. No build step, no framework, no bundler. Signing requires Web Crypto Ed25519: Chrome 113+, Firefox 130+, Safari 17+.

---

Canonical HTML: https://test.network/docs/wallet · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# XE Testnet Explorer

The explorer at https://test.network/explorer is an interactive single-page application (accounts, blocks, leases, the state chain and network conflicts) rendered in the browser from the live node. It has no static content to read as Markdown.

Agents should use the JSON API the explorer itself reads from: `https://ldn.core.test.network` (OpenAPI at https://test.network/openapi.json, reference at https://test.network/docs/api). For example `GET https://ldn.core.test.network/blocks/recent?limit=20` lists the newest blocks and `GET https://ldn.core.test.network/accounts` lists every account with its balances.

The explorer is also documented at https://test.network/docs/explorer.

---

Canonical HTML: https://test.network/explorer · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Privacy notice.

This notice covers the website at test.network and the public London testnet node it proxies to. It is written to be read, not to be scrolled past: the short version is that there are no accounts, no sign-ups and no advertising, and the site keeps as little as it can while still running. The data controller is **XE L1 Ltd.** (UK company 17245674), 128 City Road, London, EC1V 2NX.

- [Contact](https://test.network/contact)
- [About](https://test.network/about)

## What is recorded

1. **Server logs** The web server records each request's IP address, user agent, the URL requested, the time and the response status. These logs are used to keep the site up, to debug it and to defend against abuse, and are rotated on a short schedule. They are not used to build profiles and are not shared, other than with the hosting provider on whose machines they sit.
2. **API requests** Requests to /api on this site are forwarded to the London testnet node at ldn.core.test.network. The node applies per-IP rate limits, so it necessarily sees your IP address for as long as the limit window lasts. Requests you make directly to ldn.test.network go there without passing through this site at all.
3. **The ledger itself** Anything you write to the testnet — blocks, account addresses, balances, directory registrations, chat envelopes — is public by design: it is replicated to every node and shown in the explorer. Do not put personal data in a chat message or a block. The testnet may be wiped, but you should treat anything written to it as permanent and public.
4. **The assistant widget** The question box on this site is Edge Assist, a third-party service loaded from assist.edge.network. Questions you type into it are sent to Edge Network to be answered from this site's public content and are subject to their privacy policy. Nothing else on the site loads from a third party: fonts are served from test.network, and there are no analytics scripts, tracking pixels or advertising tags.

## What is not recorded

- No cookies are set by test.network for tracking, preferences or sessions; there is nothing to log in to.
- Wallet keys never reach this site or the node: the web wallet and the CLI generate and encrypt keys on your own device.
- No email addresses or names are collected by the site. If you email hello@xe.network or security@xe.network, or open a GitHub issue, that correspondence lives with the email provider or GitHub under their terms.
- No data is sold, and none is used for advertising.

## Your rights and changes

Under UK GDPR you can ask what we hold about you, ask for it to be corrected or deleted, and complain to the Information Commissioner's Office. Because the site holds only short-lived server logs keyed by IP address, most requests will find nothing to return; write to [`hello@xe.network`](mailto:hello@xe.network) or to the registered office and we will check. This notice is versioned with the site's source code, so its history is public in the repository; material changes are noted in the commit that makes them.

---

Canonical HTML: https://test.network/privacy · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)


# Every XE node ships a browser wallet.

_Web Wallet · NODE-SERVED UI_

Run `xe node --ui` and the wallet is served at `/wallet`. Private keys stay client-side, encrypted with Web Crypto. Try it now on the hosted testnet node — no install needed.

- [Open Hosted Wallet](https://ldn.test.network/wallet/)
- [Read Wallet Docs](https://test.network/docs/wallet)
- [Use CLI Wallet](https://test.network/docs/cli)

```sh
$ xe node --ui
ui:      enabled
wallet:  /wallet
keys:    client-side
```

- Client-side seed encryption
- Send and receive XE
- P2P chat and provider dashboard
- DAO signing and state inspection

---

Canonical HTML: https://test.network/wallet · This Markdown is served for `Accept: text/markdown`.
More for agents: [llms.txt](https://test.network/llms.txt) · [Full docs as Markdown](https://test.network/llms-full.txt) · [OpenAPI](https://test.network/openapi.json) · [Sitemap](https://test.network/sitemap.xml)
