@xyo-network/xl1-cli 3.0.0 → 3.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -76,50 +76,51 @@ Starts only the Producer Node, which is responsible for block production.
76
76
 
77
77
  Use `xl1 --help` for available options and configuration flags.
78
78
 
79
- ### Configuration
79
+ ## Configuration
80
80
 
81
- Certainly! Here’s a brief and well-organized section describing the three ways to configure your xl1 CLI tool:
81
+ You can configure `xl1` using CLI flags, environment variables, or a configuration file. CLI flags override environment variables, which override config file values.
82
82
 
83
-
84
-
85
- Configuration
86
-
87
- You can configure xl1 using CLI flags, environment variables, or a configuration file. All methods are optional and can be combined—CLI flags override environment variables, which override config file values.
88
-
89
- #### CLI Flags
90
-
91
- Pass options directly when running a command. For example:
83
+ ### CLI flags
92
84
 
93
85
  ```sh
94
- xl1 --logLevel=info --api.port=8080
86
+ xl1 --log.logLevel=info start api
95
87
  ```
96
88
 
97
- #### Environment Variables
89
+ ### Environment variables
98
90
 
99
- Set configuration via environment variables before running the command:
91
+ `XL1_*` variables map to the config shape with double-underscore nesting (`XL1_CONNECTIONS__LOCAL_STORE__ROOT`, etc.; see `.env.example`).
100
92
 
101
93
  ```sh
102
- export XL1_LOG_LEVEL=info
103
- export XL1_API__PORT=8080
104
- xl1
94
+ export XL1_LOG__LOG_LEVEL=info
95
+ export XL1_ACTORS__0__NAME=api
96
+ export XL1_ACTORS__0__PORT=8080
97
+ xl1 start api
105
98
  ```
106
99
 
107
- #### Config File
100
+ ### Config file
108
101
 
109
- Create a `xyo.config.json` (or .js, .yaml) file in the working directory to define default settings:
102
+ 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.
110
103
 
111
104
  ```json
112
105
  {
113
- "logLevel": "info",
114
- "api": {
115
- "port": 8080
106
+ "xl1": {
107
+ "connections": {
108
+ "local-store": { "type": "lmdb", "root": ".store" }
109
+ },
110
+ "actors": [
111
+ { "name": "api", "host": "localhost", "port": 8080 },
112
+ { "name": "producer" },
113
+ { "name": "finalizer" }
114
+ ]
116
115
  }
117
116
  }
118
117
  ```
119
118
 
120
- ## Deployment configurations
119
+ 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`.
121
120
 
122
- `xl1` actors can be assembled into many physical layouts: one process running everything, one process per actor, or grouped processes sharing one config. This section covers the actors, the dimensions you choose between, and a copy-paste recipe for each supported topology.
121
+ ## Deployment guide
122
+
123
+ `xl1` actors can be assembled into many physical layouts: one process running everything, one process per actor, or grouped processes sharing one config.
123
124
 
124
125
  ### Actors
125
126
 
@@ -137,125 +138,458 @@ Create a `xyo.config.json` (or .js, .yaml) file in the working directory to defi
137
138
 
138
139
  ### Dimensions
139
140
 
140
- You make four independent choices when deploying:
141
-
142
141
  1. **Process layout** — mono (everything in one process), split (one actor per process), or grouped (custom).
143
142
  2. **Storage backing** — `memory` (single-process only, ephemeral), `lmdb` (single-host, persistent, multi-process safe via shared root), or `mongodb` (multi-host, persistent, multi-process safe).
144
143
  3. **Provider tier** — `simple` (in-process tier 1, requires the corresponding backing), or `jsonrpc` (tier 3 proxy to another `xl1` process). The CLI picks `jsonrpc` automatically when a global `default-rpc` connection is configured; otherwise it picks `simple` when the backing is available.
145
144
  4. **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.
146
145
 
147
- ### Recipes
146
+ ### Launch patterns
148
147
 
149
- Every recipe shows a JSON config file and the command(s) to launch it. Configs use `xyo.json` (cosmiconfig also accepts `xyo.yaml`, `xyo.toml`, `xyo.js`, etc.); the CLI auto-discovers a `xyo.*` file in the current directory or accepts an explicit `-c <path>`.
148
+ Copy any example from [Development examples](#development-examples) or [Per-actor production deployment](#per-actor-production-deployment) into `xyo.json`, then:
150
149
 
151
- #### Single process, in-memory (dev)
150
+ ```sh
151
+ # Single process
152
+ xl1 -c ./xyo.json start api producer finalizer mempool
152
153
 
153
- Fastest path for local development. Everything runs in one node process; state is lost when the process exits.
154
+ # Multi-process (one OS process per actor, same config file)
155
+ xl1 -c ./xyo.json start api
156
+ xl1 -c ./xyo.json start producer
157
+ xl1 -c ./xyo.json start finalizer
158
+ xl1 -c ./xyo.json start mempool
159
+
160
+ # One config file per process
161
+ xl1 -c ./xyo.api.json start api
162
+ xl1 -c ./xyo.producer.json start producer
163
+ ```
164
+
165
+ ```sh
166
+ # Inspect resolved config (secrets redacted)
167
+ xl1 --dump-config -c ./xyo.json start api producer finalizer
168
+
169
+ # Inspect provider wiring without starting actors
170
+ xl1 --dump-providers -c ./xyo.json start api producer finalizer
171
+
172
+ # Non-interactive dev boot (skip insecure-mnemonic prompt)
173
+ xl1 --skip-insecure-confirm -c ./xyo.json start
174
+ ```
175
+
176
+ ## Connections model
177
+
178
+ Named **connections** describe where providers read/write state:
179
+
180
+ | Connection | Type | Purpose |
181
+ |------------|------|---------|
182
+ | `local-store` | `lmdb` | On-disk LMDB chain authority store |
183
+ | `chain-mongo` | `mongo` | MongoDB chain authority store |
184
+ | `dlq-store` | `lmdb` / `mongo` | Dead-letter queue backing (dev: aliases to chain store when omitted) |
185
+ | `mempool-store` | `lmdb` / `mongo` | Mempool backing (dev: aliases to chain store when omitted) |
186
+ | `indexer-store` | `lmdb` / `mongo` | Indexer-local store when split from chain authority |
187
+ | `default-rpc` | `rpc` | HTTP JSON-RPC to another XL1 API |
188
+ | `default-evm-rpc` | `evm-rpc` | EVM JSON-RPC for staking/contracts |
189
+ | `s3-finalized` / `s3-chain-state` / `s3-index` | `s3` | Object storage publish targets |
190
+ | `rest-finalized` / `rest-chain-state` / `rest-index` | `rest` | CDN / HTTP read URLs for published static files |
191
+ | `memory` | `memory` | In-process stubs (injected at boot) |
192
+
193
+ **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).
194
+
195
+ **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](#finalizer-per-actor) + [Indexer](#indexer-per-actor)) or override `providerBindings` explicitly as in [S3 write + REST read](#s3-write--rest-read).
196
+
197
+ ## Development examples
198
+
199
+ ### In-memory dev
200
+
201
+ Single process, ephemeral in-memory chain (fastest local dev). Omits `connections` — the boot preset injects an in-memory store.
154
202
 
155
- `xyo.json`:
156
203
  ```json
