@xyo-network/xl1-cli
v4.4.0
Published
XYO Layer One CLI
Keywords
Readme
@xyo-network/xl1-cli
XYO Layer One CLI
Description
The XL1 Command-Line Interface (CLI) tool is a program you run in your terminal to create a local XL1 Node. Once installed, you can invoke it from any directory using its name, the command, and any desired options like:
xl1 <command> [options]To see a list of all available commands and options run
xl1 --helpInstallation
Prerequisites
This CLI tool is built with Node.js, which is required to run the application. Node.js provides the runtime environment for executing JavaScript outside the browser allowing this CLI to function as a standalone executable on your system.
Install Node.js
To get started, install the latest Long-Term Support (LTS) version of Node.js, which includes Node Package Manager (npm) from the official site. After installation, you can verify Node.js is working by running the following in your terminal:
node -v
npm -vOnce Node.js is installed, you can install the XL1 CLI tool globally using npm or yarn.
Install the XL1 CLI
Once Node.js is installed, you can install the XL1 CLI tool globally using npm:
npm install -g @xyo-network/xl1-cliThis makes the command xl1 available anywhere in your terminal.
You can run the following command to verify the XL1 CLI was installed correctly
xl1 --helpUsage
After installing, you can run xl1 from any terminal:
xl1Runs a full XL1 Node, including both the API and Producer components. This is the default behavior when no subcommand is specified.
xl1 apiStarts only the API Node, which handles external requests and exposes HTTP endpoints.
xl1 producerStarts only the Producer Node, which is responsible for block production.
Use xl1 --help for available options and configuration flags.
Configuration
You can configure xl1 using CLI flags, environment variables, or a configuration file. CLI flags override environment variables, which override config file values.
CLI flags
xl1 --log.logLevel=info start apiEnvironment variables
XL1_* variables map to the config shape with double-underscore nesting (XL1_CONNECTIONS__LOCAL_STORE__ROOT, etc.; see .env.example).
export XL1_LOG__LOG_LEVEL=info
export XL1_ACTORS__0__NAME=api
export XL1_ACTORS__0__PORT=8080
xl1 start apiConfig file
Create a xyo.config.json (cosmiconfig also accepts xyo.yaml, xyo.toml, xyo.js, etc.) with an xl1 section. Omit -c to search the current directory, or pass -c <path> explicitly.
{
"xl1": {
"connections": {
"local-store": { "type": "lmdb", "root": ".store" }
},
"actors": [
{ "name": "api", "host": "localhost", "port": 8080 },
{ "name": "producer" },
{ "name": "finalizer" }
]
}
}Runnable xyo.json-style configs for XL1 3.0 declare every backing store, RPC endpoint, EVM RPC, and S3 bucket under connections. The pre-3.0 storage, remote, transports, and evm.jsonRpc / evm.infura fields are no longer supported. A remote API is reached with a default-rpc connection of type: "rpc", not the old remote.rpc.
Deployment guide
xl1 actors can be assembled into many physical layouts: one process running everything, one process per actor, or grouped processes sharing one config.
Actors
| Actor | Role |
|---|---|
| api | JSON-RPC gateway. Serves block / balance / transaction queries and accepts transfers. |
| producer | Block producer. Drives heartbeat blocks and packages mempool transactions. |
| finalizer | Selects a chain head from competing producer candidates and writes finalizations. Optionally publishes finalized blocks + the chain-state head to S3 (publishOnFinalize). |
| indexer | Publishes the chain index (step-summary families) to the S3 index bucket for HTTP/CDN reads. |
| mempool | Holds pending transactions until the producer pulls them. |
| bridge | EVM bridge — relays deposits / withdrawals between XL1 and an EVM chain. |
| rewardRedemption | Claims block rewards for the producer's account. |
xl1 start <actor> [actor ...] runs any subset in one process. With no positional args it defaults to api producer finalizer.
Dimensions
- Process layout — mono (everything in one process), split (one actor per process), or grouped (custom).
- Storage backing —
memory(single-process only, ephemeral),lmdb(single-host, persistent, multi-process safe via shared root), ormongodb(multi-host, persistent, multi-process safe). - Provider tier —
simple(in-process tier 1, requires the corresponding backing), orjsonrpc(tier 3 proxy to anotherxl1process). The CLI picksjsonrpcautomatically when a globaldefault-rpcconnection is configured; otherwise it pickssimplewhen the backing is available. - Config layout — single file shared across all processes (each
xl1 start <subset>filters which actors it activates), one file per process, or pure environment variables.
Launch patterns
Copy any example from Development examples or Per-actor production deployment into xyo.json, then:
# Single process
xl1 -c ./xyo.json start api producer finalizer mempool
# Multi-process (one OS process per actor, same config file)
xl1 -c ./xyo.json start api
xl1 -c ./xyo.json start producer
xl1 -c ./xyo.json start finalizer
xl1 -c ./xyo.json start mempool
# One config file per process
xl1 -c ./xyo.api.json start api
xl1 -c ./xyo.producer.json start producer# Inspect resolved config (secrets redacted)
xl1 --dump-config -c ./xyo.json start api producer finalizer
# Inspect provider wiring without starting actors
xl1 --dump-providers -c ./xyo.json start api producer finalizer
# Non-interactive dev boot (skip insecure-mnemonic prompt)
xl1 --skip-insecure-confirm -c ./xyo.json startConnections model
Named connections describe where providers read/write state:
| Connection | Type | Purpose |
|------------|------|---------|
| local-store | lmdb | On-disk LMDB chain authority store |
| chain-mongo | mongo | MongoDB chain authority store |
| dlq-store | lmdb / mongo | Dead-letter queue backing (dev: aliases to chain store when omitted) |
| mempool-store | lmdb / mongo | Mempool backing (dev: aliases to chain store when omitted) |
| default-rpc | rpc | HTTP JSON-RPC to another XL1 API |
| default-evm-rpc | evm-rpc | EVM JSON-RPC for staking/contracts |
| s3-finalized / s3-chain-state / s3-index | s3 | Object storage publish targets |
| rest-finalized / rest-chain-state / rest-index | rest | CDN / HTTP read URLs for published static files |
| memory | memory | In-process stubs (injected at boot) |
providerBindings map each provider moniker to a connection. When omitted, bindings are synthesized per connection name (chain monikers → local-store / chain-mongo, DLQ → dlq-store or dev-aliased chain store, mempool → mempool-store or dev-aliased chain store, viewers → default-rpc when present, EVM monikers → default-evm-rpc when declared, otherwise memory fallback at plan time).
Set the root option strictDependencies: true to disable implicit in-memory provider creation. In strict mode, every memory-backed dependency must have an explicit providerBindings.<Moniker>.connection pointing to a declared memory connection. An unbound required dependency fails configuration, while an unbound optional dependency is omitted. The option defaults to false; config-less runs retain the in-memory development preset and implicit memory fallback.
Index summaries use two monikers (not one provider per family):
| Moniker | Typical connection | Role |
|---|---|---|
| IndexViewer | rest-index (CDN) or memory (local dev) | Read step-summary families: balances, blocks, schemas, transfers |
| IndexPublishRunner | s3-index | Publish those families to the index bucket |
Finalized EVM contract events reuse the index bucket under an address-scoped evm/events/ layout:
| Moniker | Typical connection | Role |
|---|---|---|
| EvmEventIndexPublishRunner | s3-index | Read all logs for chain.id through Ethereum's finalized head and publish resumable range frames |
| StakeEventsViewer | rest-index | Decode the published raw logs with the staking ABI; no historical eth_getLogs calls |
The EVM index is optional. Bind EvmChainViewer → default-evm-rpc and EvmEventIndexPublishRunner → s3-index on the indexer to enable it. Bind StakeEventsViewer → rest-index on reward/staking consumers. Without the REST binding, EvmStakeEventsViewer remains the direct-RPC fallback.
Local dev omits IndexViewer — AccountBalanceViewer computes summaries on demand from BlockViewer. Bind IndexViewer → memory (SimpleIndexViewer) when you want in-process summary memoization without S3.
S3 write + REST read: The finalizer writes finalized blocks and the chain-state head to S3 (BlockPublishRunner, ChainStatePublishRunner). The indexer reads the published head and blocks over REST (ChainStateViewer, BlockViewer) and writes the index bucket via IndexPublishRunner → s3-index. Because BlockViewer must stay on the authority store for finalization, use split per-actor configs (Finalizer + Indexer) or override providerBindings explicitly as in S3 write + REST read.
Development examples
In-memory dev
Single process, ephemeral in-memory chain (fastest local dev). Omits connections — the boot preset injects an in-memory store.
{
"xl1": {
"actors": [
{
"name": "api",
"host": "localhost",
"port": 8080
},
{
"name": "producer",
"blockProductionCheckInterval": 100,
"heartbeatInterval": 100
},
{
"name": "finalizer",
"finalizationCheckInterval": 50,
"minCandidates": 1
}
]
}
}LMDB local
Single process with on-disk LMDB at .store.
{
"xl1": {
"connections": {
"local-store": {
"type": "lmdb",
"root": ".store"
}
},
"actors": [
{
"name": "api",
"host": "localhost",
"port": 8080
},
{
"name": "producer",
"heartbeatInterval": 1000
},
{
"name": "finalizer"
},
{
"name": "mempool"
}
]
}
}LMDB + EVM JSON-RPC
LMDB plus contract-backed chain via default-evm-rpc (local Hardhat/Ganache).
{
"xl1": {
"connections": {
"local-store": {
"type": "lmdb",
"root": ".store"
},
"default-evm-rpc": {
"type": "evm-rpc",
"url": "http://127.0.0.1:8545",
"chainId": "0xaa36a7"
}
},
"chain": {
"id": "dd381fbb392c85160d8b0453e446757b12384046"
},
"actors": [
{
"name": "api",
"host": "localhost",
"port": 8080
},
{
"name": "producer",
"heartbeatInterval": 1000
},
{
"name": "finalizer"
},
{
"name": "mempool"
}
]
}
}LMDB + EVM Infura
LMDB plus Sepolia EVM RPC URL.
{
"xl1": {
"connections": {
"local-store": {
"type": "lmdb",
"root": ".store"
},
"default-evm-rpc": {
"type": "evm-rpc",
"url": "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID",
"chainId": "0xaa36a7"
}
},
"chain": {
"id": "dd381fbb392c85160d8b0453e446757b12384046"
},
"actors": [
{
"name": "api",
"host": "localhost",
"port": 8080
},
{
"name": "producer",
"heartbeatInterval": 1000
},
{
"name": "finalizer"
}
]
}
}Explicit connections
Explicit connections + providerBindings overrides.
{
"xl1": {
"connections": {
"local-store": {
"type": "lmdb",
"root": ".store"
},
"default-evm-rpc": {
"type": "evm-rpc",
"url": "http://127.0.0.1:8545",
"chainId": "0xaa36a7"
}
},
"providerBindings": {
"BlockViewer": {
"connection": "local-store"
},
"MempoolRunner": {
"connection": "local-store"
},
"ChainContractViewer": {
"connection": "default-evm-rpc"
},
"StakeViewer": {
"connection": "default-evm-rpc"
}
},
"chain": {
"id": "dd381fbb392c85160d8b0453e446757b12384046"
},
"actors": [
{
"name": "api",
"host": "localhost",
"port": 8080
},
{
"name": "producer",
"heartbeatInterval": 1000
},
{
"name": "finalizer"
}
]
}
}Federated API
Read-only API proxying a remote chain RPC via explicit providerBindings.
{
"xl1": {
"connections": {
"default-rpc": {
"type": "rpc",
"protocol": "https",
"url": "https://chain.example.com/rpc"
}
},
"providerBindings": {
"AccountBalanceViewer": { "connection": "default-rpc" },
"BlockViewer": { "connection": "default-rpc" },
"FinalizationViewer": { "connection": "default-rpc" },
"MempoolRunner": { "connection": "default-rpc" },
"MempoolViewer": { "connection": "default-rpc" },
"TransactionViewer": { "connection": "default-rpc" },
"XyoRunner": { "connection": "default-rpc" },
"XyoViewer": { "connection": "default-rpc" }
},
"actors": [
{
"name": "api",
"host": "0.0.0.0",
"port": 8080
}
]
}
}MongoDB
MongoDB-backed archivist via chain-mongo.
{
"xl1": {
"connections": {
"chain-mongo": {
"type": "mongo",
"connectionString": "mongodb://root:example@localhost:27017/?retryWrites=true&w=majority",
"database": "chain",
"domain": "localhost",
"username": "root",
"password": "example"
}
},
"actors": [
{
"name": "api",
"host": "localhost",
"port": 8080
},
{
"name": "producer",
"heartbeatInterval": 1000
},
{
"name": "finalizer"
},
{
"name": "mempool"
}
]
}
}Multi-process LMDB
Shared LMDB; run one process per xl1 start <actor>.
{
"xl1": {
"connections": {
"local-store": {
"type": "lmdb",
"root": ".store"
}
},
"actors": [
{
"name": "api",
"host": "localhost",
"port": 8080
},
{
"name": "producer",
"heartbeatInterval": 1000
},
{
"name": "finalizer"
},
{
"name": "mempool",
"host": "localhost",
"port": 8081
}
]
}
}Mempool remote API
Standalone mempool over JsonRpc to a remote API.
{
"xl1": {
"connections": {
"default-rpc": {
"type": "rpc",
"protocol": "http",
"url": "http://localhost:8080/rpc"
}
},
"providerBindings": {
"MempoolRunner": {
"connection": "default-rpc"
},
"MempoolViewer": {
"connection": "default-rpc"
},
"BlockViewer": {
"connection": "default-rpc"
},
"WindowedBlockViewer": {
"connection": "default-rpc"
},
"TransactionValidationViewer": {
"connection": "default-rpc"
}
},
"actors": [
{
"name": "mempool",
"host": "localhost",
"port": 8081
}
]
}
}Mixed local + remote
Local LMDB runners + JsonRpc viewers.
{
"xl1": {
"connections": {
"local-store": {
"type": "lmdb",
"root": ".store"
},
"default-rpc": {
"type": "rpc",
"protocol": "http",
"url": "http://localhost:8080/rpc"
}
},
"providerBindings": {
"MempoolRunner": {
"connection": "local-store"
},
"BlockViewer": {
"connection": "default-rpc"
},
"MempoolViewer": {
"connection": "default-rpc"
},
"FinalizationViewer": {
"connection": "default-rpc"
},
"TransactionViewer": {
"connection": "default-rpc"
},
"AccountBalanceViewer": {
"connection": "default-rpc"
}
},
"actors": [
{
"name": "api",
"host": "localhost",
"port": 8080
},
{
"name": "mempool"
}
]
}
}Finalizer + indexer S3
Mongo store plus S3 publish connections for finalizer and indexer in one process.
{
"xl1": {
"connections": {
"chain-mongo": {
"type": "mongo",
"connectionString": "mongodb://user:[email protected]:27017",
"database": "xl1",
"domain": "internal"
},
"default-evm-rpc": {
"type": "evm-rpc",
"url": "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID",
"chainId": "0xaa36a7"
},
"s3-finalized": {
"type": "s3",
"accountId": "YOUR_R2_ACCOUNT_ID",
"accessKeyId": "YOUR_ACCESS_KEY_ID",
"secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
"prefix": "xl1/",
"bucket": "xl1-chain",
"readUrl": "https://chain.xl1.example"
},
"s3-chain-state": {
"type": "s3",
"accountId": "YOUR_R2_ACCOUNT_ID",
"accessKeyId": "YOUR_ACCESS_KEY_ID",
"secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
"prefix": "xl1/",
"bucket": "xl1-chain-state",
"readUrl": "https://chain-state.xl1.example"
},
"s3-index": {
"type": "s3",
"accountId": "YOUR_R2_ACCOUNT_ID",
"accessKeyId": "YOUR_ACCESS_KEY_ID",
"secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
"prefix": "xl1/",
"bucket": "xl1-index",
"readUrl": "https://index.xl1.example"
}
},
"providerBindings": {
"BlockViewer": { "connection": "chain-mongo" },
"BlockPublishRunner": { "connection": "s3-finalized" },
"ChainContractViewer": { "connection": "default-evm-rpc" },
"ChainStatePublishRunner": { "connection": "s3-chain-state" },
"ChainStateViewer": { "connection": "s3-chain-state" },
"EvmChainViewer": { "connection": "default-evm-rpc" },
"EvmEventIndexPublishRunner": { "connection": "s3-index" },
"FinalizationRunner": { "connection": "chain-mongo" },
"FinalizationViewer": { "connection": "chain-mongo" },
"IndexPublishRunner": { "connection": "s3-index" },
"MempoolViewer": { "connection": "chain-mongo" }
},
"actors": [
{
"name": "producer",
"heartbeatInterval": 1000
},
{
"name": "finalizer",
"publishOnFinalize": true,
"publishContentEncoding": "br",
"publishProgressInterval": 100
},
{
"name": "indexer",
"publishSyncInterval": 5000,
"publishContentEncoding": "br",
"publishProgressInterval": 100
}
]
}
}S3 write + REST read
Indexer: s3-index publish + REST CDN viewers.
{
"xl1": {
"connections": {
"s3-index": {
"type": "s3",
"accountId": "YOUR_R2_ACCOUNT_ID",
"accessKeyId": "YOUR_ACCESS_KEY_ID",
"secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
"prefix": "xl1/",
"bucket": "xl1-index",
"readUrl": "https://index.xl1.example"
},
"default-evm-rpc": {
"type": "evm-rpc",
"url": "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID",
"chainId": "0xaa36a7"
},
"rest-finalized": {
"type": "rest",
"baseUrl": "https://chain.xl1.example"
},
"rest-chain-state": {
"type": "rest",
"baseUrl": "https://chain-state.xl1.example"
},
"rest-index": {
"type": "rest",
"baseUrl": "https://index.xl1.example"
}
},
"providerBindings": {
"BlockViewer": {
"connection": "rest-finalized"
},
"ChainStateViewer": {
"connection": "rest-chain-state"
},
"EvmChainViewer": {
"connection": "default-evm-rpc"
},
"EvmEventIndexPublishRunner": {
"connection": "s3-index"
},
"IndexViewer": {
"connection": "rest-index"
},
"IndexPublishRunner": {
"connection": "s3-index"
}
},
"actors": [
{
"name": "indexer",
"publishSyncInterval": 5000,
"publishContentEncoding": "br",
"publishProgressInterval": 100
}
]
}
}Per-actor production deployment
One config per actor process — the shape a real multi-host deployment runs, where each actor is its own container/pod (see .k8s/ and the deploy-prod-* workflows). The read layer is served from S3-compatible object storage (e.g. Cloudflare R2) behind a CDN, so only the finalizer and indexer write to S3; everyone else reads the published blocks / chain-state / index over REST and keeps MongoDB only for the authority/write path.
| Actor | Backing connections | Writes to S3 | Reads |
|-------|---------------------|--------------|-------|
| api | chain-mongo (mempool + tx submission) | — | blocks / state / index over REST |
| producer | chain-mongo authority + default-evm-rpc + producer-wallet | — | authority store + EVM staking contract |
| finalizer | chain-mongo authority | s3-finalized (blocks), s3-chain-state (head) | authority store |
| indexer | default-evm-rpc (when event indexing is enabled) | s3-index | finalized blocks + chain-state over REST; finalized contract logs from EVM |
| mempool | chain-mongo | — | authority store |
| bridge | chain-mongo + default-evm-rpc + bridge-wallet + Redis | — | authority store + remote EVM chain |
| rewardRedemption | chain-mongo + default-evm-rpc + rewardRedemption-wallet | — | authority store + EVM staking contract |
- One writer per bucket. The finalizer owns
s3-finalized+s3-chain-state; the indexer ownss3-index. No other actor writes object storage. BlockVieweris not auto-bound to REST. When a process declares a local store,BlockViewerdefaults to it, so the API and indexer configs bindBlockViewer → rest-finalizedexplicitly to read finalized blocks from the CDN.ChainStateViewer/IndexViewerbind to their REST connections automatically when those connections are declared (the explicit bindings here are for clarity).- Staking.
chain.idis the staking contract address on the backing EVM. The indexer needsdefault-evm-rpconly whenEvmEventIndexPublishRunneris enabled. Staking/reward consumers can bindStakeEventsViewer → rest-indexfor indexed finalized history while retaining EVM bindings for mutating calls and point-in-time contract state. - Wallets. Wallet connections (
<actor>-wallet) and theXyoSignerbinding are auto-synthesized for signing actors (producer,bridge,rewardRedemption); they are shown explicitly here for clarity.bridgeandrewardRedemptioncannot be co-located in one process (they shareXyoSigner). - Secrets.
YOUR_*placeholders (S3 credentials, Mongo credentials, Infura project id) and wallet mnemonics should be supplied viaXL1_*env vars / your secret store in production — do not commit real credentials.
API (per-actor)
{
"xl1": {
"connections": {
"chain-mongo": {
"type": "mongo",
"connectionString": "mongodb://YOUR_MONGO_USER:[email protected]:27017/?retryWrites=true&w=majority",
"database": "xl1",
"domain": "internal"
},
"rest-finalized": {
"type": "rest",
"baseUrl": "https://chain.xl1.example"
},
"rest-chain-state": {
"type": "rest",
"baseUrl": "https://chain-state.xl1.example"
},
"rest-index": {
"type": "rest",
"baseUrl": "https://index.xl1.example"
}
},
"providerBindings": {
"AccountBalanceViewer": { "connection": "chain-mongo" },
"BlockRunner": { "connection": "chain-mongo" },
"BlockValidationViewer": { "connection": "chain-mongo" },
"BlockViewer": { "connection": "rest-finalized" },
"ChainStateViewer": { "connection": "rest-chain-state" },
"DeadLetterQueueRunner": { "connection": "chain-mongo" },
"DeadLetterQueueViewer": { "connection": "chain-mongo" },
"FinalizationRunner": { "connection": "chain-mongo" },
"FinalizationViewer": { "connection": "chain-mongo" },
"IndexViewer": { "connection": "rest-index" },
"MempoolRunner": { "connection": "chain-mongo" },
"MempoolViewer": { "connection": "chain-mongo" },
"TransactionValidationViewer": { "connection": "chain-mongo" },
"TransactionViewer": { "connection": "chain-mongo" },
"WindowedBlockViewer": { "connection": "chain-mongo" },
"XyoConnection": { "connection": "chain-mongo" },
"XyoGateway": { "connection": "chain-mongo" },
"XyoGatewayRunner": { "connection": "chain-mongo" },
"XyoRunner": { "connection": "chain-mongo" },
"XyoViewer": { "connection": "chain-mongo" }
},
"actors": [
{
"name": "api",
"host": "0.0.0.0",
"port": 8080
}
]
}
}Producer (per-actor)
{
"xl1": {
"connections": {
"chain-mongo": {
"type": "mongo",
"connectionString": "mongodb://YOUR_MONGO_USER:[email protected]:27017/?retryWrites=true&w=majority",
"database": "xl1",
"domain": "internal"
},
"default-evm-rpc": {
"type": "evm-rpc",
"url": "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID",
"chainId": "0xaa36a7"
},
"producer-wallet": {
"type": "wallet",
"accountPath": "0"
}
},
"chain": {
"id": "dd381fbb392c85160d8b0453e446757b12384046"
},
"providerBindings": {
"AccountBalanceViewer": { "connection": "chain-mongo" },
"BlockRunner": { "connection": "chain-mongo" },
"BlockValidationViewer": { "connection": "chain-mongo" },
"BlockViewer": { "connection": "chain-mongo" },
"ChainContractViewer": { "connection": "default-evm-rpc" },
"DeadLetterQueueRunner": { "connection": "chain-mongo" },
"DeadLetterQueueViewer": { "connection": "chain-mongo" },
"FinalizationRunner": { "connection": "chain-mongo" },
"FinalizationViewer": { "connection": "chain-mongo" },
"MempoolRunner": { "connection": "chain-mongo" },
"MempoolViewer": { "connection": "chain-mongo" },
"StakeEventsViewer": { "connection": "default-evm-rpc" },
"StakeTotalsViewer": { "connection": "default-evm-rpc" },
"StakeViewer": { "connection": "default-evm-rpc" },
"TimeSyncViewer": { "connection": "default-evm-rpc" },
"TransactionValidationViewer": { "connection": "chain-mongo" },
"TransactionViewer": { "connection": "chain-mongo" },
"WindowedBlockViewer": { "connection": "chain-mongo" },
"XyoConnection": { "connection": "chain-mongo" },
"XyoGateway": { "connection": "chain-mongo" },
"XyoGatewayRunner": { "connection": "chain-mongo" },
"XyoRunner": { "connection": "chain-mongo" },
"XyoViewer": { "connection": "chain-mongo" }
},
"actors": [
{
"name": "producer",
"heartbeatInterval": 1000
}
]
}
}Finalizer (per-actor)
{
"xl1": {
"connections": {
"chain-mongo": {
"type": "mongo",
"connectionString": "mongodb://YOUR_MONGO_USER:[email protected]:27017/?retryWrites=true&w=majority",
"database": "xl1",
"domain": "internal"
},
"default-evm-rpc": {
"type": "evm-rpc",
"url": "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID",
"chainId": "0xaa36a7"
},
"s3-finalized": {
"type": "s3",
"accountId": "YOUR_R2_ACCOUNT_ID",
"accessKeyId": "YOUR_ACCESS_KEY_ID",
"secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
"prefix": "xl1/",
"bucket": "xl1-chain",
"readUrl": "https://chain.xl1.example"
},
"s3-chain-state": {
"type": "s3",
"accountId": "YOUR_R2_ACCOUNT_ID",
"accessKeyId": "YOUR_ACCESS_KEY_ID",
"secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
"prefix": "xl1/",
"bucket": "xl1-chain-state",
"readUrl": "https://chain-state.xl1.example"
}
},
"chain": {
"id": "dd381fbb392c85160d8b0453e446757b12384046"
},
"providerBindings": {
"AccountBalanceViewer": { "connection": "chain-mongo" },
"BlockPublishRunner": { "connection": "s3-finalized" },
"BlockRunner": { "connection": "chain-mongo" },
"BlockValidationViewer": { "connection": "chain-mongo" },
"BlockViewer": { "connection": "chain-mongo" },
"ChainContractViewer": { "connection": "default-evm-rpc" },
"ChainStatePublishRunner": { "connection": "s3-chain-state" },
"ChainStateViewer": { "connection": "s3-chain-state" },
"DeadLetterQueueRunner": { "connection": "chain-mongo" },
"DeadLetterQueueViewer": { "connection": "chain-mongo" },
"FinalizationRunner": { "connection": "chain-mongo" },
"FinalizationViewer": { "connection": "chain-mongo" },
"MempoolRunner": { "connection": "chain-mongo" },
"MempoolViewer": { "connection": "chain-mongo" },
"TransactionValidationViewer": { "connection": "chain-mongo" },
"TransactionViewer": { "connection": "chain-mongo" },
"WindowedBlockViewer": { "connection": "chain-mongo" },
"XyoConnection": { "connection": "chain-mongo" },
"XyoGateway": { "connection": "chain-mongo" },
"XyoGatewayRunner": { "connection": "chain-mongo" },
"XyoRunner": { "connection": "chain-mongo" },
"XyoViewer": { "connection": "chain-mongo" }
},
"actors": [
{
"name": "finalizer",
"finalizationCheckInterval": 500,
"publishOnFinalize": true,
"publishContentEncoding": "br",
"publishProgressInterval": 100
}
]
}
}Indexer (per-actor)
{
"xl1": {
"connections": {
"rest-finalized": {
"type": "rest",
"baseUrl": "https://chain.xl1.example"
},
"rest-chain-state": {
"type": "rest",
"baseUrl": "https://chain-state.xl1.example"
},
"rest-index": {
"type": "rest",
"baseUrl": "https://index.xl1.example"
},
"default-evm-rpc": {
"type": "evm-rpc",
"url": "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID",
"chainId": "0xaa36a7"
},
"s3-index": {
"type": "s3",
"accountId": "YOUR_R2_ACCOUNT_ID",
"accessKeyId": "YOUR_ACCESS_KEY_ID",
"secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
"prefix": "xl1/",
"bucket": "xl1-index",
"readUrl": "https://index.xl1.example"
}
},
"providerBindings": {
"BlockViewer": {
"connection": "rest-finalized"
},
"ChainStateViewer": {
"connection": "rest-chain-state"
},
"EvmChainViewer": {
"connection": "default-evm-rpc"
},
"EvmEventIndexPublishRunner": {
"connection": "s3-index"
},
"IndexViewer": {
"connection": "rest-index"
},
"IndexPublishRunner": {
"connection": "s3-index"
}
},
"actors": [
{
"name": "indexer",
"publishSyncInterval": 5000,
"publishContentEncoding": "br",
"publishProgressInterval": 100
}
]
}
}Mempool (per-actor)
{
"xl1": {
"connections": {
"chain-mongo": {
"type": "mongo",
"connectionString": "mongodb://YOUR_MONGO_USER:[email protected]:27017/?retryWrites=true&w=majority",
"database": "xl1",
"domain": "internal"
}
},
"providerBindings": {
"AccountBalanceViewer": { "connection": "chain-mongo" },
"BlockRunner": { "connection": "chain-mongo" },
"BlockValidationViewer": { "connection": "chain-mongo" },
"BlockViewer": { "connection": "chain-mongo" },
"DeadLetterQueueRunner": { "connection": "chain-mongo" },
"DeadLetterQueueViewer": { "connection": "chain-mongo" },
"FinalizationRunner": { "connection": "chain-mongo" },
"FinalizationViewer": { "connection": "chain-mongo" },
"MempoolRunner": { "connection": "chain-mongo" },
"MempoolViewer": { "connection": "chain-mongo" },
"TransactionValidationViewer": { "connection": "chain-mongo" },
"TransactionViewer": { "connection": "chain-mongo" },
"WindowedBlockViewer": { "connection": "chain-mongo" },
"XyoConnection": { "connection": "chain-mongo" },
"XyoGateway": { "connection": "chain-mongo" },
"XyoGatewayRunner": { "connection": "chain-mongo" },
"XyoRunner": { "connection": "chain-mongo" },
"XyoViewer": { "connection": "chain-mongo" }
},
"actors": [
{
"name": "mempool",
"host": "0.0.0.0",
"port": 8081
}
]
}
}Bridge (per-actor)
{
"xl1": {
"connections": {
"chain-mongo": {
"type": "mongo",
"connectionString": "mongodb://YOUR_MONGO_USER:[email protected]:27017/?retryWrites=true&w=majority",
"database": "xl1",
"domain": "internal"
},
"default-evm-rpc": {
"type": "evm-rpc",
"url": "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID",
"chainId": "0xaa36a7"
},
"rest-datalake": {
"type": "rest",
"baseUrl": "https://api.archivist.xyo.network/dataLake"
}
},
"chain": {
"id": "dd381fbb392c85160d8b0453e446757b12384046"
},
"providerBindings": {
"AccountBalanceViewer": { "connection": "chain-mongo" },
"BlockRunner": { "connection": "chain-mongo" },
"BlockValidationViewer": { "connection": "chain-mongo" },
"BlockViewer": { "connection": "chain-mongo" },
"ChainContractViewer": { "connection": "default-evm-rpc" },
"DataLakeRunner": { "connection": "rest-datalake" },
"DataLakeViewer": { "connection": "rest-datalake" },
"DeadLetterQueueRunner": { "connection": "chain-mongo" },
"DeadLetterQueueViewer": { "connection": "chain-mongo" },
"FinalizationRunner": { "connection": "chain-mongo" },
"FinalizationViewer": { "connection": "chain-mongo" },
"MempoolRunner": { "connection": "chain-mongo" },
"MempoolViewer": { "connection": "chain-mongo" },
"StakeEventsViewer": { "connection": "default-evm-rpc" },
"StakeTotalsViewer": { "connection": "default-evm-rpc" },
"StakeViewer": { "connection": "default-evm-rpc" },
"TimeSyncViewer": { "connection": "default-evm-rpc" },
"TransactionValidationViewer": { "connection": "chain-mongo" },
"TransactionViewer": { "connection": "chain-mongo" },
"WindowedBlockViewer": { "connection": "chain-mongo" },
"XyoConnection": { "connection": "chain-mongo" },
"XyoGateway": { "connection": "chain-mongo" },
"XyoGatewayRunner": { "connection": "chain-mongo" },
"XyoRunner": { "connection": "chain-mongo" },
"XyoViewer": { "connection": "chain-mongo" }
},
"actors": [
{
"name": "bridge",
"accountPath": "0",
"redisHost": "redis.internal",
"redisPort": 6379,
"scannerIntervalMs": 30000
}
]
}
}Reward redemption (per-actor)
{
"xl1": {
"connections": {
"chain-mongo": {
"type": "mongo",
"connectionString": "mongodb://YOUR_MONGO_USER:[email protected]:27017/?retryWrites=true&w=majority",
"database": "xl1",
"domain": "internal"
},
"default-evm-rpc": {
"type": "evm-rpc",
"url": "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID",
"chainId": "0xaa36a7"
}
},
"chain": {
"id": "dd381fbb392c85160d8b0453e446757b12384046"
},
"providerBindings": {
"AccountBalanceViewer": { "connection": "chain-mongo" },
"BlockRunner": { "connection": "chain-mongo" },
"BlockValidationViewer": { "connection": "chain-mongo" },
"BlockViewer": { "connection": "chain-mongo" },
"ChainContractViewer": { "connection": "default-evm-rpc" },
"DeadLetterQueueRunner": { "connection": "chain-mongo" },
"DeadLetterQueueViewer": { "connection": "chain-mongo" },
"FinalizationRunner": { "connection": "chain-mongo" },
"FinalizationViewer": { "connection": "chain-mongo" },
"MempoolRunner": { "connection": "chain-mongo" },
"MempoolViewer": { "connection": "chain-mongo" },
"StakeEventsViewer": { "connection": "default-evm-rpc" },
"StakeTotalsViewer": { "connection": "default-evm-rpc" },
"StakeViewer": { "connection": "default-evm-rpc" },
"TimeSyncViewer": { "connection": "default-evm-rpc" },
"TransactionValidationViewer": { "connection": "chain-mongo" },
"TransactionViewer": { "connection": "chain-mongo" },
"WindowedBlockViewer": { "connection": "chain-mongo" },
"XyoConnection": { "connection": "chain-mongo" },
"XyoGateway": { "connection": "chain-mongo" },
"XyoGatewayRunner": { "connection": "chain-mongo" },
"XyoRunner": { "connection": "chain-mongo" },
"XyoViewer": { "connection": "chain-mongo" }
},
"actors": [
{
"name": "rewardRedemption",
"accountPath": "0",
"host": "0.0.0.0",
"port": 8082
}
]
}
}S3 object storage (finalized / chain-state / index publishing)
Beyond the authority store (lmdb / mongodb), the chain can publish a static, HTTP-servable copy of itself to S3-compatible object storage (e.g. Cloudflare R2). This is what serves cheap, CDN-cached reads to @xyo-network/xl1-rest-block-viewer and the wallet / explorer clients — no RPC server in the read path. It layers on top of any backing; it does not replace it.
One writer per bucket:
| Bucket | Written by | Holds |
|---|---|---|
| finalized | finalizer (when publishOnFinalize is set) | finalized blocks, payloads, manifest.json |
| chainState | finalizer (when publishOnFinalize is set) | the mutable head pointer (head.json) |
| index | indexer | step-summary families plus optional address-scoped finalized EVM event frames and their manifests/head pointers |
The indexer is gated at the finalized head the finalizer has published, so publishOnFinalize must be enabled on the finalizer for the index to advance.
Configure the buckets as named connections (s3-finalized, s3-chain-state, s3-index). Credentials and prefix can be set per connection; each bucket sets its own bucket name and public readUrl — the CDN base URL anonymous reads go through, so they hit the CDN rather than the (per-operation billed) S3 API.
The chainState bucket also carries the chain-contract manifest (minWithdrawalBlocks, rewardsContract, stakingTokenAddress), read from ChainContractViewer when the publisher starts — so a chain-state-publishing finalizer must bind ChainContractViewer to an evm-rpc connection (config compilation rejects the plan otherwise; an explicit memory binding opts into dev placeholder values instead).
xyo.json:
{
"xl1": {
"connections": {
"chain-mongo": { "type": "mongo", "connectionString": "mongodb://user:[email protected]:27017", "database": "xl1", "domain": "internal" },
"default-evm-rpc": { "type": "evm-rpc", "url": "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID", "chainId": "0xaa36a7" },
"s3-finalized": { "type": "s3", "accountId": "…", "accessKeyId": "…", "secretAccessKey": "…", "prefix": "", "bucket": "xl1-chain", "readUrl": "https://chain.xl1.example" },
"s3-chain-state": { "type": "s3", "bucket": "xl1-chain-state", "readUrl": "https://chain-state.xl1.example" },
"s3-index": { "type": "s3", "bucket": "xl1-index", "readUrl": "https://index.xl1.example" }
},
"providerBindings": {
"BlockViewer": { "connection": "chain-mongo" },
"BlockPublishRunner": { "connection": "s3-finalized" },
"ChainContractViewer": { "connection": "default-evm-rpc" },
"ChainStatePublishRunner": { "connection": "s3-chain-state" },
"ChainStateViewer": { "connection": "s3-chain-state" },
"EvmChainViewer": { "connection": "default-evm-rpc" },
"EvmEventIndexPublishRunner": { "connection": "s3-index" },
"FinalizationRunner": { "connection": "chain-mongo" },
"FinalizationViewer": { "connection": "chain-mongo" },
"IndexPublishRunner": { "connection": "s3-index" },
"MempoolViewer": { "connection": "chain-mongo" }
},
"actors": [
{ "name": "producer", "heartbeatInterval": 1000 },
{ "name": "finalizer", "publishOnFinalize": true, "publishContentEncoding": "br" },
{ "name": "indexer", "publishSyncInterval": 5000, "publishContentEncoding": "br" }
]
}
}Per-bucket credentials override the shared defaults — e.g. give s3-chain-state its own accessKeyId / secretAccessKey on that connection entry.
Actor settings that drive publishing:
| Setting | Actor(s) | Default | Meaning |
|---|---|---|---|
| publishOnFinalize | finalizer | false | Push newly finalized blocks (head pointer last) to the finalized + chainState buckets immediately after each finalization pass. |
| publishSyncInterval | indexer | 5000 | Milliseconds between index sync passes; each pass builds every family's frames up to the published finalized head, then writes the index manifest + watermark. |
| publishContentEncoding | finalizer, indexer | unset | Wire/storage encoding for published files: br, gzip, or none. |
| publishProgressInterval | finalizer, indexer | 100 | Publishes between progress log lines during a sync pass (runner default when unset). |
Environment-variable form — __ nests, and each screaming-snake segment maps to camelCase (CHAIN_STATE → chainState, READ_URL → readUrl):
export XL1_CONNECTIONS__S3_FINALIZED__TYPE=s3
export XL1_CONNECTIONS__S3_FINALIZED__ACCOUNT_ID=…
export XL1_CONNECTIONS__S3_FINALIZED__ACCESS_KEY_ID=…
export XL1_CONNECTIONS__S3_FINALIZED__SECRET_ACCESS_KEY=…
export XL1_CONNECTIONS__S3_FINALIZED__BUCKET=xl1-chain
export XL1_CONNECTIONS__S3_FINALIZED__READ_URL=https://chain.xl1.example
export XL1_CONNECTIONS__S3_CHAIN_STATE__TYPE=s3
export XL1_CONNECTIONS__S3_CHAIN_STATE__BUCKET=xl1-chain-state
export XL1_CONNECTIONS__S3_CHAIN_STATE__READ_URL=https://chain-state.xl1.example
export XL1_CONNECTIONS__S3_INDEX__TYPE=s3
export XL1_CONNECTIONS__S3_INDEX__BUCKET=xl1-index
export XL1_CONNECTIONS__S3_INDEX__READ_URL=https://index.xl1.example
# actor publish settings are array entries, e.g. if finalizer/indexer are actors 1 and 2:
export XL1_ACTORS__1__PUBLISH_ON_FINALIZE=true
export XL1_ACTORS__2__PUBLISH_SYNC_INTERVAL=5000Naming reference (bucket roles vs frozen legacy names)
The naming convention across XL1 code and config: "S3" means the S3-compatible object-storage protocol (Cloudflare R2 is one implementation — "R2" only appears in Cloudflare-specific helpers and docs), and each bucket is identified by its role: finalized, chain-state, index (plus datalake where used). Casing follows context — camelCase in TypeScript identifiers and config record keys (chainState), kebab-case in connection names, env-var segments, and physical names (s3-chain-state).
| Bucket role | Connection name | Env prefix | Typical bucket (frozen) | Endpoint subdomain (frozen) | Wire schemas served (frozen) |
|---|---|---|---|---|---|
| finalized | s3-finalized | XL1_CONNECTIONS__S3_FINALIZED__* | xl1-chain | blocks.<domain> | network.xyo.s3.chain.manifest |
| chain-state | s3-chain-state | XL1_CONNECTIONS__S3_CHAIN_STATE__* | xl1-chain-state | state.<domain> | network.xyo.s3.chain.contract |
| index | s3-index | XL1_CONNECTIONS__S3_INDEX__* | xl1-index | indexes.<domain> | network.xyo.s3.index.manifest, network.xyo.s3.index.head |
The columns marked frozen are deployed infrastructure or wire format that predates the role naming — they intentionally do not match the role names and must not be "fixed":
- The physical bucket
xl1-chainserves thefinalizedrole (it predates the role name; live buckets are a migration, not a rename). - The
blocks./state./indexes.endpoint subdomains (derived by the SDK'srestGatewayConfigFromEndpoint) are deployed DNS; thefinalizedrole maps to theblocks.subdomain. - The static-layout document schemas are namespaced
network.xyo.s3.*even though the documents are transport-neutral; renaming them would be a breaking wire change. New static-layout schemas should use a transport-neutral namespace instead.
Mnemonics & wallets
The root mnemonic field (or XL1_MNEMONIC) defines the wallet from which every actor derives an account at accountPath. Per-actor mnemonic fields are rejected — use accountPath to give each actor a distinct derivation.
{
"xl1": {
"mnemonic": "edit hill neutral usage share kite knee abandon slim open lottery abandon",
"actors": [
{ "name": "producer", "accountPath": "0" },
{ "name": "finalizer", "accountPath": "1" }
]
}
}If you omit mnemonic entirely, the CLI falls back to a built-in dev mnemonic and pauses for an interactive RETURN before starting. Pass --skip-insecure-confirm to bypass the prompt in non-interactive deployments — but never deploy production traffic against the dev mnemonic.
Inspecting a configuration
Two CLI flags help you verify what the chain would do without actually starting it:
xl1 --dump-config start <actors>— prints the fully resolved config (file + env + CLI flags merged, defaults applied) to stdout, then exits. Secret-bearing fields are redacted by default; pass--with-secretson a developer box to see raw values.xl1 --dump-providers start <actors>— runs the locator construction up to the point where actor runners would start, then prints a per-actor tree of every registered provider (implementation class, scope, dependencies). Cross-actor duplicates are flagged with⚠ also in: .... Use this to confirm that, e.g., your federated API actually picked the JsonRpc tier-3 viewers.
xl1 -c ./xyo.json --dump-providers start api producerLicense
See the LICENSE file for license rights and limitations (LGPL-3.0-only).