157
204
  {
158
205
  "xl1": {
159
206
  "actors": [
160
- { "name": "api", "host": "localhost", "port": 8080 },
161
- { "name": "mempool" },
162
- { "name": "finalizer" }
207
+ {
208
+ "name": "api",
209
+ "host": "localhost",
210
+ "port": 8080
211
+ },
212
+ {
213
+ "name": "producer",
214
+ "blockProductionCheckInterval": 100,
215
+ "heartbeatInterval": 100
216
+ },
217
+ {
218
+ "name": "finalizer",
219
+ "finalizationCheckInterval": 50,
220
+ "minCandidates": 1
221
+ }
163
222
  ]
164
223
  }
165
224
  }
166
225
  ```
167
226
 
168
- ```sh
169
- xl1 start api mempool finalizer
227
+ ### LMDB local
228
+
229
+ Single process with on-disk LMDB at `.store`.
230
+
231
+ ```json
232
+ {
233
+ "xl1": {
234
+ "connections": {
235
+ "local-store": {
236
+ "type": "lmdb",
237
+ "root": ".store"
238
+ }
239
+ },
240
+ "actors": [
241
+ {
242
+ "name": "api",
243
+ "host": "localhost",
244
+ "port": 8080
245
+ },
246
+ {
247
+ "name": "producer",
248
+ "heartbeatInterval": 1000
249
+ },
250
+ {
251
+ "name": "finalizer"
252
+ },
253
+ {
254
+ "name": "mempool"
255
+ }
256
+ ]
257
+ }
258
+ }
170
259
  ```
171
260
 
172
- The `api + mempool + finalizer` subset is the canary combo: the CLI builds a single shared provider registry for the three actors so each provider is instantiated exactly once, with no cross-actor duplication.
261
+ ### LMDB + EVM JSON-RPC
262
+
263
+ LMDB plus contract-backed chain via `default-evm-rpc` (local Hardhat/Ganache).
264
+
265
+ ```json
266
+ {
267
+ "xl1": {
268
+ "connections": {
269
+ "local-store": {
270
+ "type": "lmdb",
271
+ "root": ".store"
272
+ },
273
+ "default-evm-rpc": {
274
+ "type": "evm-rpc",
275
+ "url": "http://127.0.0.1:8545",
276
+ "chainId": "0xaa36a7"
277
+ }
278
+ },
279
+ "chain": {
280
+ "id": "dd381fbb392c85160d8b0453e446757b12384046"
281
+ },
282
+ "actors": [
283
+ {
284
+ "name": "api",
285
+ "host": "localhost",
286
+ "port": 8080
287
+ },
288
+ {
289
+ "name": "producer",
290
+ "heartbeatInterval": 1000
291
+ },
292
+ {
293
+ "name": "finalizer"
294
+ },
295
+ {
296
+ "name": "mempool"
297
+ }
298
+ ]
299
+ }
300
+ }
301
+ ```
173
302
 
174
- #### Single process, LMDB (single-host persistent)
303
+ ### LMDB + EVM Infura
175
304
 
176
- Persistent state on one machine. LMDB writes are multi-process safe via the LMDB lockfile, so you can later split actors across processes against the same `local-store` connection.
305
+ LMDB plus Sepolia EVM RPC URL.
177
306
 
178
- `xyo.json`:
179
307
  ```json
180
308
  {
181
309
  "xl1": {
182
- "connections": { "local-store": { "type": "lmdb", "root": "/var/xl1/data" } },
310
+ "connections": {
311
+ "local-store": {
312
+ "type": "lmdb",
313
+ "root": ".store"
314
+ },
315
+ "default-evm-rpc": {
316
+ "type": "evm-rpc",
317
+ "url": "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID",
318
+ "chainId": "0xaa36a7"
319
+ }
320
+ },
321
+ "chain": {
322
+ "id": "dd381fbb392c85160d8b0453e446757b12384046"
323
+ },
183
324
  "actors": [
184
- { "name": "api", "host": "localhost", "port": 8080 },
185
- { "name": "producer", "heartbeatInterval": 1000 },
186
- { "name": "finalizer" },
187
- { "name": "mempool" }
325
+ {
326
+ "name": "api",
327
+ "host": "localhost",
328
+ "port": 8080
329
+ },
330
+ {
331
+ "name": "producer",
332
+ "heartbeatInterval": 1000
333
+ },
334
+ {
335
+ "name": "finalizer"
336
+ }
188
337
  ]
189
338
  }
190
339
  }
191
340
  ```
192
341
 
193
- ```sh
194
- xl1 start api producer finalizer mempool
342
+ ### Explicit connections
343
+
344
+ Explicit `connections` + `providerBindings` overrides.
345
+
346
+ ```json
347
+ {
348
+ "xl1": {
349
+ "connections": {
350
+ "local-store": {
351
+ "type": "lmdb",
352
+ "root": ".store"
353
+ },
354
+ "default-evm-rpc": {
355
+ "type": "evm-rpc",
356
+ "url": "http://127.0.0.1:8545",
357
+ "chainId": "0xaa36a7"
358
+ }
359
+ },
360
+ "providerBindings": {
361
+ "BlockViewer": {
362
+ "connection": "local-store"
363
+ },
364
+ "MempoolRunner": {
365
+ "connection": "local-store"
366
+ },
367
+ "ChainContractViewer": {
368
+ "connection": "default-evm-rpc"
369
+ },
370
+ "StakeViewer": {
371
+ "connection": "default-evm-rpc"
372
+ }
373
+ },
374
+ "chain": {
375
+ "id": "dd381fbb392c85160d8b0453e446757b12384046"
376
+ },
377
+ "actors": [
378
+ {
379
+ "name": "api",
380
+ "host": "localhost",
381
+ "port": 8080
382
+ },
383
+ {
384
+ "name": "producer",
385
+ "heartbeatInterval": 1000
386
+ },
387
+ {
388
+ "name": "finalizer"
389
+ }
390
+ ]
391
+ }
392
+ }
195
393
  ```
196
394
 
197
- #### Single process, MongoDB (single-host with shared DB)
395
+ ### Stateless API
198
396
 
199
- Same as the LMDB variant but with a MongoDB-backed archivist. Use this when state needs to outlive any one host or be shared with off-chain tooling that already speaks Mongo.
397
+ Read-only API proxying a remote chain RPC.
398
+
399
+ ```json
400
+ {
401
+ "xl1": {
402
+ "connections": {
403
+ "default-rpc": {
404
+ "type": "rpc",
405
+ "protocol": "https",
406
+ "url": "https://chain.example.com/rpc"
407
+ }
408
+ },
409
+ "actors": [
410
+ {
411
+ "name": "api",
412
+ "host": "0.0.0.0",
413
+ "port": 8080,
414
+ "stateless": true
415
+ }
416
+ ]
417
+ }
418
+ }
419
+ ```
420
+
421
+ ### MongoDB
422
+
423
+ MongoDB-backed archivist via `chain-mongo`.
200
424
 
201
- `xyo.json`:
202
425
  ```json
203
426
  {
204
427
  "xl1": {
205
428
  "connections": {
206
429
  "chain-mongo": {
207
430
  "type": "mongo",
208
- "connectionString": "mongodb://user:pass@db.internal:27017",
209
- "database": "xl1",
210
- "domain": "internal"
431
+ "connectionString": "mongodb://root:example@localhost:27017/?retryWrites=true&w=majority",
432
+ "database": "chain",
433
+ "domain": "localhost",
434
+ "username": "root",
435
+ "password": "example"
211
436
  }
212
437
  },
213
438
  "actors": [
214
- { "name": "api", "host": "localhost", "port": 8080 },
215
- { "name": "producer", "heartbeatInterval": 1000 },
216
- { "name": "finalizer" },
217
- { "name": "mempool" }
439
+ {
440
+ "name": "api",
441
+ "host": "localhost",
442
+ "port": 8080
443
+ },
444
+ {
445
+ "name": "producer",
446
+ "heartbeatInterval": 1000
447
+ },
448
+ {
449
+ "name": "finalizer"
450
+ },
451
+ {
452
+ "name": "mempool"
453
+ }
218
454
  ]
219
455
  }
220
456
  }
221
457
  ```
222
458
 
223
- ```sh
224
- xl1 start api producer finalizer mempool
459
+ ### Multi-process LMDB
460
+
461
+ Shared LMDB; run one process per `xl1 start <actor>`.
462
+
463
+ ```json
464
+ {
465
+ "xl1": {
466
+ "connections": {
467
+ "local-store": {
468
+ "type": "lmdb",
469
+ "root": ".store"
470
+ }
471
+ },
472
+ "actors": [
473
+ {
474
+ "name": "api",
475
+ "host": "localhost",
476
+ "port": 8080
477
+ },
478
+ {
479
+ "name": "producer",
480
+ "heartbeatInterval": 1000
481
+ },
482
+ {
483
+ "name": "finalizer"
484
+ },
485
+ {
486
+ "name": "mempool",
487
+ "host": "localhost",
488
+ "port": 8081
489
+ }
490
+ ]
491
+ }
492
+ }
225
493
  ```
226
494
 
227
- #### Multi-process, shared LMDB (one host, role-isolated)
495
+ ### Mempool remote API
228
496
 
229
- Each actor runs as its own OS process. Useful when you want to restart one role independently or pin different actors to different cores. Every process loads the same config file and uses `xl1 start <name>` to activate just its slice.
497
+ Standalone mempool over JsonRpc to a remote API.
230
498
 
231
- `xyo.json` (single file, all processes load it):
232
499
  ```json
233
500
  {
234
501
  "xl1": {
235
- "connections": { "local-store": { "type": "lmdb", "root": "/var/xl1/data" } },
502
+ "connections": {
503
+ "default-rpc": {
504
+ "type": "rpc",
505
+ "protocol": "http",
506
+ "url": "http://localhost:8080/rpc"
507
+ }
508
+ },
509
+ "providerBindings": {
510
+ "MempoolRunner": {
511
+ "connection": "default-rpc"
512
+ },
513
+ "MempoolViewer": {
514
+ "connection": "default-rpc"
515
+ },
516
+ "BlockViewer": {
517
+ "connection": "default-rpc"
518
+ },
519
+ "WindowedBlockViewer": {
520
+ "connection": "default-rpc"
521
+ },
522
+ "TransactionValidationViewer": {
523
+ "connection": "default-rpc"
524
+ }
525
+ },
236
526
  "actors": [
237
- { "name": "api", "host": "localhost", "port": 8080 },
238
- { "name": "producer", "heartbeatInterval": 1000 },
239
- { "name": "finalizer" },
240
- { "name": "mempool", "host": "localhost", "port": 8081 }
527
+ {
528
+ "name": "mempool",
529
+ "host": "localhost",
530
+ "port": 8081
531
+ }
241
532
  ]
242
533
  }
243
534
  }
244
535
  ```
245
536
 
246
- ```sh
247
- # Each line is a separate process (e.g., one systemd unit per actor)
248
- xl1 start api
249
- xl1 start producer
250
- xl1 start finalizer
251
- xl1 start mempool
537
+ ### Mixed local + remote
538
+
539
+ Local LMDB runners + JsonRpc viewers.
540
+
541
+ ```json
542
+ {
543
+ "xl1": {
544
+ "connections": {
545
+ "local-store": {
546
+ "type": "lmdb",
547
+ "root": ".store"
548
+ },
549
+ "default-rpc": {
550
+ "type": "rpc",
551
+ "protocol": "http",
552
+ "url": "http://localhost:8080/rpc"
553
+ }
554
+ },
555
+ "providerBindings": {
556
+ "MempoolRunner": {
557
+ "connection": "local-store"
558
+ },
559
+ "BlockViewer": {
560
+ "connection": "default-rpc"
561
+ },
562
+ "MempoolViewer": {
563
+ "connection": "default-rpc"
564
+ },
565
+ "FinalizationViewer": {
566
+ "connection": "default-rpc"
567
+ },
568
+ "TransactionViewer": {
569
+ "connection": "default-rpc"
570
+ },
571
+ "AccountBalanceViewer": {
572
+ "connection": "default-rpc"
573
+ }
574
+ },
575
+ "actors": [
576
+ {
577
+ "name": "api",
578
+ "host": "localhost",
579
+ "port": 8080
580
+ },
581
+ {
582
+ "name": "mempool"
583
+ }
584
+ ]
585
+ }
586
+ }
252
587
  ```
253
588
 
254
- #### Multi-process, shared MongoDB (multi-host, durable)
589
+ ### Finalizer + indexer S3
255
590
 
256
- Same as above but with MongoDB as the shared store. Now each process can run on a different host, since MongoDB takes care of cross-host coordination.
591
+ Mongo store plus S3 publish connections for finalizer and indexer in one process.
257
592
 
258
- `xyo.json`:
259
593
  ```json
260
594
  {
261
595
  "xl1": {
@@ -265,87 +599,539 @@ Same as above but with MongoDB as the shared store. Now each process can run on
265
599
  "connectionString": "mongodb://user:pass@db.internal:27017",
266
600
  "database": "xl1",
267
601
  "domain": "internal"
602
+ },
603
+ "s3-finalized": {
604
+ "type": "s3",
605
+ "accountId": "YOUR_R2_ACCOUNT_ID",
606
+ "accessKeyId": "YOUR_ACCESS_KEY_ID",
607
+ "secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
608
+ "prefix": "xl1/",
609
+ "bucket": "xl1-chain",
610
+ "readUrl": "https://chain.xl1.example"
611
+ },
612
+ "s3-chain-state": {
613
+ "type": "s3",
614
+ "accountId": "YOUR_R2_ACCOUNT_ID",
615
+ "accessKeyId": "YOUR_ACCESS_KEY_ID",
616
+ "secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
617
+ "prefix": "xl1/",
618
+ "bucket": "xl1-chain-state",
619
+ "readUrl": "https://chain-state.xl1.example"
620
+ },
621
+ "s3-index": {
622
+ "type": "s3",
623
+ "accountId": "YOUR_R2_ACCOUNT_ID",
624
+ "accessKeyId": "YOUR_ACCESS_KEY_ID",
625
+ "secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
626
+ "prefix": "xl1/",
627
+ "bucket": "xl1-index",
628
+ "readUrl": "https://index.xl1.example"
268
629
  }
269
630
  },
270
631
  "actors": [
271
- { "name": "api", "host": "0.0.0.0", "port": 8080 },
272
- { "name": "producer", "heartbeatInterval": 1000 },
273
- { "name": "finalizer" },
274
- { "name": "mempool", "host": "0.0.0.0", "port": 8081 }
632
+ {
633
+ "name": "producer",
634
+ "heartbeatInterval": 1000
635
+ },
636
+ {
637
+ "name": "finalizer",
638
+ "publishOnFinalize": true,
639
+ "publishContentEncoding": "br",
640
+ "publishProgressInterval": 100
641
+ },
642
+ {
643
+ "name": "indexer",
644
+ "publishSyncInterval": 5000,
645
+ "publishContentEncoding": "br",
646
+ "publishProgressInterval": 100
647
+ }
275
648
  ]
276
649
  }
277
650
  }
278
651
  ```
279
652
 
280
- ```sh
281
- # On host A
282
- xl1 start api
653
+ ### S3 write + REST read
283
654
 
284
- # On host B
285
- xl1 start producer
655
+ Indexer: `s3-index` publish + REST CDN viewers.
286
656
 
287
- # On host C
288
- xl1 start finalizer mempool
657
+ ```json
658
+ {
659
+ "xl1": {
660
+ "connections": {
661
+ "s3-index": {
662
+ "type": "s3",
663
+ "accountId": "YOUR_R2_ACCOUNT_ID",
664
+ "accessKeyId": "YOUR_ACCESS_KEY_ID",
665
+ "secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
666
+ "prefix": "xl1/",
667
+ "bucket": "xl1-index",
668
+ "readUrl": "https://index.xl1.example"
669
+ },
670
+ "rest-finalized": {
671
+ "type": "rest",
672
+ "baseUrl": "https://chain.xl1.example"
673
+ },
674
+ "rest-chain-state": {
675
+ "type": "rest",
676
+ "baseUrl": "https://chain-state.xl1.example"
677
+ },
678
+ "rest-index": {
679
+ "type": "rest",
680
+ "baseUrl": "https://index.xl1.example"
681
+ }
682
+ },
683
+ "providerBindings": {
684
+ "BlockViewer": {
685
+ "connection": "rest-finalized"
686
+ },
687
+ "ChainStateViewer": {
688
+ "connection": "rest-chain-state"
689
+ },
690
+ "IndexViewer": {
691
+ "connection": "rest-index"
692
+ },
693
+ "IndexPublishRunner": {
694
+ "connection": "s3-index"
695
+ }
696
+ },
697
+ "actors": [
698
+ {
699
+ "name": "indexer",
700
+ "publishSyncInterval": 5000,
701
+ "publishContentEncoding": "br",
702
+ "publishProgressInterval": 100
703
+ }
704
+ ]
705
+ }
706
+ }
289
707
  ```
290
708
 
291
- #### Stateless API client (read-only proxy to a remote chain)
709
+ ## Per-actor production deployment
292
710
 
293
- The API actor in `stateless: true` mode owns no local store. All viewer providers resolve to JsonRpc proxies pointed at the `default-rpc` connection. Run several stateless API instances behind a load balancer to scale read traffic without duplicating state.
711
+ 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/R2 object storage 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.
712
+
713
+ | Actor | Backing connections | Writes to S3 | Reads |
714
+ |-------|---------------------|--------------|-------|
715
+ | `api` | `chain-mongo` (mempool + tx submission) | — | blocks / state / index over REST |
716
+ | `producer` | `chain-mongo` authority + `default-evm-rpc` + `producer-wallet` | — | authority store + EVM staking contract |
717
+ | `finalizer` | `chain-mongo` authority | `s3-finalized` (blocks), `s3-chain-state` (head) | authority store |
718
+ | `indexer` | none (reads over REST) | `s3-index` | finalized blocks + chain-state over REST |
719
+ | `mempool` | `chain-mongo` | — | authority store |
720
+ | `bridge` | `chain-mongo` + `default-evm-rpc` + `bridge-wallet` + Redis | — | authority store + remote EVM chain |
721
+ | `rewardRedemption` | `chain-mongo` + `default-evm-rpc` + `rewardRedemption-wallet` | — | authority store + EVM staking contract |
722
+
723
+ - **One writer per bucket.** The finalizer owns `s3-finalized` + `s3-chain-state`; the indexer owns `s3-index`. No other actor writes object storage.
724
+ - **`BlockViewer` is not auto-bound to REST.** When a process declares a local store, `BlockViewer` defaults to it, so the API and indexer configs bind `BlockViewer → rest-finalized` explicitly to read finalized blocks from the CDN. `ChainStateViewer` / `IndexViewer` bind to their REST connections automatically when those connections are declared (the explicit bindings here are for clarity).
725
+ - **Staking.** `chain.id` (the staking contract address on the backing EVM) plus `evm.chainId` and a `default-evm-rpc` connection enable the EVM-backed stake/contract viewers. Actors that only read published data (`api`, `indexer`) omit them; the stake viewers fall back to their chain-state-derived implementations.
726
+ - **Wallets.** Wallet connections (`<actor>-wallet`) and the `XyoSigner` binding are auto-synthesized for signing actors (`producer`, `bridge`, `rewardRedemption`); they are shown explicitly here for clarity. `bridge` and `rewardRedemption` cannot be co-located in one process (they share `XyoSigner`).
727
+ - **Secrets.** `YOUR_*` placeholders (R2 keys, Mongo credentials, Infura project id) and wallet mnemonics should be supplied via `XL1_*` env vars / your secret store in production — do not commit real credentials.
728
+
729
+ ### API (per-actor)
294
730
 
295
- `xyo.json`:
296
731
  ```json
297
732
  {
298
733
  "xl1": {
299
734
  "connections": {
300
- "default-rpc": { "type": "rpc", "protocol": "https", "url": "https://chain.example.com/rpc" }
735
+ "chain-mongo": {
736
+ "type": "mongo",
737
+ "connectionString": "mongodb://YOUR_MONGO_USER:YOUR_MONGO_PASSWORD@db.internal:27017/?retryWrites=true&w=majority",
738
+ "database": "xl1",
739
+ "domain": "internal"
740
+ },
741
+ "rest-finalized": {
742
+ "type": "rest",
743
+ "baseUrl": "https://chain.xl1.example"
744
+ },
745
+ "rest-chain-state": {
746
+ "type": "rest",
747
+ "baseUrl": "https://chain-state.xl1.example"
748
+ },
749
+ "rest-index": {
750
+ "type": "rest",
751
+ "baseUrl": "https://index.xl1.example"
752
+ }
753
+ },
754
+ "providerBindings": {
755
+ "AccountBalanceViewer": { "connection": "chain-mongo" },
756
+ "BlockRunner": { "connection": "chain-mongo" },
757
+ "BlockValidationViewer": { "connection": "chain-mongo" },
758
+ "BlockViewer": { "connection": "rest-finalized" },
759
+ "ChainStateViewer": { "connection": "rest-chain-state" },
760
+ "DeadLetterQueueRunner": { "connection": "chain-mongo" },
761
+ "DeadLetterQueueViewer": { "connection": "chain-mongo" },
762
+ "FinalizationRunner": { "connection": "chain-mongo" },
763
+ "FinalizationViewer": { "connection": "chain-mongo" },
764
+ "IndexViewer": { "connection": "rest-index" },
765
+ "MempoolRunner": { "connection": "chain-mongo" },
766
+ "MempoolViewer": { "connection": "chain-mongo" },
767
+ "TransactionValidationViewer": { "connection": "chain-mongo" },
768
+ "TransactionViewer": { "connection": "chain-mongo" },
769
+ "WindowedBlockViewer": { "connection": "chain-mongo" },
770
+ "XyoConnection": { "connection": "chain-mongo" },
771
+ "XyoGateway": { "connection": "chain-mongo" },
772
+ "XyoGatewayRunner": { "connection": "chain-mongo" },
773
+ "XyoRunner": { "connection": "chain-mongo" },
774
+ "XyoViewer": { "connection": "chain-mongo" }
301
775
  },
302
776
  "actors": [
303
- { "name": "api", "host": "0.0.0.0", "port": 8080, "stateless": true }
777
+ {
778
+ "name": "api",
779
+ "host": "0.0.0.0",
780
+ "port": 8080
781
+ }
304
782
  ]
305
783
  }
306
784
  }
307
785
  ```
308
786
 
309
- ```sh
310
- xl1 start api
311
- ```
787
+ ### Producer (per-actor)
312
788
 
313
- #### Pure environment variables (no config file)
789
+ ```json
790
+ {
791
+ "xl1": {
792
+ "connections": {
793
+ "chain-mongo": {
794
+ "type": "mongo",
795
+ "connectionString": "mongodb://YOUR_MONGO_USER:YOUR_MONGO_PASSWORD@db.internal:27017/?retryWrites=true&w=majority",
796
+ "database": "xl1",
797
+ "domain": "internal"
798
+ },
799
+ "default-evm-rpc": {
800
+ "type": "evm-rpc",
801
+ "url": "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID",
802
+ "chainId": "0xaa36a7"
803
+ },
804
+ "producer-wallet": {
805
+ "type": "wallet",
806
+ "accountPath": "0"
807
+ }
808
+ },
809
+ "chain": {
810
+ "id": "dd381fbb392c85160d8b0453e446757b12384046"
811
+ },
812
+ "providerBindings": {
813
+ "AccountBalanceViewer": { "connection": "chain-mongo" },
814
+ "BlockRunner": { "connection": "chain-mongo" },
815
+ "BlockValidationViewer": { "connection": "chain-mongo" },
816
+ "BlockViewer": { "connection": "chain-mongo" },
817
+ "ChainContractViewer": { "connection": "default-evm-rpc" },
818
+ "DeadLetterQueueRunner": { "connection": "chain-mongo" },
819
+ "DeadLetterQueueViewer": { "connection": "chain-mongo" },
820
+ "FinalizationRunner": { "connection": "chain-mongo" },
821
+ "FinalizationViewer": { "connection": "chain-mongo" },
822
+ "MempoolRunner": { "connection": "chain-mongo" },
823
+ "MempoolViewer": { "connection": "chain-mongo" },
824
+ "StakeEventsViewer": { "connection": "default-evm-rpc" },
825
+ "StakeTotalsViewer": { "connection": "default-evm-rpc" },
826
+ "StakeViewer": { "connection": "default-evm-rpc" },
827
+ "TimeSyncViewer": { "connection": "default-evm-rpc" },
828
+ "TransactionValidationViewer": { "connection": "chain-mongo" },
829
+ "TransactionViewer": { "connection": "chain-mongo" },
830
+ "WindowedBlockViewer": { "connection": "chain-mongo" },
831
+ "XyoConnection": { "connection": "chain-mongo" },
832
+ "XyoGateway": { "connection": "chain-mongo" },
833
+ "XyoGatewayRunner": { "connection": "chain-mongo" },
834
+ "XyoRunner": { "connection": "chain-mongo" },
835
+ "XyoViewer": { "connection": "chain-mongo" }
836
+ },
837
+ "actors": [
838
+ {
839
+ "name": "producer",
840
+ "heartbeatInterval": 1000
841
+ }
842
+ ]
843
+ }
844
+ }
845
+ ```
314
846
 
315
- Every config key has a `XL1_*` env var equivalent. Use double-underscore for nested keys and numeric indices for array entries — useful in container orchestration where you'd rather not mount a file.
847
+ ### Finalizer (per-actor)
316
848
 
317
- ```sh
318
- export XL1_CONNECTIONS__LOCAL_STORE__TYPE=lmdb
319
- export XL1_CONNECTIONS__LOCAL_STORE__ROOT=/var/xl1/data
320
- export XL1_ACTORS__0__NAME=api
321
- export XL1_ACTORS__0__HOST=localhost
322
- export XL1_ACTORS__0__PORT=8080
323
- export XL1_ACTORS__1__NAME=producer
324
- export XL1_ACTORS__1__HEARTBEAT_INTERVAL=1000
325
- export XL1_ACTORS__2__NAME=finalizer
326
- xl1 start api producer finalizer
849
+ ```json
850
+ {
851
+ "xl1": {
852
+ "connections": {
853
+ "chain-mongo": {
854
+ "type": "mongo",
855
+ "connectionString": "mongodb://YOUR_MONGO_USER:YOUR_MONGO_PASSWORD@db.internal:27017/?retryWrites=true&w=majority",
856
+ "database": "xl1",
857
+ "domain": "internal"
858
+ },
859
+ "s3-finalized": {
860
+ "type": "s3",
861
+ "accountId": "YOUR_R2_ACCOUNT_ID",
862
+ "accessKeyId": "YOUR_ACCESS_KEY_ID",
863
+ "secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
864
+ "prefix": "xl1/",
865
+ "bucket": "xl1-chain",
866
+ "readUrl": "https://chain.xl1.example"
867
+ },
868
+ "s3-chain-state": {
869
+ "type": "s3",
870
+ "accountId": "YOUR_R2_ACCOUNT_ID",
871
+ "accessKeyId": "YOUR_ACCESS_KEY_ID",
872
+ "secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
873
+ "prefix": "xl1/",
874
+ "bucket": "xl1-chain-state",
875
+ "readUrl": "https://chain-state.xl1.example"
876
+ }
877
+ },
878
+ "providerBindings": {
879
+ "AccountBalanceViewer": { "connection": "chain-mongo" },
880
+ "BlockPublishRunner": { "connection": "s3-finalized" },
881
+ "BlockRunner": { "connection": "chain-mongo" },
882
+ "BlockValidationViewer": { "connection": "chain-mongo" },
883
+ "BlockViewer": { "connection": "chain-mongo" },
884
+ "ChainStatePublishRunner": { "connection": "s3-chain-state" },
885
+ "ChainStateViewer": { "connection": "s3-chain-state" },
886
+ "DeadLetterQueueRunner": { "connection": "chain-mongo" },
887
+ "DeadLetterQueueViewer": { "connection": "chain-mongo" },
888
+ "FinalizationRunner": { "connection": "chain-mongo" },
889
+ "FinalizationViewer": { "connection": "chain-mongo" },
890
+ "MempoolRunner": { "connection": "chain-mongo" },
891
+ "MempoolViewer": { "connection": "chain-mongo" },
892
+ "TransactionValidationViewer": { "connection": "chain-mongo" },
893
+ "TransactionViewer": { "connection": "chain-mongo" },
894
+ "WindowedBlockViewer": { "connection": "chain-mongo" },
895
+ "XyoConnection": { "connection": "chain-mongo" },
896
+ "XyoGateway": { "connection": "chain-mongo" },
897
+ "XyoGatewayRunner": { "connection": "chain-mongo" },
898
+ "XyoRunner": { "connection": "chain-mongo" },
899
+ "XyoViewer": { "connection": "chain-mongo" }
900
+ },
901
+ "actors": [
902
+ {
903
+ "name": "finalizer",
904
+ "finalizationCheckInterval": 500,
905
+ "publishOnFinalize": true,
906
+ "publishContentEncoding": "br",
907
+ "publishProgressInterval": 100
908
+ }
909
+ ]
910
+ }
911
+ }
327
912
  ```
328
913
 
329
- #### One config file per process
330
-
331
- When per-process config needs to live in different config-management systems (Kubernetes secrets vs ConfigMaps, separate Vault paths, etc.), give each process its own file with `-c`.
914
+ ### Indexer (per-actor)
332
915
 
333
- `xyo.api.json`:
334
916
  ```json
335
- { "xl1": { "connections": { "local-store": { "type": "lmdb", "root": "/var/xl1/data" } }, "actors": [{ "name": "api", "host": "localhost", "port": 8080 }] } }
917
+ {
918
+ "xl1": {
919
+ "connections": {
920
+ "rest-finalized": {
921
+ "type": "rest",
922
+ "baseUrl": "https://chain.xl1.example"
923
+ },
924
+ "rest-chain-state": {
925
+ "type": "rest",
926
+ "baseUrl": "https://chain-state.xl1.example"
927
+ },
928
+ "rest-index": {
929
+ "type": "rest",
930
+ "baseUrl": "https://index.xl1.example"
931
+ },
932
+ "s3-index": {
933
+ "type": "s3",
934
+ "accountId": "YOUR_R2_ACCOUNT_ID",
935
+ "accessKeyId": "YOUR_ACCESS_KEY_ID",
936
+ "secretAccessKey": "YOUR_SECRET_ACCESS_KEY",
937
+ "prefix": "xl1/",
938
+ "bucket": "xl1-index",
939
+ "readUrl": "https://index.xl1.example"
940
+ }
941
+ },
942
+ "providerBindings": {
943
+ "BlockViewer": {
944
+ "connection": "rest-finalized"
945
+ },
946
+ "ChainStateViewer": {
947
+ "connection": "rest-chain-state"
948
+ },
949
+ "IndexViewer": {
950
+ "connection": "rest-index"
951
+ },
952
+ "IndexPublishRunner": {
953
+ "connection": "s3-index"
954
+ }
955
+ },
956
+ "actors": [
957
+ {
958
+ "name": "indexer",
959
+ "publishSyncInterval": 5000,
960
+ "publishContentEncoding": "br",
961
+ "publishProgressInterval": 100
962
+ }
963
+ ]
964
+ }
965
+ }
336
966
  ```
337
967
 
338
- `xyo.producer.json`:
968
+ ### Mempool (per-actor)
969
+
339
970
  ```json
340
- { "xl1": { "connections": { "local-store": { "type": "lmdb", "root": "/var/xl1/data" } }, "actors": [{ "name": "producer", "heartbeatInterval": 1000 }] } }
971
+ {
972
+ "xl1": {
973
+ "connections": {
974
+ "chain-mongo": {
975
+ "type": "mongo",
976
+ "connectionString": "mongodb://YOUR_MONGO_USER:YOUR_MONGO_PASSWORD@db.internal:27017/?retryWrites=true&w=majority",
977
+ "database": "xl1",
978
+ "domain": "internal"
979
+ }
980
+ },
981
+ "providerBindings": {
982
+ "AccountBalanceViewer": { "connection": "chain-mongo" },
983
+ "BlockRunner": { "connection": "chain-mongo" },
984
+ "BlockValidationViewer": { "connection": "chain-mongo" },
985
+ "BlockViewer": { "connection": "chain-mongo" },
986
+ "DeadLetterQueueRunner": { "connection": "chain-mongo" },
987
+ "DeadLetterQueueViewer": { "connection": "chain-mongo" },
988
+ "FinalizationRunner": { "connection": "chain-mongo" },
989
+ "FinalizationViewer": { "connection": "chain-mongo" },
990
+ "MempoolRunner": { "connection": "chain-mongo" },
991
+ "MempoolViewer": { "connection": "chain-mongo" },
992
+ "TransactionValidationViewer": { "connection": "chain-mongo" },
993
+ "TransactionViewer": { "connection": "chain-mongo" },
994
+ "WindowedBlockViewer": { "connection": "chain-mongo" },
995
+ "XyoConnection": { "connection": "chain-mongo" },
996
+ "XyoGateway": { "connection": "chain-mongo" },
997
+ "XyoGatewayRunner": { "connection": "chain-mongo" },
998
+ "XyoRunner": { "connection": "chain-mongo" },
999
+ "XyoViewer": { "connection": "chain-mongo" }
1000
+ },
1001
+ "actors": [
1002
+ {
1003
+ "name": "mempool",
1004
+ "host": "0.0.0.0",
1005
+ "port": 8081
1006
+ }
1007
+ ]
1008
+ }
1009
+ }
341
1010
  ```
342
1011
 
343
- ```sh
344
- xl1 -c ./xyo.api.json start api
345
- xl1 -c ./xyo.producer.json start producer
1012
+ ### Bridge (per-actor)
1013
+
1014
+ ```json
1015
+ {
1016
+ "xl1": {
1017
+ "connections": {
1018
+ "chain-mongo": {
1019
+ "type": "mongo",
1020
+ "connectionString": "mongodb://YOUR_MONGO_USER:YOUR_MONGO_PASSWORD@db.internal:27017/?retryWrites=true&w=majority",
1021
+ "database": "xl1",
1022
+ "domain": "internal"
1023
+ },
1024
+ "default-evm-rpc": {
1025
+ "type": "evm-rpc",
1026
+ "url": "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID",
1027
+ "chainId": "0xaa36a7"
1028
+ },
1029
+ "rest-datalake": {
1030
+ "type": "rest",
1031
+ "baseUrl": "https://api.archivist.xyo.network/dataLake"
1032
+ }
1033
+ },
1034
+ "chain": {
1035
+ "id": "dd381fbb392c85160d8b0453e446757b12384046"
1036
+ },
1037
+ "providerBindings": {
1038
+ "AccountBalanceViewer": { "connection": "chain-mongo" },
1039
+ "BlockRunner": { "connection": "chain-mongo" },
1040
+ "BlockValidationViewer": { "connection": "chain-mongo" },
1041
+ "BlockViewer": { "connection": "chain-mongo" },
1042
+ "ChainContractViewer": { "connection": "default-evm-rpc" },
1043
+ "DataLakeRunner": { "connection": "rest-datalake" },
1044
+ "DataLakeViewer": { "connection": "rest-datalake" },
1045
+ "DeadLetterQueueRunner": { "connection": "chain-mongo" },
1046
+ "DeadLetterQueueViewer": { "connection": "chain-mongo" },
1047
+ "FinalizationRunner": { "connection": "chain-mongo" },
1048
+ "FinalizationViewer": { "connection": "chain-mongo" },
1049
+ "MempoolRunner": { "connection": "chain-mongo" },
1050
+ "MempoolViewer": { "connection": "chain-mongo" },
1051
+ "StakeEventsViewer": { "connection": "default-evm-rpc" },
1052
+ "StakeTotalsViewer": { "connection": "default-evm-rpc" },
1053
+ "StakeViewer": { "connection": "default-evm-rpc" },
1054
+ "TimeSyncViewer": { "connection": "default-evm-rpc" },
1055
+ "TransactionValidationViewer": { "connection": "chain-mongo" },
1056
+ "TransactionViewer": { "connection": "chain-mongo" },
1057
+ "WindowedBlockViewer": { "connection": "chain-mongo" },
1058
+ "XyoConnection": { "connection": "chain-mongo" },
1059
+ "XyoGateway": { "connection": "chain-mongo" },
1060
+ "XyoGatewayRunner": { "connection": "chain-mongo" },
1061
+ "XyoRunner": { "connection": "chain-mongo" },
1062
+ "XyoViewer": { "connection": "chain-mongo" }
1063
+ },
1064
+ "actors": [
1065
+ {
1066
+ "name": "bridge",
1067
+ "accountPath": "0",
1068
+ "redisHost": "redis.internal",
1069
+ "redisPort": 6379,
1070
+ "scannerIntervalMs": 30000
1071
+ }
1072
+ ]
1073
+ }
1074
+ }
346
1075
  ```
347
1076
 
348
- ### S3 object storage (finalized / chain-state / index publishing)
1077
+ ### Reward redemption (per-actor)
1078
+
1079
+ ```json
1080
+ {
1081
+ "xl1": {
1082
+ "connections": {
1083
+ "chain-mongo": {
1084
+ "type": "mongo",
1085
+ "connectionString": "mongodb://YOUR_MONGO_USER:YOUR_MONGO_PASSWORD@db.internal:27017/?retryWrites=true&w=majority",
1086
+ "database": "xl1",
1087
+ "domain": "internal"
1088
+ },
1089
+ "default-evm-rpc": {
1090
+ "type": "evm-rpc",
1091
+ "url": "https://sepolia.infura.io/v3/YOUR_INFURA_PROJECT_ID",
1092
+ "chainId": "0xaa36a7"
1093
+ }
1094
+ },
1095
+ "chain": {
1096
+ "id": "dd381fbb392c85160d8b0453e446757b12384046"
1097
+ },
1098
+ "providerBindings": {
1099
+ "AccountBalanceViewer": { "connection": "chain-mongo" },
1100
+ "BlockRunner": { "connection": "chain-mongo" },
1101
+ "BlockValidationViewer": { "connection": "chain-mongo" },
1102
+ "BlockViewer": { "connection": "chain-mongo" },
1103
+ "ChainContractViewer": { "connection": "default-evm-rpc" },
1104
+ "DeadLetterQueueRunner": { "connection": "chain-mongo" },
1105
+ "DeadLetterQueueViewer": { "connection": "chain-mongo" },
1106
+ "FinalizationRunner": { "connection": "chain-mongo" },
1107
+ "FinalizationViewer": { "connection": "chain-mongo" },
1108
+ "MempoolRunner": { "connection": "chain-mongo" },
1109
+ "MempoolViewer": { "connection": "chain-mongo" },
1110
+ "StakeEventsViewer": { "connection": "default-evm-rpc" },
1111
+ "StakeTotalsViewer": { "connection": "default-evm-rpc" },
1112
+ "StakeViewer": { "connection": "default-evm-rpc" },
1113
+ "TimeSyncViewer": { "connection": "default-evm-rpc" },
1114
+ "TransactionValidationViewer": { "connection": "chain-mongo" },
1115
+ "TransactionViewer": { "connection": "chain-mongo" },
1116
+ "WindowedBlockViewer": { "connection": "chain-mongo" },
1117
+ "XyoConnection": { "connection": "chain-mongo" },
1118
+ "XyoGateway": { "connection": "chain-mongo" },
1119
+ "XyoGatewayRunner": { "connection": "chain-mongo" },
1120
+ "XyoRunner": { "connection": "chain-mongo" },
1121
+ "XyoViewer": { "connection": "chain-mongo" }
1122
+ },
1123
+ "actors": [
1124
+ {
1125
+ "name": "rewardRedemption",
1126
+ "accountPath": "0",
1127
+ "host": "0.0.0.0",
1128
+ "port": 8082
1129
+ }
1130
+ ]
1131
+ }
1132
+ }
1133
+ ```
1134
+ ## S3 object storage (finalized / chain-state / index publishing)
349
1135
 
350
1136
  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`](https://www.npmjs.com/package/@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.
351
1137
 
@@ -411,7 +1197,7 @@ export XL1_ACTORS__1__PUBLISH_ON_FINALIZE=true
411
1197
  export XL1_ACTORS__2__PUBLISH_SYNC_INTERVAL=5000
412
1198
  ```
413
1199
 
414
- ### Mnemonics & wallets
1200
+ ## Mnemonics & wallets
415
1201
 
416
1202
  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.
417
1203
 
@@ -429,7 +1215,7 @@ The root `mnemonic` field (or `XL1_MNEMONIC`) defines the wallet from which ever
429
1215
 
430
1216
  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.
431
1217
 
432
- ### Inspecting a configuration
1218
+ ## Inspecting a configuration
433
1219
 
434
1220
  Two CLI flags help you verify what the chain *would* do without actually starting it:
435
1221