# LumeScope Actions API reference Source: https://docs.lumera.io/api/actions Query Cascade and Sense actions on the Lumera network with decoded metadata, lifecycle details, and aggregated statistics. The LumeScope Actions API gives you a decoded view of every Cascade and Sense action registered on the Lumera network. Rather than parsing raw chain transactions yourself, you query a pre-indexed store. The store tracks each action through its full lifecycle, from the initial `register` transaction through `finalize` and `approve`. Three endpoints cover the most common needs. You can list actions with filters, fetch the full detail for a single action, and retrieve aggregate counts across the network. All Actions API endpoints are read-only and require no authentication. The base URL for mainnet is `https://lumescope.lumera.io`. Substitute `https://lumescope.testnet.lumera.io` for testnet. *** ## GET /v1/actions Returns a paginated list of Cascade and Sense actions with fully decoded metadata. Use the query parameters to filter by type, creator, state, and block height range. ### Query parameters Filter by action type. Accepted values are `cascade` and `sense`. Omit to return both types. Filter by the Bech32 address of the account that created the action. Filter by action state. Common values are `registered`, `finalized`, and `approved`. Filter actions associated with a specific SuperNode address. Return only actions created at or after this block height. Return only actions created at or before this block height. Maximum number of actions to return per page. Opaque pagination cursor returned by the previous response. Pass this value to fetch the next page. When `true`, each action in the list includes its full transaction lifecycle (register, finalize, approve tx hashes and heights). ### Example request ```shell Cascade actions theme={null} curl https://lumescope.lumera.io/v1/actions?type=cascade&limit=10 ``` ```shell With transactions theme={null} curl "https://lumescope.lumera.io/v1/actions?type=sense&limit=5&include_transactions=true" ``` ```shell Height range theme={null} curl "https://lumescope.lumera.io/v1/actions?fromHeight=100000&toHeight=200000&limit=20" ``` ### Example response ```json theme={null} { "actions": [ { "id": "abc123", "type": "cascade", "status": "approved", "data_hash": "sha256:deadbeef...", "created_at": "2024-01-15T10:30:00Z", "finalized_at": "2024-01-15T10:45:00Z" } ], "total": 1234, "page": 1 } ``` ### Response fields Array of action objects matching the query. Unique action identifier on the Lumera chain. The action type, either `cascade` or `sense`. The current lifecycle state, one of `registered`, `finalized`, or `approved`. SHA-256 content hash of the stored data. RFC 3339 timestamp of the registration transaction. RFC 3339 timestamp of the finalization transaction. Null if not yet finalized. Total number of actions matching the query filters, across all pages. Current page number (1-indexed). *** ## GET /v1/actions/\{id} Returns the full details for a single action, including its complete transaction lifecycle. Use this endpoint when you need register, finalize, and approve transaction hashes and block heights for a specific action. ### Path parameters The unique action ID. This is the same `id` value returned by `GET /v1/actions`. ### Example request ```shell theme={null} curl https://lumescope.lumera.io/v1/actions/abc123 ``` ### Example response ```json theme={null} { "id": "abc123", "type": "cascade", "status": "approved", "data_hash": "sha256:deadbeef...", "creator": "lumera1qg5ega6dykkxc307y25pecuufrjkxkaggkkxh", "created_at": "2024-01-15T10:30:00Z", "finalized_at": "2024-01-15T10:45:00Z", "approved_at": "2024-01-15T10:50:00Z", "transactions": { "register": { "tx_hash": "A1B2C3D4...", "height": 123456 }, "finalize": { "tx_hash": "E5F6G7H8...", "height": 123789 }, "approve": { "tx_hash": "I9J0K1L2...", "height": 123901 } } } ``` ### Response fields Unique action identifier. The action type, either `cascade` or `sense`. Current lifecycle state. SHA-256 content hash of the stored data. Bech32 address of the account that registered the action. RFC 3339 timestamp of the registration transaction. RFC 3339 timestamp of the finalization transaction. RFC 3339 timestamp of the approval transaction. Transaction lifecycle for this action. Transaction hash of the register message. Block height of the register transaction. Transaction hash of the finalize message. Block height of the finalize transaction. Transaction hash of the approve message. Block height of the approve transaction. *** ## GET /v1/actions/stats Returns aggregated action statistics across the entire network. Use this endpoint to build dashboards or monitor overall Cascade and Sense activity without paginating through individual records. ### Query parameters Scope statistics to a single action type, either `cascade` or `sense`. Omit to aggregate across both types. Start of the time window as an RFC 3339 timestamp (e.g. `2024-01-01T00:00:00Z`). End of the time window as an RFC 3339 timestamp. ### Example request ```shell All actions theme={null} curl https://lumescope.lumera.io/v1/actions/stats ``` ```shell Cascade only theme={null} curl "https://lumescope.lumera.io/v1/actions/stats?type=cascade" ``` ```shell Time-bounded theme={null} curl "https://lumescope.lumera.io/v1/actions/stats?from=2024-01-01T00:00:00Z&to=2024-12-31T23:59:59Z" ``` ### Example response ```json theme={null} { "total_actions": 5000, "cascade_count": 3200, "sense_count": 1800, "approved_count": 4800 } ``` ### Response fields Total number of actions indexed by LumeScope, across all types and states. Number of Cascade actions. Number of Sense actions. Number of actions that have reached the `approved` state. *** Combine `GET /v1/actions/stats` with time-window filters to plot approval rates over time and detect anomalies in SuperNode throughput. # CometBFT RPC reference Source: https://docs.lumera.io/api/cometbft-rpc Query blocks, transactions, and validator sets through the consensus RPC that every Lumera node serves on port 26657. Every `lumerad` node serves the CometBFT RPC on port 26657. CometBFT is the consensus engine under Lumera Protocol, and this API exposes what it sees. Raw blocks, transactions, the validator set, peer connections, and node health. Cosmos wallets, client libraries, and the Lumera SDKs use it to broadcast transactions and follow chain state. This page covers the endpoints developers reach for most. The [official CometBFT RPC documentation](https://docs.cometbft.com/v0.38/rpc/) lists the full catalog. Lumera runs the v0.38 line. ## Base URLs | Network | Endpoint | | - | - | | Mainnet (official) | `https://rpc.lumera.io` | | Mainnet (community) | `https://lumera-rpc.polkachu.com` | | Testnet (community) | `https://lumera-testnet-rpc.polkachu.com` | The official mainnet endpoint can be intermittent, so the examples below use the community endpoints. Validators keep port 26657 closed to the public. Use the public endpoints above, or run your own full node when you need guaranteed access and no rate limits. ## /status Node identity, sync state, and the latest block. Check `catching_up` to see whether a node is fully synced. ```bash theme={null} curl -fsSL https://lumera-rpc.polkachu.com/status | jq '.result.sync_info' ``` ## /abci\_info The application version the node runs. This is the same check the validator runbooks use to confirm the current network binary. ```bash theme={null} curl -fsSL https://lumera-rpc.polkachu.com/abci_info | jq -r .result.response.version ``` ## /block The latest block, or a specific block when you pass `height`. ```bash theme={null} # Latest block header curl -fsSL https://lumera-rpc.polkachu.com/block | jq '.result.block.header' # A specific height curl -fsSL "https://lumera-rpc.polkachu.com/block?height=1000000" | jq '.result.block.header' ``` ## /tx A transaction by hash. The hash must carry the `0x` prefix. A `code` of `0` in `tx_result` means the transaction succeeded. ```bash theme={null} curl -fsSL "https://lumera-rpc.polkachu.com/tx?hash=0xTX_HASH" | jq '.result.tx_result.code' ``` ## /validators The active validator set at the latest height, or at a given `height`. Lumera caps the active set at 50 validators, so one page covers it. Governance can change the cap. ```bash theme={null} curl -fsSL "https://lumera-rpc.polkachu.com/validators?per_page=100" | jq '.result.total' ``` ## /net\_info Peer count and peer details. Useful when you run your own node and want to verify connectivity. ```bash theme={null} curl -fsSL https://lumera-rpc.polkachu.com/net_info | jq '.result.n_peers' ``` ## /broadcast\_tx\_sync Broadcasts a signed transaction and returns after the mempool check. Most applications broadcast through `lumerad` or an SDK instead of calling this endpoint by hand. ```bash theme={null} curl -fsSL "https://lumera-testnet-rpc.polkachu.com/broadcast_tx_sync?tx=0xSIGNED_TX_BYTES" ``` ## Beyond HTTP GET Every endpoint also accepts JSON-RPC over HTTP POST, and a WebSocket at `/websocket` supports event subscriptions. See the [CometBFT RPC documentation](https://docs.cometbft.com/v0.38/rpc/) for parameters, response schemas, and the endpoints not shown here. ## Next steps Ethereum-style RPC on EVM enabled networks. All Lumera API surfaces at a glance. Chain IDs, endpoints, and seeds for mainnet and testnet. # EVM JSON-RPC reference Source: https://docs.lumera.io/api/evm-jsonrpc Call standard Ethereum JSON-RPC methods on Lumera EVM nodes over HTTP and WebSocket. Lumera Protocol nodes on EVM enabled networks serve a standard Ethereum JSON-RPC API. HTTP runs on port 8545 and WebSocket on port 8546, both enabled by default on upgraded nodes. Any tool that speaks Ethereum RPC works unchanged. Hardhat, Foundry, Ethers, viem, and MetaMask all connect by pointing at the endpoint and chain ID below. The EVM is live on testnet today. Mainnet runs `v1.12.0` and gains the EVM JSON-RPC with its [EVM upgrade](/upgrades/evm-upgrade). ## Base URLs and chain ID | Network | HTTP endpoint | Status | | - | - | - | | Testnet | `https://evm-rpc.testnet.lumera.io` | Live | | Mainnet | `https://evm-rpc.lumera.io` | Answers after the EVM upgrade | | Your own node | `http://localhost:8545` | EVM enabled builds (v1.20.0 and later) | The EVM chain ID is `76857769` on every Lumera network. JSON-RPC returns it as hex `0x494c1a9`. The currency symbol is LUME, exposed to the EVM as the 18 decimal denom `alume`. WebSocket subscriptions run on port 8546 on nodes you operate yourself. ## Quick checks Verify connectivity with `eth_chainId` and `eth_blockNumber`. ```bash eth_chainId theme={null} curl -s -X POST https://evm-rpc.testnet.lumera.io \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' | jq -r '.result' ``` ```bash eth_blockNumber theme={null} curl -s -X POST https://evm-rpc.testnet.lumera.io \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' | jq -r '.result' ``` `eth_chainId` returns `0x494c1a9`. `eth_blockNumber` returns the latest height as a hex number. ## Namespaces Methods are grouped by namespace prefix. The default node configuration enables `eth`, `net`, `web3`, and `rpc`. Node operators can enable additional namespaces such as `debug` and `txpool` in `app.toml`, so their availability depends on the endpoint you call. The fee market implements EIP-1559 with a dynamic base fee, so fee estimation behaves the way Ethereum tooling expects. ## OpenRPC discovery The node ships a machine readable catalog of every JSON-RPC method it supports, in [OpenRPC](https://open-rpc.org/) format. The spec regenerates at build time from the RPC implementation, so it always matches the running node. There are two ways to fetch it. * Call `rpc_discover` (or the alias `rpc.discover`) on the JSON-RPC endpoint. * Fetch `/openrpc.json` from the Cosmos REST API port (1317) with a plain GET. ```bash theme={null} curl -s -X POST https://evm-rpc.testnet.lumera.io \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"rpc_discover","params":[],"id":1}' | jq '.result.info' ``` The catalog covers about 743 methods with parameters, return types, and examples. Browse and execute them interactively in the [OpenRPC playground](/smart-contracts/openrpc-playground). ## Next steps What the Lumera EVM offers contract developers. Explore every RPC method from the browser. Add the Lumera network to MetaMask. # CascadeService gRPC reference Source: https://docs.lumera.io/api/grpc/cascade-service CascadeService provides streaming gRPC RPCs for registering files in Cascade permanent storage and downloading stored data from a Lumera SuperNode. `CascadeService` is the gRPC interface you use to register files in Lumera's Cascade permanent-storage layer and to retrieve them later. Both operations use gRPC streaming. The `Register` RPC is bidirectional. You stream file chunks to the SuperNode while it streams progress events back. The `Download` RPC streams chunks from the SuperNode to you. The SuperNode handles RaptorQ encoding, P2P distribution, and on-chain finalization. Your client only needs to send the data and act on the event stream. Two layers reach Cascade. Applications go through [SN-API](/api/sn-api), the HTTP gateway in front of the SuperNode network, which the [JavaScript](/sdk/javascript) and [Rust](/sdk/rust) SDKs use. `CascadeService` is the direct path to a SuperNode on port 4444, used by the [Go SDK](/sdk/go) and operator tooling. Use SN-API for app-facing upload and download. ## Proto definition ```proto theme={null} service CascadeService { rpc Register (stream RegisterRequest) returns (stream RegisterResponse); rpc Download (DownloadRequest) returns (stream DownloadResponse); } ``` *** ## Register `rpc Register(stream RegisterRequest) returns (stream RegisterResponse)` Use `Register` to upload a file and anchor it permanently on the Lumera network. The RPC is a bidirectional stream. Your client opens the stream, sends the file in `DataChunk` messages, then sends a final `Metadata` message to identify the pre-created on-chain action. The SuperNode encodes the data with RaptorQ, distributes the encoded symbols across the P2P network, and finalizes the action on-chain. It streams `RegisterResponse` progress events back to you throughout. ### Client stream `RegisterRequest` Each message in your outgoing stream is one of two types (`oneof`). A slice of the file's raw bytes. Send as many chunk messages as needed to transfer the complete file. There is no prescribed chunk size. A few hundred kilobytes per message is a reasonable default. The raw binary content of this chunk. Sent **once**, after all `DataChunk` messages, to tell the SuperNode which on-chain action this upload corresponds to. A client-assigned task identifier used for correlating logs and events. The on-chain Cascade action ID that was created before calling this RPC. The SuperNode reads the fee and ownership details from this action. ### Server stream `RegisterResponse` A progress milestone. See the event table below. Human-readable description of the event, useful for logging. Populated on the `ACTION_FINALIZED` event. It holds the transaction hash of the finalization transaction broadcast to the Lumera chain. ### Registration event sequence The server emits these events in order during a successful registration. | Event | Description | | - | - | | `ACTION_RETRIEVED` | Action details fetched from the Lumera chain | | `ACTION_FEE_VERIFIED` | Required fee confirmed as paid | | `TOP_SUPERNODE_CHECK_PASSED` | This node confirmed as a top-ranked SuperNode | | `INPUT_ENCODED` | File data encoded with RaptorQ into erasure-coded symbols | | `RQID_GENERATED` | RaptorQ symbol identifiers generated | | `ARTEFACTS_STORED` | Encoded symbols distributed and stored in the P2P network | | `ACTION_FINALIZED` | Finalization transaction submitted on-chain with `tx_hash` set | ### grpcurl example ```shell theme={null} # Stream file registration (sends metadata only; pipe real file data in production) grpcurl -plaintext \ -d '{"metadata": {"task_id": "task-001", "action_id": "action-abc"}}' \ localhost:4444 \ lumera.supernode.v1.CascadeService/Register ``` Production clients should use a gRPC library (e.g., the [supernode Go SDK](https://github.com/LumeraProtocol/supernode/tree/master/sdk)) to open a real bidirectional stream and send file data as `DataChunk` messages before sending the `Metadata` message. The `grpcurl` snippet above illustrates the shape of the metadata message only. *** ## Download `rpc Download(DownloadRequest) returns (stream DownloadResponse)` Use `Download` to retrieve a file that was previously registered via Cascade. You send a single unary request containing the action ID and a signature proving ownership. The SuperNode streams back progress events followed by the raw file data in chunks. ### Request message `DownloadRequest` The on-chain Cascade action ID whose data you want to retrieve. A base64-encoded signature over the `action_id`, produced with the LumeraID key associated with the action's owner address. The SuperNode verifies this signature before serving the data. ### Server stream `DownloadResponse` Each message in the response stream is one of two types (`oneof`). Progress milestone emitted during the retrieval phase. The event milestone. Download emits `ACTION_RETRIEVED`, `METADATA_DECODED`, `NETWORK_RETRIEVE_STARTED`, `ARTEFACTS_DOWNLOADED`, and `DECODE_COMPLETED`, then `SERVE_READY` immediately before the first `chunk`. Human-readable description of the event. A slice of the reconstructed file's raw bytes. After all progress events are emitted, the SuperNode streams the complete file as a sequence of `DataChunk` messages. Raw binary content of this chunk of the recovered file. ### grpcurl example ```shell theme={null} grpcurl -plaintext \ -d '{"action_id": "action-abc", "signature": "base64-sig..."}' \ localhost:4444 \ lumera.supernode.v1.CascadeService/Download ``` The `signature` field must be produced by signing the `action_id` string with the **LumeraID key** linked to the owner address recorded on the action. Signatures produced with a different key are rejected with an `UNAUTHENTICATED` gRPC status. See the LumeraID documentation for how to generate a compatible signature. *** ## SupernodeEventType reference The `SupernodeEventType` enum is shared by both `Register` and `Download`. Not every event appears in every flow. | Value | Name | Flows | | - | - | - | | `0` | `UNKNOWN` | none | | `1` | `ACTION_RETRIEVED` | Register, Download | | `2` | `ACTION_FEE_VERIFIED` | Register | | `3` | `TOP_SUPERNODE_CHECK_PASSED` | Register | | `4` | `METADATA_DECODED` | Download | | `5` | `DATA_HASH_VERIFIED` | Download | | `6` | `INPUT_ENCODED` | Register | | `7` | `SIGNATURE_VERIFIED` | Download | | `8` | `RQID_GENERATED` | Register | | `9` | `RQID_VERIFIED` | Download | | `10` | `FINALIZE_SIMULATED` | Register | | `11` | `ARTEFACTS_STORED` | Register | | `12` | `ACTION_FINALIZED` | Register | | `13` | `ARTEFACTS_DOWNLOADED` | Download | | `14` | `FINALIZE_SIMULATION_FAILED` | Register | | `15` | `NETWORK_RETRIEVE_STARTED` | Download | | `16` | `DECODE_COMPLETED` | Download | | `17` | `SERVE_READY` | Download | *** ## HTTP alternative `CascadeService` has no HTTP gateway. The SuperNode's own gateway on port 8002 exposes `SupernodeService` only, documented in [`docs/gateway.md`](https://github.com/LumeraProtocol/supernode/blob/master/docs/gateway.md). To move Cascade files over HTTP, use [SN-API](/api/sn-api). The HTTP gateway is useful for quick testing and browser-based tooling, but for production file transfers you should use the native gRPC streaming interface or the Go SDK. Streaming large files over the HTTP gateway may be subject to buffering and timeout constraints that the gRPC path avoids. # SupernodeService gRPC reference Source: https://docs.lumera.io/api/grpc/supernode-service Query system status and resource metrics for a Lumera SuperNode over gRPC, including CPU, memory, storage, P2P, and DHT data. `SupernodeService` is a gRPC service exposed by every Lumera SuperNode. It gives you a real-time snapshot of the node's health. The snapshot covers software version, uptime, CPU and memory load, and connected peers. When you opt in, it also includes detailed DHT diagnostics. You can use this service to build monitoring dashboards, alerting pipelines, or operational tooling without any on-chain queries. ## Connection SuperNodes listen on **port 4444** by default (configurable via `supernode.port` in `config.yml`). You can verify connectivity with `grpcurl`. ```shell theme={null} grpcurl -plaintext localhost:4444 lumera.supernode.v1.SupernodeService/GetStatus ``` If TLS is enabled on the SuperNode, replace `-plaintext` with the appropriate `-cacert` / `-cert` / `-key` flags. Most production deployments terminate TLS at a reverse proxy and expose plaintext gRPC internally. *** ## Proto definition ```proto theme={null} service SupernodeService { rpc GetStatus(StatusRequest) returns (StatusResponse); } ``` *** ## GetStatus Returns a point-in-time status snapshot of the SuperNode. The request lets you toggle expensive P2P diagnostics on or off, so routine polling stays lightweight. ### Request message `StatusRequest` When `true`, the response includes the full `p2p_metrics` object containing DHT store/retrieve history, handle counters, ban list, database stats, and disk usage. Set this to `false` (or omit it) for routine health polling. The `peers_count` field is always fast and is returned regardless of this flag. Heavier diagnostics are served from a cached last-known-good snapshot. ### Response message `StatusResponse` The SuperNode software version string (e.g., `"v2.5.2"`). Seconds elapsed since the SuperNode process started. Hardware resource utilisation at the time of the call. Current CPU utilisation across all cores, expressed as a percentage (0 to 100). Number of logical CPU cores available to the process. Total physical memory installed, in GB. Memory currently in use, in GB. Memory available to new allocations, in GB. Memory utilisation as a percentage (0 to 100). Storage volumes monitored by the SuperNode. Currently the array contains one entry. That entry is the filesystem that hosts the configured base directory (`--basedir`). Filesystem mount path being reported (e.g., `"/"`). Total capacity of the volume in bytes. Bytes currently consumed on the volume. Bytes available for new writes. Volume utilisation as a percentage (0 to 100). Human-readable summary of the node's hardware profile (e.g., `"8 cores / 32GB RAM"`). The service names this SuperNode is registered to provide. This is typically `["cascade", "sense"]` or a subset of it. P2P network connectivity information. Number of peers currently connected in the P2P network. Always present, even when `include_p2p_metrics` is `false`. List of connected peer addresses in `"ID@IP:Port"` format. This node's rank in the top-SuperNodes list as computed by the chain. Returns `0` if the node is not currently in the top list. The publicly-advertised IP address and service port of this SuperNode (e.g., `"203.0.113.45:4444"`). Detailed P2P and DHT diagnostics. **Only present when `include_p2p_metrics` is `true`.** Recent DHT operation history. Recent DHT store-operation data points. Each entry includes `time_unix` (epoch seconds), `requests` (total node RPCs attempted), `successful` (successful node RPCs), and `success_rate` (percentage 0 to 100). Recent batch-retrieval data points. Each entry includes `time_unix`, `keys` (keys requested), `required`, `found_local`, `found_network`, and `duration_ms`. Number of times a hot-path request was skipped because the target was banned. Number of times a ban counter was incremented on the hot path. Per-handler RPC counters. Each `HandleCounters` value contains `total`, `success`, `failure`, and `timeout` int64 fields. Connection pool statistics keyed by metric name. Peers currently banned by this node. Each entry contains `id`, `ip`, `port` (uint32), `count` (ban increment count), `created_at_unix`, and `age_seconds`. P2P key-value database stats, containing `p2p_db_size_mb` (double) and `p2p_db_records_count` (int64). Disk usage for the P2P data directory, reported as `all_mb`, `used_mb`, and `free_mb` (all double). *** ## Example response ```json theme={null} { "version": "v2.5.2", "uptime_seconds": 86400, "resources": { "cpu": { "usage_percent": 12.5, "cores": 8 }, "memory": { "total_gb": 64, "used_gb": 12.3, "available_gb": 51.7, "usage_percent": 19.2 }, "storage_volumes": [ { "path": "/", "total_bytes": 2000398934016, "used_bytes": 480374956032, "available_bytes": 1520023977984, "usage_percent": 24.0 } ], "hardware_summary": "8-core CPU, 64GB RAM, 2TB storage" }, "registered_services": ["cascade", "sense"], "network": { "peers_count": 15, "peer_addresses": [ "12D3KooWExample1@203.0.113.10:4445", "12D3KooWExample2@203.0.113.20:4445" ] }, "rank": 3, "ip_address": "203.0.113.45:4444" } ``` *** ## Full example with P2P metrics Pass `include_p2p_metrics: true` to receive DHT diagnostics alongside the standard fields. ```shell theme={null} grpcurl -plaintext \ -d '{"include_p2p_metrics": true}' \ localhost:4444 \ lumera.supernode.v1.SupernodeService/GetStatus ``` # Deploy LumeScope with Docker Source: https://docs.lumera.io/api/lumescope Run your own LumeScope instance with Docker to aggregate Lumera action metadata, SuperNode metrics, and network stats. LumeScope is an open-source, self-hosted API aggregator that indexes Lumera chain data and serves it through a clean REST interface. It is written in Go using only the standard library `net/http` package, with no third-party web frameworks. It bundles an embedded PostgreSQL 14 database and runs as a single Docker container. No separate data store is required. When you deploy your own instance you control the sync cadence and keep all indexed data inside your infrastructure. You can also build analytics pipelines on top without depending on a third-party service. ## When to deploy your own instance The public mainnet and testnet endpoints (`https://lumescope.lumera.io` and `https://lumescope.testnet.lumera.io`) are suitable for most integrations. Consider running your own LumeScope instance when you need any of the following. * **Tune sync intervals.** Adjust how frequently actions, validators, SuperNodes, and probes refresh. * **Ensure data sovereignty.** Keep all network data inside your own infrastructure. * **Build analytics.** Connect your own dashboards or data pipelines directly to the embedded PostgreSQL database. * **Run with high availability.** Run multiple replicas backed by a shared external PostgreSQL for failover. *** ## Quickstart Get LumeScope running locally in under a minute with Docker. ```shell theme={null} # Connect to Mainnet docker run -d \ -p 18080:18080 \ -e LUMERA_API_BASE=https://lcd.lumera.io \ --name lumescope \ ghcr.io/lumeraprotocol/lumescope:latest ``` For testnet, replace the `LUMERA_API_BASE` value with `https://lcd.testnet.lumera.io`. ```shell theme={null} # Liveness check. Returns 200 immediately if the process is running curl -i http://localhost:18080/healthz # Readiness check. Returns 200 once the first sync has completed curl -i http://localhost:18080/readyz # Fetch the first three actions curl -s 'http://localhost:18080/v1/actions?limit=3' | jq . ``` Open `http://localhost:18080/docs` in your browser to browse and test every endpoint using the embedded Swagger UI. The machine-readable OpenAPI 3.0 spec is available at `http://localhost:18080/openapi.json`. *** ## Configuration LumeScope reads all configuration from environment variables at startup. Copy `.env.example` from the repository and customise it, or pass variables directly with `-e` flags. ### Key environment variables | Variable | Required | Default | Description | | - | - | - | - | | `LUMERA_API_BASE` | **Yes** | `http://localhost:1317` | Lumera REST (LCD) endpoint URL | | `PORT` | No | `18080` | HTTP server listen port | | `CORS_ALLOW_ORIGINS` | No | `*` | Comma-separated allowed CORS origins | | `VALIDATORS_SYNC_INTERVAL` | No | `5m` | Validators sync frequency | | `SUPERNODES_SYNC_INTERVAL` | No | `2m` | SuperNodes sync frequency | | `ACTIONS_SYNC_INTERVAL` | No | `30s` | Actions sync frequency | | `PROBE_INTERVAL` | No | `1m` | SuperNode probe frequency | LumeScope reads more environment variables than shown here, including database connection settings, HTTP timeouts, and TCP dial timeouts. Its README lists them all and ships with each LumeScope release in [LumeraProtocol/releases](https://github.com/LumeraProtocol/releases). *** ## Production deployment ### Persistent data volume The embedded PostgreSQL 14 database stores its data files at `/var/lib/postgresql/data` inside the container. Mount a named volume to retain indexed data across container restarts and upgrades. ```shell theme={null} docker run -d \ -p 18080:18080 \ -e LUMERA_API_BASE=https://lcd.lumera.io \ -e POSTGRES_PASSWORD=your-secure-password \ -v lumescope_data:/var/lib/postgresql/data \ --name lumescope \ ghcr.io/lumeraprotocol/lumescope:latest ``` Always set `POSTGRES_PASSWORD` to a strong value in production. The default password (`postgres`) is suitable only for local development. ### External PostgreSQL database If you prefer to manage the database separately, point LumeScope at an external PostgreSQL 13+ instance. ```shell theme={null} docker run -d \ -p 18080:18080 \ -e LUMERA_API_BASE=https://lcd.lumera.io \ -e DB_DSN="postgres://user:pass@your-pg-host:5432/lumescope?sslmode=require" \ --name lumescope \ ghcr.io/lumeraprotocol/lumescope:latest ``` LumeScope automatically creates all required tables and indexes on first startup. ### High-availability setup Run multiple replicas pointing to a shared external PostgreSQL. ```shell theme={null} # Instance 1 docker run -d -p 18081:18080 \ -e LUMERA_API_BASE=https://lcd.lumera.io \ -e DB_DSN="postgres://user:pass@pg-host:5432/lumescope?sslmode=require" \ --name lumescope-1 \ ghcr.io/lumeraprotocol/lumescope:latest # Instance 2 docker run -d -p 18082:18080 \ -e LUMERA_API_BASE=https://lcd.lumera.io \ -e DB_DSN="postgres://user:pass@pg-host:5432/lumescope?sslmode=require" \ --name lumescope-2 \ ghcr.io/lumeraprotocol/lumescope:latest ``` Place nginx, HAProxy, or a cloud load balancer in front of both instances. *** ## Available endpoints Once LumeScope is running, the following paths are available on your host. | Endpoint | Description | | - | - | | `GET /healthz` | Liveness probe, always `200` while the process is alive | | `GET /readyz` | Readiness probe, `200` once the database and first sync are ready | | `GET /v1/actions` | List decoded Cascade and Sense actions | | `GET /v1/supernodes/metrics` | SuperNode hardware metrics and probe status | | `GET /openapi.json` | OpenAPI 3.0 specification | | `GET /docs` | Interactive Swagger UI | | `GET /metrics` | Prometheus metrics endpoint (currently stub data) | *** ## Troubleshooting **Symptom.** The container starts but action and SuperNode data never populates. Container logs show connection errors. **Steps.** 1. Verify that `LUMERA_API_BASE` is set to the correct LCD URL for your target network. ```shell theme={null} # Mainnet curl https://lcd.lumera.io/cosmos/base/tendermint/v1beta1/node_info # Testnet curl https://lcd.testnet.lumera.io/cosmos/base/tendermint/v1beta1/node_info ``` 2. Check connectivity from inside the container. ```shell theme={null} docker exec lumescope wget -qO- https://lcd.lumera.io/healthz ``` 3. If you are pointing at a local `lumerad` node, confirm that port `1317` is exposed and the REST API is enabled in `app.toml` (`api.enable = true`). **Symptom.** The browser blocks your frontend application with a CORS policy error. Direct `curl` calls still succeed. **Steps.** Set `CORS_ALLOW_ORIGINS` to your frontend's exact origin. ```shell theme={null} docker run -d \ -p 18080:18080 \ -e LUMERA_API_BASE=https://lcd.lumera.io \ -e CORS_ALLOW_ORIGINS="https://your-app.com" \ --name lumescope \ ghcr.io/lumeraprotocol/lumescope:latest ``` Use `*` only during local development. Wildcard origins are not recommended in production because they allow any website to call your API. **Symptom.** Several SuperNodes appear in `GET /v1/supernodes/unavailable` and their `failed_probe_counter` is incrementing. **Cause.** LumeScope probes each SuperNode on ports 4444, 4445, and 8002. Some operators firewall these ports or disable their status API endpoint. This causes the probe to fail. **Steps.** 1. This is **expected behavior** for a subset of SuperNodes. Probe failures do not indicate a LumeScope misconfiguration. 2. Query `GET /v1/supernodes/unavailable` to see which specific nodes are affected and how long they have been unreachable. 3. If a node you operate is listed, verify that its status API port is publicly accessible and that no firewall rule is blocking LumeScope's outbound TCP dial. 4. Adjust `PROBE_INTERVAL` if you want to probe less (or more) frequently to reduce noise. **Symptom.** The container exits shortly after starting, or queries return errors. Logs mention a database connection failure. **Steps.** 1. For the embedded Postgres, inspect the container logs for PostgreSQL startup errors. ```shell theme={null} docker logs lumescope ``` 2. For an external database, verify your `DB_DSN` connection string. ```shell theme={null} psql "postgres://user:pass@host:5432/lumescope?sslmode=disable" -c "\dt" ``` 3. Ensure the database user has `CREATE TABLE` and `CREATE INDEX` privileges. LumeScope sets up the required schema automatically on first startup. # Lumera REST API overview Source: https://docs.lumera.io/api/overview Learn the base URLs, authentication model, and response format for the LumeScope, LCD, and SN-API REST surfaces. Lumera Protocol gives you three complementary REST API surfaces. The **Lumera LCD** endpoint speaks directly to the chain. Use it to query state and broadcast transactions with no middleware in the way. **LumeScope** sits in front of the network as a neutral, read-only aggregator. It decodes Action metadata for Cascade and Sense, tracks SuperNode hardware metrics, and assembles network-wide statistics. Your application does not have to reconstruct that data from raw chain state. **SN-API** is the HTTP gateway in front of the SuperNode network and carries Cascade file transfers. All three return JSON. LCD and LumeScope need no authentication for reads; SN-API uploads and private downloads require an ADR-036 signature. ## API surfaces **Lumera LCD (native chain API)** is the standard Cosmos SDK REST interface served by any `lumerad` node. Use it when you need to submit transactions, query on-chain parameters, or access data that LumeScope does not index. **LumeScope API** is a purpose-built aggregator that decodes, stores, and serves Lumera-specific data in developer-friendly shapes. It is the recommended starting point for application developers who want paginated Action lists, decoded metadata, and SuperNode health without writing their own chain-indexing logic. **SN-API** is the HTTP gateway that fronts the SuperNode network for Cascade uploads and downloads. The JavaScript and Rust SDKs call it for you. Call it directly when you already hold an on-chain action ID and an upload signature. The direct SuperNode gRPC service is a separate, lower-level path documented in the [CascadeService reference](/api/grpc/cascade-service). ## Base URLs | Network | LumeScope Base | LCD Base | SN-API Base | | - | - | - | - | | Mainnet | `https://lumescope.lumera.io` | `https://lcd.lumera.io` | `https://snapi.lumera.io` | | Testnet | `https://lumescope.testnet.lumera.io` | `https://lcd.testnet.lumera.io` | `https://snapi.testnet.lumera.io` | All examples in this documentation target mainnet. Replace the base URL with the testnet equivalent when developing against **lumera-testnet-2**. ## Authentication All LumeScope endpoints are **public**. No API key or bearer token is required. You can call any endpoint directly from a browser, curl, or your application's HTTP client. ## Response format Every LumeScope response is JSON. Successful responses use HTTP `200` status codes. Error responses include a machine-readable `error` field and an appropriate 4xx or 5xx status code. ## OpenAPI specification and interactive docs LumeScope embeds a full OpenAPI 3.0 specification and an interactive Swagger UI. Use them to explore and test every endpoint. | Resource | URL | | - | - | | OpenAPI JSON spec | `{base}/openapi.json` | | Swagger UI | `{base}/docs` | For example, on mainnet browse to `https://lumescope.lumera.io/docs` to open the interactive documentation. ## Explore the API Query Cascade and Sense actions with decoded metadata, transaction lifecycle tracking, and aggregated statistics. Fetch SuperNode hardware metrics, payment statistics, version compatibility matrix, and availability status. Upload and download Cascade files through the SuperNode HTTP gateway. ## Self-hosting LumeScope You can run LumeScope yourself with a single Docker command. A private deployment gives you custom sync intervals, data sovereignty, and your own analytics pipeline. See the [LumeScope deployment guide](/api/lumescope) for instructions. # SN-API REST gateway reference Source: https://docs.lumera.io/api/sn-api Upload and download Cascade files through the REST gateway that fronts the SuperNode network. SN-API is the REST gateway in front of SuperNodes. It handles Cascade file uploads and downloads over plain HTTPS. The [JavaScript SDK](/sdk/javascript) and the [Rust SDK](/sdk/rust) route all file transfers through it. The [Go SDK](/sdk/go) skips the gateway and talks gRPC directly to SuperNodes on port 4444. Call SN-API directly when you build in a language without an official SDK or need full control over the HTTP layer. Register the action on chain first. The gateway only moves bytes for actions that already exist. ## Base URLs | Network | Base URL | | - | - | | Mainnet | `https://snapi.lumera.io` | | Testnet | `https://snapi.testnet.lumera.io` | The JavaScript SDK picks the endpoint from its `preset` configuration. The Rust SDK reads it from the `SNAPI_BASE` environment variable. ## Authentication Uploads and private downloads require an ADR-036 signature. ADR-036 is the Cosmos standard for signing arbitrary data off chain. The SDKs produce these signatures for you. When calling the API directly, follow two steps. 1. Sign the relevant data with ADR-036 `signArbitrary`. Uploads sign the data hash. Downloads sign the action ID. 2. Include the Base64 encoded signature in the request body. *** ## POST /api/v1/actions/cascade Starts a Cascade upload. Send the file as `multipart/form-data` after the on-chain registration. ### Form fields Action ID returned by the on-chain registration transaction. Base64 encoded ADR-036 auth signature. The file data. ### Example response ```json theme={null} { "task_id": "uuid-task-id" } ``` Upload task identifier. Use it with the task endpoints below. ### Status codes | Code | Meaning | | - | - | | 202 | Accepted. The upload task was created and the response carries the `task_id`. | | 400 | Invalid request. Missing fields or bad signature. | | 401 | Unauthorized | | 403 | Forbidden | | 404 | Action not found. Retry after a short delay. | | 409 | Conflict | | 500 | Server error | SuperNodes need a moment to index a freshly registered action. The SDKs retry this endpoint up to 5 times with 3 second delays. Apply the same retry policy when you call it directly. *** ## GET /api/v1/actions/cascade/tasks/\{task\_id} Returns the stored request metadata for an upload task. This endpoint describes what was requested, not how far it has progressed. For task state use the status stream or the history endpoint below. ### Path parameters Task ID returned by the upload endpoint. ### Example response ```json theme={null} { "task_id": "uuid", "action_id": "232794", "file_name": "example.png", "file_path": "/data/uploads/example.png", "file_size_kbs": 11, "signature": "base64-encoded ADR-036 signature", "created_at": "2026-01-01T00:00:00Z" } ``` ### Response fields Upload task identifier. The on-chain action this upload belongs to. Name of the uploaded file. Path the SuperNode stored the incoming file at. File size in kilobytes. The ADR-036 signature submitted with the upload. RFC 3339 timestamp of task creation. *** ## GET /api/v1/actions/cascade/tasks/\{task\_id}/status Streams progress updates for an upload task as Server-Sent Events (SSE), a one-way HTTP stream of messages. Send `Accept: text/event-stream`. Task ID returned by the upload endpoint. Each event carries a JSON payload on a `data:` line with the fields `id`, `task_id`, `status`, `data`, and `created_at`. *** ## GET /api/v1/actions/cascade/tasks/\{task\_id}/history Returns the status history for an upload task as JSON. Use this when you want to poll rather than hold a stream open. Task ID returned by the upload endpoint. *** ## POST /api/v1/actions/cascade/\{action\_id}/downloads Requests a download task for a stored file. Private files need an ADR-036 signature. Files stored with `isPublic: true` can be requested without one. ### Path parameters The on-chain action ID of the stored file. ### Query parameters Set to `true` for a file that was stored with `isPublic: true`. The signature then becomes optional. ### Request body Base64 encoded ADR-036 signature of the action ID. Required unless you pass `public=true`. ### Example request for a private file ```json theme={null} { "signature": "base64-encoded ADR-036 signature of the action_id" } ``` ### Example request for a public file ```bash theme={null} curl -X POST "https://snapi.lumera.io/api/v1/actions/cascade/{action_id}/downloads?public=true" ``` Requesting a private file without a signature returns `400` with the message `signature is required`. ### Example response ```json theme={null} { "task_id": "uuid-download-task-id" } ``` Download task identifier. Use it with the status and file endpoints below. ### Status codes | Code | Meaning | | - | - | | 202 | Accepted. The download task was created and the response carries the `task_id`. | | 400 | Invalid request | | 404 | Action not found | | 500 | Server error | As with the upload endpoint, this creates a task and returns `202 Accepted`. The file itself comes from the file endpoint below. *** ## GET /api/v1/downloads/cascade/\{task\_id}/status Streams progress updates as Server-Sent Events (SSE), a one-way HTTP stream of messages. Send `Accept: text/event-stream`. Download task ID returned by the download request endpoint. Each event carries the same fields as a history entry, so `id`, `task_id`, `status`, `data`, and `created_at`. There is no percentage field. Progress is expressed as a sequence of named states, and `data` carries any detail that state has. ### Example stream ```text theme={null} data: {"id":1,"task_id":"7ca7b6c9","status":"sdk:started","data":{},"created_at":"2026-01-01T00:00:00Z"} data: {"id":2,"task_id":"7ca7b6c9","status":"sdk:supernodes_found","data":{"count":7,"total":10},"created_at":"2026-01-01T00:00:03Z"} data: {"id":3,"task_id":"7ca7b6c9","status":"sdk:completed","data":{},"created_at":"2026-01-01T00:00:41Z"} ``` States are namespaced by who reported them. `sdk:` states come from the gateway's client, and `supernode:` states come from the SuperNode serving the request. The terminal states are `sdk:completed` and `sdk:failed`. In browsers, consume the stream with `EventSource`. ```ts theme={null} const es = new EventSource( `https://snapi.testnet.lumera.io/api/v1/downloads/cascade/${taskId}/status` ); es.onmessage = (event) => { const update = JSON.parse(event.data); console.log(update.status, update.data); if (update.status === "sdk:completed" || update.status === "sdk:failed") { es.close(); } }; ``` To poll for status as JSON instead of holding this stream open, use the history endpoint below. *** ## GET /api/v1/downloads/cascade/\{task\_id}/history Returns the status history for a download task as JSON. This is the polling counterpart to the stream above. Download task ID returned by the download request endpoint. *** ## GET /api/v1/downloads/cascade/\{task\_id}/file Streams the file bytes once the download task completes. The response arrives with `Content-Type: application/octet-stream`. Download task ID returned by the download request endpoint. Consume the response as a `ReadableStream` to keep memory use flat on large files. *** ## Version fallback The SDKs try versioned paths first (`/api/v1/...`) and fall back to legacy paths (`/api/...`) on a 404. This keeps them compatible across SN-API versions. Prefer the `/api/v1/` prefix when calling the API directly. ## Errors Error bodies carry a message and a machine readable code. ```json theme={null} { "error": "action not found", "code": "NOT_FOUND" } ``` | Code | Cause | | - | - | | `NOT_FOUND` | The action does not exist or is not indexed yet | | `UNAUTHORIZED` | The ADR-036 signature is invalid | | `SERVICE_UNAVAILABLE` | No SuperNode is available to serve the request | ## Rate limits The public SN-API endpoints may rate limit during high traffic. For high throughput production workloads, run your own SuperNode. The [SuperNode overview](/supernodes/overview) explains the requirements. ## Next steps What happens between registration and DONE. How SuperNodes reassemble your file from symbols. Retry strategies for uploads and downloads. # LumeScope SuperNodes API reference Source: https://docs.lumera.io/api/supernodes Query SuperNode hardware metrics, payment statistics, version compatibility matrix, and availability status using the LumeScope read-only REST API. The LumeScope SuperNodes API aggregates hardware telemetry, payment statistics, availability probes, and version compatibility data for every SuperNode registered on the Lumera network. LumeScope probes each SuperNode on a configurable interval and stores the results. Your application can query current status, rank individual nodes by resources, and identify nodes that are temporarily unreachable. You never need direct access to an operator's machine. All SuperNodes API endpoints are read-only and require no authentication. The base URL for mainnet is `https://lumescope.lumera.io`. Substitute `https://lumescope.testnet.lumera.io` for testnet. *** ## GET /v1/supernodes/metrics Returns hardware and network metrics for all SuperNodes. Use query parameters to filter by status, version, or failure threshold, and to paginate large result sets. ### Query parameters Filter by probe status. Common values are `available` and `unavailable`. Filter by the node's on-chain validator state. Filter to nodes running a specific software version string. Return only nodes whose consecutive probe failure count is at or above this threshold. Useful for identifying persistently offline nodes. Maximum number of SuperNodes to return per page. Opaque pagination cursor returned by the previous response. Pass this value to retrieve the next page. ### Example request ```shell All supernodes theme={null} curl https://lumescope.lumera.io/v1/supernodes/metrics ``` ```shell Available nodes only theme={null} curl "https://lumescope.lumera.io/v1/supernodes/metrics?status=available&limit=10" ``` ### Example response ```json theme={null} { "supernodes": [ { "id": "lumera15abc...", "ip": "192.0.2.10", "status": "available", "rank": 1, "version": "1.2.0", "registered_services": ["cascade", "sense"], "peer_count": 42, "cpu_cores": 16, "cpu_usage_percent": 23.4, "memory_total_gb": 64, "memory_used_gb": 18.2, "storage_total_gb": 2000, "storage_used_gb": 450, "failed_probe_counter": 0 } ], "cursor": "eyJpZCI6Imx1bWVyYTE1YWJjIn0=" } ``` ### Response fields Array of SuperNode metric objects. Bech32 operator address of the SuperNode. Public IP address of the node. The probe status, either `available` or `unavailable`. Relative ranking among all SuperNodes (lower is better). Software version string reported by the node. List of services the node has registered, e.g. `cascade`, `sense`. Number of active peer connections at the time of the last probe. Total CPU core count on the node. CPU utilization percentage at the time of the last probe. Total installed RAM in gigabytes. RAM in use at the time of the last probe. Total storage capacity in gigabytes. Storage consumed at the time of the last probe. Number of consecutive failed probes since the last successful contact. Pagination cursor. Pass this as the `cursor` query parameter to retrieve the next page. Absent when there are no more results. *** ## GET /v1/supernodes/\{id}/metrics Returns the current hardware and network metrics for a single SuperNode identified by its operator address. ### Path parameters The Bech32 operator address of the SuperNode (e.g. `lumera15abc...`). ### Example request ```shell theme={null} curl https://lumescope.lumera.io/v1/supernodes/lumera15abc.../metrics ``` ### Example response ```json theme={null} { "id": "lumera15abc...", "ip": "192.0.2.10", "status": "available", "rank": 1, "version": "1.2.0", "registered_services": ["cascade", "sense"], "peer_count": 42, "cpu_cores": 16, "cpu_usage_percent": 23.4, "memory_total_gb": 64, "memory_used_gb": 18.2, "storage_total_gb": 2000, "storage_used_gb": 450, "failed_probe_counter": 0 } ``` The response object uses the same field schema as the individual entries in `GET /v1/supernodes/metrics`. *** ## GET /v1/supernodes/\{id}/paymentInfo Returns payment statistics broken down by denomination for a specific SuperNode. Use this endpoint to audit reward distribution or inspect historical payment totals. ### Path parameters The Bech32 operator address of the SuperNode. ### Example request ```shell theme={null} curl https://lumescope.lumera.io/v1/supernodes/lumera15abc.../paymentInfo ``` ### Example response ```json theme={null} { "id": "lumera15abc...", "payments": [ { "denom": "ulume", "total_amount": "1500000000", "payment_count": 3200 } ] } ``` ### Response fields Bech32 operator address of the SuperNode. List of payment totals, one entry per denomination. Token denomination (e.g. `ulume`). Cumulative payment amount in the smallest unit of the denomination. Number of individual payment transactions included in this total. *** ## GET /v1/supernodes/stats Returns aggregated hardware statistics across all SuperNodes. You get network-wide totals and averages for CPU, memory, and storage. Use this endpoint for capacity-planning dashboards and fleet health snapshots. ### Example request ```shell theme={null} curl https://lumescope.lumera.io/v1/supernodes/stats ``` ### Example response ```json theme={null} { "total_supernodes": 120, "available_count": 115, "unavailable_count": 5, "avg_cpu_usage_percent": 21.8, "total_storage_tb": 240, "total_memory_gb": 7680 } ``` ### Response fields Total number of SuperNodes tracked by LumeScope. Number of SuperNodes with a passing probe status. Number of SuperNodes that failed their most recent probe. Fleet-wide average CPU utilization percentage. Combined storage capacity across all available SuperNodes, in terabytes. Combined RAM across all available SuperNodes, in gigabytes. *** ## GET /v1/supernodes/action-stats Returns per-SuperNode action statistics. The response shows how many Cascade and Sense actions each node has participated in. Use this endpoint to identify the most active nodes or detect imbalanced workload distribution. ### Example request ```shell theme={null} curl https://lumescope.lumera.io/v1/supernodes/action-stats ``` ### Example response ```json theme={null} { "supernodes": [ { "id": "lumera15abc...", "cascade_count": 420, "sense_count": 180, "total_count": 600 } ] } ``` ### Response fields Array of per-SuperNode action count objects. Bech32 operator address of the SuperNode. Number of Cascade actions this node has been involved in. Number of Sense actions this node has been involved in. Combined Cascade and Sense action count. *** ## GET /v1/supernodes/unavailable Returns the list of SuperNodes whose most recent probe reported an unavailable status. This endpoint is the quickest way to triage connectivity or operator issues without filtering the full metrics list. ### Query parameters Filter by on-chain validator state, e.g. `active`, `jailed`. ### Example request ```shell theme={null} curl https://lumescope.lumera.io/v1/supernodes/unavailable ``` ### Example response ```json theme={null} { "supernodes": [ { "id": "lumera1xyz...", "ip": "192.0.2.99", "failed_probe_counter": 12, "last_seen_at": "2024-01-14T08:00:00Z" } ] } ``` ### Response fields Array of unavailable SuperNode objects. Bech32 operator address. Public IP address of the node. Consecutive probe failures since the node was last reachable. RFC 3339 timestamp of the last successful probe. *** ## POST /v1/supernodes/sync Triggers an immediate, out-of-schedule sync and probe of all SuperNodes. Under normal operation LumeScope syncs automatically on the configured `SUPERNODES_SYNC_INTERVAL`. Use this endpoint when you need fresh data immediately after an operator change or network event. Manual sync is only available when the LumeScope instance is configured to allow it. On the public mainnet and testnet endpoints this endpoint may return `403 Forbidden`. ### Example request ```shell theme={null} curl -X POST https://lumescope.lumera.io/v1/supernodes/sync ``` ### Example response ```json theme={null} { "status": "sync triggered", "triggered_at": "2024-01-15T11:00:00Z" } ``` ### Response fields Confirmation message indicating the sync was accepted. RFC 3339 timestamp at which the sync was initiated. *** ## GET /v1/version/matrix Returns the version compatibility matrix for SuperNode software, implementing a partial view of the Lumera Enhancement Proposal 2 (LEP2) versioning scheme. Use this endpoint to determine which node versions are compatible with the current network state. ### Example request ```shell theme={null} curl https://lumescope.lumera.io/v1/version/matrix ``` ### Example response ```json theme={null} { "matrix": [ { "node_version": "1.2.0", "compatible_versions": ["1.1.0", "1.2.0"], "deprecated": false }, { "node_version": "1.1.0", "compatible_versions": ["1.1.0"], "deprecated": true } ], "current_recommended": "1.2.0" } ``` ### Response fields Array of version compatibility entries. The SuperNode software version this entry describes. List of other software versions that this version can inter-operate with. `true` if this version is end-of-life and operators should upgrade. The latest recommended SuperNode software version according to the compatibility matrix. *** Poll `GET /v1/supernodes/unavailable` on a schedule and alert your team when `failed_probe_counter` exceeds a threshold. This gives you early warning of persistent connectivity issues before they affect action processing. # Protocol architecture Source: https://docs.lumera.io/architecture How Lumera's layered architecture coordinates consensus, protocol services, and decentralized execution. Lumera separates consensus, protocol logic, decentralized services, and developer interfaces into distinct architectural layers. The blockchain serves as the protocol's control plane, coordinating requests, governance, settlement, and cryptographic proofs, while economically bonded SuperNodes execute decentralized intelligent services. Together, these layers allow protocol services to scale independently while preserving the security and trust guarantees of blockchain consensus. Lumera protocol stack Lumera protocol stack ## Protocol Foundation The protocol foundation provides consensus, accounts, governance, interoperability, and multiple smart contract execution environments. Together, these components provide the shared foundation upon which Lumera's protocol services are built. | Component | Purpose | | - | - | | **Cosmos SDK** | Core blockchain framework providing accounts, staking, governance, transactions, gRPC, and REST APIs shipped as a single `lumerad` binary. | | **CometBFT** | Byzantine Fault Tolerant consensus engine providing fast deterministic finality. | | **CosmWasm** | Deterministic Rust smart contracts executed within a WebAssembly sandbox and gas metering. | | **Cosmos EVM** | Native Solidity execution with Ethereum-compatible tooling and accounts. [Find out how →](/smart-contracts/overview) | | **IBC** | Native interoperability with other Cosmos chains through token transfers, messaging, and Interchain Accounts. [Learn more →](/cascade/concepts/interchain-accounts) | ## Validator–SuperNode architecture Lumera separates consensus from decentralized service execution through two complementary node types. This architectural separation allows the blockchain to establish shared truth while specialized infrastructure performs the protocol services applications consume. **Validators** secure the network through Delegated Proof of Stake, proposing and validating blocks with CometBFT, maintaining consensus, and participating in on-chain governance. Together, they provide the security guarantees upon which every protocol service depends. [Run a validator →](/validators/overview) **SuperNodes** are specialized network nodes operated alongside validators. They execute decentralized intelligent services—including permanent storage, verification, retrieval, and decentralized execution—while remaining economically bonded to the validator they serve. Because protocol services execute outside of consensus, Lumera can scale sophisticated decentralized services without increasing the cost or complexity of block production. [Learn more about SuperNodes →](/supernodes/overview) The blockchain acts as the protocol's **control plane**, recording requests, commitments, payments, governance, and cryptographic proofs. SuperNodes form the **execution and data plane**, performing work directly for applications and returning verifiable evidence of completion. Data moves directly between clients and SuperNodes without passing through consensus, allowing the network to remain both scalable and trust-minimized. ## Protocol Modules Lumera's protocol logic is implemented through a set of custom Cosmos SDK modules that coordinate protocol services, decentralized identity, SuperNode operations, and network state. Support modules introduced with the Cosmos EVM extend interoperability while preserving the same architectural model. The EVM upgrade has added support modules (`x/vm`, `x/feemarket`, `x/precisebank`, `x/erc20`, `x/evmigration`) documented [here](/upgrades/evm-upgrade). | Module | What it does | | - | - | | `x/action` | Coordinates the lifecycle of protocol Actions from registration through settlement | | `x/supernode` | Registers SuperNodes, maintains the active service set, tracks performance, and distributes Proof-of-Service rewards. | | `x/lumeraid` | Anchors decentralized identities to Lumera addresses | | `x/claim` | Handles legacy token claims and migration | ### The action lifecycle Every decentralized intelligent service is executed as one or more protocol Actions—on-chain records that move through a common lifecycle from registration to settlement. A client submits the registration transaction. The service fee is locked in escrow and the action gets a unique ID. The state is `PENDING`. Top ranked SuperNodes pick up the action, receive the file over gRPC, and do the work. The state is `PROCESSING`. When the work completes and results are committed, the state becomes `DONE`. An action still unfinalized at its `expirationTime` becomes `EXPIRED` and the chain refunds the escrowed fee. Where a flow requires explicit approval, an approval transaction moves the action to `APPROVED` and the escrowed fee pays out to the SuperNodes that served it. Fee escrow aligns incentives for both clients and service providers. Applications pay only when protocol services complete successfully, while SuperNodes are compensated only after verifiable completion. Governance determines the fee parameters for each protocol service. ### SuperNode registry and ranking The `x/supernode` module is the source of truth for SuperNode registrations. It stores each node's address, endpoint, and stake, maintains the ranked top list that `x/action` routes work to, and accounts for completed actions. A SuperNode must stay tied to an active validator. If the validator is jailed or unbonds, the SuperNode drops off the list. ## Protocol Services Protocol services are the decentralized capabilities applications consume through Lumera. Every service is implemented through the Action framework, executed by economically bonded SuperNodes, and coordinated by the blockchain through a common execution and settlement lifecycle. Current protocol services demonstrate this architecture today, while future services extend the same framework rather than introducing separate systems. Each SuperNode runs the `supernode` binary alongside `lumerad`. ### Cascade Cascade is permanent file storage, paid once at upload. The SuperNode receives a file through its streaming gRPC endpoint, encodes it with RaptorQ erasure coding, and spreads the symbols across a Kademlia DHT. Any sufficiently large subset of symbols rebuilds the file, so the network self heals when nodes leave. Start with [how Cascade works](/cascade/how-cascade-works) and go deeper in [erasure coding](/cascade/concepts/erasure-coding). ### Identity with LumeraID LumeraID anchors identity records to Lumera addresses through the `x/lumeraid` module. There is no separate username. Your address is your identity, and the record is controlled by your private key. The protocol uses it wherever ownership must be proven. Cascade downloads of private files require a signature that matches the registering identity, which is why files are private by default even though their encoded symbols live on a public network. Signatures follow ADR-036, the Cosmos standard for signing arbitrary data. ### Future Services Lumera's execution framework is designed to support a growing ecosystem of decentralized intelligent services. Because every service shares the same Action framework, execution lifecycle, settlement model, and verification architecture, new capabilities extend the protocol rather than introducing separate systems. Future protocol services extend this same architectural foundation to capabilities such as durable memory, decentralized identity, verifiable execution, settlement, and additional decentralized intelligent services. ## Developer Interfaces **Lumera Hub** is the official client for web, desktop, and mobile at [hub.lumera.io](https://hub.lumera.io). It covers balances, staking, storage, and identity. See the [Hub overview](/hub/overview). **LumeScope** is a read-only REST API aggregator for developers who want decoded network state without running a node. It decodes action metadata, aggregates SuperNode metrics, and serves 16 endpoints with a Swagger UI. See the [LumeScope API reference](/api/lumescope). **`lumerad`** is the chain binary and CLI. It runs nodes, manages keys, broadcasts transactions, and queries every module. See the [CLI reference](/sdk/lumerad-cli). ## Extending the Protocol The architecture above is intentionally composable. Every new decentralized intelligent service is implemented as another protocol Action moving through the same registration, execution, verification, and settlement lifecycle. Rather than introducing separate systems for memory, identity, execution, settlement, or future protocol capabilities, Lumera extends a shared architectural foundation built on common protocol modules, economically bonded SuperNodes, and a unified execution framework. As the protocol evolves, developers continue building against the same APIs, execution model, and architectural foundation while inheriting new protocol capabilities as native services. The [SDKs](/sdk/javascript) wrap the chain APIs and the SuperNode protocol for JavaScript and Go applications. ## Fee model Every protocol Action pays a one-time service fee in `ulume` (1 LUME = 1,000,000 ulume). The fee is a pure function of data size, so you can estimate it before submitting. File size is rounded up to the next whole kilobyte. ```text theme={null} fee = base_action_fee + fee_per_kbyte × ceil(fileBytes / 1024) ``` | Parameter | Value | In LUME | | - | - | - | | `base_action_fee` | `30000` ulume | 0.03 LUME | | `fee_per_kbyte` | `10` ulume | 0.00001 LUME per KB | Every successful storage adds `30000 + 10 × sizeKB` ulume to what you have spent. | File size | Storage fee (ulume) | In LUME | | - | - | - | | 1 KB | 30,010 | 0.03001 | | 1 MB (1,024 KB) | 40,240 | 0.04024 | | 10 MB (10,240 KB) | 132,400 | 0.13240 | | 100 MB (102,400 KB) | 1,054,000 | 1.05400 | | 1 GB (1,048,576 KB) | 10,515,760 | 10.51576 | | Parameter | Value | In LUME | | - | - | - | | `base_action_fee` | `10000` ulume | 0.01 LUME | | `fee_per_kbyte` | `10` ulume | 0.00001 LUME per KB | Every successful storage adds `10000 + 10 × sizeKB` ulume to what you have spent. | File size | Storage fee (ulume) | In LUME | | - | - | - | | 1 KB | 10,010 | 0.01001 | | 1 MB (1,024 KB) | 20,240 | 0.02024 | | 10 MB (10,240 KB) | 112,400 | 0.11240 | | 100 MB (102,400 KB) | 1,034,000 | 1.03400 | | 1 GB (1,048,576 KB) | 10,495,760 | 10.49576 | Governance can change these parameters, so query the exact amount from the chain instead of hardcoding it. The SDK expects the size in KB, rounded up, and returns the fee in `ulume`. ```ts theme={null} const fileSizeKb = Math.ceil(file.length / 1024); const { amount } = await client.Blockchain.Action.getActionFee(fileSizeKb); // amount is a string in ulume. The SDK sets this as the action price. ``` The fee is locked in escrow at registration and released when the Action settles, following [the action lifecycle](#the-action-lifecycle). On mainnet, 98% of each fee pays the SuperNodes that served the Action and 2% goes to the foundation. If the Action expires before finalization, the chain refunds the full fee. Storage is paid once, with no recurring charges, and reads are not metered by the protocol. The service fee is separate from the gas fee charged on every transaction. The minimum gas price is `0.025ulume`. See [Networks](/networks) for chain parameters and [Protocol revenue](/protocol-revenue) for how fees flow through the network. ## Next steps The full journey of a Cascade upload through these layers. The EVM environment on the base layer. Join the consensus layer. # Set up the Cascade client Source: https://docs.lumera.io/cascade/client-setup Create a Lumera client with network presets, custom endpoints, and a wallet signer. Every Cascade operation starts with a client. The `createLumeraClient` factory connects to the Lumera Protocol blockchain and configures the SuperNode API client in one call. This page shows how to build the client with each supported signer and how to point it at any network. ## Chain configuration Keplr requires a chain configuration to register the Lumera network. Define it once and reuse it. ```ts chain-config.ts theme={null} export const CHAIN_ID = "lumera-testnet-2"; export const LUMERA_CHAIN_INFO = { chainId: CHAIN_ID, chainName: "Lumera Testnet", rpc: "https://rpc.testnet.lumera.io", rest: "https://lcd.testnet.lumera.io", bip44: { coinType: 60 }, bech32Config: { bech32PrefixAccAddr: "lumera", bech32PrefixAccPub: "lumerapub", bech32PrefixValAddr: "lumeravaloper", bech32PrefixValPub: "lumeravaloperpub", bech32PrefixConsAddr: "lumeravalcons", bech32PrefixConsPub: "lumeravalconspub", }, currencies: [ { coinDenom: "LUME", coinMinimalDenom: "ulume", coinDecimals: 6 }, ], feeCurrencies: [ { coinDenom: "LUME", coinMinimalDenom: "ulume", coinDecimals: 6, gasPriceStep: { low: 0.025, average: 0.03, high: 0.04 }, }, ], stakeCurrency: { coinDenom: "LUME", coinMinimalDenom: "ulume", coinDecimals: 6, }, features: ["stargate", "ibc-transfer", "eth-address-gen", "eth-key-sign"], }; ``` ## Choose a signer The client accepts any signer that provides `signDirect`, `signAmino`, and `signArbitrary`. The `signArbitrary` method produces ADR-036 signatures, the Cosmos standard for signing arbitrary offline data. Cascade uses those signatures to authenticate with SuperNodes. ### Browser with Keplr ```ts keplr-client.ts theme={null} import { createLumeraClient, getKeplrSigner } from "@lumera-protocol/sdk-js"; import { CHAIN_ID, LUMERA_CHAIN_INFO } from "./chain-config"; // Register the Lumera chain and request wallet access await window.keplr.experimentalSuggestChain(LUMERA_CHAIN_INFO); // getKeplrSigner returns a unified signer with getAccounts, signDirect, // signAmino, and signArbitrary already wired up. const signer = await getKeplrSigner(CHAIN_ID); const [account] = await signer.getAccounts(); const client = await createLumeraClient({ preset: "testnet", signer, address: account.address, gasPrice: "0.025ulume", }); ``` ### Node.js with a mnemonic In Node.js you build the signer from a mnemonic. You need a Direct signer for transaction signing and an Amino signer for `signArbitrary`. ```ts node-client.ts theme={null} import { createLumeraClient } from "@lumera-protocol/sdk-js"; import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import { Secp256k1HdWallet, makeSignDoc as makeAminoSignDoc, } from "@cosmjs/amino"; const mnemonic = process.env.LUMERA_MNEMONIC!; const directWallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "lumera", }); const aminoWallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "lumera", }); const [account] = await directWallet.getAccounts(); const signer = { getAccounts: () => directWallet.getAccounts(), signDirect: (addr: string, doc: any) => directWallet.signDirect(addr, doc), signAmino: (addr: string, doc: any) => aminoWallet.signAmino(addr, doc), async signArbitrary( chainId: string, signerAddress: string, data: string ) { const signDoc = makeAminoSignDoc( [ { type: "sign/MsgSignData", value: { signer: signerAddress, data: Buffer.from(data).toString("base64"), }, }, ], { gas: "0", amount: [] }, "", "", 0, 0 ); const { signature } = await aminoWallet.signAmino(signerAddress, signDoc); return { signed: data, signature: signature.signature, pub_key: signature.pub_key, }; }, }; const client = await createLumeraClient({ preset: "testnet", signer: signer as any, address: account.address, gasPrice: "0.025ulume", }); ``` Never hardcode mnemonics. Use environment variables or a secrets manager. ## Configuration options ```ts theme={null} interface LumeraClientConfig { // Use a preset for testnet or mainnet endpoints preset?: "testnet" | "mainnet"; // Or specify endpoints manually // (without a preset, rpcUrl, chainId, and snapiUrl are all required) rpcUrl?: string; lcdUrl?: string; chainId?: string; snapiUrl?: string; // Signer (wallet or DirectSecp256k1HdWallet) - required signer: OfflineSigner; // Sender bech32 address - required address: string; // Gas price for transaction fee estimation gasPrice?: string; // HTTP client options for SN-API requests http?: { timeout?: number; // default 30000 maxRetries?: number; // default 3 }; } ``` Set `gasPrice` to `0.025ulume`. That is the minimum gas price on both networks. ### Presets | Preset | Chain ID | RPC | LCD | SN-API | | - | - | - | - | - | | `testnet` | `lumera-testnet-2` | `https://rpc.testnet.lumera.io` | `https://lcd.testnet.lumera.io` | `https://snapi.testnet.lumera.io` | | `mainnet` | `lumera-mainnet-1` | `https://rpc.lumera.io` | `https://lcd.lumera.io` | `https://snapi.lumera.io` | The public mainnet RPC and LCD endpoints are coming soon. Use the `testnet` preset while you build and test. ### Custom endpoints Pass explicit endpoints for local development or self-hosted nodes. ```ts theme={null} const client = await createLumeraClient({ chainId: "", // whatever you passed to `lumerad init --chain-id` rpcUrl: "http://localhost:26657", lcdUrl: "http://localhost:1317", snapiUrl: "http://localhost:8080", signer: wallet, address: account.address, gasPrice: "0.025ulume", }); ``` ## Client structure Once created, the client exposes two primary sub-clients. ```ts theme={null} // --- Blockchain queries (nested under Action / Supernode / Tx) --- client.Blockchain.Action.getParams(); client.Blockchain.Action.getAction(actionId); client.Blockchain.Action.getActionFee(dataSizeKb); // size in KB, rounded up client.Blockchain.Supernode.listSupernodes(); client.Blockchain.Tx.signAndBroadcast(signerAddress, messages, fee); // --- Cascade storage (high-level) --- client.Cascade.uploader.uploadFile(file, params); // upload + monitor client.Cascade.downloader.download(actionId); // signs the action ID via ADR-036 ``` ## Go client The Go SDK follows the same shape. The `lumerasdk.New` entry point takes a config and a keyring, then surfaces `client.Blockchain` for chain queries and `client.Cascade` for storage. ```go main.go theme={null} package main import ( "context" "fmt" "github.com/cosmos/cosmos-sdk/crypto/keyring" lumerasdk "github.com/LumeraProtocol/sdk-go/client" "go.uber.org/zap" ) func main() { ctx := context.Background() kr, _ := keyring.New("lumera", "test", "/tmp", nil) client, err := lumerasdk.New(ctx, lumerasdk.Config{ ChainID: "lumera-testnet-2", GRPCEndpoint: "grpc.testnet.lumera.io:443", RPCEndpoint: "https://rpc.testnet.lumera.io", Address: "lumera1...", KeyName: "my-key", }, kr, lumerasdk.WithLogger(zap.NewExample())) if err != nil { panic(err) } defer client.Close() } ``` See [Go SDK](/sdk/go) for uploads, downloads, and event subscriptions in Go. ## Next steps See every phase of an upload in detail. Diagnose failures and tune retries and timeouts. Build a browser upload app with Keplr. # Cascade download lifecycle Source: https://docs.lumera.io/cascade/concepts/download-lifecycle How the SDK authenticates, tracks, and streams a file download from SuperNodes. To retrieve a file from Cascade, Lumera Protocol's permanent storage service, you only need the `action_id` returned during upload. Knowing the four steps behind a download helps you show progress in your app and handle failures cleanly. Every download request is authenticated with an [ADR-036](https://docs.cosmos.network/main/build/architecture/adr-036) signature. ADR-036 is the Cosmos standard for signing arbitrary data outside a transaction. The SDK signs the `action_id` string with the connected wallet. ```ts theme={null} const signature = await signer.signArbitrary(chainId, address, actionId); ``` This proves the requester holds the private key for their Lumera address. Any wallet can download a public file. Only authorized wallets can download a private file. The SDK sends a download request to the SN-API, the HTTP API served by SuperNodes. ```text theme={null} POST /api/v1/actions/cascade/{action_id}/downloads Content-Type: application/json { "signature": "base64-encoded ADR-036 signature" } ``` The SN-API responds with a `task_id` that tracks the download preparation. The SDK retries this request up to 3 times on transient server errors (HTTP 500). SuperNodes may need time to locate and gather the required chunks. The SDK monitors download progress over Server-Sent Events (SSE), a one-way HTTP stream of status updates. ```text theme={null} GET /api/v1/downloads/cascade/{task_id}/status data: {"status": "processing", "progress": 45} data: {"status": "processing", "progress": 80} data: {"status": "completed", "progress": 100} ``` In browsers the SDK uses the native `EventSource` API. In Node.js it uses a custom fetch-based SSE parser. Once the task completes, the file is available as a binary stream. ```text theme={null} GET /api/v1/downloads/cascade/{task_id}/file Content-Type: application/octet-stream ``` The SDK returns a `ReadableStream` that your application consumes. ```ts theme={null} const stream = await client.Cascade.downloader.download(actionId); // Read the stream const reader = stream.getReader(); const chunks: Uint8Array[] = []; while (true) { const { done, value } = await reader.read(); if (done) break; chunks.push(value); } // Reassemble into a single buffer const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { result.set(chunk, offset); offset += chunk.length; } // Decode as text (if applicable) const text = new TextDecoder().decode(result); ``` ## Download task states | Status | Meaning | | - | - | | `sdk:download_completed` | File ready for streaming | | `sdk:completed` | Generic success | | `sdk:download_failure` | Reconstruction failed | | `sdk:failed` | Generic failure | ## Convenience methods ### Basic download ```ts theme={null} // Returns a ReadableStream const stream = await client.Cascade.downloader.download(actionId); ``` ### Download with options ```ts theme={null} const stream = await client.Cascade.downloader.downloadFile({ actionId: "your-action-id", taskOptions: { pollInterval: 2000, timeout: 120000, }, }); ``` ### Private download The call is identical for private files. The auth signature restricts who can download. ```ts theme={null} const stream = await client.Cascade.downloader.downloadPrivate(actionId); ``` ## SN-API fallback The SN-API client tries versioned endpoints first (`/api/v1/...`). On a 404 response it falls back to legacy paths (`/api/...`). This keeps the SDK compatible across SN-API versions. ## Next steps What happens between selecting a file and permanent storage. How SuperNodes reconstruct your file from a subset of symbols. Full method reference for uploads, downloads, and task options. # RaptorQ erasure coding in Cascade Source: https://docs.lumera.io/cascade/concepts/erasure-coding Why Cascade uses RaptorQ fountain codes and how LEP-1 derives layout IDs. Erasure coding is why files stored through Cascade on Lumera Protocol stay available even when many SuperNodes go offline. Understanding it explains Cascade's durability guarantees and the layout IDs that appear in every upload. ## Why erasure coding beats replication Simple replication stores N full copies of a file. That is wasteful. If your data must survive half the nodes going offline, replication needs 2x the storage. Erasure coding reaches the same reliability with only about 1.5x overhead. Cascade uses RaptorQ, a fountain code standardized in [RFC 6330](https://www.rfc-editor.org/rfc/rfc6330). A fountain code generates a practically unlimited stream of encoded symbols from a source file. RaptorQ has three properties that matter for permanent storage. * It encodes a file into N symbols where any K symbols (`K < N`) reconstruct the original. * Its decoding failure probability is near zero. * It operates efficiently on large files. ## Encoding during upload RaptorQ Encoding Flow RaptorQ Encoding Flow 1. The SDK loads the file into the RaptorQ WASM module (`rq-library-wasm`). 2. The encoder produces a layout that describes source blocks and encoding parameters. 3. Encoded symbols are distributed across SuperNodes. 4. Each SuperNode stores a subset of the symbols. SuperNodes run the same encoding through [`rq-go`](https://pkg.go.dev/lumera.build/rq-go), the official RaptorQ Go library. ## Decoding during download 1. The SN-API, the HTTP API served by SuperNodes, gathers available symbols from responsive SuperNodes. 2. As long as at least K symbols are available, the original file is reconstructed. 3. The BLAKE3 hash of the reconstructed file is verified against the on-chain record. ## LEP-1 layout and ID derivation LEP-1 (Lumera Enhancement Proposal 1) defines the deterministic algorithm that derives layout IDs from the encoding layout. ### Layout generation ```ts theme={null} const layout = await createSingleBlockLayout(fileBytes, { rq_ids_ic: randomSeed, rq_ids_max: actionParams.rq_ids_max, }); ``` `rq_ids_ic` is a random counter seed. `rq_ids_max` sets how many layout IDs are derived. The layout JSON is compacted (no whitespace) before hashing so every implementation produces identical bytes. ### ID derivation algorithm Each layout ID is derived deterministically so the on-chain Go implementation can independently verify it. ```text theme={null} For i = 0 to rq_ids_max - 1: counter = rq_ids_ic + i input = Base64(compacted_layout_json) + "." + Base64(adr036_signature) + "." + String(counter) compressed = zstd_compress(input, level=3) hash = BLAKE3(compressed) layout_id = Base58(hash) ``` ### Why determinism matters * **Determinism.** The same file, signer, and seed always produce the same layout IDs. * **Verifiability.** The chain can check that the claimed layout IDs match the submitted layout. * **Uniqueness.** Each symbol gets a globally unique identifier. ## The LEP-1 index file The index file aggregates the layout IDs and the layout signature. ```json theme={null} { "version": 1, "layout_ids": ["7K9xQ2b...", "3Jm2pNd...", "..."], "layout_signature": "base64-encoded ADR-036 signature" } ``` The index file is itself signed with ADR-036, the Cosmos standard for signing arbitrary data, and included in the `MsgRequestAction` transaction. ## Next steps Where layout generation and ID derivation fit in an upload. Store to Cascade from any IBC-connected Cosmos chain. Full method reference for uploads, downloads, and task options. # Interchain accounts and Cascade Source: https://docs.lumera.io/cascade/concepts/interchain-accounts Use Cascade storage from any IBC-connected Cosmos chain through ICS-27. Your application does not need to run on Lumera Protocol to use Cascade. Any IBC-connected Cosmos chain can register storage actions through [Interchain Accounts (ICS-27)](https://ibc.cosmos.network/v8/apps/interchain-accounts/overview/), the IBC standard that lets one chain control an account on another. This opens cross-chain use cases. * A DeFi protocol on Injective can store trade receipts permanently on Cascade. * An NFT marketplace on Osmosis can use Cascade for immutable media storage. * A DAO on the Cosmos Hub can archive governance proposals. ## How it works Interchain Accounts (ICA) Flow Interchain Accounts (ICA) Flow The flow has six steps. 1. The controller chain submits `MsgSendTx` containing a packed `MsgRequestAction`. 2. The IBC relayer delivers the packet to Lumera, the host chain. 3. Lumera's ICA host module unpacks and executes the `MsgRequestAction`. 4. An ICA address on Lumera pays the escrow fee. It must be pre-funded. 5. The action is registered on-chain with an `action_id`. 6. The application uploads the file directly to the SN-API, the HTTP API served by SuperNodes, using the `action_id`. ## Application-level keypairs The ICA address on Lumera has no user-held private key. The controller chain controls it. Cascade solves this with an `app_pubkey` field in `MsgRequestAction`. * The application generates a keypair on the controller side. * The public key is included in the action registration. * The private key signs file uploads to the SN-API. * This separates the control plane (IBC messages) from the data plane (file transfers). ## ICA helpers in the Go SDK The [Go SDK](/sdk/go) provides helpers for ICA-based Cascade. ```go theme={null} import "github.com/LumeraProtocol/sdk-go/client" // Pack a MsgRequestAction for ICA submission packedMsg := client.PackRequestForICA(msg) // Pack a MsgApproveAction for ICA submission packedApprove := client.PackApproveForICA(approveMsg) // Extract action IDs from the IBC acknowledgement actionIDs := client.ExtractRequestActionIDsFromAck(ackData) ``` Configure the SDK client for ICA. ```go theme={null} cfg := &client.Config{ ICAOwnerKeyName: "ica-controller-key", ICAOwnerHRP: "cosmos", // Bech32 prefix of the controller chain } ``` ## The ICA client CLI The [Lumera ICA client](https://github.com/LumeraProtocol/lumera-ica-client) provides a reference CLI. ```bash theme={null} # Upload a file via ICA lumera-ica-client upload --file document.pdf --controller-chain cosmos # Check action status lumera-ica-client action status --action-id # Download a file lumera-ica-client download --action-id ``` ## Security considerations * **Control plane isolation.** IBC messages carry only metadata and fee escrow. File data never touches the IBC channel. * **Data plane authentication.** File uploads to the SN-API use application-level signatures, independent of the ICA address. * **Fee pre-funding.** The ICA address must hold `ulume` before registering actions. Monitor its balance to prevent failed registrations. ## Next steps Install and configure the Go SDK used for ICA flows. What happens after the action registers on-chain. The end-to-end Cascade storage model in one page. # Cascade upload lifecycle Source: https://docs.lumera.io/cascade/concepts/upload-lifecycle Phase by phase internals of a Cascade upload from file hash to permanent storage. Knowing what happens inside a Cascade upload helps you debug failed uploads and build custom flows on Lumera Protocol. This page walks through each phase, the on-chain action states, and the task states the SDK reports. A Cascade upload has three phases. The `uploadFile` method in the JavaScript SDK (`@lumera-protocol/sdk-js`) runs all three for you. You can also run them individually for fine-grained control. | Phase | SDK method | | - | - | | Prepare | `prepareFile` | | Register | `registerAction` | | Send | `sendFileToSupernodes` | ## Prepare the file ```ts theme={null} const { fileBytes, dataHash } = await client.Cascade.uploader.prepareFile(file); ``` The prepare phase does two things. 1. It converts the input file (a `Uint8Array`, `File`, or `Blob`) to a `Uint8Array`. 2. It computes a BLAKE3 hash over the file bytes. This hash becomes the permanent content identifier stored on-chain. ## Register the action on-chain ```ts theme={null} const { actionId, authSignature } = await client.Cascade.uploader.registerAction( { fileBytes, dataHash }, { fileName: "paper.pdf", isPublic: true, expirationTime: String(Math.floor(Date.now() / 1000) + 25 * 60 * 60), // 25 hours } ); ``` Registration runs seven sub-steps. ### Fetch action parameters The SDK queries the chain for the current action parameters. ```ts theme={null} const actionParams = await client.Blockchain.Action.getParams(); // Returns: { max_raptor_q_symbols, ... } ``` These parameters determine the RaptorQ encoding configuration. The SDK caches the result for 5 minutes. ### Generate the RaptorQ layout The SDK runs the file through the RaptorQ WASM module to generate an erasure coding layout. * `rq_ids_ic` is a random counter seed in `[0, max_raptor_q_symbols)`. * The layout contains block-level encoding metadata that SuperNodes need to store and reconstruct the file. * The layout JSON is compacted (no whitespace) to match the Go reference implementation. See [erasure coding](/cascade/concepts/erasure-coding) for how the layout works. ### Sign the layout with ADR-036 The compacted layout bytes are signed with [ADR-036](https://docs.cosmos.network/main/build/architecture/adr-036) `signArbitrary`. ADR-036 is the Cosmos standard for signing arbitrary data outside a transaction. ```ts theme={null} signer.signArbitrary(chainId, address, layoutBytes) ``` This signature proves the layout was created by the account that registered the action. ### Derive layout IDs Layout IDs are deterministic identifiers derived from the layout and its signature. The algorithm must match the on-chain Go implementation exactly. ```text theme={null} For each i in [0, rq_ids_max): counter = rq_ids_ic + i input = Base64(layout) + "." + Base64(signature) + "." + decimal(counter) compressed = zstd(input, level=3) hash = BLAKE3(compressed) id = Base58(hash) ``` These IDs are included in the on-chain action. SuperNodes use them to verify data integrity. ### Build the LEP-1 index file LEP-1 (Lumera Enhancement Proposal 1) defines a compact index that aggregates the layout IDs and the layout signature. ```json theme={null} { "version": 1, "layout_ids": ["7K9x...", "3Jm2...", "..."], "layout_signature": "base64..." } ``` The index file is signed with ADR-036 and included in the on-chain registration. ### Create the auth signature The SDK creates a separate signature over the `dataHash`. This auth signature later authenticates the file upload to the SN-API, the HTTP API served by SuperNodes. ### Broadcast MsgRequestAction The SDK simulates the transaction to estimate gas, then signs and broadcasts it. ```ts theme={null} // Simplified. The SDK builds this internally. const msg = { typeUrl: "/lumera.action.v1.MsgRequestAction", value: { creator: address, actionType: "ACTION_TYPE_CASCADE", dataHash: dataHash, rqIds: layoutIds, // ... other fields }, }; ``` The chain validates the message, escrows the storage fee, and emits an `action_registered` event. The SDK extracts the `action_id` from the event attributes. The `action_id` is your permanent reference to the stored file. ## Send the file to SuperNodes ```ts theme={null} const uploadResult = await client.Cascade.uploader.sendFileToSupernodes( actionId, authSignature, fileBytes, { taskOptions: { pollInterval: 2000, timeout: 300000 } } ); ``` The send phase has four steps. 1. The SDK sends the file as multipart form data to `POST /api/v1/actions/cascade`. 2. The SN-API distributes chunks to the SuperNode mesh. 3. The SDK polls `GET /api/v1/actions/cascade/tasks/{task_id}` at the configured interval. 4. The upload completes when the task reaches a terminal status. The multipart request carries three fields. ```text theme={null} FormData { action_id: string signature: string (auth signature) file: Blob } ``` ## Upload in one call For most use cases, call `uploadFile` to run all three phases. ```ts theme={null} const task = await client.Cascade.uploader.uploadFile(fileBytes, { fileName: "document.pdf", isPublic: true, expirationTime: String(Math.floor(Date.now() / 1000) + 25 * 60 * 60), // 25 hours taskOptions: { pollInterval: 2000, // Check every 2 seconds timeout: 300000, // Fail after 5 minutes }, }); ``` `uploadFile` resolves to the completed SN-API task, not the action ID. If you need to store the action ID, run the three phases yourself as shown above. ## Action states on chain (simplified) This is the simplified path a Cascade upload takes. The chain's `ActionState` enum has eight values, including `UNSPECIFIED`, `APPROVED` and `REJECTED`, which a Cascade upload does not pass through. See [Actions](/intelligence-layer/verifiable-execution/actions) for the full enum. ```text theme={null} PENDING --> PROCESSING --> DONE | | +------------+--> FAILED / EXPIRED ``` | State | Meaning | | - | - | | `PENDING` | Action registered and fee escrowed, waiting for the file | | `PROCESSING` | SuperNodes encode the file and distribute symbols | | `DONE` | Symbols stored, the file is permanently available | | `FAILED` | Finalization failed | | `EXPIRED` | No SuperNode finalized before `expirationTime` | The storage fee is escrowed at registration. The chain refunds it if the action expires before finalization. The current mainnet fee is a 30,000 `ulume` base plus 10 `ulume` per KB. A 1 MB file costs about 0.04 LUME. Testnet uses a 10,000 `ulume` base, so the same file costs about 0.02 LUME there. Fee parameters can change through governance. ## SDK task states The SDK reports progress through task statuses. | Status | Phase | Meaning | | - | - | - | | `sdk:completed` | Upload | File successfully stored | | `sdk:upload_completed` | Upload | Upload confirmed | | `sdk:failed` | Upload | Generic failure | | `sdk:supernodes_unavailable` | Upload | No SuperNode accepted the task | | `sdk:registration_failure` | Register | On-chain registration failed | | `sdk:upload_failed` | Upload | File transfer to the SN-API failed | | `sdk:processing_failed` | Upload | The SN-API could not process the file | | `sdk:processing_timeout` | Upload | Processing exceeded the timeout | ## Retry behavior The SDK retries the SN-API upload up to 5 times with a 3-second delay between attempts. This covers the window where a SuperNode has not yet indexed the on-chain action. The chain and the SN-API are eventually consistent. ## Next steps How authenticated downloads stream files back from SuperNodes. How RaptorQ encoding and LEP-1 layout IDs work. Handle failed task states and retries in production. # Handle Cascade errors Source: https://docs.lumera.io/cascade/error-handling Diagnose common Cascade failures, tune retries and timeouts, and understand automatic refunds. Cascade uploads touch two systems, the Lumera Protocol chain and the SuperNode network, so errors can surface in several places. This page lists the failures you are most likely to hit, the fix for each one, and how the SDK handles retries, timeouts, and refunds. ## Common errors ### RPC connection errors If the Lumera RPC node is down or unreachable, client creation and blockchain transactions fail. ```ts theme={null} try { const client = await createLumeraClient({ preset: "testnet", signer, address, gasPrice: "0.025ulume" }); } catch (err) { // "Failed to connect to RPC endpoint" or "ECONNREFUSED" console.error("Cannot reach RPC node. Check your network or try a different endpoint."); } ``` Switch to a different RPC endpoint, or wait and retry. You can pass a custom endpoint instead of the preset. ```ts theme={null} const client = await createLumeraClient({ chainId: "lumera-testnet-2", rpcUrl: "https://lumera-testnet-rpc.polkachu.com", lcdUrl: "https://lumera-testnet-api.polkachu.com", snapiUrl: "https://snapi.testnet.lumera.io", signer, address, gasPrice: "0.025ulume", }); ``` ### Insufficient funds Uploads need LUME to pay the action fee. An empty wallet fails with this error. ```text theme={null} "insufficient funds: 0ulume is smaller than 50000ulume" ``` Fund your wallet with testnet tokens from the [faucet](/faucet) before uploading. ### Keplr extension not found The user has not installed Keplr, or the page loaded before the extension injected `window.keplr`. Wait for the extension before you access it. ```ts theme={null} async function waitForKeplr(timeout = 3000): Promise { if (window.keplr) return true; return new Promise((resolve) => { const timer = setTimeout(() => resolve(false), timeout); window.addEventListener("keplr_keystorechange", () => { clearTimeout(timer); resolve(true); }); }); } ``` ### Wallet popup rejected Keplr asks the user to approve every signature request. A click on Reject surfaces as an error. ```ts theme={null} try { await client.Cascade.uploader.uploadFile(file, params); } catch (err) { if (err.message?.includes("Request rejected")) { console.error("Please approve the signature request to continue"); } } ``` Prompt the user to try again and approve the popup. Cascade requests ADR-036 signatures for authentication, not transactions. Consider a pre-sign dialog that explains what is being signed. One upload can trigger several popups, one each for the layout, the index, and auth. ### SuperNodes unavailable The task fails when no SuperNodes are online to handle your request. ```text theme={null} "Task failed with status: sdk:supernodes_unavailable" ``` Wait a few minutes and retry. This usually means the testnet SuperNodes are temporarily down or overloaded. ### Upload timeout Large files or slow networks can push a task past the default timeout of 5 minutes. ```text theme={null} "Task timed out after 300000ms" ``` Increase the timeout in `taskOptions`. ```ts theme={null} const result = await client.Cascade.uploader.uploadFile(file, { fileName: "large-file.zip", isPublic: true, expirationTime: String(Math.floor(Date.now() / 1000) + 25 * 60 * 60), taskOptions: { timeout: 600000, // 10 minutes }, }); ``` ### Action not registered The transaction went through, but the SDK could not find the `action_registered` event in the response. ```text theme={null} "action_registered event not found in transaction. Available events: coin_spent, coin_received, transfer, message, tx" ``` This usually means the upload started too soon after the client was created, or the transaction failed silently on chain. Work through three checks. 1. Add a short delay before uploading so the chain can finalize the transaction. 2. Confirm the transaction succeeded on [Lumera Portal](https://portal.testnet.lumera.io/lumera-testnet-2). 3. Make sure you hold enough funds for the action fee. The transaction may have failed on gas. ### Action not found The action was registered on chain, but the SuperNode has not indexed it yet, or the action ID is invalid. SN-API responds with a 404. ```ts theme={null} try { const stream = await client.Cascade.downloader.download("99999"); } catch (err) { // HttpError with statusCode 404 console.error("Action not found. Check the Action ID."); } ``` The SDK retries this automatically with 5 attempts and 3-second delays. If the error persists, work through these steps. 1. Verify the action exists on chain in [Lumera Portal](https://portal.testnet.lumera.io/lumera-testnet-2). 2. Increase the timeout in `taskOptions`. 3. Try a different SN-API endpoint. ### Download failed or empty stream The SuperNodes could not reconstruct the file. This happens with very recent uploads that are still processing, or with actions that have expired. 1. Wait a few minutes after upload before you attempt a download. 2. Verify the action status with `client.Blockchain.Action.getAction(actionId)`. 3. Check that `state === "ACTION_STATE_DONE"`. ### Private file access denied A wallet that did not upload a private file cannot download it. ```text theme={null} "failed to verify download signature" ``` Only the wallet that uploaded the file can download it when `isPublic: false`. Connect with the wallet that created the action. ## Automatic refunds The storage fee is escrowed on chain when you register an action. The chain refunds it to your wallet automatically when the action expires because no SuperNode finalized it before its `expirationTime`. A rejected finalization does not refund on its own; the action stays open until it is finalized or expires. You do not file a claim or send another transaction. The refund is part of the protocol. ## Built-in retries The SDK retries failed HTTP requests with exponential backoff, which doubles the wait after each attempt. The defaults are listed below. | Setting | Default | | - | - | | Max attempts | `3` | | Initial delay | `1000ms` | | Max delay | `30000ms` | | Backoff multiplier | `2x` | | Retryable status codes | `408, 429, 500, 502, 503, 504` | You can override the defaults when you create the client. ```ts theme={null} const client = await createLumeraClient({ preset: "testnet", signer, address, gasPrice: "0.025ulume", http: { timeout: 60000, retry: { maxAttempts: 5, initialDelay: 2000, }, }, }); ``` File uploads and downloads skip automatic retries because the request body or response stream can only be consumed once. The SDK retries everything else automatically, such as status checks and metadata queries. ## Task options and status polling SuperNodes process upload and download tasks asynchronously. The SDK polls the task status until the task completes, then returns the result. Two settings control the polling. ```ts theme={null} taskOptions: { timeout: 300000, // 5 min default, how long to wait before giving up pollInterval: 2000, // 2s default, how often to check task status } ``` If an upload times out, the task may still be processing on the SuperNode. Do not retry immediately or you may create a duplicate action on chain. ## Getting help | Channel | Where | | - | - | | Discord | [discord.com/invite/lumeraprotocol](https://discord.com/invite/lumeraprotocol) | | GitHub issues | [LumeraProtocol/releases](https://github.com/LumeraProtocol/releases/issues) (choose `sdk-js` as the product) | | Lumera Portal | [portal.testnet.lumera.io](https://portal.testnet.lumera.io/lumera-testnet-2) | ## Next steps See every phase of an upload in detail. Learn how files are reconstructed and streamed back. # Cascade Everlight overview Source: https://docs.lumera.io/cascade/everlight/overview Learn how Everlight funds ongoing Cascade storage from a single upfront payment. Everlight is the retention economics behind Cascade, the permanent storage service of Lumera Protocol. It pays SuperNodes ongoing compensation for the Cascade data they keep, so the network can afford to hold your files long after you upload them. You still pay once. Everlight changes how that payment keeps working over time. Everlight is rolling out in phases. The chain-side pieces ship through governance-approved upgrades. This page describes the model, not a delivery schedule. ## The problem it solves Cascade charges one fee when you register a file. See [How Cascade works](/cascade/how-cascade-works) for that flow. Storage, however, is not a one-time cost. The SuperNodes that hold your data pay for disk, bandwidth, and hardware replacement every month, for as long as the data exists. Without ongoing funding, every upload creates a storage obligation that nobody is paid to meet. Operators would carry the cost themselves, and the permanence promise would rest on goodwill instead of economics. Everlight closes that gap. It turns the one-time fee model into a funded, recurring compensation stream for the operators who actually retain the data. ## The promise The user-facing contract is simple. > Pay once. Stored as long as the network sustains it. Three things follow from that sentence. * Registration is a one-time payment. There are no subscriptions and no renewals. * Retention is funded, not assumed. A dedicated pool pays SuperNodes every payment period for the Cascade bytes they hold. * The guarantee is honest. Under normal conditions data is expected to persist indefinitely, but the current model is best-effort rather than a bounded time commitment. A later evolution of Everlight adds an endowment. Part of a registration payment would be staked, and the staking yield would fund retention for a guaranteed minimum period, with best-effort retention continuing beyond it. That keeps the pay-once experience while adding a bounded guarantee. The endowment is a design direction, not a shipped feature, and no launch date is committed. ## How it works Everlight adds two mechanisms to the protocol. 1. **The Everlight SN Pool.** A module account inside `x/supernode` that collects LUME from several funding sources. Every payment period it distributes to eligible SuperNodes in proportion to the Cascade data each one retains. [How Everlight payments work](/cascade/everlight/payments) covers the pool, the periods, and the funding sources. 2. **The STORAGE\_FULL state.** A SuperNode that fills its disk stops receiving new storage assignments but keeps serving the data it holds, keeps earning Everlight payouts for that data, and stays available for compute work. [Everlight for SuperNode operators](/cascade/everlight/supernode-operators) covers the state machine and payout eligibility. ## What changes for you If you upload to Cascade, nothing changes. You register an action, pay one fee, and upload your file exactly as before. Everlight works behind the scenes to fund the retention of what you stored. If you run a SuperNode, Everlight is new revenue. You earn recurring payouts for the Cascade data you retain, on top of the existing action fees. Start with the [SuperNode overview](/supernodes/overview) if you are new to operating one. ## Next steps The pool, payment periods, and funding sources. The STORAGE\_FULL state and payout eligibility. Follow an upload from registration to permanent storage. # How Everlight payments work Source: https://docs.lumera.io/cascade/everlight/payments See how the Everlight SN Pool collects funding and pays SuperNodes for retained Cascade data. Everlight pays SuperNodes recurring compensation for the Cascade data they retain. This page explains where that money accumulates, when it is paid out, how each SuperNode's share is calculated, and where the funding comes from. Read the [Everlight overview](/cascade/everlight/overview) first if you are new to the model. Everlight is rolling out in phases. The mechanisms on this page ship through governance-approved chain upgrades, and parameters can change through governance at any time. Everlight funding and payout flow Everlight funding and payout flow ## The Everlight SN Pool The Everlight SN Pool is a module account inside `x/supernode`, the module that manages SuperNode registration, ranking, and rewards. There is no separate Everlight module. The pool account accumulates LUME between distributions, and any address can send funds to it with a standard bank transfer. The distribution logic does not care where the balance came from. It pays out whatever the pool holds when a payment period ends. ## Payment periods Payment periods are measured in block height, not wall-clock time. A governance parameter, `payment_period_blocks`, sets the number of blocks between distributions. At the end of every block the module checks how many blocks have passed since the last distribution. Once the interval has elapsed, the distribution runs automatically in that block. No transaction or off-chain trigger is involved. Two edge cases are handled cleanly. * If the pool is empty at the boundary, the period simply advances and SuperNodes receive nothing that period. * If no SuperNode is eligible at the boundary, the balance carries over to the next period. ## Proportional distribution The pool distributes to eligible SuperNodes in proportion to the Cascade data each one retains. The weight metric is `cascade_kademlia_db_bytes`, the number of bytes a SuperNode holds in its Cascade Kademlia store. SuperNodes report this metric through LEP-4, the Lumera Enhancement Proposal that defines self-reported node metrics. See the [LEP repository](https://github.com/LumeraProtocol/lumera/tree/master/docs/leps) for the proposal texts. ```text theme={null} payout = pool_balance * (your_retained_bytes / total_eligible_bytes) ``` A SuperNode is eligible for a distribution when all of the following hold. * Its state is `ACTIVE` or `STORAGE_FULL`. See [Everlight for SuperNode operators](/cascade/everlight/supernode-operators) for the state rules. * Its retained Cascade bytes meet the minimum payment threshold. * Its latest metrics report is recent enough to count as fresh. Reported bytes pass through guardrails before they become payout weight. A smoothing window averages reports over several periods, a growth cap limits how fast a node's reported share can rise, and new SuperNodes ramp up to full weight gradually. These guardrails make point-in-time manipulation of the metric unprofitable. ## Funding sources The pool draws from several sources, so no single stream is a point of failure. | Source | How it reaches the pool | | - | - | | Foundation staking rewards | The Foundation claims its own staking rewards and sends them to the pool with a standard bank transfer. No chain change is needed. | | Share of action registration fees | A governance parameter routes a share of every action registration fee to the pool. The designed share is 2 percent. | | Community Pool transfers | Any token holder can propose a transfer to the pool through governance with `MsgCommunityPoolSpend`. | Registration fees themselves do not go up. The routed share comes out of the existing fee split, so uploaders pay the same one-time price. You are never billed again after registration. ## Governance parameters Every economic knob in Everlight is a governance parameter. Token holders can tune the system through standard proposals, without a hard fork. | Parameter | Controls | | - | - | | `payment_period_blocks` | Blocks between distributions | | `registration_fee_share_bps` | Share of action registration fees routed to the pool | | `min_cascade_bytes_for_payment` | Minimum retained bytes to qualify for payouts | | `measurement_smoothing_periods` | Rolling average window for payout weight | | `new_sn_ramp_up_periods` | Ramp-up length for new SuperNodes | | `usage_growth_cap_bps_per_period` | Maximum growth rate of reported bytes per period | Query current values on chain once the parameters are live, and treat any number in this page as changeable by governance. ## Next steps The STORAGE\_FULL state and what payouts mean for your node. How parameter changes are proposed and voted on. The upload flow that Everlight keeps funded. # Everlight for SuperNode operators Source: https://docs.lumera.io/cascade/everlight/supernode-operators Understand the STORAGE_FULL state, payout eligibility, and what Everlight means for your node. Everlight turns retained Cascade data into recurring revenue for your SuperNode. This page covers what changes on the operator side, the new `STORAGE_FULL` state, and what keeps your node eligible for payouts. Read [How Everlight payments work](/cascade/everlight/payments) for the distribution mechanics. SuperNode software compatibility for Everlight shipped in `v2.5.0-testnet`. Everlight itself is rolling out in phases, and the chain-side pieces arrive through governance-approved upgrades. Keep your node current with `sn-manager` and watch upgrade announcements. The [operations guide](/supernodes/operations) covers updates and monitoring. ## The STORAGE\_FULL state Today a SuperNode that violates any compliance threshold is moved to `POSTPONED` and excluded from all work. That is the right response to an unreliable node, but a full disk is not unreliability. A node that has filled its storage allocation still holds its Cascade data and can still run compute services. Everlight introduces `STORAGE_FULL`, a state for exactly this case. The transition rules are strict. * A node enters `STORAGE_FULL` when its `disk_usage_percent` exceeds `max_storage_usage_percent` and no other compliance check is failing. The threshold is an existing SuperNode parameter, and governance can change it. * If any other check fails, with or without a full disk, the node goes to `POSTPONED` as before. The more restrictive state always wins. * When the node frees storage below the threshold, it returns to `ACTIVE`. ```text theme={null} ACTIVE ........ storage full, nothing else wrong ....... STORAGE_FULL ACTIVE ........ any other violation .................... POSTPONED STORAGE_FULL .. storage freed .......................... ACTIVE STORAGE_FULL .. another violation appears .............. POSTPONED ``` ## STORAGE\_FULL versus POSTPONED The two states look similar but their economics are opposites. | Capability | STORAGE\_FULL | POSTPONED | | - | - | - | | New Cascade storage assignments | Excluded | Excluded | | Sense (compute) | Eligible | Excluded | | Agents (compute) | Eligible | Excluded | | Everlight payouts | Paid for held data | Not eligible | A `STORAGE_FULL` node keeps earning Everlight payouts for every byte of Cascade data it already holds, and it stays eligible for compute work such as Sense verification and AI inference. Only new storage assignments stop. A `POSTPONED` node earns nothing from Everlight until it resolves its violations. Filling your disk does not end your storage revenue. Held data keeps paying, and compute work keeps flowing. Adding capacity puts you back in line for new storage assignments on top of that. ## Payout eligibility Your node qualifies for a distribution when all of the following hold. * Its state is `ACTIVE` or `STORAGE_FULL`. * Its retained Cascade bytes, reported as `cascade_kademlia_db_bytes` through LEP-4 metrics, meet the minimum payment threshold. * Its latest metrics report is fresh. Payouts are proportional to retained bytes, so the more Cascade data your node holds, the larger its share of each distribution. New nodes ramp up to full payout weight over several periods, and a smoothing window averages reported bytes across periods. All thresholds are governance parameters and can change. ## What to do as an operator 1. Run a compatible SuperNode version. Compatibility shipped in `v2.5.0-testnet`, and `sn-manager` keeps your node on current releases. 2. Keep your node compliant. Everlight rewards retention, but only for nodes that are `ACTIVE` or `STORAGE_FULL`. A `POSTPONED` node forfeits payouts entirely. 3. Watch your disk headroom. `STORAGE_FULL` protects your revenue on held data, but only `ACTIVE` nodes receive new storage assignments that grow it. 4. Follow governance. Payment period length, minimum thresholds, and the fee share that funds the pool are all on-chain parameters. ## Next steps Updates, configuration, and monitoring for your SuperNode. The pool, payment periods, and proportional distribution. What SuperNodes do and how they earn. # Build a browser app Source: https://docs.lumera.io/cascade/guides/browser-app Build a Vite and TypeScript single page app that stores files on Cascade through Keplr. In this guide you build a Vite and TypeScript single page app that connects to the Keplr wallet, uploads files to Cascade, and retrieves them again by action ID. Cascade is the permanent storage service of Lumera Protocol. Storage is paid once at upload, and reads are not metered by the protocol. Every upload returns an action ID. This is the permanent on-chain reference you later use to download the file. The app talks to Cascade straight from the browser. The SDK handles the SN-API transfer, so you do not need a backend server for storage. Browser-first Cascade architecture Browser-first Cascade architecture ## What you need * [Node.js](https://nodejs.org/) 18 or later * The [Keplr](https://www.keplr.app/) browser extension * Testnet LUME for fees. Request tokens from the [faucet](/faucet). ## Build the app Create a new Vite project and install the SDK together with the browser build tooling. ```bash theme={null} npm create vite@latest my-cascade-app -- --template vanilla-ts cd my-cascade-app npm install @lumera-protocol/sdk-js @cosmjs/proto-signing @cosmjs/stargate npm install -D vite-plugin-node-polyfills vite-plugin-wasm vite-plugin-top-level-await ``` The SDK depends on WASM modules and Node.js globals that need polyfilling in the browser. ```ts vite.config.ts theme={null} import { defineConfig } from "vite"; import { nodePolyfills } from "vite-plugin-node-polyfills"; import wasm from "vite-plugin-wasm"; import topLevelAwait from "vite-plugin-top-level-await"; export default defineConfig({ plugins: [ wasm(), topLevelAwait(), nodePolyfills({ include: ["buffer", "process"], globals: { Buffer: true, global: true, process: true }, }), ], optimizeDeps: { exclude: ["undici", "@bokuweb/zstd-wasm"], }, define: { "process.env": "{}", }, }); ``` This file holds the testnet chain ID, the gas price, and the chain record the app suggests to Keplr. ```ts src/config.ts theme={null} export const CHAIN_ID = "lumera-testnet-2"; export const GAS_PRICE = "0.025ulume"; export const CHAIN_INFO = { chainId: CHAIN_ID, chainName: "Lumera Testnet", rpc: "https://rpc.testnet.lumera.io", rest: "https://lcd.testnet.lumera.io", bip44: { coinType: 60 }, bech32Config: { bech32PrefixAccAddr: "lumera", bech32PrefixAccPub: "lumerapub", bech32PrefixValAddr: "lumeravaloper", bech32PrefixValPub: "lumeravaloperpub", bech32PrefixConsAddr: "lumeravalcons", bech32PrefixConsPub: "lumeravalconspub", }, currencies: [ { coinDenom: "LUME", coinMinimalDenom: "ulume", coinDecimals: 6 }, ], feeCurrencies: [ { coinDenom: "LUME", coinMinimalDenom: "ulume", coinDecimals: 6, gasPriceStep: { low: 0.025, average: 0.03, high: 0.04 }, }, ], stakeCurrency: { coinDenom: "LUME", coinMinimalDenom: "ulume", coinDecimals: 6, }, features: ["stargate", "ibc-transfer", "eth-address-gen", "eth-key-sign"], }; ``` `experimentalSuggestChain` registers the Lumera testnet in Keplr the first time a user connects. ```ts src/wallet.ts theme={null} import { CHAIN_ID, CHAIN_INFO } from "./config"; export async function connectKeplr(): Promise<{ address: string; signer: any; }> { if (!window.keplr) { throw new Error("Keplr extension not found. Please install Keplr."); } // Suggest the Lumera chain to Keplr await window.keplr.experimentalSuggestChain(CHAIN_INFO); await window.keplr.enable(CHAIN_ID); const offlineSigner = await window.keplr.getOfflineSignerAuto(CHAIN_ID); const accounts = await offlineSigner.getAccounts(); return { address: accounts[0].address, signer: offlineSigner, }; } ``` The `getKeplrSigner` helper wraps the Keplr signer with the ADR-036 `signArbitrary` support that Cascade uses to authenticate storage operations. ADR-036 is the Cosmos standard for signing arbitrary off-chain messages. ```ts src/cascade.ts theme={null} import { createLumeraClient, getKeplrSigner } from "@lumera-protocol/sdk-js"; import { CHAIN_ID, GAS_PRICE } from "./config"; let client: Awaited> | null = null; export async function initCascade(address: string) { const signer = await getKeplrSigner(CHAIN_ID); client = await createLumeraClient({ preset: "testnet", signer, address, gasPrice: GAS_PRICE, }); return client; } export async function uploadFile( file: File, onProgress?: (status: string) => void ): Promise { if (!client) throw new Error("Client not initialized"); onProgress?.("Reading file..."); const fileBytes = new Uint8Array(await file.arrayBuffer()); const uploader = client.Cascade.uploader; const prepared = await uploader.prepareFile(fileBytes); onProgress?.("Registering action on-chain..."); const { actionId, authSignature } = await uploader.registerAction(prepared, { fileName: file.name, isPublic: true, expirationTime: String(Math.floor(Date.now() / 1000) + 25 * 60 * 60), // 25 hours }); onProgress?.("Sending file to SuperNodes..."); await uploader.sendFileToSupernodes(actionId, authSignature, prepared.fileBytes, { taskOptions: { pollInterval: 2000, timeout: 300000 }, }); onProgress?.(`Upload complete! Action ID: ${actionId}`); return actionId; } export async function downloadFile(actionId: string): Promise { if (!client) throw new Error("Client not initialized"); const stream = await client.Cascade.downloader.download(actionId); const reader = stream.getReader(); const chunks: Uint8Array[] = []; while (true) { const { done, value } = await reader.read(); if (done) break; if (value) chunks.push(value); } // Reassemble chunks const totalLength = chunks.reduce((sum, c) => sum + c.length, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { result.set(chunk, offset); offset += chunk.length; } return result; } ``` The page has three controls. A connect button, an upload button with a file picker, and a download button with an action ID field. ```ts src/main.ts theme={null} import { connectKeplr } from "./wallet"; import { initCascade, uploadFile, downloadFile } from "./cascade"; const connectBtn = document.getElementById("connect") as HTMLButtonElement; const uploadBtn = document.getElementById("upload") as HTMLButtonElement; const downloadBtn = document.getElementById("download") as HTMLButtonElement; const fileInput = document.getElementById("file") as HTMLInputElement; const actionIdInput = document.getElementById("actionId") as HTMLInputElement; const status = document.getElementById("status") as HTMLDivElement; let address: string | null = null; connectBtn.addEventListener("click", async () => { try { const wallet = await connectKeplr(); address = wallet.address; await initCascade(address); status.textContent = `Connected: ${address}`; uploadBtn.disabled = false; downloadBtn.disabled = false; } catch (err: any) { status.textContent = `Error: ${err.message}`; } }); uploadBtn.addEventListener("click", async () => { const file = fileInput.files?.[0]; if (!file) { status.textContent = "Please select a file first."; return; } try { const actionId = await uploadFile(file, (s) => { status.textContent = s; }); actionIdInput.value = actionId; status.textContent = `File stored permanently! Action ID: ${actionId}`; } catch (err: any) { status.textContent = `Upload error: ${err.message}`; } }); downloadBtn.addEventListener("click", async () => { const actionId = actionIdInput.value.trim(); if (!actionId) { status.textContent = "Enter an action ID to download."; return; } try { status.textContent = "Downloading..."; const data = await downloadFile(actionId); status.textContent = `Downloaded ${data.length} bytes.`; // Trigger browser download const blob = new Blob([data]); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = "cascade-file"; a.click(); URL.revokeObjectURL(url); } catch (err: any) { status.textContent = `Download error: ${err.message}`; } }); ``` ```html index.html theme={null} Cascade File Storage

Cascade File Storage


Upload


Download

Not connected
```
```bash theme={null} npm run dev ``` Open `http://localhost:5173`, connect Keplr, and upload a file. The returned action ID is your permanent reference. Use it to retrieve the file from anywhere, at any time.
## Key takeaways 1. Use `getKeplrSigner` from the SDK. It wraps the Keplr extension signer with the ADR-036 `signArbitrary` support that Cascade requires. 2. The action ID is your permanent reference. Store it in your database, on chain, or wherever your application tracks state. 3. Uploads run in two phases. The SDK first registers the action on chain, then transfers the file to a SuperNode. 4. Downloads are streamed. Read from the `ReadableStream` so large files never sit fully in memory. ## Next steps Encrypt files client-side before uploading them to Cascade. Explore a full publishing platform built on Cascade. Run the same SDK on a server with a programmatic signer. # Store encrypted files on Cascade Source: https://docs.lumera.io/cascade/guides/encrypted-storage Encrypt files client-side with wallet-derived keys before uploading them to Cascade. Cascade, the permanent storage service of Lumera Protocol, stores files publicly by default. Any wallet can download a file if it knows the action ID. For private data, encrypt files client-side before you upload them. This guide implements wallet-based encryption with libsodium, so users only need their wallet and never a separate password. You need a browser app with a connected Cascade client and a signer that supports `signArbitrary` (ADR-036). Follow [Build a browser app](/cascade/guides/browser-app) first if you are starting fresh. The [Research Archive](/cascade/guides/research-archive) uses this exact pattern for encrypted drafts with secure collaboration. ## How it works The scheme uses two keys. The wallet key is re-derived from a wallet signature whenever needed. A random document key encrypts the file itself. A wallet signature over a fixed ADR-036 message is hashed with BLAKE2b into a wallet key that is never stored. The wallet key encrypts a random per-file document key, the document key encrypts the file bytes, and the result is packaged into a manifest JSON and uploaded to Cascade, where it is public but unreadable without the keys. A wallet signature over a fixed ADR-036 message is hashed with BLAKE2b into a wallet key that is never stored. The wallet key encrypts a random per-file document key, the document key encrypts the file bytes, and the result is packaged into a manifest JSON and uploaded to Cascade, where it is public but unreadable without the keys. ## Implement the pattern ```bash npm theme={null} npm install libsodium-wrappers-sumo ``` ```bash yarn theme={null} yarn add libsodium-wrappers-sumo ``` ```bash pnpm theme={null} pnpm add libsodium-wrappers-sumo ``` If you use Vite, exclude libsodium from dependency optimization. ```ts vite.config.ts theme={null} export default defineConfig({ // ... other config optimizeDeps: { exclude: ["libsodium-sumo", "libsodium-wrappers-sumo"], }, resolve: { alias: { "libsodium-sumo": "libsodium-sumo/dist/modules/libsodium-sumo.js", }, }, }); ``` Instead of asking users to manage separate encryption passwords, derive a deterministic key from a wallet signature. ```ts src/crypto.ts theme={null} import sodium from "libsodium-wrappers-sumo"; await sodium.ready; const DERIVE_MESSAGE = "Lumera App: Derive encryption key"; export async function deriveKeyFromWallet( chainId: string, address: string, signArbitrary: (chainId: string, address: string, data: string) => Promise ): Promise { // Sign a fixed message with ADR-036 const signResult = await signArbitrary(chainId, address, DERIVE_MESSAGE); const signatureBytes = Uint8Array.from( atob(signResult.signature), (c) => c.charCodeAt(0) ); // Hash the signature with BLAKE2b to produce a 256-bit key const key = sodium.crypto_generichash( sodium.crypto_secretbox_KEYBYTES, signatureBytes ); return key; } ``` **Why ADR-036?** Signing a fixed message means the same wallet always produces the same signature, which derives the same encryption key. The key is never stored. It is re-derived on demand, so users only need their wallet to decrypt. The helpers wrap libsodium's `crypto_secretbox` authenticated encryption. ```ts src/crypto.ts theme={null} export function encrypt( plaintext: Uint8Array, key: Uint8Array ): { ciphertext: Uint8Array; nonce: Uint8Array } { const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES); const ciphertext = sodium.crypto_secretbox_easy(plaintext, nonce, key); return { ciphertext, nonce }; } export function decrypt( ciphertext: Uint8Array, nonce: Uint8Array, key: Uint8Array ): Uint8Array { return sodium.crypto_secretbox_open_easy(ciphertext, nonce, key); } ``` Each file gets its own random document key. The document key encrypts the file. The wallet key encrypts the document key so the owner can recover it later. Everything travels inside one JSON manifest. ```ts src/encrypted-cascade.ts theme={null} import { encrypt, deriveKeyFromWallet } from "./crypto"; export async function uploadEncrypted( client: any, file: Uint8Array, fileName: string, walletKey: Uint8Array ): Promise<{ actionId: string; documentKey: string }> { // Generate a random document key (not the wallet key) const documentKey = sodium.crypto_secretbox_keygen(); // Encrypt the file with the document key const { ciphertext, nonce } = encrypt(file, documentKey); // Build a manifest with metadata + encrypted content const manifest = { version: 1, fileName, encrypted: true, nonce: sodium.to_base64(nonce), ciphertext: sodium.to_base64(ciphertext), // Encrypt the document key with the wallet key for recovery encryptedDocumentKey: sodium.to_base64( encrypt(documentKey, walletKey).ciphertext ), encryptedDocumentKeyNonce: sodium.to_base64( encrypt(documentKey, walletKey).nonce ), }; const manifestBytes = new TextEncoder().encode(JSON.stringify(manifest)); const uploader = client.Cascade.uploader; const prepared = await uploader.prepareFile(manifestBytes); const { actionId, authSignature } = await uploader.registerAction(prepared, { fileName: `${fileName}.encrypted.json`, isPublic: true, // The encryption handles confidentiality expirationTime: String(Math.floor(Date.now() / 1000) + 25 * 60 * 60), // 25 hours }); await uploader.sendFileToSupernodes(actionId, authSignature, prepared.fileBytes, { taskOptions: { pollInterval: 2000, timeout: 300000 }, }); return { actionId, documentKey: sodium.to_base64(documentKey), }; } ``` Files are uploaded as `isPublic: true` even when encrypted. The `isPublic` flag controls access at the SuperNode API level, but client-side encryption is the real confidentiality mechanism. This keeps the encrypted blob available to any collaborator you share the key with. Download the manifest, recover the document key with the wallet key, then decrypt the file. ```ts src/encrypted-cascade.ts theme={null} export async function downloadDecrypted( client: any, actionId: string, walletKey: Uint8Array ): Promise<{ plaintext: Uint8Array; fileName: string }> { // Download the encrypted manifest const stream = await client.Cascade.downloader.download(actionId); const reader = stream.getReader(); const chunks: Uint8Array[] = []; while (true) { const { done, value } = await reader.read(); if (done) break; if (value) chunks.push(value); } const totalLength = chunks.reduce((sum, c) => sum + c.length, 0); const bytes = new Uint8Array(totalLength); let offset = 0; for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.length; } const manifest = JSON.parse(new TextDecoder().decode(bytes)); // Recover the document key using the wallet key const documentKey = decrypt( sodium.from_base64(manifest.encryptedDocumentKey), sodium.from_base64(manifest.encryptedDocumentKeyNonce), walletKey ); // Decrypt the file with the document key const plaintext = decrypt( sodium.from_base64(manifest.ciphertext), sodium.from_base64(manifest.nonce), documentKey ); return { plaintext, fileName: manifest.fileName }; } ``` To share an encrypted file, re-encrypt the document key under the collaborator's wallet-derived key. ```ts theme={null} // Owner creates a share invitation // collaboratorKey is the collaborator's wallet-derived key const { ciphertext, nonce } = encrypt(documentKey, collaboratorKey); const invitation = { draftId: actionId, encryptedDocumentKey: sodium.to_base64(ciphertext), nonce: sodium.to_base64(nonce), }; // Upload the invitation to Cascade await client.Cascade.uploader.uploadFile( new TextEncoder().encode(JSON.stringify(invitation)), { fileName: `invitation_${collaboratorAddress}_${actionId}.json`, isPublic: true, expirationTime: String(Math.floor(Date.now() / 1000) + 25 * 60 * 60), // 25 hours } ); ``` The collaborator downloads the invitation, decrypts the document key with their wallet, and uses it to decrypt the file. For a complete implementation of this pattern, see the [Research Archive example](/cascade/guides/research-archive). ## Next steps See this encryption pattern inside a full application. Follow an upload from registration to completion. Look up the uploadFile and download options. # Use the SDK in Node.js Source: https://docs.lumera.io/cascade/guides/nodejs Run the Lumera JavaScript SDK on a server with a programmatic signer. The JavaScript SDK for Lumera Protocol works in both browsers and Node.js. This guide covers the Node.js setup for server-side applications, CLI tools, and backend services. In this environment a programmatic `DirectSecp256k1HdWallet` signer replaces browser wallet extensions. You build two small scripts. One uploads a file to Cascade and prints its action ID, the permanent on-chain reference for the file. The other downloads the file again. ## What you need * [Node.js](https://nodejs.org/) 18 or later * A testnet account mnemonic with LUME for fees. Request tokens from the [faucet](/faucet). ## Build the scripts ```bash npm theme={null} npm install @lumera-protocol/sdk-js @cosmjs/proto-signing @cosmjs/stargate @cosmjs/amino ``` ```bash yarn theme={null} yarn add @lumera-protocol/sdk-js @cosmjs/proto-signing @cosmjs/stargate @cosmjs/amino ``` ```bash pnpm theme={null} pnpm add @lumera-protocol/sdk-js @cosmjs/proto-signing @cosmjs/stargate @cosmjs/amino ``` The scripts below use top-level await, so set `"type": "module"` in your `package.json`. In Node.js there is no browser wallet. Use `DirectSecp256k1HdWallet` for transaction signing and build a custom signer that adds `signArbitrary` (ADR-036) for Cascade's off-chain signatures. ```ts signer.ts theme={null} import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import { Secp256k1HdWallet, makeSignDoc as makeAminoSignDoc } from "@cosmjs/amino"; export async function createNodeSigner(mnemonic: string) { const directWallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "lumera" }); const aminoWallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "lumera" }); const [account] = await directWallet.getAccounts(); const signer = { getAccounts: () => directWallet.getAccounts(), signDirect: (addr: string, doc: any) => directWallet.signDirect(addr, doc), signAmino: (addr: string, doc: any) => aminoWallet.signAmino(addr, doc), async signArbitrary(chainId: string, signerAddress: string, data: string) { const signDoc = makeAminoSignDoc( [{ type: "sign/MsgSignData", value: { signer: signerAddress, data: Buffer.from(data).toString("base64"), }, }], { gas: "0", amount: [] }, "", "", 0, 0 ); const { signature } = await aminoWallet.signAmino(signerAddress, signDoc); return { signed: data, signature: signature.signature, pub_key: signature.pub_key }; }, }; return { signer, account }; } ``` The scripts read the account mnemonic from the `LUMERA_MNEMONIC` environment variable. ```bash theme={null} export LUMERA_MNEMONIC="your twelve word mnemonic goes here" ``` Treat the mnemonic like a private key. Load it from an environment variable or a secrets manager and keep it out of source control. The upload registers an action on chain, escrows the storage fee, and transfers the file to a SuperNode. The optional `expirationTime` sets a deadline for the upload. If no SuperNode finalizes the action by then, it becomes `EXPIRED` and the escrowed fee is refunded. ```ts upload.ts theme={null} import fs from "node:fs"; import { createLumeraClient } from "@lumera-protocol/sdk-js"; import { createNodeSigner } from "./signer"; const { signer, account } = await createNodeSigner(process.env.LUMERA_MNEMONIC!); const client = await createLumeraClient({ preset: "testnet", signer: signer as any, address: account.address, gasPrice: "0.025ulume", }); const file = await fs.promises.readFile("./my-document.pdf"); const expirationTime = String(Math.floor(Date.now() / 1000) + 25 * 60 * 60); // 25 hours const uploader = client.Cascade.uploader; const prepared = await uploader.prepareFile(new Uint8Array(file)); const { actionId, authSignature } = await uploader.registerAction(prepared, { fileName: "my-document.pdf", isPublic: true, expirationTime, }); await uploader.sendFileToSupernodes(actionId, authSignature, prepared.fileBytes, { taskOptions: { pollInterval: 2000, timeout: 300000 }, }); console.log("Stored with action ID:", actionId); ``` Downloads arrive as a stream of chunks. Reassemble them and write the result to disk. ```ts download.ts theme={null} import fs from "node:fs"; import { createLumeraClient } from "@lumera-protocol/sdk-js"; import { createNodeSigner } from "./signer"; const { signer, account } = await createNodeSigner(process.env.LUMERA_MNEMONIC!); const client = await createLumeraClient({ preset: "testnet", signer: signer as any, address: account.address, gasPrice: "0.025ulume", }); const stream = await client.Cascade.downloader.download("your-action-id"); const reader = stream.getReader(); const chunks: Uint8Array[] = []; while (true) { const { done, value } = await reader.read(); if (done) break; if (value) chunks.push(value); } const fileBytes = new Uint8Array(chunks.reduce((acc, c) => acc + c.length, 0)); let offset = 0; for (const chunk of chunks) { fileBytes.set(chunk, offset); offset += chunk.length; } await fs.promises.writeFile("./downloaded-file.pdf", fileBytes); ``` ## Server-side alternatives If you prefer Go on the server, the [Go SDK](/sdk/go) covers the same Cascade upload and download operations. ## Next steps Run the same flows in the browser with Keplr. Look up every client method and option. Handle failed uploads, timeouts, and retries. # Research Archive example app Source: https://docs.lumera.io/cascade/guides/research-archive Explore a decentralized academic publishing platform built on Cascade. The [Lumera Research Archive](https://github.com/kaleababayneh/Lumera-Research-Archive) is an open source decentralized academic publishing platform. It is a production grade example of integrating Cascade, the permanent storage service of Lumera Protocol. This page walks through its architecture so you can reuse the same patterns in your own app. The app supports four things. * Public paper publishing with permanent archival on Cascade * Encrypted private drafts with wallet-based XChaCha20-Poly1305 encryption * Secure collaboration through wallet-derived key exchange * Paper discovery through LumeScope, a read-only REST aggregator that indexes Cascade actions To run it locally you need Node.js 18 or later, the Keplr extension, and testnet LUME from the [faucet](/faucet). ## Tech stack | Component | Technology | | - | - | | Runtime | Vite (vanilla TypeScript SPA) | | Blockchain SDK | `@lumera-protocol/sdk-js` v0.2.7 | | Cosmos | `@cosmjs/proto-signing`, `@cosmjs/stargate` | | Encryption | `libsodium-wrappers-sumo` (XChaCha20-Poly1305) | | Wallet | Keplr browser extension | | Indexer | LumeScope API | | Chain | `lumera-testnet-2` | ## Architecture The key design decision is that there is no backend server. The entire application runs in the browser. It talks to the Lumera blockchain over RPC for transactions and to the SuperNode API for file storage. ## Three file types on Cascade The app stores three distinct file types on Cascade and tells them apart by filename conventions. ### Published papers Public, unencrypted academic papers permanently archived on Cascade. ```ts theme={null} const manifest = { title: "My Research Paper", abstract: "A study on...", authors: ["lumera1abc..."], keywords: ["blockchain", "storage"], content: btoa(paperContent), // Base64-encoded plaintext publishedAt: new Date().toISOString(), }; const result = await client.Cascade.uploader.uploadFile( new TextEncoder().encode(JSON.stringify(manifest)), { fileName: `${manifest.title}.json`, isPublic: true, expirationTime: String(Math.floor(Date.now() / 1000) + 25 * 60 * 60), // 25 hours taskOptions: { pollInterval: 2000, timeout: 300000 }, } ); ``` ### Encrypted drafts Private drafts encrypted with a per-document key. The title stays readable as public metadata. ```ts theme={null} const manifest = { type: "draft", draftId: crypto.randomUUID(), title: "Work in Progress", // Public metadata (not encrypted) version: 1, encrypted: true, nonce: sodium.to_base64(nonce), ciphertext: sodium.to_base64(encryptedContent), encryptedDocumentKey: sodium.to_base64(encKeyForOwner), keyNonce: sodium.to_base64(keyNonce), }; await client.Cascade.uploader.uploadFile(manifestBytes, { fileName: `draft_${draftId}_v${version}.json`, isPublic: true, // Encrypted, so publicly accessible but unreadable expirationTime: String(Math.floor(Date.now() / 1000) + 25 * 60 * 60), // 25 hours }); ``` ### Collaboration invitations Key-exchange files that grant draft access to collaborators. ```ts theme={null} const invitation = { type: "invitation", draftId: draftId, from: ownerAddress, to: collaboratorAddress, encryptedDocumentKey: sodium.to_base64(reEncryptedKey), nonce: sodium.to_base64(inviteNonce), }; await client.Cascade.uploader.uploadFile(invitationBytes, { fileName: `invitation_${collaboratorAddress}_${draftId}.json`, isPublic: true, expirationTime: String(Math.floor(Date.now() / 1000) + 25 * 60 * 60), // 25 hours }); ``` ## Discovery with LumeScope The app uses [LumeScope](/api/lumescope) to discover files without scanning the entire blockchain. ```ts theme={null} // Fetch all actions by a specific creator const response = await fetch( `${LUMESCOPE_API}/v1/actions?creator=${address}&limit=10000&type=ACTION_TYPE_CASCADE` ); const actions = await response.json(); // Filter by state and filename pattern const papers = actions.filter( (a) => a.state === "ACTION_STATE_DONE" && !a.fileName?.startsWith("draft_") ); ``` ## Collaboration flow Sharing a draft moves a wrapped document key from the owner to the collaborator. Collaboration flow Collaboration flow The app link carries key material in the URL hash fragment (`#key=...`). Browsers never send the hash fragment to servers, so it stays on the client. That makes the link safe to share over any messaging channel. ## Local state The app uses `localStorage` for three things. * Draft metadata (IDs, titles, versions) * Encrypted document keys wrapped with the wallet-derived key * Collaboration invitations Cascade is the source of truth for file content. `localStorage` only caches keys and metadata. ## Source code Browse the full implementation at [github.com/kaleababayneh/Lumera-Research-Archive](https://github.com/kaleababayneh/Lumera-Research-Archive). ## Next steps Implement the wallet-derived encryption pattern used for drafts. Start your own Vite and Keplr app from scratch. Look up every SDK method the archive calls. # How Cascade works Source: https://docs.lumera.io/cascade/how-cascade-works Follow an upload from on-chain registration to permanent storage across the SuperNode network. Cascade is the permanent storage service of Lumera Protocol. You pay one fee at upload time, and the network stores your file for good. This page follows one upload from start to finish, so you know what the SDK does on your behalf. ## Architecture at a glance Cascade splits the work between two planes. The control plane is the Lumera blockchain. It records every stored file as an action and holds the storage fee. The data plane is the SuperNode network. It encodes, stores, and serves the actual bytes. The SDK drives both, so your app makes a single function call. ### Which layer you integrate against Two layers reach Cascade, and they are not the same API. 1. **Application path.** The [JavaScript](/sdk/javascript) and [Rust](/sdk/rust) SDKs call [SN-API](/api/sn-api), the HTTP gateway in front of the SuperNode network. This is the path app and browser code should take. 2. **Direct SuperNode path.** [`CascadeService`](/api/grpc/cascade-service) gRPC streaming on a SuperNode, used by the [Go SDK](/sdk/go) and operator tooling. It needs outbound access to the node's gRPC port. Use SN-API for app-facing upload and download. Reach for direct gRPC only when you are integrating with a SuperNode itself. ## The upload flow Upload flow from your app through the SDK, registering the action on chain, uploading the file to SN-API, RaptorQ encoding across the SuperNode mesh, and finalizing on chain. Upload flow from your app through the SDK, registering the action on chain, uploading the file to SN-API, RaptorQ encoding across the SuperNode mesh, and finalizing on chain. The SDK prepares the file before it touches the chain. 1. It computes a BLAKE3 hash of the file. This `data_hash` becomes the permanent content identifier stored on chain. 2. It generates a RaptorQ layout. The layout is metadata that describes how the file will be encoded, not the encoding itself. 3. It signs the layout, derives deterministic layout IDs, and builds a signed index file. The SDK then submits a `MsgRequestAction` transaction to the Lumera chain. The message carries the `data_hash`, the file name, a visibility flag for public or private access, and the one-time storage fee in `ulume`. The chain validates the transaction, escrows the fee, and emits an `action_registered` event that contains the `action_id`. The SDK extracts the ID from the transaction response. The `action_id` is the permanent on-chain reference for your file. The SDK sends a multipart POST to `/api/v1/actions/cascade` on SN-API, the REST gateway in front of the SuperNode network. The request carries three fields. | Field | Contents | | - | - | | `action_id` | The on-chain reference from the previous step | | `signature` | An ADR-036 signature of the `data_hash` | | `file` | The raw file bytes | ADR-036 is the Cosmos standard for signing arbitrary data offline. Cascade uses it to prove you control the uploading wallet without an extra transaction. SN-API returns a `task_id` that the SDK polls for progress. The SuperNode that received the file does three things. 1. It verifies that the action exists on chain, the fee is valid, the BLAKE3 hash matches, and all signatures are correct. 2. It encodes the file with RaptorQ erasure coding. RaptorQ produces redundant symbols, and any sufficiently large subset of them can rebuild the file. 3. It distributes the encoded symbols across the SuperNode mesh, so no single node becomes a point of failure. After encoding and distribution complete, the SuperNode submits a `MsgFinalizeAction` transaction back to the chain. The action state moves from `PENDING` through `PROCESSING` to `DONE`. If encoding or distribution fails, the state becomes `FAILED` instead. On success, the chain records the file as stored and retrievable, and the escrowed fee is released to the participating SuperNodes. ## Paid once Cascade charges one storage fee when the action registers, and never bills again. The current mainnet fee is 30,000 `ulume` base plus 10 `ulume` per KB. A 1 MB file costs about 0.04 LUME. Testnet uses a 10,000 `ulume` base, so the same file costs about 0.02 LUME there. These parameters can change through governance. The protocol handles payment automatically. An upload still unfinalized at its `expirationTime` returns the escrowed fee to your wallet. ## Retrieving your file Once the action reaches `DONE`, the file is permanently stored. To retrieve it, the SDK signs the `action_id` with an ADR-036 signature, authenticates with the SuperNode network, and streams the reconstructed file back to your app. See [Download lifecycle](/cascade/concepts/download-lifecycle) for the full flow. ## Next steps Upload your first file in under five minutes. Install the JavaScript and Go SDKs. See every phase of an upload in detail. # Install the Cascade SDKs Source: https://docs.lumera.io/cascade/installation Install the JavaScript and Go SDKs and prepare your bundler for the browser. Lumera Protocol ships official SDKs for JavaScript and Go. This page installs both, explains the peer dependencies, and covers the bundler configuration that browser apps sometimes need. ## Requirements | SDK | Requirement | | - | - | | JavaScript | Node.js 18 or later | | Go | Go 1.26 | You also need two things before you upload. * A Lumera wallet. Use a mnemonic for programmatic signing, or [Keplr](https://www.keplr.app/) in the browser. * Testnet `ulume` from the [faucet](/faucet). ## Install the JavaScript SDK The JavaScript and TypeScript SDK is the main way to use Cascade from web and Node.js applications. ```bash npm theme={null} npm install @lumera-protocol/sdk-js @cosmjs/proto-signing @cosmjs/stargate ``` ```bash yarn theme={null} yarn add @lumera-protocol/sdk-js @cosmjs/proto-signing @cosmjs/stargate ``` ```bash pnpm theme={null} pnpm add @lumera-protocol/sdk-js @cosmjs/proto-signing @cosmjs/stargate ``` `@cosmjs/proto-signing` and `@cosmjs/stargate` are peer dependencies. The SDK uses them for wallet and transaction handling. For a full walkthrough, see [JavaScript SDK](/sdk/javascript). ## Browser bundler configuration If you use Vite, you may need polyfills for Node.js globals. ```bash npm theme={null} npm install vite-plugin-node-polyfills vite-plugin-wasm vite-plugin-top-level-await ``` ```bash yarn theme={null} yarn add vite-plugin-node-polyfills vite-plugin-wasm vite-plugin-top-level-await ``` ```bash pnpm theme={null} pnpm add vite-plugin-node-polyfills vite-plugin-wasm vite-plugin-top-level-await ``` ```ts vite.config.ts theme={null} import { defineConfig } from "vite"; import { nodePolyfills } from "vite-plugin-node-polyfills"; import wasm from "vite-plugin-wasm"; import topLevelAwait from "vite-plugin-top-level-await"; export default defineConfig({ plugins: [ wasm(), topLevelAwait(), nodePolyfills({ include: ["buffer", "process"], globals: { Buffer: true, global: true, process: true }, }), ], optimizeDeps: { exclude: ["undici", "@bokuweb/zstd-wasm"], }, define: { "process.env": "{}", }, }); ``` For webpack in Next.js, add fallbacks in `next.config.js`. ```js next.config.js theme={null} /** @type {import('next').NextConfig} */ module.exports = { webpack: (config) => { config.resolve.fallback = { ...config.resolve.fallback, fs: false, path: false, crypto: false, }; return config; }, }; ``` ## Install the Go SDK The Go SDK targets server-side applications, CLI tools, and backend services. It queries the chain over gRPC and connects to SuperNodes directly. Add it inside a Go module. ```bash theme={null} # Initialize a module if you don't have one yet go mod init your-project # Add the SDK go get github.com/LumeraProtocol/sdk-go@latest ``` For a full walkthrough, see [Go SDK](/sdk/go). ## Verify the installation Follow the [Quickstart](/quickstart) to confirm everything works. It creates a client, connects to testnet, and uploads a file in under 50 lines. ## Next steps Create a client with presets, custom endpoints, and a signer. Walk through the JavaScript SDK from setup to upload. Build server-side uploads with the Go SDK. # Cascade Source: https://docs.lumera.io/cascade/overview The technical model behind Cascade: content addressing, RaptorQ fountain coding, availability commitments, storage challenges, and self-healing. Cascade is the Protocol's permanent storage service. A file is hashed, erasure-coded into a redundant symbol set, and distributed across the SuperNode mesh. Reconstruction requires only a subset of those symbols, so availability does not depend on any particular operator staying online. This page covers the storage model and the mechanisms underneath it. The end-to-end request flow is in [How Cascade works](/cascade/how-cascade-works), and LEP-1 layout ID derivation is in [RaptorQ erasure coding](/cascade/concepts/erasure-coding). ## The storage model Cascade is content-addressed and immutable. An object is identified by what it contains rather than where it sits, and it is never modified in place. | Identifier | Derived from | Purpose | | - | - | - | | `data_hash` | BLAKE3 of the file bytes | Permanent content identifier, recorded on chain | | `action_id` | The registering transaction | On-chain handle for the stored object | | Layout IDs | Deterministic derivation over the compacted layout, signature, and counter | Globally unique symbol identifiers the chain can verify independently | | Merkle root | BLAKE3 tree over fixed-size file chunks | Binds the uploader to the file's exact contents | Integrity is intrinsic. A retrieved file either hashes to the recorded `data_hash` or it does not, and that check is a local computation over bytes you already hold. Immutability means there is no edit, rename, or delete. Storing a modified file produces a new object with a new hash. Applications that need mutable pointers hold those pointers themselves. ## Two partitions, two purposes Cascade divides a file twice, for unrelated reasons. Conflating them is the most common source of confusion. | Partition | Unit | Size | Used for | | - | - | - | - | | RaptorQ encoding | Symbol | Uniform symbol size `T` | Redundancy and reconstruction | | Availability commitment | Chunk | Power of two, up to 256 KiB | Proving an operator holds the file | Symbols exist so the file survives node loss. Chunks exist so an operator can be challenged to prove possession. The commitment is built over chunks of the original file rather than over symbols, because chunk hashing needs only BLAKE3 over raw bytes and avoids depending on bit-identical RaptorQ library versions across client and SuperNode. ## RaptorQ Cascade encodes with RaptorQ, the fountain code standardized in [RFC 6330](https://www.rfc-editor.org/rfc/rfc6330). | Term | Meaning | | - | - | | Source block | A unit the encoder operates over. The SDK's `createSingleBlockLayout` produces a single-block layout | | Source symbol | One of K fixed-size pieces of the source block | | Symbol size (`T`) | Byte length of every symbol, uniform across the block | | `K'` | K padded up to the nearest supported value from the RFC's table | | Repair symbol | Any encoding symbol beyond the source symbols. Supply is effectively unbounded | | ESI | Encoding Symbol Identifier, distinguishing symbols within a block | | SBN | Source Block Number, distinguishing blocks within a file | RaptorQ is **systematic**, so the first K encoding symbols are the source symbols themselves. A decoder that receives exactly those does no reconstruction work. ## Why a fountain code Fountain codes are **rateless**. A Reed-Solomon style code fixes N and K at encode time. A fountain code fixes only K and then emits as many encoding symbols as asked, up to 2^24 - 1 per source block under RFC 6330. Ratelessness is what makes permanence tractable. Redundancy is not a decision frozen at upload. If symbols are lost as operators leave, more can be generated from any recovered source block, without the original uploader and without a pristine copy existing anywhere. That property is the foundation the repair machinery is built on. ## LT codes, Raptor, RaptorQ RaptorQ is the third generation of one idea, and each generation fixed a defect in the last. **LT codes** were the first practical fountain codes. Each encoding symbol is the XOR of some number of randomly chosen source symbols, with that number drawn from a tuned degree distribution. Decoding peels the graph. Find a degree-one symbol, which reveals a source symbol directly, substitute it into every symbol referencing it, repeat. It works, but peeling stalls unless the distribution is tuned aggressively, recovering the last few symbols is expensive, and decoding cost grows as O(K log K). **Raptor codes** added a pre-code. The source block is first expanded into a larger set of intermediate symbols using an outer erasure code. The LT stage then only needs to recover *most* intermediate symbols, because the pre-code reconstructs the rest. That relaxation buys linear-time decoding and low overhead. **RaptorQ** strengthens the pre-code and widens the arithmetic. Its constraints combine a low-density parity-check component over GF(2), which is plain XOR and fast, with a high-density component over GF(256). Working in a larger field for that portion sharply reduces the chance of rank deficiency during decoding, at an arithmetic cost paid on only a small part of the matrix. RaptorQ also supports much larger source blocks and defines sub-blocking so decoding memory can be bounded independently of block size. The result is the reception overhead. | Symbols received | Approximate decode probability | | - | - | | `K'` | 99% | | `K'` + 1 | 99.99% | | `K'` + 2 | 99.9999% | Two symbols of slack over the theoretical minimum puts failure near one in a million. ## Distribution Encoding creates redundancy. Distribution converts it into fault tolerance. Symbols are spread across the SuperNode mesh and stored in a Kademlia DHT, with placement following the keyspace rather than the uploader's choice. No operator holds a complete copy. This is why the guarantee is a threshold rather than a replica count. Reconstruction needs any sufficiently large subset of symbols, not particular symbols from particular operators. ## Durability versus replication Replication is intuitive and wasteful. Surviving the loss of half the network with whole copies costs roughly 2x the stored bytes. Erasure coding reaches comparable reliability at roughly 1.5x, because redundancy is spread across the symbol space instead of duplicated wholesale. The advantage grows with file size. ## Reconstruction Retrieval inverts the process. Symbols are gathered from responsive SuperNodes, the decoder rebuilds the source block once it holds enough, and the result is hashed with BLAKE3 and compared against the on-chain `data_hash`. Verification is end-to-end and client-side. A corrupted or substituted symbol set cannot produce bytes matching the recorded hash. Access control for private objects is enforced separately by signature, covered in [Download lifecycle](/cascade/concepts/download-lifecycle). ## Proving possession at finalization Erasure coding says nothing about whether an operator actually received the file. Because everything needed to compute valid layout IDs is derivable from on-chain data, an operator could once have finalized an action and collected fees without storing anything. **LEP-5, the Cascade Availability Commitment, closes that gap.** It is implemented. At registration the client splits the file into fixed-size chunks, hashes each with domain separation, builds a Merkle tree, and commits the root together with a set of challenge indices. ```text theme={null} leaf_i = BLAKE3(0x00 || uint32be(i) || chunk_i) parent = BLAKE3(0x01 || left || right) ``` Domain separation prevents a leaf from being confused with an internal node. Odd levels duplicate the final node. Chunking is constrained by the chain. Chunk size is a power of two up to 256 KiB, defaulting to 256 KiB for files of 1 MiB or more, reduced for smaller files so that every commitment yields at least four chunks. Minimum file size is 4 bytes. The number of challenged chunks is `min(svc_challenge_count, num_chunks)`, which lands between four and eight. At finalization the SuperNode rebuilds the tree from the file it received, confirms the root matches the on-chain commitment, and submits Merkle proofs for exactly the challenged indices. The chain verifies each proof against the stored root before moving the action to `DONE` and releasing fees. Proof size is O(log N) per chunk. Partial storage is caught probabilistically. An operator holding fraction `p` of chunks evades `m` challenges with probability `p^m`. | Chunks held | `m` = 5 | `m` = 8 | | - | - | - | | 90% | 59% | 43% | | 80% | 33% | 17% | | 50% | 3.1% | 0.39% | At the mainnet default of eight challenges, holding less than half the file has under a 0.4% chance of passing a single round. ## Ongoing challenges and self-healing LEP-5 proves possession once, at finalization. Retention over time is enforced separately, by periodic storage challenges. The current mechanism is epoch-based. Challengers, recipients, and observers are selected deterministically, a challenged operator returns a hash over a byte range of a stored artifact, and observers reach quorum on the result. Evidence is batched into epoch health reports rather than submitted as individual transactions. Repair already exists as well, gated by request, verify, and commit, with reconstructed content validated against the expected hash before it is persisted. **LEP-6, Storage-Truth Enforcement and Ticket-Driven Self-Healing, is approved** and upgrades both halves. The runtime ships in SuperNode v2.6.0 and later, gated by operator config and by the `x/audit` parameter `storage_truth_enforcement_mode`, which disables every LEP-6 service while it is `UNSPECIFIED`. Its substantive changes are worth knowing because they define the direction of the integrity model. * One third of active SuperNodes are challenged per epoch, deterministically selected, keeping traffic bounded. * Each challenged operator receives one compound challenge containing a **recent-ticket** and an **old-ticket** subchallenge, so ingestion and long-term retention are tested separately. * Byte sampling moves from a fixed 1 KiB prefix to four deterministic 256-byte ranges, which removes the value of optimizing for the start of a file. * Outcomes update three distinct on-chain scores. **Node suspicion** drives audit enforcement. **Ticket deterioration** drives repair scheduling. **Reporter reliability** penalizes operators whose reports diverge from the network consensus. * Repair is scheduled from ticket deterioration rather than an indirect watchlist, assigned to a single deterministic healer, and accepted only after independent verifiers confirm the healer actually restored the data, followed by a probation window. Separating node suspicion from ticket deterioration is the key design point. One question is whether an operator should be penalized. The other is whether a piece of data needs repair. They are not the same question and they no longer share a signal. Track status in the [LEP repository](https://github.com/LumeraProtocol/lumera/tree/master/docs/leps). ## Retention economics Challenges establish whether data is held. They do not explain why an operator keeps holding it after the upload fee is consumed. That is [Everlight](/cascade/everlight/overview). Payouts are proportional to retained Cascade bytes, reported through LEP-4 node metrics as `cascade_kademlia_db_bytes`. Because that metric is self-reported, it passes through guardrails before becoming payout weight, including a smoothing window across periods, a growth cap, and a ramp-up for new operators. The two mechanisms are complementary and worth keeping distinct. Everlight decides compensation from reported volume. Storage challenges establish truth and carry consequences. See [Everlight for SuperNode operators](/cascade/everlight/supernode-operators). ## Properties and limits * **Immutable and content-addressed.** No in-place edit, delete, or rename. * **Opaque bytes.** The network does not parse, index, or interpret content. Filenames and schemas are not searchable. * **No retrieval by meaning.** Objects are fetched by address. Semantic recall belongs above the Protocol. * **Private by default.** Private objects require a signature resolving to a permitted identity. For sensitive data, encrypt before upload so the network provably retains a record it cannot read. See [Store encrypted files on Cascade](/cascade/guides/encrypted-storage). * **Asynchronous write path.** Registration is a transaction, but encoding, distribution, and proof submission complete afterward, moving the action through `PENDING` and `PROCESSING` before `DONE`. * **Minimum file size is 4 bytes**, a consequence of the four-chunk floor on availability commitments. The end-to-end upload and registration flow. LEP-1 layouts and deterministic ID derivation. How long-term retention is funded. The RaptorQ library SuperNodes encode with. # Interchain accounts in practice Source: https://docs.lumera.io/cross-chain/interchain-accounts Run the full ICS-27 Cascade flow from a controller chain with the Go SDK. This page is the hands-on runbook for driving Cascade over ICS-27. You register an interchain account on Lumera, fund it, send a packed `MsgRequestAction` across IBC, upload the file, and approve the finished action. The theory behind the flow, including why file bytes never touch the IBC channel, is on the [interchain accounts concept page](/cascade/concepts/interchain-accounts). Read it first if ICS-27 is new to you. Before you start you need three things. * The [Go SDK](/sdk/go) installed. Its `ica` package assembles the packets. * An IBC connection between your controller chain and Lumera with a running relayer. See [relayer setup](/cross-chain/relayer-setup). * A funded key on the controller chain. The [lumera-ica-client](https://github.com/LumeraProtocol/lumera-ica-client) reference CLI wraps this whole flow. Run it first if you want to see a working end to end example before writing code. ## Step 1. Register the interchain account Your controller chain's ICA controller module opens a channel to Lumera and creates the account. On CosmWasm chains a contract can own the account through [cw-ica-controller](https://github.com/srdtrk/cw-ica-controller). Once the channel handshake completes, query the controller module for the host address. It is a normal `lumera1...` address, controlled only by your chain. ## Step 2. Fund the ICA address The ICA address pays the fee escrow for every action it registers. Send `ulume` to it before you submit anything. Current mainnet fees are 30,000 `ulume` base plus 10 `ulume` per KB, about 0.04 LUME for a 1 MB file. Testnet uses a 10,000 `ulume` base, about 0.02 LUME for the same file. Governance can change these parameters. You can top the address up from any Lumera account, or over IBC from a treasury on the controller chain. On testnet, the [faucet](/faucet) covers experimentation. An underfunded ICA address makes registrations fail on the host side. Monitor its balance and alert before it runs out. ## Step 3. Build and pack the MsgRequestAction Create a Cascade client configured with the ICA owner settings. `ICAOwnerHRP` is the bech32 prefix of your controller chain. ```go theme={null} import "github.com/LumeraProtocol/sdk-go/cascade" cascadeClient, err := cascade.New(ctx, cascade.Config{ ChainID: "lumera-testnet-2", GRPCAddr: "grpc.testnet.lumera.io:443", Address: hostAddr, KeyName: hostKeyName, ICAOwnerKeyName: controllerKeyName, ICAOwnerHRP: "cosmos", // bech32 prefix of the controller chain Timeout: 30 * time.Second, }, kr) ``` Build the action message without broadcasting it, then pack it for ICA delivery. ```go theme={null} import "github.com/LumeraProtocol/sdk-go/ica" // Build a MsgRequestAction for the ICA address without broadcasting it msg, err := cascadeClient.CreateRequestActionMessage(ctx, icaAddr, filePath, opts) // Pack it into protobuf Any bytes packedMsg := ica.PackRequestForICA(msg) // Wrap it into InterchainAccountPacketData for EXECUTE_TX packetData := ica.BuildICAPacketData(packedMsg) ``` ## Step 4. Send the packet from the controller chain Submit the packet with `MsgSendTx` on the controller chain. `BuildMsgSendTx` assembles it for you, and your controller chain signer broadcasts it. You can also submit the same packet data through your controller chain's CLI if you prefer scripts. ```go theme={null} // Build the controller side MsgSendTx, then broadcast it with your controller chain signer sendTx := ica.BuildMsgSendTx(owner, connectionID, packetData) ``` The relayer delivers the packet. Lumera's ICA host module unpacks the `MsgRequestAction` and executes it, and the ICA address pays the escrow. ## Step 5. Extract the action ID from the acknowledgement The IBC acknowledgement carries the registration result back to the controller chain. ```go theme={null} actionIDs := ica.ExtractRequestActionIDsFromAck(ackData) ``` If you drive the flow from scripts, the `ica` package also ships `ParseTxHashJSON`, `ExtractPacketInfoFromTxJSON`, and `DecodePacketAcknowledgementJSON` for decoding CLI transaction output. ## Step 6. Upload the file The ICA address has no user-held private key, so uploads authenticate with an application level keypair instead. Generate the keypair on the controller side. The public key travels with the action as `app_pubkey`, and the private key signs the upload. Pass the ICA options to `Upload`. ```go theme={null} res, err := cascadeClient.Upload(ctx, addr, nil, filePath, cascade.WithICACreatorAddress(icaAddr), cascade.WithAppPubkey(pubkey), cascade.WithICASendFunc(sendFunc), ) ``` `WithICASendFunc` takes the callback the SDK invokes when it needs to send an ICA message from your controller chain. The file itself streams over gRPC directly to SuperNodes. It never crosses the IBC channel. Track progress with `SubscribeToEvents`, or poll `GetAction` until the state moves from `PENDING` through `PROCESSING` to `DONE`. An action still unfinalized at its `expirationTime` becomes `EXPIRED` and the escrowed fee is refunded. ## Step 7. Download the file Downloads are authenticated too. Sign the request with a controller chain key by passing the signer address. ```go theme={null} _, err := cascadeClient.Download(ctx, actionID, destDir, cascade.WithDownloadSignerAddress(controllerAddr), ) ``` ## Step 8. Approve the action Cross-chain actions support a final confirmation step. After the action reaches `DONE`, build a `MsgApproveAction`, pack it, and send it through the same ICA path. The action then moves to `APPROVED`. ```go theme={null} // Build and pack the approval, then send it in a second MsgSendTx approveMsg, err := cascadeClient.CreateApproveActionMessage(ctx, actionID) packedApprove := ica.PackApproveForICA(approveMsg) ``` Wait for the acknowledgement, then query the action until its state reads `APPROVED`. The [sdk-go examples](https://pkg.go.dev/github.com/LumeraProtocol/sdk-go/examples) contain runnable samples for the ICA request and approve flows. ## Operational notes * **Control plane isolation.** IBC packets carry only metadata and fee escrow. File data moves on the direct gRPC data plane. * **Data plane authentication.** Uploads and downloads use application level signatures, independent of the ICA address. * **Relayer health.** Every step that crosses IBC waits on the relayer. A backed up relayer stalls registrations and approvals, so keep ICA writes off any user-facing hot path. ## Next steps Run the Hermes relayer this flow depends on. When contract driven ICA is the right choice, and when it is not. Every ICA helper and Cascade option in one place. # Cross-chain storage on Lumera Source: https://docs.lumera.io/cross-chain/overview Use Cascade as the permanent storage layer for apps on any chain. Your application does not need to run on Lumera Protocol to use Cascade. An app on any chain can register a storage action, upload the bytes, and keep only a pointer in its own state. The pointer is the numeric `action_id`. Cascade holds the bytes permanently on the SuperNode network. This section covers the two ways to wire that up, the relayer plumbing for Cosmos chains, and the integration patterns proven in real reference builds. ## Why cross-chain storage Most chains keep consensus-critical state on-chain and push large supporting data to IPFS pins or cloud buckets. That data survives only as long as someone pays the hosting bill. Cascade replaces the recurring bill with a one time on-chain payment, RaptorQ erasure coding across SuperNodes, and self-healing redundancy. Your chain keeps the small state. Cascade keeps the large, citable bytes. The `action_id` ties them together. ## Two integration families **Cosmos chains over IBC.** Lumera is IBC native. Through [ICS-27 interchain accounts](/cascade/concepts/interchain-accounts), a module or contract on your chain controls an account on Lumera and registers Cascade actions remotely. This needs an IBC channel and a running relayer, and it is the only path where a contract itself can be the signer. **Chains without IBC.** A backend service holds a funded Lumera account and writes to Cascade on behalf of your users, through the SDKs or an API gateway. This works from any stack that can run an SDK or make an HTTP call, including EVM chains. ## Reference integrations Two worked integrations shaped the patterns in this section. Cross-chain integration shape Cross-chain integration shape * **Injective.** A CosmWasm chain build that exercises all three write patterns, with contracts holding `action_id` pointers to permanent receipts and evidence. * **LUKSO.** An EVM chain build where Universal Profile metadata points at hash-bound Cascade URLs, so standard LSP readers verify the bytes without knowing Cascade exists. ## What is in this section The full ICS-27 flow with the Go SDK, from registration to approval. The Lumera values you plug into a Hermes chain config. User signed, server signed, and contract driven, and when to pick each. How interchain accounts and application keypairs work under the hood. ## Next steps Decide who signs the Cascade write before touching any plumbing. The storage model every pattern builds on. # Cross-chain integration patterns Source: https://docs.lumera.io/cross-chain/patterns Three proven ways to write to Cascade from an app on another chain. Every cross-chain Cascade integration answers one question. Who signs the Cascade write? This page generalizes the worked Injective and LUKSO integrations into three patterns, with the trade-offs that decide between them. Whatever you pick, the read side stays the same, because reading by `action_id` needs no signature at all. ## The action\_id pointer pattern Cascade's unit of storage is an action. Registering one returns a numeric `action_id`, and the bytes behind it stay retrievable permanently. Your app or contract stores that ID as an opaque string. Cascade holds the bytes. ```rust A CosmWasm contract storing a Cascade pointer theme={null} pub struct StoredArtifact { pub cid: String, // Cascade action_id, for example "15823" pub submitter: Addr, pub block_height: u64, } ``` The contract validates only that the field is non-empty. It cannot read across chains synchronously, so resolution and integrity checks happen at read time, in an indexer or frontend, off the consensus path. That single string is the entire cross-chain coupling, which is why the same pattern works from CosmWasm, Solidity, or a plain backend. ## Pattern A. User signed The user holds their own Lumera account and signs the Cascade write directly, usually from the same wallet that signs on your chain. Pattern A user signed Cascade write Pattern A user signed Cascade write **Flow.** The app signs two transactions per artifact. First a Lumera transaction uploads the file and returns an `action_id`. Then a transaction on your chain stores that `action_id` in contract or app state. **Choose it when** provenance matters. Every artifact carries a direct on-chain link to the user's own Lumera key, and your service holds no keys at all. **Trust assumptions.** None beyond the two chains. The cost is UX. The user needs a funded Lumera address, pays the `ulume` fee, and signs twice. On chains that use `eth_secp256k1` keys, such as Injective, the same wallet mnemonic produces different addresses on each chain, so your UI must keep the two identities distinct. ## Pattern B. Server signed Your backend holds one funded Lumera key and uploads on behalf of users. Users interact only with your chain or app, and the Cascade write is a server side call. Pattern B server signed via a backend Pattern B server signed via a backend **Flow.** The user submits content to your backend. The backend uploads it to Cascade with its own key, gets the `action_id`, and returns it. The user, or the backend, then records the `action_id` on your chain. You can hold the Lumera key yourself with an SDK, or forward uploads to a Cascade API gateway that holds the key and pays the fee for you. ```ts Server side upload with your own Lumera key theme={null} import { createLumeraClient } from "@lumera-protocol/sdk-js"; // One funded Lumera key uploads on behalf of users const client = await createLumeraClient({ preset: "testnet", signer, address: serverLumeraAddress, gasPrice: "0.025ulume", }); const uploader = client.Cascade.uploader; const prepared = await uploader.prepareFile(file); const { actionId, authSignature } = await uploader.registerAction(prepared, { fileName: "artifact.json", isPublic: true, expirationTime: String(Math.floor(Date.now() / 1000) + 25 * 60 * 60), // 25 hours }); await uploader.sendFileToSupernodes(actionId, authSignature, prepared.fileBytes); // actionId is the permanent reference to the stored bytes ``` **Choose it when** UX matters. One signature instead of two, no Lumera address required from users, and a single key you can monitor, quota, and rotate. This is the default in the Injective reference integration and the fastest path to shipping. **Trust assumptions.** The operator key signs every artifact, so the Lumera signature proves your service uploaded it, not the user. If you need per-user provenance, have your contract store the `(user, action_id)` pair so the binding lives on your chain. Fund the key with `ulume`, watch its balance, and put quotas on any user-facing upload path so it cannot be drained. **The EVM variant.** LUKSO shows Pattern B on a chain with no IBC at all. Universal Profile metadata stores a hash-bound URL pointing at a Cascade gateway. Readers fetch the bytes, recompute the hash, and reject any mismatch, so the gateway can deny service but never tamper. The same shape works on any EVM chain whose metadata standard carries a hash beside the URL. ## Pattern C. Contract driven over ICA A contract or module on the controller chain owns an interchain account on Lumera and is itself the signer of the Cascade write. **Flow.** The contract packs a `MsgRequestAction`, sends it across the IBC channel with `MsgSendTx`, and the interchain account on Lumera executes it and pays the escrow. The [interchain accounts page](/cross-chain/interchain-accounts) walks the full flow, including the application keypair that authenticates the file upload. **Choose it when** the artifact must exist exactly when the state transition happens and there is genuinely no user or operator in the loop. Canonical, contract-deterministic writes are the narrow case this pattern exists for. **Trust assumptions.** The strongest of the three. No user key, no operator key, only the contract and the IBC light clients. The price is latency and operations. The write takes an IBC round-trip of roughly 30 to 60 seconds, stalls when the relayer backs up, and requires you to keep the ICA address funded. Never put it on a user-facing hot path. ## Choosing a pattern | | Pattern A user signed | Pattern B server signed | Pattern C contract driven | | - | - | - | - | | Best for | On-chain provenance to the user's key | Fast UX, most products | Canonical writes with no user in the loop | | Who pays `ulume` | The user | Your service key | The ICA address | | Signatures per artifact | Two, one per chain | One, on your chain | None from users | | Key custody | None | Operator holds one Lumera key | Contract owns the ICA | | Latency | Two confirmations | One confirmation plus an upload | IBC round-trip, 30 to 60 seconds | | Works without IBC | Yes | Yes | No | Start with Pattern B. Move specific artifact types to A when user provenance is a requirement, and reserve C for contract-only canonical writes where IBC latency is acceptable. The patterns are not exclusive, and many deployments use more than one, chosen per artifact type. ## Next steps The full Pattern C runbook with the Go SDK. The client used for Pattern A and B uploads. Keep cross-chain artifacts private while storing them permanently. # Hermes relayer setup for Lumera Source: https://docs.lumera.io/cross-chain/relayer-setup The Lumera values to plug into a standard Hermes chain configuration. ICS-27 packets only move when a relayer carries them. This page gives you the Lumera specific values for a [Hermes](https://hermes.informal.systems) relayer serving a channel between Lumera and another Cosmos chain. It is not a general Hermes tutorial. Install Hermes, create the chain entries, and manage channels by following the official Hermes documentation, then plug in the values below. ## Lumera chain values Add a chain entry for Lumera to your Hermes config with these values. The counterparty chain entry uses that chain's own documented values. | Setting | Value | | - | - | | Chain ID | `lumera-mainnet-1` | | RPC | `https://rpc.lumera.io` | | gRPC | `https://grpc.lumera.io` | | Account prefix | `lumera` | | Gas price | `0.025ulume` | The official mainnet RPC can be intermittent. A community alternative is `https://lumera-rpc.polkachu.com`. | Setting | Value | | - | - | | Chain ID | `lumera-testnet-2` | | RPC | `https://lumera-testnet-rpc.polkachu.com` | | gRPC | `grpc.testnet.lumera.io:443` | | Account prefix | `lumera` | | Gas price | `0.025ulume` | Fund the relayer's Lumera key with `ulume` to pay for packet delivery. The minimum gas price on Lumera is `0.025ulume`, and governance can change it. ## Key derivation depends on the network This is the part that catches relayer operators. Lumera networks that have the EVM upgrade use Ethereum style keys, and networks that do not use standard Cosmos keys. **Testnet, and any network after its EVM upgrade.** The Lumera chain entry needs the ethermint address type. ```toml theme={null} address_type = { derivation = 'ethermint', proto_type = { pk_type = '/cosmos.evm.crypto.v1.ethsecp256k1.PubKey' } } ``` Add the relayer's Lumera key with the Ethereum HD path `m/44'/60'/0'/0/0`. A key added with the default Cosmos path produces the wrong address and every transaction fails. **Mainnet today.** Mainnet runs `v1.12.0` and has not had the EVM upgrade yet. Use standard Cosmos key derivation with no `address_type` override. When mainnet gains the EVM with its upgrade, existing mainnet relayers must re-key to the ethermint derivation. Follow the official [relayer migration guide](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/user-guides/relayer-migration.md) and see the [EVM upgrade page](/upgrades/evm-upgrade) for operator checklists. ## Verify the setup Once the chain entries are in place, use standard Hermes commands to health check both chains, create or restore the channel, and start relaying. Confirm the setup end to end by running the [interchain accounts flow](/cross-chain/interchain-accounts). A successful `MsgSendTx` acknowledgement proves packets move in both directions. ## Next steps The ICS-27 flow your relayer now carries. What changes for relayers when a network gains the EVM. All Lumera endpoints and chain parameters. # Ecosystem Source: https://docs.lumera.io/ecosystem Official applications, developer tools, infrastructure, wallets, and community resources for the Lumera Protocol. The Lumera ecosystem brings together the official applications, developer tools, infrastructure, wallets, and community resources that power the protocol. Whether you're building decentralized applications, operating infrastructure, or participating in governance, this page serves as a central directory for the tools and resources available across the network. ## Official Applications Official web, desktop, and mobile application for managing LUME, interacting with protocol services, and managing decentralized identity. Network explorer, staking dashboard, governance portal, and validator monitoring for Mainnet. Explorer, faucet, and operator dashboard for Testnet. ## Developer Tools Official SDKs, APIs, node software, and libraries for building applications on the Lumera Protocol. | Tool | Description | | - | - | | [`@lumera-protocol/sdk-js`](https://www.npmjs.com/package/@lumera-protocol/sdk-js) | TypeScript SDK for Node.js and browsers. | | [`sdk-go`](https://pkg.go.dev/github.com/LumeraProtocol/sdk-go) | Go SDK for gRPC queries, transactions, and protocol services. | | [`sdk-rs`](/sdk/rust) | Rust SDK for server-side applications. | | [`lumerad`](https://github.com/LumeraProtocol/lumera) | Official chain binary and CLI for developers and node operators. | | [`rq-go`](https://pkg.go.dev/lumera.build/rq-go) | Official Go implementation of RaptorQ (RFC 6330) erasure coding. | | [LumeScope](/api/lumescope) | Read-only REST API for decoded protocol data, SuperNode metrics, and Action metadata. | Start with the [JavaScript SDK](/sdk/javascript) or [Go SDK](/sdk/go). LumeScope endpoints are documented in the [API reference](/api/overview). ## Infrastructure Infrastructure repositories and operational resources for running and supporting the Lumera Protocol. | Resource | Description | | - | - | | [Validator Documentation](/validators/overview) | Operate a validator, participate in consensus, and secure the network. | | [SuperNode Documentation](/supernodes/overview) | Provide decentralized protocol services and earn protocol-service fees. | | [`lumera-networks`](https://github.com/LumeraProtocol/lumera-networks) | Genesis files, chain configuration, seeds, snapshots, and network resources. | | [`supernode`](https://github.com/LumeraProtocol/supernode) | Open-source SuperNode implementation. | | [`lumerad`](https://github.com/LumeraProtocol/lumera) | Official chain binary for validators, developers, and node operators. | ## Wallets | Wallet | Description | | - | - | | Keplr | Cosmos wallet supporting native `lumera1...` addresses. | | MetaMask | Ethereum-compatible wallet supporting Lumera's [Cosmos EVM](/smart-contracts/overview). | See the [Hub Wallet Guide](/hub/wallet) for setup instructions. ## Community & Resources Ask questions, get support, and connect with the Lumera community. Browse the protocol source code, SDKs, and infrastructure repositories. Follow protocol announcements, releases, and ecosystem updates. ## Building on Lumera Whether you're building decentralized applications, operating infrastructure, or contributing to the protocol, every component of the Lumera ecosystem is designed around a common architectural foundation. Developers build against shared SDKs and protocol services, operators provide decentralized infrastructure through Validators and SuperNodes, and the community governs the protocol through staked LUME. ## Ready to Build? Upload your first file and build your first application on Lumera. Explore the JavaScript, Go, and Rust SDKs. Understand how the protocol, SuperNodes, and execution framework work together. # EVM Migration Source: https://docs.lumera.io/evm-migration Move your Lumera account to its EVM compatible address within three months of the mainnet upgrade to keep your staking. Lumera mainnet is upgrading to full EVM compatibility. The upgrade changes how account keys derive, which means your existing address is replaced by a new one derived from the same recovery phrase. Migration moves everything you hold at the old address to the new one. It is free, it takes a few minutes, and there is a deadline. **The mainnet upgrade has been rescheduled. The new date is to be announced.** It was planned for block `6,715,000`. Mainnet passed that height on `v1.12.0` without upgrading, and no upgrade plan is on chain yet. Once the upgrade activates, you have three calendar months to migrate. [Network status and upgrades](/networks-status) carries the current versions and the queries behind them. Open the EVM Migration page in the Lumera Portal once the mainnet upgrade activates. ## What happens if you do nothing This is the part worth reading twice, because the two halves of your account behave differently. **Your liquid balance is safe either way.** It stays at your legacy address and cannot be lost. You can still recover it after the window closes. **Your staking is not.** Delegations, unbonding delegations, redelegations, and accrued rewards become permanently unrecoverable once the window expires. There is no recovery path and no exception process. If you have anything staked, migrating inside the window is not optional. ## The four things to know Zero gas. The migration transaction carries no fee, so you do not need a balance to migrate. Your legacy address is permanently blocked once migration completes. Always use the official flow. Everything moves in a single transaction. There is no partial state to recover from. The window opens at the upgrade block and closes three calendar months later. Testnet has run the same window since 2 July 2026. ## Why Lumera is doing this Lumera is adding a native Ethereum execution layer alongside the existing Cosmos layer. Both environments run on the same chain, share the same state, and share the same security. In practice that means MetaMask compatible accounts, full Ethereum JSON-RPC, Solidity and Vyper contracts, and precompiles that let EVM contracts call Lumera staking and SuperNode logic directly. Developers deploy with Hardhat, Foundry, or Remix without learning a new SDK. Wallets, indexers, and explorers built for EVM chains integrate without custom work. The reason is reach. Lumera is infrastructure for permanent data and decentralized services, and that depends on developers being able to build on it. Ethereum has the largest pool of contract developers and the deepest tooling of any platform. Reaching them from a Cosmos chain previously meant bridges and wrappers, which add friction for everyone. This removes it. Nothing on the Cosmos side goes away. IBC, validators, SuperNodes, governance, and bech32 addresses all keep working exactly as they do today. ## Do you need to migrate Everyone holding a pre-upgrade account does. What differs is the path. | You are | What to do | Full guide | | - | - | - | | A token holder | Migrate through the Portal wizard, the helper scripts, or the CLI | [Migration guide](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/user-guides/migration.md) | | Holding many accounts | Use the bundled scripts, which add pre-flight checks and verification | [Helper scripts](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/user-guides/migration-scripts.md) | | A validator operator | Plan a maintenance window, then migrate the operator key | [Validator guide](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/user-guides/validator-migration.md) | | A SuperNode operator | Set `evm_key_name` and restart, or migrate first and let the daemon detect it | [SuperNode guide](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/user-guides/supernode-migration.md) | | A Hermes relayer operator | Re-key Hermes with the new derivation path before migrating | [Relayer guide](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/user-guides/relayer-migration.md) | | A node operator | Review the new EVM sections of `app.toml` | [Node config guide](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/user-guides/node-evm-config-guide.md) | | Connecting MetaMask | Add the Lumera network and import your migrated account | [MetaMask guide](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/user-guides/metamask-configuration.md) | ## How to migrate Most people should use the Portal wizard. It handles both signatures and shows you the result. Keplr needs the coin type 60 profile alongside your existing one. You will end up with two Lumera profiles, the legacy one and the EVM one, and this is expected. Connect with your legacy account. The page shows your legacy address, your new address, and everything currently held at the legacy address. Confirm the destination matches what your recovery phrase derives under coin type 60. Migration is irreversible, so this is the step to slow down on. The transaction carries two proofs, an ADR-036 signature from the legacy key and an EIP-191 signature from the new key. The wizard prompts for both. There is no fee. Re-import your account under the EVM profile. The state panel on the migration page confirms what moved. Your balance and delegations should appear at the new address. Prefer the command line, or migrating in bulk? The [migration guide](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/user-guides/migration.md) covers the shell scripts and the raw `lumerad` path, including the offline four step ceremony for multisig accounts. ## What moves Migration transfers all on-chain state in one transaction. * Bank balances in every denomination * Staking delegations, unbonding delegations, and redelegations * Authz grants and feegrant allowances, as granter and as grantee * Action records and claim records * SuperNode registration, where applicable * Vesting schedules, where applicable For validators it additionally re-keys the operator address, every delegation pointing at the validator, distribution state, and any bound SuperNode. ## After you migrate One account now works in both wallets. Keplr shows the bech32 form starting `lumera1`. MetaMask shows the hex form starting `0x`. Both point at the same funds. To use MetaMask, add the Lumera network and import the migrated account. The [MetaMask guide](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/user-guides/metamask-configuration.md) has the network values and the full walkthrough. ## Questions No. Nothing works differently until mainnet upgrades. If you want to rehearse, testnet has run the identical flow since 2 July 2026. The testnet window closes on 2 October 2026. The upgrade records it on chain as the `migration_end_time` parameter, three calendar months after the upgrade block time. Governance can change it. Once your network has upgraded, check it with `lumerad query evmigration params`. No. The same phrase now derives a second address under coin type 60. That new address is your EVM compatible account, and migration moves your state to it. Your liquid balance stays recoverable at the legacy address. Staking, delegations, unbonding, and rewards do not. They are permanently lost once the window closes, three calendar months after the upgrade. No. The migration transaction is fee-free by design, so an empty account can still migrate. No. The legacy account is removed from the auth module once migration completes, and the mapping is permanent. Only ever migrate through the official Portal flow or the guides linked here. No. Only your operator key changes, from `secp256k1` to `eth_secp256k1`. Your `priv_validator_key.json` is untouched. Validators must use `MsgMigrateValidator`, which the chain requires instead of the standard claim message. A K-of-N legacy multisig migrates to a K-of-N EVM multisig, same K and same N. The chain enforces this. The destination can do everything on the Cosmos side but cannot originate EVM transactions, so set up a separate single-key account if you need EVM DeFi access. Yes, until the window closes. Migrating early costs nothing and removes the risk of forgetting, so there is no reason to wait. ## Get help Ask in the support channel. Fastest route to a human. The technical release record, new modules, and the developer view. # Get testnet LUME Source: https://docs.lumera.io/faucet Request free testnet LUME from the faucet on the Lumera Portal. Transactions on the Lumera Protocol testnet need LUME for gas, and Cascade uploads escrow a storage fee. The faucet on the Lumera Portal sends free testnet LUME to your address. Create a wallet with Keplr, or generate a key from the command line. ```bash theme={null} lumerad keys add my-key ``` Your address starts with `lumera1`. Open the [Portal faucet](https://portal.testnet.lumera.io/lumera-testnet-2/faucet), paste your address, and submit the request. The community [AstroStake faucet](https://faucet.astrostake.xyz/lumera) is an alternative if the Portal faucet is empty. Faucets limit requests per wallet per day. Open the [Lumera Portal](https://portal.testnet.lumera.io/lumera-testnet-2) and search for your address. Your balance appears on the account page once the faucet transaction lands. Testnet LUME has no value. There is no faucet on mainnet. ## Next steps Upload your first file to Cascade from TypeScript. Find chain IDs, endpoints, seeds, and genesis files. # Protocol Governance Source: https://docs.lumera.io/governance How the Lumera Protocol evolves through on-chain governance. Protocol Governance allows the Lumera Protocol to evolve without relying on centralized decision-making or disruptive hard forks. Economic parameters, protocol services, software upgrades, and ecosystem funding are all governed on-chain through proposals approved by holders of staked LUME. Governance is therefore part of the protocol itself. Rather than treating the network as static after launch, Lumera allows the community to adapt protocol parameters as technology, network usage, and economic conditions evolve. Voting power is determined by **staked LUME**. One staked LUME represents one unit of voting power, aligning governance with the economic participants responsible for securing the network. Unstaked tokens do not participate in governance. ## What Governance Controls * **Issuance** - the target bonded ratio, issuance bounds, and rate of change * **Fees** - transaction fee parameters, the burn share, and distribution splits * **Protocol Service** - service pricing, Action parameters, execution costs, and protocol-service economics * **Ecosystem funding** - spending from the community pool * **Protocol upgrades** - coordinated chain upgrades and module changes * **Governance itself** - deposits, periods, and thresholds ## Governance as Protocol Evolution Governance is the mechanism through which Lumera evolves over time. Rather than introducing separate versions of the protocol for every economic adjustment or service enhancement, protocol parameters can be modified through transparent, on-chain governance. As new decentralized intelligent services are introduced, they inherit the same governance framework as the rest of the protocol. Economic parameters, service pricing, protocol modules, and ecosystem funding all evolve through one consistent governance process. ## Proposal Process A proposal is submitted with an initial deposit. Anyone holding LUME can submit one. The proposal must reach the minimum deposit threshold before it advances. Any community member can contribute to the deposit, so a proposal with support can clear the bar even if the author cannot fund it alone. If the threshold is not met before the deposit period expires, the proposal is removed and the deposit is refunded. Staked LUME votes. Options are Yes, No, Abstain, and No With Veto. Approved proposals are executed according to their proposal type. Parameter changes take effect automatically, while software upgrades and governance-approved actions follow their defined execution path. Deposits are burned only when a proposal is vetoed. That puts a real cost on harmful proposals. A proposal that fails, misses quorum, or never reaches its minimum deposit refunds its depositors. ## Governance Parameters Governance parameters are set per network and change through proposals. Query the live values on any node with this command. ```bash theme={null} lumerad query gov params ``` | Parameter | Mainnet | Testnet | | - | - | - | | Minimum deposit | 1,000 LUME | 1,000 LUME | | Minimum initial deposit ratio | 0.25% | 0.25% | | Minimum deposit ratio | 1.0% | 1.0% | | Maximum deposit period | 3 days (`259200s`) | 3 days (`259200s`) | | Voting period | 4 days (`345600s`) | 3 days (`259200s`) | | Expedited minimum deposit | 5,000 LUME | 5,000 LUME | | Expedited voting period | 1 day (`86400s`) | 12 hours (`43200s`) | | Expedited threshold | 66.7% | 66.7% | | Quorum | 33.4% | 33.4% | | Threshold | 50% | 50% | | Veto threshold | 40% | 40% | | Burn deposit on veto | Yes | Yes | | Burn deposit when quorum fails | No | No | | Burn deposit when the deposit period expires | No | No | Values as of 28 September 2026, at mainnet block 7,123,917 and testnet block 6,839,029. Expedited proposals exist for changes that cannot wait a full voting period. They require a higher deposit and a 66.7% Yes threshold, and they pass through a shorter vote. ## Validators and Delegators Governance follows Lumera's delegated staking model. Validators cast votes on behalf of delegated stake by default. Delegators remain fully sovereign and may override their validator's vote on any proposal without affecting their broader staking relationship. The practical effect is that passive holders stay represented while active holders retain full control. It also makes governance behavior part of how delegators evaluate validators, since a validator's voting record is public and applies to delegated stake. ## Veto A proposal receiving **No With Veto** votes above the veto threshold is rejected regardless of the simple majority outcome, and its deposit is burned. The veto mechanism protects the protocol from proposals considered harmful by a substantial minority, ensuring that governance reflects more than simple majority rule. The operational walkthrough, including CLI commands. Staking, supply mechanics, and voting power. The fee structures governance can change. # Lumera Hub Overview Source: https://docs.lumera.io/hub/overview Lumera Hub is the official cross-platform app for the Lumera network on web, desktop, and mobile. Lumera Hub is the official application for interacting with Lumera Protocol, a Cosmos SDK Layer 1 blockchain built for decentralized storage, AI data services, and IBC-connected DeFi. It lets you manage your LUME wallet, stake to validators, participate in governance, and use the Cascade and Sense services. It runs on any device and requires no technical background to get started. ## Platforms Lumera Hub is available across three platforms so you can interact with the network wherever you are. All three share the same feature set and connect to the same mainnet and testnet environments. Access Lumera Hub directly in your browser at **hub.lumera.io** with no installation required. The web app is the fastest way to get started. A native desktop application for **Windows, macOS, or Linux**. Manage your wallet and interact with the network on **iOS or Android**. ## Key features Lumera Hub covers everything you need to participate fully in the Lumera ecosystem. ### Wallet management Create a new LUME wallet or import an existing one using a 24-word mnemonic seed phrase. Lumera Hub lets you manage multiple accounts, check your balance at a glance, and send LUME to any address on the network. ### Staking and delegation Put your LUME to work by delegating it to one or more validators in the active set. Staking earns you a share of block rewards, and you can claim or restake your rewards at any time. Keep in mind that undelegating LUME starts an unbonding period before tokens become freely transferable again. ### Governance participation Lumera is a community-governed chain. Use Lumera Hub to browse active governance proposals, review their details, and cast your vote (YES, NO, NO WITH VETO, or ABSTAIN) directly from your wallet. ### Cascade storage Cascade is Lumera Protocol's decentralized file storage service. Through Lumera Hub you can upload files to the network and retrieve them later, with storage secured and replicated across the SuperNode network. ### Sense duplication detection Sense is Lumera Protocol's near-duplicate detection service for NFT media assets. Submit your media fingerprints through Lumera Hub to check for near-duplicates across the network before minting. ### Token claiming (PSL → LUME migration) If you held PSL tokens before the Lumera migration, you can claim your equivalent LUME allocation directly through Lumera Hub. The claiming interface walks you through verifying eligibility and completing the transfer. ### IBC transfers Send LUME to other Cosmos chains or receive tokens from them using Inter-Blockchain Communication (IBC). Lumera Hub includes a built-in IBC transfer interface so you can move assets across the Cosmos ecosystem without leaving the app. ## Networks Lumera Hub connects to both the production network and the test network. | Network | Chain ID | Purpose | | - | - | - | | Mainnet | `lumera-mainnet-1` | Production, real LUME tokens | | Testnet | `lumera-testnet-2` | Testing, free testnet tokens | Always confirm which network you are connected to before signing any transaction. The current network is displayed in the Lumera Hub interface. Use **lumera-testnet-2** for experimentation and **lumera-mainnet-1** for real funds. ## Get started Create or import a Lumera wallet, check your LUME balance, send tokens, and secure your seed phrase. Delegate LUME to validators, track your staking rewards, and participate in network governance. # Lumera Hub Wallet Guide Source: https://docs.lumera.io/hub/wallet Create or import a Lumera wallet in Lumera Hub and manage, send, and stake your LUME. Your Lumera wallet is your identity on the network. It holds your LUME tokens, controls your staking positions and governance votes, and authorizes every action you take through Lumera Hub. This page walks you through creating or importing a wallet. It then covers the day-to-day operations you can perform, from checking your balance and sending tokens to staking, claiming rewards, and voting on proposals. **Your seed phrase is the master key to your wallet.** Anyone who obtains your 24-word mnemonic can take full, permanent control of your funds. Never share it with anyone, including support staff. Never enter it on any website other than an application you have independently verified as authentic. Lumera Protocol support will never ask for your seed phrase. ## Wallet setup Open Lumera Hub on whichever platform you prefer. * **Web**. Navigate to [hub.lumera.io](https://hub.lumera.io) in your browser. * **Desktop**. Launch the Lumera Hub application you installed on Windows, macOS, or Linux. * **Mobile**. Open the Lumera Hub app on your iOS or Android device. You will land on the wallet setup screen the first time you open the app. Select **Create New Wallet** to generate a brand-new Lumera account. Lumera Hub generates a **24-word mnemonic seed phrase** unique to your wallet. This is the only backup of your account. There is no account recovery without it. Write down every word in order on paper (or stamp them into a metal backup plate). Once you have recorded the phrase, complete these steps. 1. Click **Continue** to proceed to the verification step. 2. Confirm your seed phrase by selecting the words in the correct order when prompted. 3. Set a local password or PIN to protect the app on this device. After verification, your wallet address (beginning with `lumera1...`) is created and ready to use. If you already have a Lumera wallet, or are migrating from a compatible Cosmos wallet, select **Import Wallet** instead. Enter your 24-word (or 12-word) seed phrase, one word per field. Lumera Hub derives your wallet address from the phrase and restores full access to your account. Connect your Ledger device via USB or Bluetooth, unlock it, and open the **Cosmos** app on the device. Select **Connect Ledger** in Lumera Hub and follow the on-screen pairing steps. Your private key never leaves the hardware device. Your new wallet starts with a zero balance. You can acquire LUME in three ways. * **From an exchange**. Withdraw LUME to your `lumera1...` address. * **From another wallet**. Ask the sender to transfer LUME to your address. * **From testnet faucet**. If you are on **lumera-testnet-2**, use the [community faucet](/faucet) to request test tokens at no cost. Copy your wallet address from the **Receive** panel in Lumera Hub and share it with the sender. Lumera Hub supports both available networks. Select the appropriate one from the network switcher in the app settings or the top navigation bar. | Network | Chain ID | Use For | | - | - | - | | Mainnet | `lumera-mainnet-1` | Real LUME, live transactions | | Testnet | `lumera-testnet-2` | Practice, development, testing | Confirm the active network before signing any transaction. Transactions submitted to the wrong network cannot be reversed. ## Wallet features ### Checking your LUME balance Your balance appears on the **Portfolio** or **Dashboard** screen as soon as your wallet is loaded. Lumera Hub displays four balances. * **Available balance**. LUME you can freely send or delegate. * **Staked balance**. LUME currently delegated to validators. * **Unbonding balance**. LUME that has been undelegated and is still in the unbonding period. * **Claimable rewards**. Staking rewards ready to withdraw. ### Sending LUME 1. Open the **Send** panel. 2. Enter the recipient's `lumera1...` address. 3. Enter the amount of LUME to send. 4. Review the transaction details (recipient, amount, and estimated gas fee). 5. Confirm and sign the transaction. Double-check the recipient address before confirming. Cosmos transactions are irreversible. LUME sent to a wrong address cannot be recovered. ### Staking and delegating LUME Staking locks your LUME with a validator to help secure the network and earn you a share of block rewards. 1. Go to the **Staking** section. 2. Browse the validator list and select a validator to delegate to. Consider the validator's commission rate, uptime, and community reputation. 3. Enter the amount of LUME to delegate. 4. Confirm and sign the delegation transaction. When you undelegate LUME, it enters a **21-day unbonding period**. During this period it earns no rewards and cannot be transferred. Plan your liquidity needs accordingly. You can delegate to multiple validators to spread risk across the active set. ### Claiming staking rewards Staking rewards accumulate continuously but are not automatically credited to your available balance. To claim them, follow these steps. 1. Go to the **Staking** section and find your active delegations. 2. Click **Claim Rewards** next to a validator, or use **Claim All** to collect rewards from every delegation in a single transaction. 3. Confirm and sign the transaction. Rewards are added to your available balance immediately. You can also **restake** (compound) rewards by delegating them back to the same validator in one step. ### IBC transfers IBC lets you send LUME to other Cosmos chains (such as Osmosis or Cosmos Hub) or receive tokens from them. 1. Open the **IBC Transfer** panel. 2. Select the destination chain from the dropdown. 3. Enter the recipient address on the destination chain. 4. Enter the amount and the channel ID for the route you want to use. 5. Confirm and sign the transaction. IBC transfers require an active relayer between Lumera and the destination chain. If a transfer appears stuck, it may be waiting for a relayer to relay the packet. Funds are not lost. The transfer will either complete or time out and be refunded. ### Voting on governance proposals LUME holders can vote on on-chain governance proposals that shape the future of the network, including parameter changes, software upgrades, and community spending. 1. Go to the **Governance** section. 2. Browse open proposals and click one to read its full description. 3. Choose **Yes**, **No**, **No With Veto**, or **Abstain**. 4. Confirm and sign the vote transaction. Voting power is proportional to your total staked LUME at the time the proposal's voting period ends. ## Security best practices Write your 24-word seed phrase on paper immediately after generating it. For long-term storage, consider stamping or engraving the words on a metal plate that is resistant to fire and water. **Never** save your seed phrase in a text file, screenshot, cloud note, email, or messaging app. Any digital copy is a potential attack surface. Keep physical backups in secure, separate locations. For large holdings, use a Ledger hardware wallet. Lumera Hub supports Ledger devices. Your private key is generated and stored on the hardware device and never exposed to your computer or phone. Every transaction must be physically confirmed on the device. Make sure you are running the latest Ledger firmware and the Cosmos app. Purchase Ledger devices only from the official Ledger website to avoid supply-chain attacks. Always read the full transaction summary before tapping **Confirm**. Check the recipient address, the amount, and the network. Attackers sometimes use clipboard hijacking to silently replace copied addresses with their own. On Ledger devices, verify the transaction details on the hardware screen before approving, not just in the app. Lumera Hub clearly displays the active network. Before performing any transaction, confirm you are on the correct network. * Use **lumera-mainnet-1** for real LUME and live operations. * Use **lumera-testnet-2** for experimentation and development. Gas fees and token balances are separate between networks. Testnet tokens have no monetary value. **Keplr wallet integration.** If you already use Keplr to manage Cosmos accounts, you can add the Lumera mainnet or testnet to Keplr and import your existing Lumera address. Use the same seed phrase you used when setting up your Lumera Hub wallet to access the same account across both applications. # Lumera Protocol Source: https://docs.lumera.io/index Infrastructure for permanent, provable data and decentralized intelligent services. Lumera is a decentralized protocol that enables enterprises and developers to build applications on permanent, provable data and decentralized intelligent services. By combining permanent data, verifiable execution, and long-lived digital state within a common protocol framework, Lumera provides infrastructure that remains transparent, auditable, and independent of centralized providers. Unlike general-purpose blockchains, Lumera is built around a simple architectural principle: consensus should secure the network, not perform every service. Validators maintain consensus and governance, while economically bonded SuperNodes provide storage, verification, retrieval, and decentralized execution. This separation allows sophisticated services to scale independently without compromising security or decentralization. The blockchain serves as the network's coordination layer, recording requests, commitments, payments, proofs, and governance, while SuperNodes perform the underlying work and return cryptographic evidence of completion. Rather than forcing every computation into consensus, Lumera combines the scalability of distributed systems with the trust guarantees of blockchain infrastructure. Lumera is a protocol designed to support an expanding ecosystem of decentralized intelligent services built on a shared architectural foundation. As the network evolves, each new capability extends the protocol through the same architectural principles, execution model, and economic framework—creating a unified platform where permanent storage, verification, identity, memory, execution, and settlement reinforce one another rather than exist as independent systems. ## Key Features Store data once and retain it indefinitely. SuperNodes continuously prove they still hold the data they are responsible for, with cryptographic proofs recorded on chain. Validators secure consensus while dedicated SuperNodes provide storage, verification, retrieval, and decentralized services, allowing execution to scale independently of consensus. Every decentralized intelligent service is delivered as a modular protocol service with a common lifecycle for registration, execution, settlement, and verification. New services extend the protocol without introducing separate systems. Protocol fees compensate network operators, sustain long-lived services, and align economic incentives between developers, users, validators, and SuperNodes. Build with familiar EVM tooling, integrate across the Cosmos ecosystem through IBC, or consume Lumera's services without migrating existing applications. LUME holders govern protocol parameters, service economics, treasury allocation, and future network upgrades through decentralized governance. ## Design Principle Lumera is built around a simple architectural principle: > **Reason off-chain. Verify on-chain.** **Consensus should establish truth—not perform every computation.** The blockchain acts as the protocol's control plane, recording commitments, permissions, payments, governance, and cryptographic proofs, while SuperNodes perform storage, verification, retrieval, and decentralized services. By separating consensus from execution, Lumera allows sophisticated protocol services to scale independently while ensuring every operation can produce verifiable evidence rather than requiring users to trust the party performing the work. Your app sends a request and fee to the control plane. Data moves directly to the execution plane without passing through consensus. SuperNodes return evidence of completion to the chain. Your app sends a request and fee to the control plane. Data moves directly to the execution plane without passing through consensus. SuperNodes return evidence of completion to the chain. ## The Action and Agent framework Every decentralized intelligent service on Lumera is implemented through a common execution framework built around **Actions** and **Agents**. Together, they provide a consistent model for how protocol services are requested, executed, verified, and settled across the network. **Actions** are the atomic building blocks of decentralized intelligent services. Each Action represents a single verifiable unit of work executed by SuperNodes, following a common lifecycle for registration, execution, settlement, and proof generation. Because every Action follows the same framework, new protocol services extend the network without introducing new execution models. ### Example: Cascade Cascade is an Action that permanently stores data using erasure coding, decentralized SuperNode storage, and continuous cryptographic retention proofs. **Agents** build on top of Actions. Rather than performing work directly, Agents coordinate and compose one or more Actions into higher-level decentralized intelligent services, allowing applications to orchestrate storage, verification, computation, and future protocol services through a single execution framework. This layered architecture allows Lumera to evolve through composition rather than reinvention. Every new decentralized intelligent service shares the same execution model, verification framework, and settlement lifecycle, creating a unified protocol where services remain interoperable by design. ## Building on Lumera Applications built on Lumera consume protocol services through a common execution framework. Today, developers can permanently store data using Cascade, with every file erasure-coded, distributed across economically bonded SuperNodes, and continuously verified through cryptographic retention proofs. Rather than relying on a centralized storage provider, applications receive permanent, verifiable data backed by the protocol itself. This foundation enables developers to build applications that require long-lived data, verifiable infrastructure, and decentralized execution without managing storage networks or trusted intermediaries. As additional protocol services are introduced, they become available through the same framework and developer experience. Store your first file from TypeScript in just a few minutes. Learn how permanent storage, fees, and retention proofs work. Build using TypeScript, Go, or Rust. ## Protocol Architecture Lumera separates consensus from service execution through a Validator–SuperNode architecture. Validators secure the network through delegated Proof of Stake, proposing and validating blocks while governing protocol parameters. SuperNodes provide the protocol services applications consume, including storage, verification, retrieval, and decentralized execution. A SuperNode remains active only while bonded to an eligible validator, ensuring service capacity is backed by real economic commitment. The blockchain serves as the protocol's control plane, recording requests, commitments, payments, governance, and cryptographic proofs. SuperNodes serve as the execution and memory layers, performing work directly for clients without routing data through consensus. LUME powers staking, transaction fees, governance, and protocol services, aligning the network's economic incentives around a single native asset. Learn how validators, SuperNodes, modules, and clients interact. Explore token utility, supply mechanics, and staking. Understand protocol revenue, fee distribution, and sustainability. Learn how protocol upgrades and parameter changes are governed. ## Lumera Vision Lumera is designed as a long-lived protocol rather than a collection of independent products. The same architectural principles that enable permanent, provable data naturally extend to durable memory, decentralized identity, verifiable execution, settlement, trust, and future decentralized intelligent services. Rather than introducing separate systems for each new capability, Lumera extends a common execution framework, economic model, and security architecture. Every new protocol service builds upon the same foundation, allowing the network to evolve through composition rather than reinvention. The result is a unified protocol where storage, memory, identity, verification, execution, settlement, and intelligent services reinforce one another through a consistent architectural model—providing developers with a stable platform that becomes more capable over time without requiring them to rebuild on a different foundation. # The Five Foundational Capabilities Source: https://docs.lumera.io/intelligence-layer/capabilities The five capabilities the Intelligence Layer is built from, how they depend on each other, and which are available today. The Intelligence Layer is built from five capabilities. They are not five products. Each is a property the Protocol needs in order to support Decentralized Intelligent Services, and each becomes more useful in the presence of the others. | Capability | What it provides | Available today | | - | - | - | | [Identity](/intelligence-layer/identity) | Sovereign on-chain identity, delegated authority, capability grants, attestation | LumeraID and claims | | [Durable Memory](/intelligence-layer/durable-memory) | Persistent, integrity-checked state that outlives a session | Yes, through Cascade | | [Verifiable Execution](/intelligence-layer/verifiable-execution) | Work performed by SuperNodes with signed receipts, settled on confirmed completion | Storage and attestation Actions | | [Economic Coordination](/intelligence-layer/economic-coordination) | Funding, escrow, and settlement handled by the Protocol | Escrow and settlement for existing Actions | | [Trust](/intelligence-layer/trust) | A record compiled from what participants actually did | Seeded by retention and settlement history | ## Identity Every participant is represented by a LumeraID, an on-chain identity controlled by its holder's keys. People, applications, autonomous processes, datasets, and service providers are participants in the same sense. Identity is the root of the dependency graph. Authorization needs a subject, escrow needs an account, a receipt needs a signer, and a reputation record needs something stable to attach to. ## Durable Memory State that survives the end of a session, with integrity that can be demonstrated rather than assumed. [Cascade](/cascade/how-cascade-works) provides this today. Data is erasure-coded across SuperNodes, retained for a single upfront payment, and re-proved continuously, with long-term retention funded by [Everlight](/cascade/everlight/overview). The architecture treats memory as existing at more than one temperature, with permanent artifacts and mutable working state served by the same identity and settlement model. ## Verifiable Execution Work requested as an Action, performed by SuperNodes, evidenced by signed receipts, and settled only when completion is confirmed against the on-chain record. This is the existing Action Lifecycle applied to a wider range of work. Storage was the first kind of work to move through it. It is not the only kind it can carry. ## Economic Coordination Funding, escrow, and settlement treated as a coordination problem the Protocol owns rather than one participants negotiate. A participant funds once and consumes across the network. Fees are escrowed before work begins and released on confirmed completion. Metered work and long-lived obligations are funded through deliberately separate mechanisms, because per-use fees cannot fund permanence. ## Trust A record compiled from identity claims, delegations, attestations, signed receipts, settlements, disputes, and retention proofs. Trust is derived rather than designed. It adds interpretation to evidence the other capabilities already produce, which is why it is the clearest example of composition in the section. ## How the capabilities depend on each other The five form an acyclic graph. Identity is the root, and nothing it depends on sits above it. Execution needs to know who is asking. Memory needs to know who may read a private record. Settlement needs an account to draw against. Trust needs a stable subject and a stream of evidence. The ordering matters because it constrains what can be built when. A capability cannot be completed before the ones it consumes. ## Why Durable Memory comes first in practice Dependency order is not adoption order. Adoption follows usefulness, and the capability with the most immediate practical value is Durable Memory. It is available on mainnet now. A developer can build against it today without waiting for anything else. The two orderings are compatible because memory usage produces exactly the evidence the later capabilities consume. Retention proofs, storage settlements, and retrieval patterns are behavioral records anchored to identity, and they are objectively checkable. They are the first edges of the trust record, which is why trust does not have to wait for an execution layer before it becomes meaningful. Start where the Protocol is strongest. Store your first file from TypeScript. The capability available today. # Durable Memory Source: https://docs.lumera.io/intelligence-layer/durable-memory Persistent, integrity-checked state that outlives a session, and how the Protocol provides it at more than one temperature. Durable Memory is the capability that gives software state which outlives the process that created it. It is the capability available today. [Cascade](/cascade/how-cascade-works) has been live on mainnet since before the rest of the Intelligence Layer was defined, which means a developer can build against Durable Memory now without waiting for anything else. ## Why it exists Most software keeps its state somewhere convenient. A session buffer, a hosted database, a vendor's object store. That works while a person is supervising the software and can restate context when it is lost. Autonomous processes do not have that fallback. A process that forgets between invocations cannot be held to a decision it made yesterday. A process whose state lives on a single provider inherits that provider's incentives, retention policy, and failure modes. And a process that needs to prove to a counterparty that a record has not changed cannot do so if the only assurance is a service agreement. Durable Memory addresses three distinct requirements at once. State must survive. Its integrity must be demonstrable rather than asserted. And it must not belong to a vendor. ## Memory at more than one temperature Not all state has the same shape, and the Protocol does not store it all the same way. The split is deliberate. | Where state lives | What it holds | Characteristics | | - | - | - | | Cascade | Artifacts. Datasets, model artifacts, transcripts, evidence, anything large and long-lived | Content-addressed, permanent, integrity-critical, written once | | Structured store | Working state. Application data, preferences, session metadata, indexes | Mutable, low latency, read and written continuously | | The chain | Identity, permissions, commitments, registries, escrows, settlement, dispute outcomes | Strongly consistent, and not memory | The third row matters as much as the first two. The Control Plane is not a place to keep application state. It holds what must be strongly consistent, and nothing else. Treating it as a database would put consensus in the path of every read. The first two rows share one identity system and one settlement model. That is the practical benefit of memory being a Protocol capability rather than a product. A developer building on Lumera does not run an auth server, and does not run a database server. ## What Cascade provides Cascade is the artifact layer. Data is erasure-coded across economically bonded SuperNodes, retained for a single upfront payment, and re-proved continuously so that retention is demonstrated rather than promised. The mechanics are documented in full. Start with [How Cascade works](/cascade/how-cascade-works) for the lifecycle, fees, and guarantees, and the [Quickstart](/quickstart) to store something. ## What makes permanence sustainable A one-time payment for indefinite storage only works if something keeps paying operators after that payment is exhausted. [Everlight](/cascade/everlight/overview) is that mechanism. It funds retention from protocol reward flows, fee shares, and endowment yield, and it pays operators for as long as they continue to hold data and prove they still have it. This is why permanence is an economic property of the network rather than a commitment by a company. Nothing needs to remain solvent for stored data to remain retained. ## Access and privacy Stored data is private by default. Retrieving a private artifact requires a signature that resolves to an identity permitted to read it, which is [Identity](/intelligence-layer/identity) doing its job inside an existing flow rather than a separate access system. The network stores opaque bytes. It does not index filenames, schemas, or semantic content. For anything commercially sensitive, encrypt before upload, which lets the network prove it still holds a record it cannot read. See [Store encrypted files on Cascade](/cascade/guides/encrypted-storage). ## What this does not provide Retrieval by meaning is not a Protocol capability. Cascade retrieves by content address, and structured state retrieves by key. Neither performs semantic search over accumulated memory. Applications that need it build it above the Protocol, using Lumera for durability and provenance and their own index for recall. This boundary is worth knowing before designing around it. ## How it fits the architecture Durable Memory is the memory plane in the [plane model](/architecture). Data moves directly between clients and SuperNodes and never passes through consensus, which is why throughput is not bounded by block time. Memory is also where the rest of the Intelligence Layer starts. Every write produces evidence anchored to an identity, including retention proofs, settlements, and retrieval patterns. Those records are objectively checkable, and they are the first inputs to [Trust](/intelligence-layer/trust). Storing memory on the network generates trust data from the first write, which is why this capability is the one to build against first. The artifact layer, documented in full. How long-term retention is funded. Store your first file from TypeScript. What memory usage accumulates into. # Agent Memory Source: https://docs.lumera.io/intelligence-layer/durable-memory/agent-memory Append-only, client-encrypted memory for autonomous agents, anchored in Cascade with retention proven on chain. This page documents a design that is published ahead of availability. The architecture, data model, and durability semantics below are settled. The client libraries are not yet released, and this page intentionally carries no dates and no pricing. It describes how the system is built, not something you can install today. Agent Memory is a memory layer for autonomous agents built directly on [Cascade](/cascade/how-cascade-works). An agent appends to a log, the log is sealed into immutable objects anchored on chain, and the network can be asked to prove that any given object is still retrievable. It is a client surface over an existing Protocol Service rather than a new protocol capability. Everything beneath the client runs on mainnet today. There are **no new chain modules** anywhere in the design, which is the reason its behavior can be described with this much precision before release. ## Why agents need this An agent that loses its state between invocations cannot be held to a decision it made earlier. That much is obvious, and hosted memory products solve it. What they do not solve is the second problem. When memory is load-bearing, the agent's owner eventually needs to answer a question about the past: what did this agent know, when did it know it, and can you show me that the record has not been altered since. A hosted store answers that with a service agreement. It cannot answer it with evidence, because the store is also the party being asked. Agent Memory separates those roles. The record is append-only and content-addressed, so alteration is detectable. Retention is funded by [Everlight](/cascade/everlight/overview) and evidenced by storage challenges, so continued possession is demonstrated by parties other than the one making the claim. ## System layers Agent frameworks reach the Agent Memory client through an MCP connector or a framework memory provider. The client is the encryption boundary. Gateway mode passes through a hosted gateway, self-custody mode anchors directly, and both settle on Lumera mainnet, which is unchanged. Agent frameworks reach the Agent Memory client through an MCP connector or a framework memory provider. The client is the encryption boundary. Gateway mode passes through a hosted gateway, self-custody mode anchors directly, and both settle on Lumera mainnet, which is unchanged. Two modes exist because two different developers exist. Gateway mode hides keys, gas, and SuperNode routing entirely, so a developer never touches a wallet. Self-custody mode removes the gateway from the path and has the client anchor directly. The data written is identical either way, and a log created in one mode remains readable in the other. ## Data model Three objects, one invariant. | Object | What it is | | - | - | | **Entry** | The atomic unit an agent writes. Small, from bytes to tens of kilobytes. Addressed by `entry_cid = BLAKE3(entry bytes)` | | **Segment** | A set of sequenced entries sealed into one immutable object and anchored as a single Cascade action | | **Manifest** | A periodic checkpoint mapping entry CID to its segment, offset, and length. Manifests are themselves stored in Cascade and hash-linked into a chain | The invariant is that the log is **append-only**. Entries are never edited and never deleted. A correction is a new entry that supersedes an earlier one, which means the history of what the agent believed remains intact and auditable rather than being overwritten. Sealing entries into segments is what makes the economics work. One chain action per entry would be unaffordable. One action per sealed segment, with the manifest providing individual addressability, gives per-entry granularity at per-segment cost. The latest manifest CID is the portable root of the entire log. ## Durability states The design refuses to present a fast acknowledgement as durability. Every entry is in exactly one of three states, and the client always reports which. An entry moves from accepted, a fast acknowledgement that is the only centralized window, to anchored at block H, to proven at block X. An entry moves from accepted, a fast acknowledgement that is the only centralized window, to anchored at block H, to proven at block X. | State | Meaning | Backed by | | - | - | - | | `accepted` | Held in the gateway's replicated staging log, or the client's local buffer in self-custody mode. Fast acknowledgement | Gateway infrastructure only | | `anchored(H)` | The segment's Cascade action finalized at block H. LEP-5 possession proofs verified on chain, fee paid, retention funding engaged | Chain, SuperNodes, and Everlight | | `proven(X)` | The most recent storage challenge covering the segment passed at block X | Ongoing challenge evidence | The window between `accepted` and `anchored` is the only centralized durability window in the system. It is handled by keeping it short and by never describing it as anything else. A write returns `accepted`, and an acknowledged write is not represented as funded permanence. This distinction is the point of the design rather than a caveat attached to it. ## Encryption Encryption is on by default and is not a configuration option. The client generates a log key and encrypts each entry before any bytes leave the agent's process. The gateway, the SuperNodes, and the chain see ciphertext only. Retention proofs operate on ciphertext and are unaffected, which produces the useful property that the network proves it is still holding data it cannot read. The consequence is stated plainly rather than buried. **Losing the log key means losing the memory.** There is no escrow and no recovery path in the design. Logs can optionally be created public, in plaintext, for cases where visible content is the purpose. Private is the default. ## Proving retention This is the capability that does not exist in a hosted memory product. Because every segment is a Cascade action, an object CID resolves to an action, and that action resolves to on-chain evidence. That gives a direct answer to a question a service agreement can only assert: *this object was last proven retrievable at block X.* The chain of evidence behind that statement is verifiable independently. LEP-5 availability commitments prove possession at finalization, and ongoing storage challenges test retention afterward. Both are described in [Cascade](/cascade/how-cascade-works). ## Portability A manifest CID plus the log key is the whole memory. Anyone holding both can reconstruct the log from Cascade without the gateway, without the client library, and without Lumera's cooperation. Verification runs offline against the manifest chain. This is what distinguishes portable memory from exportable memory. An export is a copy a vendor chooses to give you. A manifest root is the object itself. ## The client surface Described as capabilities rather than signatures, because the API is not yet published. * Append an entry, returning its CID, sequence number, and state * Read recent entries, or entries since a point in the log * Fetch and decrypt a specific entry by CID * Force a seal, producing segment receipts * Query the durability state of any entry * Request a retention proof for an entry * Export the manifest root * Verify a manifest offline, with no gateway involved The same shape is provided in Python and TypeScript. Framework integrations expose a subset of these as tools, including an MCP connector for hosts that support it. ## What this does not do Stated as design scope, not fine print. * **No semantic recall.** Recency and lookup only. Retrieval by meaning is not provided, and applications that need it build it above this layer. * **No hot mutable state.** This is the durable tier. Mutable, low-latency state is a separate tier in the [Durable Memory](/intelligence-layer/durable-memory) architecture. * **No multi-writer logs.** One writer per log. Shared memory across agents requires delegation and the hot tier. * **No sharing or access control lists.** Sharing a log means handing over the manifest CID and the key, which works but is manual. * **No key escrow or recovery.** * **No durability claim for `accepted`** beyond the stated staging behavior. The design sells provenance and permanence. It does not claim retrieval quality. ## How it settles Anchoring a segment is an ordinary Cascade action and is priced as one, using the existing base plus per-kilobyte fee. No new billing rail is introduced, and no separate subscription exists in the design. Pricing for the hosted gateway is not yet published. The storage service Agent Memory anchors to. Where this sits in the Protocol architecture. # Economic Coordination Source: https://docs.lumera.io/intelligence-layer/economic-coordination How the Protocol funds work, holds it in escrow, and settles it without bilateral relationships between participants. Economic Coordination is the capability that lets participants pay and be paid without establishing a relationship with each other first. It exists because payment is not incidental to autonomous work. It is part of the same transaction. An Action that cannot be funded cannot be requested, and an operator that cannot be paid on confirmed completion has no reason to serve. The Protocol treats funding, escrow, and settlement as a coordination problem it owns rather than one it leaves to its participants. ## Why coordination belongs to the Protocol Consider the alternative. If every provider handled its own billing, an agent using ten services would hold ten accounts, ten credentials, and ten separate trust assumptions. Adding a provider would mean a new commercial relationship. Switching would mean unwinding one. Every provider would be a point of failure for the agent's ability to transact at all. That model does not survive contact with autonomous operation. It puts a negotiation in the path of every new counterparty, and negotiation is the thing an agent cannot do unsupervised. Moving coordination into the Protocol removes the negotiation. A participant funds once. Consumption draws against that balance wherever it happens on the network. A provider is paid by the Protocol on confirmed completion rather than by the requester on trust. Neither side has to have met. ## Escrow Escrow is the mechanism that makes this safe in both directions. When an Action is registered, its fee is committed on chain before work begins. The requester's funds are locked and cannot be spent elsewhere. The operator's compensation is real but contingent on completion. If the Action completes and is confirmed, the escrow releases to the SuperNodes that served it. If it expires before finalization, the escrow returns to the requester. Neither outcome depends on either party's cooperation after the fact, because the condition is evaluated against the on-chain record. This is the same discipline documented in the Action Lifecycle. Economic Coordination generalizes it rather than introducing a second payment path. ## Settlement is gated, not promised Settlement follows confirmation. Payment flows for work the chain can see was completed. The consequence is that a requester never needs to trust an operator's invoice, and an operator never needs to chase a payment. There is no accounts-receivable relationship between participants, because the Protocol is the counterparty to both sides of the exchange. Fees are quoted in LUME and denominated by the resource consumed. Where the fee goes once released, and how it splits between operators, validators, and the burn, is documented in [Protocol Economics](/protocol-revenue). ## Two settlement modes, kept separate Not all obligations have the same shape, and the Protocol does not fund them the same way. **Metered settlement** covers work with a defined beginning and end. A request is escrowed, performed, confirmed, and settled. Cost tracks consumption. **Sustained funding** covers obligations that outlive the payment that created them. Permanent storage is the clearest case. A one-time fee cannot pay an operator to hold data indefinitely, because the obligation continues after the payment is exhausted. [Everlight](/cascade/everlight/overview) is the mechanism for that class, funded through protocol reward flows, fee shares, and endowment yield. Conflating the two would be a design error in both directions. Per-use fees cannot fund permanence. An endowment cannot meter individual requests. Keeping them separate is what allows the Protocol to make a durable promise without accumulating an unpriced liability. ## Relationship to Protocol Services Every Protocol Service is priced, escrowed, and settled through the same path. A service is not a separate商 arrangement with its own billing. It is an Action type with a fee schedule. The practical effect for a developer is that adding a capability to an application does not add a commercial integration. If the capability exists as a Protocol Service, it is already payable from the same balance, on the same terms, with the same evidence. The asset fees are denominated and settled in. How fees are distributed once settled. # Escrow and Settlement Source: https://docs.lumera.io/intelligence-layer/economic-coordination/escrow-and-settlement How the Protocol holds a service fee during execution and releases it only against verified completion. Settlement on Lumera is conditional. A service fee is committed on chain before work begins and released only when the chain has verified that the work completed. The mechanism is escrow, and it is what allows a requester and an operator with no relationship to transact safely. ## Escrow at registration When an action is registered, its fee is transferred out of the creator's account and held by the Protocol. This happens in the same transaction that creates the action, before any operator sees the request. Two things are true from that moment. The creator's funds are committed and cannot be spent elsewhere. The operator's compensation exists but is contingent. Neither party is exposed to the other's good behavior. The requester is not trusting an operator to deliver after payment, and the operator is not performing work on the promise of payment. ## Release on verified completion The escrow releases when an action reaches `DONE`, which requires the chain to accept a finalization message carrying whatever evidence that action type demands. For Cascade that evidence is Merkle proofs for the chunks committed at registration. An operator that never received the file cannot produce them, so it cannot reach `DONE`, so it cannot be paid. The economic consequence is enforced by verification rather than by dispute. Payment goes to the SuperNodes that served the action. ## Refund on expiration If the action is not finalized before its expiration, the state becomes `EXPIRED` and the escrow returns to the creator automatically. A finalization the chain rejects settles nothing and leaves the action open until a later attempt succeeds or it expires. No claim is filed and no counterparty has to cooperate. The refund is a consequence of the state transition, evaluated by the chain against the on-chain record. This is the property that makes the write path safe to retry. An upload that never finalizes costs a transaction fee, not the service fee. ## Where the fee goes The released fee does not all reach the serving operators. A share of every action registration fee is routed to the Everlight SN Pool, governed by `registration_fee_share_bps` and designed at 2 percent. That routing is what connects one-time payment to long-lived obligation. The fee that pays for storing a file also contributes to the pool that compensates operators for continuing to hold it. See [Everlight](/cascade/everlight/overview). Registration fees do not increase to fund this. The share comes out of the existing split. ## Service fees and gas are different Two payments occur when you register an action, and conflating them causes confusion when reconciling costs. | Payment | What it pays for | Where it goes | | - | - | - | | Service fee | The work itself. Escrowed, then released or refunded | Serving SuperNodes, with a share to the Everlight pool | | Transaction fee | Including the transaction in a block | Validators and delegators, with a share burned | The service fee is refundable on expiration. Gas is not, because the transaction was processed regardless of whether the work succeeded. Distribution of both is covered in [Protocol Economics](/protocol-revenue). ## The approval path Some flows insert an explicit approval before settlement. In those cases the action moves to `APPROVED` through `MsgApproveAction`, and the escrow releases on that approval rather than on finalization alone. This exists primarily for cross-chain requests, where the party that pays and the party that authorizes completion are not the same account. See [Cross-chain overview](/cross-chain/overview). ## Why this is a Protocol capability The alternative is bilateral. Every provider bills its own customers, every customer holds an account with every provider, and every new counterparty is a commercial negotiation. That model cannot support software acting without supervision, because negotiation is precisely what an autonomous caller cannot do. Moving settlement into the Protocol removes it. The Protocol is counterparty to both sides, so there is no invoice to dispute and no receivable to chase. The state transitions escrow is tied to. How the escrowed amount is determined. Where fees go once released. The pool a share of every fee funds. # Service Pricing Source: https://docs.lumera.io/intelligence-layer/economic-coordination/service-pricing How Protocol Service fees are calculated, what they include, and which parameters governance controls. Protocol Service fees are priced by the resource consumed and quoted in LUME. There is no subscription, no tier, and no recurring charge for a completed action. ## How a Cascade fee is calculated Cascade is priced with a base fee plus a per-size component. ```text theme={null} fee = base_fee + (fee_per_kb × ceil(size_in_bytes / 1024)) ``` Current mainnet parameters are a base of 30,000 `ulume` plus 10 `ulume` per KB. For a 1 MB file that is roughly 0.04 LUME. Testnet uses the same per KB rate with a 10,000 `ulume` base. `ulume` is the base denomination. One LUME is 1,000,000 `ulume`. | File size | Mainnet fee | Testnet fee | | - | - | - | | 100 KB | 0.031 LUME | 0.011 LUME | | 1 MB | 0.04 LUME | 0.02 LUME | | 10 MB | 0.13 LUME | 0.11 LUME | | 100 MB | 1.05 LUME | 1.03 LUME | Treat these as current values. Every parameter is governable, and the authoritative numbers live on chain. ## How a Sense fee differs Sense is priced by the size of the data and the analysis performed, and it costs less than storing the same file through Cascade. The reason is what persists. Sense records an analysis result rather than retaining the submitted media, so the network takes on no long-term retention obligation. This is the general rule behind Protocol pricing. Fees track the obligation created, not the size of the request. ## What the fee includes and excludes **Included.** Encoding, distribution across the SuperNode mesh, verification at finalization, and the contribution to long-term retention funding. For Cascade, retrieval is not separately metered. **Excluded.** The transaction fee that gets your registration into a block. That is gas, it is paid at the network minimum gas price, currently `0.025ulume`, and it is not refundable if the action later fails. ## One payment, not a subscription A completed Cascade action is never billed again. Continued retention is funded by [Everlight](/cascade/everlight/overview), which draws from protocol reward flows, a share of registration fees, and endowment yield rather than from the uploader. This is why permanence can be priced once. The obligation is funded by a mechanism designed for it, instead of by a fee that has already been spent. ## Expired actions refund If an action expires before finalization, the escrowed service fee returns to the creator. You pay gas for the attempt and nothing for the service. See [Escrow and settlement](/intelligence-layer/economic-coordination/escrow-and-settlement). ## Governance parameters Pricing is set by on-chain parameters, so it changes by proposal rather than by announcement. | Parameter | Controls | | - | - | | Base action fee | The fixed component of a service fee | | Fee per KB | The size-dependent component | | `registration_fee_share_bps` | Share of the fee routed to the Everlight pool | | Minimum gas price | The floor for transaction fees | Query current values before quoting a cost to anyone. ```bash theme={null} lumerad query action params ``` ## Estimating cost in an application Two practical notes for anyone building a cost model. Round up. The per-KB component applies to whole kilobytes, so many small objects cost more in aggregate than the same bytes batched into fewer, larger ones. Applications writing frequent small records usually batch before anchoring. Budget for gas separately. It is small relative to the service fee for large files and proportionally significant for very small ones. What happens to the fee after registration. How pricing parameters change. The asset fees are denominated in. The action a Cascade fee pays for. # Identity Source: https://docs.lumera.io/intelligence-layer/identity How the Protocol represents participants, delegates authority, and records verifiable claims about them. Identity is the root of the Intelligence Layer. Every other capability consumes it, and it consumes nothing above it. The reason is mechanical rather than philosophical. Authorization needs a subject. Escrow needs an account to draw against. A receipt needs a signer. A reputation record needs something stable to attach to. Without a durable notion of who is acting, none of the other capabilities can be expressed. ## What protocol identity is A participant on Lumera is represented by a **LumeraID**, an on-chain identity controlled by its holder's keys. The set of things that can hold an identity is deliberately wide. People, applications, autonomous agents, datasets, and service providers are all participants in the same sense. An agent is not a secondary object owned by a user account. It is a principal that can be granted authority, hold a balance, sign work, and accumulate a record of its own. Identity here is not a user profile. A profile describes someone. A LumeraID is what permissions attach to, what receipts are signed against, and what the Protocol checks before releasing a private record or a payment. ## Claims State is added to an identity through **claims**, which are on-chain transactions that assert something verifiable. | Claim type | Purpose | | - | - | | Profile | Establishes and updates the identity's own published record | | Key rotation | Replaces keys without abandoning the identity or its history | | Delegation | Transfers scoped authority from one identity to another | | Capability grant | Defines which operations a holder is permitted to perform | | Application and asset identity | Extends identity to software and to data | | Attestation | Records a verifiable statement made by one participant about another | | Revocation | Withdraws a grant or invalidates a prior claim | Because claims are transactions, they inherit the properties of the Control Plane. They are ordered, public in structure, attributable to a signer, and permanent. An authorization decision made against them can be replayed and audited later. Two consequences matter in practice. Key rotation means a compromised key does not destroy an accumulated record, which is what makes long-lived agent identity viable. Revocation means authority can be withdrawn as explicitly as it was granted. ## Delegated authority Delegation is what makes autonomous operation safe to permit. An owner does not hand an agent unrestricted control. An owner grants a scoped capability, on chain, to a specific identity. The grant states what may be done and within what limits. It can be narrowed, and it can be revoked. Grants compose. If an enterprise delegates authority to an agent, and that agent grants a narrower capability to a specialist it engages, the chain holds a verifiable path from the original authority to the final actor. Any party can inspect that path and see where the authority came from and where it stops. This is why authority is a Control Plane concern. Delegation must be strongly consistent, because two conflicting views of what an agent is allowed to do is not a degraded experience, it is a security failure. ## What identity does not own The boundary is as important as the function. Identity owns identity, delegation, capability, attestation, and revocation. It does not own application data, working state, model selection, or memory. Those belong to other capabilities and other planes. Keeping the boundary strict is what keeps the dependency graph acyclic. If identity began storing mutable application state, everything that depends on identity would inherit that state's failure modes. ## Attestation An attestation is a claim by one participant about another, recorded on chain and attributable to its author. It is a general mechanism rather than a single feature. Content authenticity results are one kind of attestation. An operator vouching for a peer, a verifier confirming that an execution conformed to its declared shape, or an enterprise certifying that an agent passed review are all the same shape of record. Attestations are the raw material the [Trust](/intelligence-layer/trust) record is compiled from. Identity produces the statements. Trust interprets them. ## How it fits the rest of the Protocol Identity is not a new subsystem bolted alongside the chain. It is a Protocol Module holding identity records and claims in the Control Plane, checked by other modules during the Action Lifecycle. When a private artifact is retrieved from [Cascade](/cascade/how-cascade-works), the request must carry a signature that resolves to an identity permitted to read it. That is identity doing its job inside an existing flow, not a parallel access system. How claims and receipts compile into reputation. How authority is checked when work is performed. # LumeraID Source: https://docs.lumera.io/intelligence-layer/identity/lumeraid How identity works on Lumera today: addresses as identities, ADR-036 signatures, and how access to private objects is enforced. LumeraID is the Protocol's identity primitive. This page covers how it behaves today and what you need to do to work with it. For why identity is the root of the Intelligence Layer and where the claim model is heading, see [Identity](/intelligence-layer/identity). ## Your address is your identity There is no separate identity registration and no username. A Lumera address is a LumeraID, and the private key that controls the address controls the identity. Identity records are anchored to addresses by `x/lumeraid`. Nothing needs to be created before you can act. If you can sign for an address, you have an identity the Protocol recognizes. The practical consequence is that identity and key custody are the same problem. Losing the key does not lock you out of an account you can recover through support. It removes your ability to act as that identity at all. ## What identity is used for today Identity is checked wherever the Protocol needs to know that a request comes from a party entitled to make it. | Operation | What identity proves | | - | - | | Registering an action | The transaction signer is the creator and pays the fee | | Uploading to Cascade | The uploader controls the account that registered the action | | Retrieving a private object | The requester is permitted to read it | The first is ordinary transaction signing. The second and third are not, because they happen over HTTP against a SuperNode rather than as a transaction on chain. Those use ADR-036. ## ADR-036 signatures ADR-036 is the Cosmos standard for signing arbitrary data. It produces a signature that proves control of a key without submitting a transaction. Cascade uses it because upload and download are direct calls to a SuperNode. A SuperNode needs to verify you control the registering account before it accepts bytes or serves a private object, and requiring a transaction for that would add a block of latency and a gas cost to every data operation. What gets signed differs by operation. | Operation | Signed payload | | - | - | | Upload | The `data_hash` of the file | | Download | The `action_id` of the stored object | The signature travels with the request. The SuperNode recovers the signer and checks it against the on-chain action record. ### Producing one Two details trip people up, and both come from the standard rather than from Lumera. **The `chain_id` must be empty.** ADR-036 signs data that is not bound to a chain, so the sign document carries an empty string. Passing your real chain ID produces a signature the SuperNode will reject. **CosmJS does not expose `signArbitrary` on a direct wallet.** Protobuf signing and amino signing are separate paths, and ADR-036 requires amino. The usual approach is to hold both a direct wallet for transactions and an amino wallet for arbitrary signing, and expose both through one signer object. The sign document takes a fixed shape. ```ts theme={null} const signDoc = makeAminoSignDoc( [{ type: "sign/MsgSignData", value: { signer: signerAddress, data: Buffer.from(payload).toString("base64"), }, }], { gas: "0", amount: [] }, // no fee, nothing is broadcast "", // chain_id must be empty for ADR-036 "", // memo 0, // account number 0 // sequence ); ``` Browser wallets such as Keplr implement `signArbitrary` directly, so in a browser context you call it and skip the wrapper. The [Quickstart](/quickstart) shows the server-side construction, and [Build a browser app](/cascade/guides/browser-app) shows the wallet path. ## Public and private objects Every Cascade action carries a visibility flag set at registration. A **private** object requires a valid ADR-036 signature resolving to a permitted identity before a SuperNode will serve it. This is why private data can live on a public network. The encoded symbols are distributed across independent operators, but retrieval is gated on identity. A **public** object is served without a signature. Use it when open availability is the point. Visibility is not encryption. A private object is access-controlled, not unreadable. For data whose contents must never be visible to operators, encrypt client-side before upload. The network then provably retains bytes it cannot read. See [Store encrypted files on Cascade](/cascade/guides/encrypted-storage). ## Signing agents and applications Because identity is just an address with a key, non-human participants use the same mechanism. An agent with its own keypair has its own LumeraID, distinct from its operator's, and its actions are attributable to it rather than to whoever deployed it. That separation is worth setting up deliberately even now. Actions registered under a shared operator key are indistinguishable from each other in the on-chain record, and the record is what later capabilities read. Giving each agent its own identity today costs nothing and preserves attribution you cannot reconstruct afterward. Scoped delegation, which lets an owner grant an agent bounded authority rather than a copy of a key, is described in [Identity](/intelligence-layer/identity) as part of the claim model. ## Key handling A few consequences follow from identity being key-controlled. * **Never hardcode a mnemonic.** Load it from an environment variable or a secrets manager. The Quickstart says this in a warning for a reason. * **Use separate keys for separate roles.** A validator operator key, a SuperNode account key, and an application signing key should not be the same key. * **Treat key loss as identity loss.** There is no recovery path outside your own backups. Why identity is the root of the architecture. A working ADR-036 signer in context. Signing with Keplr. What `x/lumeraid` stores. # What Is the Intelligence Layer? Source: https://docs.lumera.io/intelligence-layer/overview The part of the Protocol concerned with autonomous software, and the gap in existing infrastructure it addresses. The Intelligence Layer is the part of the Protocol concerned with autonomous software. It defines how a participant is identified, how it remembers, how its work is executed and evidenced, how that work is paid for, and how its record accumulates into something others can rely on. It is not a separate network, a sidechain, or a parallel stack. Every capability in this section is expressed through the primitives documented in [Protocol Architecture](/architecture). The chain remains the Control Plane. SuperNodes remain the Execution Plane. Work is still requested as an Action and settles through the Action Lifecycle. ## Why it exists Software that runs without a person watching it needs guarantees that human-facing software does not. A developer using an API holds context in their head, reads the output and judges it, and resolves a billing dispute by email. An autonomous process does none of that. It needs state that survives the end of a session, an identity that means something to a counterparty it has never met, evidence that the work it paid for actually happened, and a way to settle without a commercial relationship with every provider it touches. Most of that does not exist yet. Agents today call centralized endpoints, authenticate with API keys, and pay with a card attached to those keys. There is no record of which model ran or what data it consumed. If a provider changes behavior, raises prices, or withholds a response, the calling application has no recourse and no proof of what it was promised. Every one of those gaps is a coordination problem. Coordination problems are what protocols are for. ## What it is not Being precise about the boundary prevents a common misreading. The Intelligence Layer is not a model host. Running inference is one kind of work among many, and it is the kind that commoditizes fastest. The Protocol coordinates, evidences, and settles work regardless of where the computation happens. It is not an agent framework. It does not compete with the libraries developers use to build agents. It is the layer underneath them, providing the identity, memory, evidence, and settlement those frameworks currently have to assume or fake. It is not a claim that the Protocol produces intelligence. Reasoning happens off chain. What lands on chain is the commitment, the receipt, and the outcome. ## Relationship to Protocol Services Lumera already exposes work as Protocol Services. A service is priced by the resource it consumes, requested as an Action, escrowed on registration, and settled on confirmed completion. The Intelligence Layer does not replace that model. It extends the range of work the model can carry, and it adds the capabilities autonomous callers need in order to use it without supervision. Decentralized Intelligent Services are Protocol Services consumed by software rather than by people. This is the consistent pattern across the section. Each capability extends existing machinery rather than introducing a new subsystem beside it. ## Where to go next The design rules every capability follows. What the Protocol provides, and what is available today. # Architectural Principles Source: https://docs.lumera.io/intelligence-layer/principles The design rules that govern how capabilities are added to the Protocol, and what each one means in practice. The Intelligence Layer is governed by a small number of design rules. They are worth reading before the capability pages, because each capability is an application of them rather than an independent design. Each principle below states the rule and what follows from it for someone building on the Protocol. ## The chain commits, SuperNodes execute The Control Plane owns what must be strongly consistent. That means identity, permissions, budgets, prices, escrows, commitments, receipts, and dispute outcomes. The Execution Plane performs work. The memory plane holds artifacts and state. Nothing in the Execution Plane or the memory plane ever becomes the source of truth for anything the Control Plane owns. **What this means for you.** Authorization and settlement answers come from the chain and are consistent. Data movement does not pass through consensus, so throughput is not bounded by block time. When those two facts appear to conflict, the chain is authoritative. ## Reason off-chain, verify on-chain Computation that cannot be made deterministic happens off chain. What is committed on chain is the evidence that it happened. **What this means for you.** A receipt is a commitment to an event, not a copy of its content. You can prove work occurred without publishing what was in it, which is what keeps verification and confidentiality from being mutually exclusive. ## Composition over reinvention New capabilities extend existing machinery. They are new Protocol Modules and new Action types, not new consensus, not a second settlement path, and not a parallel identity system. **What this means for you.** What you learn about the Action Lifecycle transfers. A new capability arrives as a new kind of Action moving through a lifecycle you already understand, priced and settled the way everything else is. ## Authority is graded A fluent answer is not a proof, and confidence is not a permission. The Protocol grades what a result entitles a caller to do, from advisory output through independently verified results and reproducible computation, up to narrowly scoped protocol-native effects. **What this means for you.** Irreversible effects stay in your application. The Protocol returns signed results and structured plans. Your application revalidates and executes. That ordering is deliberate. ## Objective faults are penalized, subjective quality is priced Faults that can be demonstrated carry consequences. Timeouts, missing or invalid receipts, wrong versions, unavailable artifacts, deterministic mismatches, and policy violations are all provable. Whether an answer was good is not provable, so it is not penalized. It accumulates in the trust record instead, where a history of outcomes prices it over time. **What this means for you.** Slashing is predictable and scoped to things an operator controls. Quality signals come from reputation, not from consensus. ## Boundaries are part of the design Each capability has a defined scope and an explicit list of what it must never own. Identity does not hold application data. Metered settlement does not fund long-lived obligations. The composition layer does not take custody of irreversible effects. **What this means for you.** The dependency graph stays acyclic, so a failure in one capability does not propagate into the ones it depends on. ## Dependency order is not adoption order The capabilities form a dependency graph with identity at the root. That is the order the architecture is built in. It is not the order a developer meets them in, which follows practical usefulness. **What this means for you.** Start with the capability that solves a problem you have now. The [capability page](/intelligence-layer/capabilities) sets out both orderings and what is available today. # Trust Source: https://docs.lumera.io/intelligence-layer/trust How the Protocol turns identity, receipts, and settlement history into a verifiable record participants can use to evaluate each other. Trust is the capability that lets a participant evaluate a counterparty it has no relationship with. It is the last of the five capabilities to become useful, because it is derived rather than designed. Identity provides the subject. Execution and settlement provide the evidence. Trust is what accumulates when those records are read together over time. ## Why trust becomes a protocol concern When software is operated by people, trust is a human judgment. A developer picks a provider based on its reputation, reads the output, and forms a view. Autonomous operation removes the person from that loop. An agent selecting another agent for a subtask, consuming data from a source it did not vet, or committing a budget based on an analysis it cannot review has no way to make that judgment. Brand does not help. Neither does a self-reported rating. What is needed instead is a record of behavior that cannot be authored by its subject. That requirement is what makes trust a protocol capability rather than an application feature. Only the Protocol sees the whole event stream, and only the Protocol can anchor it to identity in a way that cannot be forged. ## Compiled from protocol events The trust record is not submitted. It is compiled from things that already happen. | Source | What it evidences | | - | - | | Identity claims and delegations | Who a participant is and whose authority it holds | | Attestations | Verifiable statements other participants made about it | | Signed receipts | Work performed, attributable to the identity that performed it | | Settlements | Obligations that were met and paid | | Disputes and penalties | Obligations that were not | | Retention proofs | Data still held, demonstrated repeatedly over time | Each of these is already produced by another capability. Trust adds interpretation, not new instrumentation. That is why it is composition rather than a new subsystem. The properties that follow are structural. A record of ten thousand acknowledged Actions cannot be fabricated, because producing it requires spending real LUME and performing real work that others independently confirmed. A retention history cannot be back-dated, because the proofs are on chain. Behavior is expensive to fake in a way that self-reported reputation is not. ## Reputation is a vector, not a score Collapsing a participant's history into one number would discard the information that makes it useful. An agent may have an extensive, clean record for research work and no record at all for handling capital. Those are different questions, and the distinction is exactly what a caller needs. The trust record is therefore multi-dimensional and context-dependent. It is also time-weighted, because recent behavior is more predictive than old behavior. The practical consequence is that trust is an input to routing and selection rather than a badge. A caller asks whether this participant has a record in the specific context at hand. ## Trust composes through delegation Because authority is granted on chain, trust follows the same paths authority does. If an enterprise delegates to an agent, and that agent grants a narrower capability to a specialist, there is a verifiable chain from the original authority to the final actor. The record can weigh that path. Trust extends past direct relationships without anyone having to assert it. Reputation is also portable. It attaches to a LumeraID rather than to a venue, so a record earned in one context carries into another. Key rotation preserves it, which is what allows a long-lived identity to accumulate history without becoming brittle. ## Commitments, not content The trust record is built from commitments, metadata, and outcomes. It is not built from stored inputs and outputs. A receipt commits to what happened. It does not publish what was said. Content stays off chain and reachable only under identity-gated authorization. This is a deliberate boundary. A trust record that required publishing the work it evaluated would make the record and the privacy of its subject mutually exclusive, and the Protocol declines that trade. ## Where trust begins Trust does not have to wait for an execution layer to exist, because storage already produces the right kind of evidence. Every participant storing data on the network generates behavioral records from its first write. Retention challenges passed and failed, settlements, and retrieval patterns are all anchored to identity and all objectively checkable. Those are the first edges of the record. This is the practical reason Durable Memory is the capability to build against first. It is useful on its own, and using it produces the evidence every later capability reads. The subject that trust attaches to. The capability that seeds the record today. # Trust Graph Source: https://docs.lumera.io/intelligence-layer/trust/trust-graph The reputation mechanics running on Lumera today, the evidence they are compiled from, and how scores decay and drive enforcement. The trust graph is often described as something a protocol builds later, once it has history to draw on. On Lumera the first subgraph already operates, and it decides which operators receive work and get paid. This page covers those mechanics. For why trust belongs to the Protocol and how it relates to identity, see [Trust](/intelligence-layer/trust). ## Reputation already governs who gets work `x/supernode` maintains a ranked list of SuperNodes, and `x/action` routes work to the top of it. Rank is a function of stake and performance, and performance comes from the epoch reports collected by `x/audit`. That loop is a complete reputation system. Behavior is observed, compiled into a position, and the position determines income. An operator does not choose its rank and cannot assert it. What makes it credible is the cost of manipulation. Rank cannot be bought without bonding real stake under LEP-3, and it cannot be earned without performing work that other nodes verify. ## Evidence the graph is compiled from today Every input below is produced by a capability that already runs. | Evidence | Source | What it demonstrates | | - | - | - | | Storage challenge outcomes | Deterministic challenger and observer quorum over the SuperNode mesh | Whether a node holds what it claims | | Retained volume | LEP-4 epoch metrics, `cascade_kademlia_db_bytes` | Scale of obligation carried | | Report freshness | Epoch report submission | Liveness as a service participant | | Action finalization history | `x/action` state transitions | Work completed against work abandoned | | Bond status | Validator delegation under LEP-3 | Capital committed and currently at risk | None of it is self-attested in the way a review or an endorsement is. Retained volume is self-reported, which is why it passes through smoothing, a growth cap, and a ramp-up before it becomes payout weight, and why challenges exist to test the claim independently. ## Three scores, deliberately separate LEP-6, Storage-Truth Enforcement and Ticket-Driven Self-Healing, is approved and specifies the scoring model explicitly. The scores are computed and applied only while the `x/audit` parameter `storage_truth_enforcement_mode` is above `UNSPECIFIED`. The design decision worth understanding is the separation. | Score | Question it answers | Used for | | - | - | - | | **Node suspicion** | Is this operator failing its obligations? | Audit enforcement against the node | | **Ticket deterioration** | Does this piece of data need repair? | Scheduling self-healing | | **Reporter reliability** | Can I trust what this node says about others? | Weighting a node's reports and its eligibility to challenge | Collapsing these into one number would break all three. Data can deteriorate because several holders churned, with no single operator at fault. An operator can fail while the data survives elsewhere. And a node can be a reliable storer while being an unreliable witness. Keeping them apart means an enforcement decision and a repair decision never contaminate each other. ## Reputation decays by construction Each score decays as epochs pass without new evidence, applied lazily on update. ```text theme={null} score_new = floor(score_old × decay^epochs_since_last_update) + delta ``` Node suspicion decays at 0.92 per epoch. Reporter reliability and ticket deterioration decay at 0.90. This is not leniency. It is what makes the score a **current assessment** rather than a permanent record. An operator that failed twenty epochs ago and has performed cleanly since is a different counterparty than one failing now, and a system that could not express that difference would be useless for routing decisions. Recovery from an enforcement band requires both decay below the threshold and a run of clean passes. Time alone does not clear a fault. ## Scores map to graduated consequence Node suspicion drives action in bands rather than at a single cliff. | Band | Consequence | | - | - | | Low | Baseline challenge rate, no action | | Elevated | Storage watch, increased challenge frequency | | High | Probation | | Severe | Postpone candidate, then strong postpone | Escalation also considers pattern rather than magnitude alone. Repeated failures on distinct data, or failures across both recently assigned and long-held data, escalate faster than the same score reached by isolated events. Failing at both ends is a different signal than failing once. ## Scoring the witness, not just the subject Reporter reliability is the least obvious of the three and the most interesting. A node that reports failures which independent rechecks overturn accumulates a penalty. So does a node whose negative reports run persistently above the network median without being confirmed. The consequence is that its future reports carry less weight, and at the extreme it becomes temporarily ineligible to act as a challenger. This closes an attack that most reputation systems leave open. If reporting badly about a competitor were free, it would be rational. Here the report is itself evidence, and being wrong about others is a fault attributable to you. Provisional suspicion contributed by a low-reliability reporter is scaled down until an independent recheck confirms it. Accusation and finding are separate states. ## What the graph does not yet cover Stated plainly, because the boundary matters when evaluating the architecture. * **Operators only.** Today's reputation is about SuperNodes serving work. Requesters and agents accumulate history in the on-chain record, but no score is compiled from it. * **Storage-weighted.** The strongest signals concern retention, because storage is the mature service. Reputation for other work types arrives with those work types. * **Not yet delegation-aware.** Trust composing along delegation chains is described in [Trust](/intelligence-layer/trust) as architecture and depends on the claim model. * **Not a public score API.** Rank is observable. A general-purpose reputation query surface is not part of the current interface. ## Inspecting it Rank and node state are readable on chain and through the explorer, so you can see which operators are serving your actions and what state they are in. ```bash theme={null} lumerad query supernode list-super-nodes ``` Track the specification in the [LEP repository](https://github.com/LumeraProtocol/lumera/tree/master/docs/leps). Why reputation is a Protocol capability. What happens to an operator when scores rise. How retention converts into payouts. The service most evidence is produced by. # Verifiable Execution Source: https://docs.lumera.io/intelligence-layer/verifiable-execution How work is requested, performed by SuperNodes, evidenced by receipts, and settled only on confirmed completion. Verifiable Execution is the capability that lets one participant rely on work performed by another without trusting the party that performed it. It is the existing Action framework applied to a wider range of work. The lifecycle does not change. A request is registered on chain, a fee is escrowed, SuperNodes in the Execution Plane perform the work, and settlement follows confirmed completion. Storage was the first kind of work to move through that lifecycle. It is not the only kind it can carry. ## Actions and Agents Two terms do distinct jobs, and the distinction is the basis of the whole model. An **Action** is a single unit of work with an on-chain record and a defined lifecycle. It is stateless and atomic. It has a requester, a fee, a set of SuperNodes that serve it, and a terminal state. An **Agent** is a process that pursues a goal by composing Actions. It retrieves state, requests work, evaluates results, and acts on them, without a person driving each step. The Protocol commits Actions. Agents are the reason Actions need to be composable, evidenced, and settleable without human supervision. Agents are not a privileged category inside the Protocol. They are participants, with identities, that use the same interfaces anything else uses. ## Why execution is verifiable The Protocol does not ask a requester to trust the operator that served them. It makes completion demonstrable. Three properties do the work. **Escrow before execution.** The fee is committed on chain when the Action is registered, before any work begins. The requester's funds are locked and the operator's compensation is contingent. Neither side is exposed to the other's good behavior. **A receipt for each step.** Work produces signed evidence attributable to the identity that performed it. A receipt is a commitment to what happened, not a copy of the content. It records that a step occurred, by whom, over what inputs, with what result shape. **Settlement gated on acknowledgment.** Payment releases when completion is confirmed against the on-chain record. Actions that expire before finalization refund the escrow. The requester is not paying for a promise, and the operator is not serving on credit. Together these mean a requester can verify that work happened without having watched it happen, and without a relationship with the operator. ## Verification and its limits Verification is deliberately scoped to what can be checked objectively. Some work is reproducible. A deterministic computation with declared inputs and outputs can be re-executed by an independent SuperNode and compared. Disagreement is a provable fault. Some work is not. A model producing an answer is not reproducible in that sense, and no amount of re-execution settles whether the answer was good. The Protocol handles the two cases differently rather than pretending they are the same. What is objectively checkable is verified and can carry penalties. That includes timeouts, missing or invalid receipts, wrong versions, unavailable artifacts, deterministic mismatches, and policy violations. What is subjective is not slashed. It flows to the [Trust](/intelligence-layer/trust) record, where a history of outcomes prices it over time. ## Authority is graded An answer from a model is not a permission. The architecture makes that explicit by grading what a result entitles a caller to do. | Result | Authority it carries | | - | - | | Advisory result | An answer with a trace and a receipt. No automatic effect. | | Verified advisory result | An independent check confirmed structure, citations, and policy compliance. Higher confidence, still advisory. | | Deterministic result | Reproducible by independent nodes. Objective authority. | | Action plan | A structured recommendation with preconditions and limits. The calling application decides. | | Protocol-native effect | Reserved for tightly scoped operations, and only where identity, escrow, audit, and rollback semantics are established. | The practical consequence for a developer is that irreversible effects stay in the application. The Protocol returns signed results and plans. The application revalidates and executes. That ordering is intentional, and it is what makes the model safe to build on before a trust record has depth. ## Composition Work that no single Action completes is expressed as composition rather than as a new subsystem. A composed workflow is published as a content-addressed, versioned bundle. The chain registers its hash and metadata. The payload is stored in [Cascade](/cascade/how-cascade-works). Invoking it means invoking a specific version by hash, which makes an execution reproducible and auditable rather than incidental. Execution follows a runner and verifier pattern. One SuperNode executes. An independent SuperNode checks what is objectively checkable, including structure, schema conformance, declared citations, policy compliance, and any deterministic steps by replay. Payment depends on lifecycle completion, never on a judgment about answer quality. A composed result is trustworthy because each component is individually evidenced, individually settled, and individually auditable. It is not trustworthy because the arrangement is elaborate. How escrow and settlement are funded. How authority is granted and checked. # Actions and the Action Lifecycle Source: https://docs.lumera.io/intelligence-layer/verifiable-execution/actions How work is requested on Lumera, the states an action moves through, and why the lifecycle is the Protocol's extension point. An Action is the unit of work on Lumera. Every Protocol Service request becomes an action, which is an on-chain record with an identifier, a creator, a state machine, and an escrowed fee. Actions are handled by `x/action`. The module is deliberately service-agnostic. It knows how to register work, hold a fee, route the request to operators, verify completion, and settle. It does not know what the work is. ## Action types Two types exist today, and both run through the same lifecycle. | Type | Work performed | Priced by | | - | - | - | | **Cascade** | Erasure-code a file and store it permanently across the SuperNode mesh | Size of the data | | **Sense** | Evaluate media for near duplicates and record a verdict | Size of the data and the analysis performed | The difference between them is what SuperNodes do in the execution phase. Registration, escrow, verification, and settlement are identical. That is what makes adding a service type a smaller change than it appears, and it is the clearest example of the Protocol growing by composition rather than reinvention. ## The record on chain Each action is stored keyed by `action_id` and carries its type, creator, current state, and escrowed fee. You can read any of it directly. ```bash theme={null} lumerad query action action ``` The `action_id` is returned in an `action_registered` event when the registering transaction is included, and it remains the permanent on-chain handle for the work. ## States An action starts PENDING with its fee escrowed, moves to PROCESSING while SuperNodes execute, and ends DONE with the fee released. From either PENDING or PROCESSING it can instead end EXPIRED, which returns the escrow. An action starts PENDING with its fee escrowed, moves to PROCESSING while SuperNodes execute, and ends DONE with the fee released. From either PENDING or PROCESSING it can instead end EXPIRED, which returns the escrow. | State | Meaning | | - | - | | `UNSPECIFIED` | Zero value. Also the sentinel a handler returns for no state change | | `PENDING` | Registered on chain and the fee is escrowed. No operator has begun work | | `PROCESSING` | One or more SuperNodes are executing the request | | `DONE` | Completion was verified on chain. The escrowed fee is released | | `APPROVED` | Used by cross-chain flows that require an explicit approval step before settlement | | `REJECTED` | Reserved in the enum. No chain logic sets it | | `FAILED` | Finalization was rejected. The escrow is neither released nor refunded | | `EXPIRED` | The expiration passed while the action was still `PENDING` or `PROCESSING`. The escrow returns to the creator | Wire values carry the `ACTION_STATE_` prefix, as in `ACTION_STATE_DONE`. The terminal states are what a caller should build against. `DONE` means the chain verified completion, not that an operator claimed it. ## The lifecycle The creator submits `MsgRequestAction` with the action type, the service metadata, an expiration, and the fee. The chain validates the message, escrows the fee, and emits the `action_id`. For Cascade, registration also carries the content hash and an availability commitment. See [How Cascade works](/cascade/how-cascade-works). `x/action` routes the request to the ranked top set of SuperNodes maintained by `x/supernode`. Rank is a function of stake and performance, so assignment is a consequence of on-chain state rather than a negotiation. Selected SuperNodes perform the work in the Execution Plane. Data moves directly between the client and the operators and does not pass through consensus, which is why throughput here is not bounded by block time. An operator submits the finalization message with whatever evidence the action type requires. For Cascade that includes Merkle proofs for the chunks committed at registration, which the chain verifies before accepting the finalization. On verified finalization the state becomes `DONE` and the escrow releases to the participating SuperNodes. If finalization never arrives before the expiration, the state becomes `EXPIRED` and the escrow returns. A finalization the chain rejects settles nothing. See [Escrow and settlement](/intelligence-layer/economic-coordination/escrow-and-settlement). ## Expiration Every action carries an expiration supplied at registration. It is a deadline for operators to complete the work, and it exists so escrowed funds cannot be held indefinitely by inaction. An expired action refunds. A caller therefore never has capital stranded by an unresponsive operator, which is what makes it safe to register work with a party you have no relationship with. ## Why the lifecycle matters architecturally The lifecycle is the Protocol's extension point. A new capability arrives as a new action type moving through these same states, priced the same way, escrowed the same way, and settled on the same conditions. That has a practical consequence worth internalizing. What you learn from integrating one service transfers to the next. The verification evidence differs by type, and everything around it does not. What happens to the fee at each state transition. How the fee is calculated. The lifecycle applied to permanent storage. `x/action` state and messages. # Networks and Endpoints Source: https://docs.lumera.io/networks Network configuration, public endpoints, genesis files, and connection information for Lumera Mainnet and Testnet. Lumera operates two public networks: **Mainnet**, the production network for applications and protocol services, and **Testnet**, a development environment for testing integrations, deploying smart contracts, and onboarding validators and SuperNode operators. Both networks use the same `lumerad` binary and protocol architecture. Network-specific configuration—including chain IDs, genesis files, seed nodes, and public endpoints—is provided below. ## Public Endpoints Public endpoints allow applications and developers to query network state without running a node. These endpoints may be rate-limited and should not be treated as guaranteed production infrastructure. For high-volume or production workloads, run your own node or use a dedicated provider. | Service | Mainnet | Testnet | | - | - | - | | **gRPC** | `grpc.lumera.io:443` | `grpc.testnet.lumera.io:443` | | **RPC** | `https://rpc.lumera.io` | `https://lumera-testnet-rpc.polkachu.com` | | **LCD (REST)** | `https://lcd.lumera.io` | `https://lumera-testnet-api.polkachu.com` | | **SN-API** | `https://snapi.lumera.io` | `https://snapi.testnet.lumera.io` | Community infrastructure providers such as [Polkachu](https://www.polkachu.com/networks/lumera) also operate public Lumera endpoints. Validator snapshots and additional connection resources are available in the [validator setup guides](/validators/mainnet/node-setup). ## Network Configuration Select a network below to view its chain parameters, genesis file, seed nodes, and node configuration. Mainnet is Lumera's production network. LUME carries economic value, validators and SuperNodes earn protocol rewards, and data stored through Cascade is retained under the network's permanent-storage model. Protect all Mainnet keys using production-grade security practices. ### Network Parameters | Parameter | Value | | - | - | | **Chain ID** | `lumera-mainnet-1` | | **Native denom** | `ulume` | | **Minimum gas price** | `0.025ulume` | | **Explorer** | [Lumera Portal](https://portal.lumera.io/lumera-mainnet-1) | ### Genesis File Download the network genesis file from the official `lumera-networks` repository: ```bash theme={null} curl -o genesis.json https://raw.githubusercontent.com/LumeraProtocol/lumera-networks/master/mainnet/genesis.json ``` Move the file into the node configuration directory: ```bash theme={null} cp genesis.json ~/.lumera/config/genesis.json ``` The claims file loaded into chain state at genesis is published next to it: [mainnet/claims.csv](https://raw.githubusercontent.com/LumeraProtocol/lumera-networks/master/mainnet/claims.csv). ### Seed Nodes Add the network seed node to the `[p2p]` section of `~/.lumera/config/config.toml`: ```toml theme={null} seeds = "10ed1e176d874c8bb3c7c065685d2da6a4b86475@seed-lumera.ibs.team:16656" ``` ### Minimum Gas Price Set the minimum accepted gas price in `~/.lumera/config/app.toml`: ```toml theme={null} minimum-gas-prices = "0.025ulume" ``` ### Quick Configuration ```bash theme={null} # Initialize node (replace with your node name) lumerad init --chain-id lumera-mainnet-1 # Download genesis curl -o ~/.lumera/config/genesis.json \ https://raw.githubusercontent.com/LumeraProtocol/lumera-networks/master/mainnet/genesis.json # Set seeds in config.toml sed -i 's/seeds = ""/seeds = "10ed1e176d874c8bb3c7c065685d2da6a4b86475@seed-lumera.ibs.team:16656"/' \ ~/.lumera/config/config.toml # Set gas price in app.toml sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025ulume"/' \ ~/.lumera/config/app.toml # Start the node lumerad start ``` Never share your validator key at `~/.lumera/config/priv_validator_key.json` or your operator key mnemonic. Back both up securely before starting the node. Testnet is where new validators onboard, developers test integrations, and SuperNode operators rehearse before staking on mainnet. Testnet LUME has no value. Request some from the [faucet](/faucet). ### Network Parameters | Parameter | Value | | - | - | | **Chain ID** | `lumera-testnet-2` | | **Native denom** | `ulume` | | **Minimum gas price** | `0.025ulume` | | **Explorer** | [Lumera Portal](https://portal.testnet.lumera.io/lumera-testnet-2) | | **Faucet** | [Portal faucet](https://portal.testnet.lumera.io/lumera-testnet-2/faucet) | ### Genesis File Download the network genesis file from the official `lumera-networks` repository: ```bash theme={null} curl -o genesis.json https://raw.githubusercontent.com/LumeraProtocol/lumera-networks/master/testnet-2/genesis.json ``` Move the file into the node configuration directory: ```bash theme={null} cp genesis.json ~/.lumera/config/genesis.json ``` The claims file loaded into chain state at genesis is published next to it: [testnet-2/claims.csv](https://raw.githubusercontent.com/LumeraProtocol/lumera-networks/master/testnet-2/claims.csv). ### Peers Testnet has no seed node. Download the address book that Lumera's snapshot service keeps current; your node dials peers from it: ```bash theme={null} curl -fsSL -o ~/.lumera/config/addrbook.json \ https://snapshots.lumera.io/testnet/addrbook.json ``` ### Minimum Gas Price Set the minimum accepted gas price in `~/.lumera/config/app.toml`: ```toml theme={null} minimum-gas-prices = "0.025ulume" ``` ### Quick Configuration ```bash theme={null} # Initialize node (replace with your node name) lumerad init --chain-id lumera-testnet-2 # Download genesis curl -o ~/.lumera/config/genesis.json \ https://raw.githubusercontent.com/LumeraProtocol/lumera-networks/master/testnet-2/genesis.json # Download the address book (testnet has no seed node) curl -fsSL -o ~/.lumera/config/addrbook.json \ https://snapshots.lumera.io/testnet/addrbook.json # Set gas price in app.toml sed -i 's/minimum-gas-prices = ""/minimum-gas-prices = "0.025ulume"/' \ ~/.lumera/config/app.toml # Start the node lumerad start ``` Use the [Lumera Portal](https://portal.testnet.lumera.io/lumera-testnet-2) to watch your validator status, inspect transactions, and confirm your node produces blocks. ## Node Requirements The following specifications are the minimum recommended baseline for a production validator. SuperNode operators should provision additional CPU, memory, storage, and network capacity based on the protocol services they intend to support. | Component | Minimum | | - | - | | **CPU** | 8+ cores | | **Memory** | 64 GB RAM | | **Storage** | 2 TB+ SSD | | **Network** | High bandwidth with low latency | Underpowered validators may miss blocks, become jailed, or incur slashing penalties. SuperNodes that fail performance or availability requirements may fall out of the active service set and lose protocol-service rewards. Note: Chain state and protocol-service storage requirements grow over time. Maintain sufficient capacity headroom and continuously monitor disk usage, memory utilization, network performance, and service availability. ## EVM Access Beginning with `v1.20.0`, Lumera nodes enable the EVM JSON-RPC server and OpenRPC discovery by default, allowing a node to support Ethereum-compatible tooling such as Hardhat, Foundry, and standard wallet integrations. Mainnet and Testnet may upgrade on different schedules. Confirm the software version currently deployed on a network before relying on EVM functionality. See the [Smart Contracts overview](/smart-contracts/overview) for configuration and development details. ## Resources and Support Join our growing community and use the following resources for technical support, source code, protocol announcements, and network upgrade information. Get help with Validator, SuperNode, and developer questions. Read the source for `lumerad`, the `supernode` daemon, and the SDKs. Follow protocol announcements and upgrade notices. ## Next Steps Connect to Lumera and store your first file through Cascade. Request Testnet LUME for development and testing. Configure a node and prepare to join the validator set. # Network status and upgrades Source: https://docs.lumera.io/networks-status Current mainnet and testnet software versions, the next scheduled upgrade, and the applied upgrade history. Chain versions and upgrade heights change, and any value written into a page goes stale. This page records the current state with a timestamp, and every figure here is one command away from being re-checked. ## Check it yourself These queries are the source of truth. Run them before relying on anything below. ```bash Mainnet theme={null} # running version and height curl -s https://rpc.lumera.io/abci_info | jq -r '.result.response | "\(.version) at height \(.last_block_height)"' # scheduled upgrade, null when none is on chain curl -s https://lcd.lumera.io/cosmos/upgrade/v1beta1/current_plan | jq . ``` ```bash Testnet theme={null} curl -s https://lumera-testnet-rpc.polkachu.com/abci_info | jq -r '.result.response | "\(.version) at height \(.last_block_height)"' curl -s https://lumera-testnet-api.polkachu.com/cosmos/upgrade/v1beta1/current_plan | jq . ``` From a synced node, `lumerad status` and `lumerad query upgrade plan` return the same information. ## Current versions | | Mainnet (`lumera-mainnet-1`) | Testnet (`lumera-testnet-2`) | | - | - | - | | `lumerad` | `v1.12.0` | `v1.20.2-rc1` | | Height when checked | 7,184,647 | 6,900,051 | | Scheduled upgrade | None on chain | None on chain | | SuperNode release in widest use | `v2.5.2` | `v2.6.x-testnet` | Checked 2 October 2026. SuperNode versions vary across operators; the live distribution is in the [version matrix](https://lumescope.lumera.io/v1/version/matrix). Mainnet runs the `v1.12.0` line and testnet runs the `v1.20.x` line, so testnet carries features mainnet does not yet have, including the EVM. Check a feature's availability on your target network before building against it. ## Next upgrade Neither network has an upgrade plan on chain. `current_plan` returns `null` on both. The mainnet EVM upgrade (Aurora Convergence, the `v1.20.x` line) was planned for block `6,715,000` and did not execute; mainnet passed that height on `v1.12.0`. It has been rescheduled and the new date is to be announced. See [the EVM upgrade](/upgrades/evm-upgrade). ## Applied upgrades Heights at which each upgrade handler executed, read from `applied_plan`. A dash means the upgrade was never applied on that network. | Upgrade | Mainnet | Testnet | | - | - | - | | `v1.6.1` | 434,000 | — | | `v1.8.0` | — | 1,740,000 | | `v1.8.4` | 2,270,000 | 1,922,000 | | `v1.9.0` | 2,964,000 | 2,630,000 | | `v1.10.1` | 3,770,000 | 3,410,000 | | `v1.11.0` | — | 3,763,000 | | `v1.11.1` | 4,218,000 | — | | `v1.12.0` | 5,193,000 | 4,833,000 | | `v1.20.0` | — | 5,547,000 | | `v1.20.1` | — | 5,615,000 | | `v1.20.2` | — | 6,149,000 | Query any one of them directly: ```bash theme={null} curl -s https://lcd.lumera.io/cosmos/upgrade/v1beta1/applied_plan/v1.12.0 | jq . ``` Build your node from a tag that carries every handler your target network has already applied. A binary missing a handler halts at that upgrade height and cannot sync past it. Testnet has applied `v1.20.2`, so a testnet node needs `v1.20.2` or newer. ## Next steps Chain IDs, endpoints, and denominations. What Aurora Convergence changes and what operators must do. # Protocol Economics Source: https://docs.lumera.io/protocol-revenue How issuance, transaction fees, protocol-service revenue, and long-term sustainability work across Lumera. Lumera's economic model coordinates four distinct functions: securing consensus, pricing block space, compensating protocol-service operators, and funding long-lived network obligations. Newly issued LUME rewards validators and delegators for securing the network. Transaction fees compensate block producers while permanently removing a portion of supply. Protocol-service fees are paid by applications requesting work and earned by the SuperNodes that perform it. Everlight supports obligations—such as permanent data retention—that continue long after an individual service request has settled. | Economic flow | Source | Primary recipients | Purpose | | - | - | - | - | | Block Rewards | Newly issued LUME | Validators and Delegators | Secure consensus and maintain a target bonded ratio | | Transaction Fees | Gas paid on transactions | Validators and Delegators, with a portion burned | Price block space, deter spam, and offset issuance | | Protocol-Service fees | Payments for executing Actions | SuperNodes | Compensate operators that provide decentralized services | | Sustainability Funding | Protocol reward flows, fee shares, and endowments | Eligible service operators | Fund long-lived protocol obligations | ## Block Rewards Newly issued LUME is distributed each block to validators and their delegators in exchange for securing consensus. Issuance is adaptive rather than fixed: it increases when the bonded share of supply falls below the protocol's target and decreases when staking participation exceeds it. See [LUME supply mechanics](/token). | Allocation | Share | | - | - | | Validators and delegators | 98% | | Community pool | 2% | The community pool supports protocol development, ecosystem initiatives, and other governance-approved expenditures. ## Transaction Fees The burn creates a direct link between network activity and LUME supply. As transaction volume increases, more LUME is permanently removed from circulation, partially offsetting the dilution created by security issuance. | Allocation | Share | | - | - | | Validators and delegators | 80% | | Burned | 20% | The burn is the network's deflationary link to usage: the more the chain is used, the more LUME is permanently destroyed. This is not a fixed emission schedule running independently of activity. It responds to it. ## Protocol-Service Fees Protocol-service fees are Lumera's demand-driven economic flow. They are paid by applications requesting decentralized services and earned by the SuperNodes that perform and verifiably complete the work. **Who pays.** Users, developers, smart contracts, and applications that request protocol Actions. Fees are established when an Action is registered, escrowed on-chain, and released according to the service's completion and settlement rules. **Who earns.** SuperNodes earn fees for the protocol services they successfully provide. Service revenue is distinct from staking rewards: operating a SuperNode is an independent economic activity requiring infrastructure, availability, and service-specific capabilities. **How fees are calculated.** Each protocol service prices the resources and obligations it consumes. Pricing may reflect data size, execution requirements, verification costs, duration, or other service-specific factors established by protocol parameters. ### Example: Cascade Pricing Cascade uses a base registration fee plus a size-based component reflecting the amount of data placed into permanent storage. | Component | Basis | | - | - | | **Base fee** | Charged when the storage Action is registered | | **Size-based fee** | Calculated from the amount of data stored | | **Long-term retention** | Supported through Everlight rather than recurring charges to the uploader | Current parameters are queryable on-chain and adjustable through governance. See [How Cascade Works](/cascade/how-cascade-works) for current pricing and lifecycle details. ### Keeping Service Pricing Competitive Protocol-service pricing must account for two competing requirements: services should remain commercially competitive for users, while operator compensation must remain sufficient to support reliable infrastructure. Where enabled by a service's pricing model, a **dynamic adjustment factor** can reduce the effect of changes in LUME's market value on real-world service pricing. Without such an adjustment, services denominated entirely in an appreciating native asset could become increasingly expensive relative to centralized alternatives. A separate **base-cost adjustment** can account for changes in the real-world expenses operators bear, including hardware, storage, bandwidth, and power. Together, these mechanisms are intended to preserve affordable service pricing without undermining the economics required to retain capable operators. ## Sustainability and Everlight Some protocol services create obligations that outlive the transaction that created them. Permanent storage is the clearest example: a single upfront payment must support data retention long after the original Action has settled. Per-use service fees and long-term sustainability funding therefore serve different economic purposes. Service fees compensate operators for completing immediate work. **Everlight** supports persistent obligations by directing protocol reward flows, registration-fee shares, and endowment yield toward eligible operators over time. SuperNodes must continue proving that required data remains available to remain eligible for retention compensation. Permanence is therefore supported by ongoing economic incentives and cryptographic evidence rather than by a contractual promise from a centralized company. Everlight provides the model for funding other long-lived protocol obligations as Lumera expands, including persistent artifacts, service availability, and additional infrastructure that cannot be sustained through one-time execution fees alone. See [Everlight](/cascade/everlight/overview). ## How the Economic Flows Interact Lumera's economic flows are designed to reinforce one another while serving distinct purposes. Security issuance rewards validators and delegators for maintaining consensus, but increases circulating supply. Transaction-fee burns offset a portion of that issuance in proportion to network activity. Protocol-service fees compensate the SuperNodes that provide capacity, allowing infrastructure supply to grow in response to application demand rather than relying solely on higher token issuance. Everlight closes the remaining gap by funding obligations that persist after an individual Action settles. This prevents permanent services from accumulating unfunded liabilities and allows one-time service payments to support long-lived outcomes. The resulting loop connects usage directly to network capacity and economic security: applications consume services, fees compensate operators, increased demand attracts additional capacity, and transaction activity removes a portion of LUME from circulation. ### Illustrative Cascade Transaction At current mainnet parameters, a Cascade Action includes a base fee of `30,000 ulume` plus `10 ulume` per KB. That places a 1 MB upload at approximately `0.04 LUME`. Testnet uses a `10,000 ulume` base, so the same upload costs about `0.02 LUME` there. The service fee is escrowed when the Action is registered and released according to the Cascade settlement lifecycle. The registration transaction also incurs gas, of which 80% is distributed to validators and delegators and 20% is burned. Everlight subsequently supports the network's ongoing retention obligation. These figures are governance-controlled protocol parameters and may change. Query the network or refer the [Cascade documentation](/cascade/how-cascade-works) for current values. Understand token utility, supply mechanics, staking, and burns. Provide protocol services and earn service fees. Learn how economic parameters and fee structures are governed. # Governance Proposals Source: https://docs.lumera.io/protocol/governance Submitting proposals, voting, software upgrades, and protocol parameter changes on Lumera. The Governance overview explains how protocol governance works and what it controls. This page focuses on participating in governance: submitting proposals, voting, software upgrades, and the operational mechanics of the governance module. Lumera uses the Cosmos SDK governance module (`x/gov`). Protocol proposals are submitted and voted on entirely on-chain, allowing the network to evolve through transparent community coordination rather than centralized administration. ## Proposal lifecycle Every governance proposal follows the same lifecycle from submission to execution. A proposal is submitted with an initial deposit. Once the required minimum deposit is reached, the proposal enters the voting period. During the voting period, holders of staked LUME cast votes. Validators vote with their delegated stake by default, while delegators may override their validator's vote for their own stake. At the end of the voting period, votes are tallied according to the protocol's governance rules, including quorum, threshold, and veto requirements. Approved proposals execute according to their proposal type. Parameter changes take effect automatically, while software upgrade proposals schedule coordinated network upgrades. Supported vote options are: * `yes` * `no` * `abstain` * `no_with_veto` A `no_with_veto` vote signals that a proposal is considered harmful or malicious rather than simply undesirable. *** ## Voting with lumerad Query all governance proposals currently on-chain. ```bash theme={null} lumerad query gov proposals ``` Vote on an active proposal. ```bash theme={null} lumerad tx gov vote yes \ --from \ --chain-id lumera-mainnet-1 \ --fees 10000ulume ``` Replace `yes` with `no`, `abstain`, or `no_with_veto` as appropriate. For Testnet, replace the chain ID with: ```text theme={null} lumera-testnet-2 ``` *** ## Browsing proposals Governance activity can also be viewed through the Lumera Portal. * **Mainnet:** [https://portal.lumera.io](https://portal.lumera.io) * **Testnet:** [https://portal.testnet.lumera.io/lumera-testnet-2](https://portal.testnet.lumera.io/lumera-testnet-2) The Portal displays proposal status, voting windows, deposits, validator voting records, and final vote tallies. *** ## Software upgrades Software upgrades are coordinated through on-chain governance. Upgrade proposals specify the target software version together with an upgrade height. Once the blockchain reaches that height, block production pauses until validators install the approved binary and resume consensus. Operators are encouraged to automate this process using **Cosmovisor**. See the [Validator Operations](/validators/mainnet/operations) guide for recommended upgrade procedures. *** ## Lumera Enhancement Proposals (LEPs) Lumera Enhancement Proposals (LEPs) are the protocol's design documents. They describe architectural changes, protocol improvements, and new capabilities before those changes are proposed through on-chain governance. LEPs are maintained in the Lumera repository: [https://github.com/LumeraProtocol/lumera/tree/master/docs/leps](https://github.com/LumeraProtocol/lumera/tree/master/docs/leps) A LEP documents the technical design and rationale behind a proposal. When implementation requires protocol changes, the corresponding governance proposal becomes the mechanism through which the network formally adopts that design. *** ## Governable protocol parameters Most protocol behavior is configurable through governance rather than fixed in software. Examples include: * Protocol-service pricing * Security issuance parameters * Transaction fee distribution * Everlight funding allocations * SuperNode operating parameters * Governance thresholds and voting periods * Community Pool expenditures Current parameter values can be queried directly from the network and may change through approved governance proposals. *** ## Related documentation Learn how governance fits into Lumera's protocol architecture. Configure automated software upgrades using Cosmovisor. Explore the modules and parameters governed on-chain. # Lumera module reference Source: https://docs.lumera.io/protocol/modules A guided map of the custom, EVM, and standard modules on the Lumera chain. Lumera Protocol runs on the Cosmos SDK with five custom modules that implement the protocol logic, an EVM module family added by the v1.20.0 upgrade, and the standard Cosmos modules. This page maps each module, the state it holds, its key messages, and where the deeper documentation lives. For how the layers fit together, read the [architecture overview](/architecture). | Module | Role | Deeper docs | | - | - | - | | `x/action` | Cascade and Sense action lifecycle | [How Cascade works](/cascade/how-cascade-works) | | `x/supernode` | SuperNode registry, ranking, rewards, Everlight pool | [SuperNode overview](/supernodes/overview) | | `x/lumeraid` | Identity anchored to Lumera addresses | [Architecture](/architecture) | | `x/claim` | Legacy Pastel token claims | Not documented | | `x/audit` | Epoch metric reports from SuperNodes | [Everlight for operators](/cascade/everlight/supernode-operators) | ## x/action `x/action` records every Cascade and Sense request as an action, an on-chain record with a state machine. It escrows the service fee at registration and refunds it when an action expires unfinalized. States run `PENDING`, `PROCESSING`, then `DONE`, `FAILED`, or `EXPIRED`. Cross-chain flows can add `APPROVED` after an explicit approval. The module stores action records keyed by `action_id`, each with its type, creator, current state, and escrowed fee. Its key messages are `MsgRequestAction` to register an action and `MsgApproveAction` for flows that require approval. Query any record with `lumerad query action action `. Deeper docs live at [how Cascade works](/cascade/how-cascade-works) for storage flows, [Sense](/protocol/sense) for authentication flows, and the [cross-chain overview](/cross-chain/overview) for the approval path. ## x/supernode `x/supernode` is the source of truth for SuperNodes. It registers nodes, maintains the ranked top list that `x/action` routes work to, tracks operational state and metrics, and pays rewards. A SuperNode must stay tied to an active validator to remain on the list. The module stores each node's account, endpoint, stake delegation, and state. One state worth knowing is `STORAGE_FULL`, entered when a node's disk usage passes its configured limit. A `STORAGE_FULL` node is excluded from new storage assignments but stays eligible for compute work like Sense and keeps earning Everlight payouts for the data it already holds. The module also hosts the Everlight SN Pool, a named module account that compensates SuperNodes for retaining Cascade data. Every payment period, measured in block height, the pool pays eligible nodes in proportion to the Cascade data they retain. The [Everlight overview](/cascade/everlight/overview) explains the funding model. Registration happens on chain with `lumerad tx supernode register-supernode VALOPER SN_ENDPOINT SN_ACCOUNT`, signed by the validator operator. The stake requirement combines validator self delegation and SuperNode account delegation. Check the current minimum with `lumerad query supernode params`. The [registration guide](/supernodes/register) walks the full flow. ## x/lumeraid `x/lumeraid` anchors identity records to Lumera addresses. There is no separate username. Your address is your identity and your private key controls the record. Signatures follow ADR-036, the Cosmos standard for signing arbitrary data, and authenticate Cascade uploads and downloads. The [architecture page](/architecture) covers how identity threads through the protocol. ## x/claim `x/claim` handles token claims from the legacy Pastel Network, which Lumera evolved from. The claim flow is not part of the developer surface and is not documented here. ## x/audit `x/audit` collects epoch reports from SuperNodes. Each epoch, nodes report service metrics defined by LEP-4, including `cascade_kademlia_db_bytes`, the amount of Cascade data a node holds. These reports feed SuperNode ranking and drive Everlight payouts. See [Everlight for SuperNode operators](/cascade/everlight/supernode-operators). ## EVM modules The v1.20.0 upgrade added Cosmos EVM support through five modules. The EVM is live on testnet today. Mainnet runs v1.12.0 and gains the EVM with its upgrade. | Module | Role | | - | - | | `x/vm` | Executes Solidity contracts on the Cosmos EVM | | `x/feemarket` | EIP-1559 dynamic base fee for EVM transactions | | `x/precisebank` | Bridges the 6 decimal `ulume` denom to the 18 decimal `alume` EVM denom | | `x/erc20` | Maps Cosmos tokens to ERC-20 interfaces | | `x/evmigration` | Migrates legacy accounts to EVM compatible keys, atomically and fee free | Start building with the [smart contracts overview](/smart-contracts/overview). The [EVM upgrade page](/upgrades/evm-upgrade) covers what changed at the chain level and the migration path. ## Standard Cosmos modules Lumera also ships the standard Cosmos SDK modules, which behave as they do on any Cosmos chain. `x/bank` handles transfers, `x/staking` runs delegated proof of stake, `x/gov` runs proposals and voting, `x/distribution` pays staking rewards, and `x/slashing` penalizes misbehaving validators. Governance behavior specific to Lumera is covered on the [governance page](/protocol/governance). ## Next steps Content authentication built on x/action. How proposals change module parameters. The v1.20.0 upgrade that added the EVM module family. # Sense content authentication Source: https://docs.lumera.io/protocol/sense Submit a file for AI powered near-duplicate analysis and record the result permanently on chain. Sense is Lumera Protocol's AI powered content authentication service. Submit a file and top ranked SuperNodes analyze it with perceptual fingerprinting. A perceptual fingerprint is a feature vector that captures what the content looks like rather than its exact bytes. The network compares it against every previously registered asset and records a verifiable similarity result on chain through the `x/action` module. Slightly altered copies still match, which plain hash comparison cannot do. This page covers what Sense is for, how an analysis runs, and how to submit and query Sense actions with `lumerad`. ## Use cases Prove that a digital artwork is not a near duplicate of a registered asset before minting. Verify the originality of visual or audio content before publishing or licensing it. Establish a timestamped on-chain record that a file existed in a unique form at a specific block height. Keep near duplicates from flooding marketplaces and diluting the value of originals. ## How Sense works You submit a registration transaction carrying the hash of your file. The `x/action` module escrows the fee and assigns a unique `action_id`. The action starts in the `PENDING` state. Top ranked SuperNodes, selected through `x/supernode`, pick up the action and process the file. Each one computes a perceptual fingerprint of the content. The state is `PROCESSING`. The SuperNodes compare the fingerprint against the network's registered assets. They compute a similarity score and collect any duplicate candidates that exceed the near-duplicate threshold. The aggregated result is committed and the action moves to `DONE`. The similarity score, duplicate candidates, and fingerprint hash become part of the permanent record, queryable by anyone. An action still unfinalized at its `expirationTime` becomes `EXPIRED` and the escrowed fee is refunded. The [architecture page](/architecture) walks the full action state machine that Sense shares with Cascade. ## Submitting a Sense action Register a Sense action with the `request-action` command. It takes four positional arguments, the action type, a metadata JSON string, the service price you offer, and an expiration timestamp. ```shell Register a Sense action theme={null} lumerad tx action request-action SENSE '' \ --from \ --chain-id lumera-mainnet-1 \ --fees 10000ulume ``` ```shell Register a Sense action theme={null} lumerad tx action request-action SENSE '' \ --from \ --chain-id lumera-testnet-2 \ --fees 10000ulume ``` The metadata JSON carries the hash of the file you want analyzed in its `data_hash` field. The price is denominated in `ulume` and the expiration time is a Unix timestamp for when the action expires. After broadcasting, note the `action_id` returned in the transaction response. ## Querying Sense results Once the action reaches `DONE`, the result lives in the on-chain action record. ```shell Query a Sense action theme={null} lumerad query action action ``` The response includes the action metadata, the current state, and the encoded Sense result payload. For a decoded, human readable view over REST, use the [LumeScope API](/api/lumescope). ## Result fields | Field | Meaning | | - | - | | Similarity score | A value between 0 and 1. Near 0 means highly unique, near 1 means a close match to a registered asset. | | Duplicate candidates | Action IDs of previously registered files that exceeded the near-duplicate threshold. | | Fingerprint hash | Hash of the perceptual fingerprint vector, usable to verify the result independently. | | Block height | The block where the result was recorded, a tamper-proof timestamp. | Sense results are permanent. Anyone can query the record, check the similarity score, and audit the duplicate candidates, which gives your assets a transparent provenance trail. ## Pricing A Sense action pays two fees. * A standard transaction fee in `ulume` for the registration message. * A service fee escrowed by `x/action` and paid to the SuperNodes that run the analysis. Sense costs less than Cascade because only the analysis result is stored, never the file itself. Governance can change fee parameters at any time. ## Next steps How x/action and x/supernode coordinate Sense under the hood. Decoded action results and network state over REST. Permanent file storage, the sibling service to Sense. # Get started with Lumera Source: https://docs.lumera.io/quickstart Connect to a Lumera network, fund an address, and pick the path for what you are building. Lumera Protocol is a Cosmos SDK Layer 1 blockchain. The chain coordinates requests, payments, and proofs, while economically bonded SuperNodes perform storage, verification, and retrieval. You reach all of it through SDKs, smart contracts, or plain REST and gRPC. This page gets you connected to a network and points you at the right section for what you are building. It takes a few minutes and needs nothing but `curl`. This page is for application developers. Node operators can start with the [validator quickstart](/quickstart-validator) or the [SuperNode quickstart](/quickstart-supernode). ## What you can build Lumera exposes four surfaces. Most applications use more than one. | Surface | What it gives you | Start here | | - | - | - | | Protocol services | Permanent storage, identity, and verifiable execution through Actions | [Cascade](/cascade/overview) | | Smart contracts | Solidity through Cosmos EVM and Rust through CosmWasm | [Smart contracts](/smart-contracts/overview) | | Read APIs | Chain state, SuperNode metrics, and Action metadata | [API reference](/api/overview) | | Network operation | Run a validator or a SuperNode | [Operate the network](/validators/overview) | ## Choose a network Build on testnet first. Testnet tokens are free and the EVM is available there today. | | Testnet | Mainnet | | - | - | - | | Chain ID | `lumera-testnet-2` | `lumera-mainnet-1` | | RPC | `https://lumera-testnet-rpc.polkachu.com` | `https://rpc.lumera.io` | | LCD (REST) | `https://lumera-testnet-api.polkachu.com` | `https://lcd.lumera.io` | | gRPC | `grpc.testnet.lumera.io:443` | `grpc.lumera.io:443` | | SN-API | `https://snapi.testnet.lumera.io` | `https://snapi.lumera.io` | | Tokens | Free from the [faucet](/faucet) | Real LUME | | Cosmos EVM | Available | Not yet available | Public endpoints are rate limited. Run your own node or use a dedicated provider for production traffic. See [Networks and endpoints](/networks) for genesis files and seed nodes. ## Connect to the network Query the RPC endpoint and check which network answers. ```bash theme={null} curl -s https://lumera-testnet-rpc.polkachu.com/status | jq -r '.result.node_info.network' ``` You should see `lumera-testnet-2`. If you get nothing back, the endpoint is rate limiting you or is down. Try another provider from [Networks and endpoints](/networks). The REST endpoint reports the application version the network is on. ```bash theme={null} curl -s https://lumera-testnet-api.polkachu.com/cosmos/base/tendermint/v1beta1/node_info \ | jq -r '.application_version.version' ``` Match this version when you install `lumerad` or pin an SDK. Mainnet and testnet run different versions. Lumera uses bech32 addresses with the `lumera` prefix. Get one from [Lumera Hub](/hub/wallet), from a Cosmos wallet such as Keplr, or from the CLI. ```bash theme={null} lumerad keys add my-key ``` Save the mnemonic somewhere safe. See the [`lumerad` CLI reference](/sdk/lumerad-cli) for keyring backends. Most operations cost gas, and protocol services escrow a service fee on top. Request testnet LUME from the [faucet](/faucet), then confirm the balance arrived. ```bash theme={null} curl -s https://lumera-testnet-api.polkachu.com/cosmos/bank/v1beta1/balances/ | jq ``` Balances are in `ulume`, where 1 LUME is 1,000,000 `ulume`. You are connected. Pick the path that matches what you are building. ## Choose your path Use Cascade to store files that stay retrievable. Storage is paid once at upload, and reads are not metered by the protocol. Deploy Solidity through the Cosmos EVM with MetaMask and Remix, or Rust through CosmWasm. JavaScript for browsers and Node.js, Go for backends with direct SuperNode access, and Rust for system integrations. Read chain state, SuperNode metrics, and Action metadata over REST, gRPC, and JSON-RPC. ## Next steps How consensus, SuperNodes, and protocol services fit together. Set up the JavaScript or Go SDK and configure your bundler. The capabilities Lumera is building for autonomous software. # Run a SuperNode Source: https://docs.lumera.io/quickstart-supernode What it takes to extend your validator with a SuperNode, condensed to one page. A SuperNode extends a Lumera Protocol validator with Cascade storage, Sense verification, and AI inference. It earns additional rewards for that work on top of staking income. This page is the condensed orientation. The full runbook lives in the [SuperNode section](/supernodes/overview). ## Before you start You need four things. * **A validator in a bonded state.** If you do not have one yet, start with [Run a validator](/quickstart-validator). * **A separate server.** The SuperNode must never share a host with the validator. It keeps its own account, keys, and stake delegation, and talks to the chain over gRPC. * **Access to the validator operator key.** It signs the registration transaction. * **SuperNode hardware.** Plan for at least 8 CPU cores, 16 GB RAM, a 1 TB NVMe SSD, and a 1 Gbps connection. Ports 4444 (gRPC) and 4445 (P2P) must be reachable. The pairing itself is two on-chain records. A registration signed by the validator operator binds the SuperNode endpoint and account to the validator, and a stake delegation from the SuperNode account backs it. ## The path Four stages take you from a bonded validator to a registered SuperNode. * **Provision a second server.** Ubuntu 22.04 LTS or later on x86\_64 hardware that meets the specs above. * **Install with `sn-manager`.** The manager downloads the `supernode` binary, applies updates automatically, and supervises the service. Configuration lives in `~/.supernode/config.yml`. * **Fund the SuperNode account and delegate.** LEP-3, a Lumera Enhancement Proposal, defines a dual stake rule. Your validator self-delegation plus the delegation from the SuperNode account must together meet the minimum, 10,000 LUME on testnet and 25,000 LUME on mainnet. Governance can change these values. * **Register from the validator host.** The validator operator key signs the transaction that binds the SuperNode endpoint and account to your validator. Two commands anchor the flow. The runbook walks through everything around them. ```bash theme={null} # Check the current combined stake minimum lumerad query supernode params # Bind the SuperNode to your validator, signed by the operator key lumerad tx supernode register-supernode ``` Verify the stake minimum before you register. The `minimum_stake_for_sn` parameter is set by governance and can change. ## Earning for retention SuperNodes that keep Cascade data available receive ongoing payouts from the Everlight SN Pool, proportional to the data they retain. [Everlight for SuperNode operators](/cascade/everlight/supernode-operators) explains eligibility and payout mechanics. ## Next steps Architecture, staking requirements, and hardware in full. Install and configure the SuperNode with `sn-manager`. How retention compensation works for operators. # Run a Validator Source: https://docs.lumera.io/quickstart-validator What it takes to run a Lumera validator, condensed to one page. Lumera Protocol is a Cosmos SDK Layer 1 blockchain secured by CometBFT. Validators produce blocks, vote on governance proposals, and keep the network honest. In return they earn staking rewards plus commission from their delegators. This page is the condensed orientation. The full runbooks with every command live in the validator sections linked at the end. ## What you need A dedicated x86\_64 server and some LUME. The table shows the published minimums for each network. The overview pages list the recommended specs. | Component | Testnet | Mainnet | | - | - | - | | CPU | 4 vCPU (x86\_64) | 8 cores (x86\_64) | | RAM | 8 GB | 32 GB | | Storage | 250 GB NVMe SSD | 2 TB NVMe SSD | | Network | 1 Gbps | 1 Gbps dedicated | | OS | Ubuntu 22.04 LTS or later | Ubuntu 22.04 LTS or later | Use NVMe SSDs. HDDs cannot sustain a live CometBFT node. ARM (including Apple Silicon) is not supported for production nodes. On testnet you get free LUME from the [faucet](/faucet). On mainnet you need at least 1 LUME for the minimum self-delegation and fees, and far more than that to enter the active set. ## The path Five stages take you from an empty server to a bonded validator. * **Provision a server.** Ubuntu 22.04 LTS or later on x86\_64 hardware that meets the table above. * **Install `lumerad` from the prebuilt release.** Each release ships a linux/amd64 binary, the CosmWasm shared library, and an installer, so you do not need Go or a compiler. * **Sync from a snapshot.** Polkachu publishes snapshots for both networks. This is much faster than replaying every block from genesis. * **Create and fund a key.** This wallet pays transaction fees and holds your self-delegation. * **Submit the create-validator transaction.** Set your moniker, commission, and self-delegation, then confirm the validator appears on the Lumera Portal. Match the binary version to the network before you install. For example, this returns the version mainnet currently runs. ```bash theme={null} curl -fsSL https://lumera-rpc.polkachu.com/abci_info | jq -r .result.response.version ``` ## Choose your network Practice on testnet first. Tokens are free, mistakes cost nothing, and the process is identical. When your setup is solid, repeat it on mainnet with real stake. * [Testnet validator guide](/validators/overview) covers `lumera-testnet-2`, currently on the `v1.20.2` line with the EVM live. * [Mainnet validator guide](/validators/overview) covers `lumera-mainnet-1`, currently on `v1.12.0`. The active set is capped at 50 validators ranked by total stake. Joining it means attracting more bonded stake than the smallest active validator, which your validator then displaces. ## Next steps Rehearse the full setup with tokens that have no value. Go to production with real stake on `lumera-mainnet-1`. Extend your validator with storage and compute services. # Go SDK Source: https://docs.lumera.io/sdk/go Build server-side applications and CLI tools that upload to Cascade and query the Lumera chain. The [Go SDK](https://pkg.go.dev/github.com/LumeraProtocol/sdk-go) is the official SDK for backend services, CLI tools, and infrastructure that talk to Lumera Protocol. It puts chain queries, transactions, and Cascade file storage behind one client. The current release is v1.2.0 and it needs Go 1.26. The SDK unifies three interfaces. | Layer | Access via | Purpose | | - | - | - | | Lumera API (gRPC) | `client.Blockchain` | Chain queries and transactions for actions, SuperNodes, and fees | | SuperNode SDK | `client.Cascade` | File upload, download, and event subscriptions | | SnApi (gRPC) | Wrapped by `client.Cascade` | Direct SuperNode communication | Unlike the [JavaScript SDK](/sdk/javascript), which routes file transfers through a REST gateway, the Go SDK talks to SuperNodes directly over gRPC on port 4444. Your machine needs network access to that port. ## Install ```bash theme={null} go mod init your-project go get github.com/LumeraProtocol/sdk-go ``` ## Create a client `lumerasdk.New` takes a config, a Cosmos SDK keyring, and options. Close the client when you are done. ```go main.go theme={null} package main import ( "context" "log" "github.com/cosmos/cosmos-sdk/crypto/keyring" lumerasdk "github.com/LumeraProtocol/sdk-go/client" "go.uber.org/zap" ) func main() { ctx := context.Background() kr, err := keyring.New("lumera", "test", "/tmp", nil) if err != nil { log.Fatal(err) } client, err := lumerasdk.New(ctx, lumerasdk.Config{ ChainID: "lumera-testnet-2", GRPCEndpoint: "grpc.testnet.lumera.io:443", RPCEndpoint: "https://rpc.testnet.lumera.io", Address: "lumera1...", KeyName: "my-key", }, kr, lumerasdk.WithLogger(zap.NewExample())) if err != nil { log.Fatal(err) } defer client.Close() } ``` The `test` keyring backend stores keys unencrypted. Use the `os` backend and a funded key for anything beyond local experiments. Never commit mnemonic files. ## Upload a file `Upload` registers the action on chain, waits for inclusion, and streams the file to one of the selected SuperNodes. The result carries the `ActionID`, the permanent on-chain reference to your file. ```go theme={null} res, err := client.Cascade.Upload(ctx, addr, nil, "./my-document.pdf") if err != nil { log.Fatal(err) } log.Println("Stored with action ID:", res.ActionID) ``` The block height of the registration transaction seeds the deterministic selection of processing SuperNodes. The SDK tries each selected node in order and uploads to the first that responds. ## Download a file `Download` fetches the file by action ID into a local directory. ```go theme={null} _, err := client.Cascade.Download(ctx, actionID, "./downloads/") if err != nil { log.Fatal(err) } ``` ## Query actions `client.Blockchain` exposes the chain modules. Action queries return the on-chain state of a storage request. ```go theme={null} action, err := client.Blockchain.Action.GetAction(ctx, "action-123") if err != nil { log.Fatal(err) } log.Printf("Action state: %s", action.State) ``` The state moves from `PENDING` to `PROCESSING` to `DONE`. An upload still unfinalized at its `expirationTime` ends at `EXPIRED` and the escrowed fee is refunded. Subscribe to task events with `client.Cascade.SubscribeToEvents` when you need progress callbacks instead of polling. ## Multiple accounts The factory pattern reuses one configuration and one set of connections across several signers. ```go theme={null} factory, _ := lumerasdk.NewFactory(lumerasdk.Config{ ChainID: "lumera-testnet-2", GRPCEndpoint: "grpc.testnet.lumera.io:443", RPCEndpoint: "https://rpc.testnet.lumera.io", }, kr) alice, _ := factory.WithSigner(ctx, aliceAddr, "alice") bob, _ := factory.WithSigner(ctx, bobAddr, "bob") defer alice.Close() defer bob.Close() ``` ## EVM and cross-chain support The SDK is EVM ready. The keyring supports both `secp256k1` (Cosmos, coin type 118) and `eth_secp256k1` (EVM, coin type 60) key types. The repo ships examples for EVM balance reads, Ethereum format transfers, ERC20 conversion, and calling Lumera precompiles from the EVM. See the [smart contracts overview](/smart-contracts/overview) for what the precompiles expose. Full ICS-27 interchain account support lets an app on another Cosmos chain drive Cascade through IBC. The [interchain accounts concept page](/cascade/concepts/interchain-accounts) explains the flow. ## Next steps Config fields, client surfaces, and crypto helpers. The storage flow behind Upload and Download. Drive Cascade from another Cosmos chain. # Go SDK reference Source: https://docs.lumera.io/sdk/go-reference Config fields, client surfaces, ICA helpers, and crypto utilities in the Lumera Go SDK. This page lists the public surface of `github.com/LumeraProtocol/sdk-go`. For the generated API docs see [pkg.go.dev](https://pkg.go.dev/github.com/LumeraProtocol/sdk-go). For a guided start see the [Go SDK page](/sdk/go). ## Client factory ```go theme={null} import lumerasdk "github.com/LumeraProtocol/sdk-go/client" client, err := lumerasdk.New(ctx, cfg, kr, opts...) defer client.Close() ``` ### Config fields | Field | Type | Description | | - | - | - | | `ChainID` | `string` | `lumera-testnet-2` or `lumera-mainnet-1` | | `GRPCEndpoint` | `string` | Chain gRPC endpoint, for example `grpc.testnet.lumera.io:443` | | `RPCEndpoint` | `string` | CometBFT RPC endpoint | | `Address` | `string` | Your bech32 sender address | | `KeyName` | `string` | Key name inside the keyring | Options include `lumerasdk.WithLogger(*zap.Logger)`. The keyring argument is a standard Cosmos SDK `keyring.Keyring`. ### Factory for multiple signers ```go theme={null} factory, err := lumerasdk.NewFactory(cfg, kr) alice, err := factory.WithSigner(ctx, aliceAddr, "alice") ``` `NewFactory` shares one configuration and one set of transports. `WithSigner` returns a per account client. Close each client when done. ## Blockchain surface `client.Blockchain` wraps the Lumera gRPC API with per module clients. | Client | Example methods | | - | - | | `Blockchain.Action` | `GetAction(ctx, actionID)` returns the action record with its state | | `Blockchain.SuperNode` | SuperNode registration state and listings | Action states are `PENDING`, `PROCESSING`, `DONE`, `FAILED`, and `EXPIRED`. Approved cross-chain actions can also reach `APPROVED` after `MsgApproveAction`. ## Cascade surface `client.Cascade` wraps the SuperNode SDK for file operations. | Method | Signature shape | Purpose | | - | - | - | | `Upload` | `Upload(ctx, addr, nil, filePath, opts...)` | Register the action and stream the file to a SuperNode | | `Download` | `Download(ctx, actionID, destDir, opts...)` | Retrieve a file by action ID into a directory | | `SubscribeToEvents` | `SubscribeToEvents(ctx, ...)` | Receive task lifecycle events instead of polling | | `CreateRequestActionMessage` | `CreateRequestActionMessage(ctx, addr, filePath, opts)` | Build a `MsgRequestAction` without broadcasting it | | `CreateApproveActionMessage` | `CreateApproveActionMessage(ctx, actionID, opts...)` | Build a `MsgApproveAction` for cross-chain flows | Upload options for interchain flows include `cascade.WithICACreatorAddress`, `cascade.WithAppPubkey`, and `cascade.WithICASendFunc`. Download accepts `cascade.WithDownloadSignerAddress` to sign with a controller chain key. ### Standalone Cascade client For interchain account flows you can build the Cascade client directly with owner settings. ```go theme={null} import "github.com/LumeraProtocol/sdk-go/cascade" cascadeClient, err := cascade.New(ctx, cascade.Config{ ChainID: chainID, GRPCAddr: grpcAddr, Address: hostAddr, KeyName: hostKeyName, ICAOwnerKeyName: controllerKeyName, ICAOwnerHRP: "cosmos", Timeout: 30 * time.Second, }, kr) ``` ## ICA helpers The `ica` package assembles ICS-27 packets and decodes acknowledgements. | Function | Purpose | | - | - | | `PackRequestForICA` | Pack a `MsgRequestAction` into protobuf `Any` bytes | | `BuildICAPacketData` | Wrap messages into `InterchainAccountPacketData` for `EXECUTE_TX` | | `BuildMsgSendTx` | Build the controller side `MsgSendTx` | | `ExtractRequestActionIDsFromAck` | Pull action IDs out of an IBC acknowledgement | | `ExtractRequestActionIDsFromTxMsgData` | Same, from decoded `TxMsgData` | | `ParseTxHashJSON`, `ExtractPacketInfoFromTxJSON`, `DecodePacketAcknowledgementJSON` | CLI friendly helpers for tx hash, packet info, and acks | The [interchain accounts concept page](/cascade/concepts/interchain-accounts) walks through the full flow these helpers support. ## Crypto helpers The `pkg/crypto` package covers keyring setup and signing. | Helper | Purpose | | - | - | | `DefaultKeyringParams`, `NewKeyring` | Consistent keyring creation | | `KeyType` (`KeyTypeCosmos`, `KeyTypeEVM`) | Select `secp256k1` coin type 118 or `eth_secp256k1` coin type 60 | | `LoadKeyring(keyName, mnemonicFile, keyType)` | Create a test keyring from a mnemonic file | | `ImportKey(kr, keyName, mnemonicFile, hrp, keyType)` | Import a mnemonic into an existing keyring | | `AddressFromKey(kr, keyName, hrp)` | Derive an address for any bech32 prefix | | `NewDefaultTxConfig`, `SignTxWithKeyring` | Sign transactions with Cosmos SDK builders | One keyring can hold both key types under different names. The ICA controller config accepts a separate `HostKeyName` when the controller and host chains use different key types. ```go theme={null} kr, _ := sdkcrypto.NewKeyring(sdkcrypto.DefaultKeyringParams()) sdkcrypto.ImportKey(kr, "controller-key", "mnemonic.txt", "lumera", sdkcrypto.KeyTypeCosmos) sdkcrypto.ImportKey(kr, "host-key", "mnemonic.txt", "inj", sdkcrypto.KeyTypeEVM) ``` ## Examples The [examples](https://pkg.go.dev/github.com/LumeraProtocol/sdk-go/examples) in the module have runnable samples for cascade upload and download, action queries, multi account factories, EVM balance reads, Ethereum format transfers, ERC20 conversion, precompile calls, and ICA request and approve flows. ## Next steps Install the SDK and run your first upload. What the EVM examples build on. # JavaScript SDK Source: https://docs.lumera.io/sdk/javascript Install the Lumera JavaScript SDK and store your first file on Cascade. `@lumera-protocol/sdk-js` is the official JavaScript and TypeScript SDK for Lumera Protocol. It puts chain queries, transaction signing, and Cascade storage behind a single client. Cascade is the Lumera permanent storage network, paid once at upload. The SDK runs in Node.js 18 or later and in modern browsers with the Keplr wallet. File transfers go through SN-API, a REST gateway in front of SuperNodes. The current release is 0.3.0. This page takes you from install to your first upload and download. Every method is listed in the [JavaScript SDK reference](/sdk/javascript-reference). ## Install the SDK Install the package together with the CosmJS peer dependencies for signing and chain queries. ```bash npm theme={null} npm install @lumera-protocol/sdk-js @cosmjs/proto-signing @cosmjs/stargate ``` ```bash yarn theme={null} yarn add @lumera-protocol/sdk-js @cosmjs/proto-signing @cosmjs/stargate ``` ```bash pnpm theme={null} pnpm install @lumera-protocol/sdk-js @cosmjs/proto-signing @cosmjs/stargate ``` ## Create a client `createLumeraClient` is the entry point for everything. The `testnet` preset fills in the chain ID `lumera-testnet-2` and the public testnet endpoints. Use `mainnet` for `lumera-mainnet-1`. To target your own node instead, pass custom endpoints as shown in the [JavaScript SDK reference](/sdk/javascript-reference). ```ts theme={null} import { createLumeraClient } from "@lumera-protocol/sdk-js"; const client = await createLumeraClient({ preset: "testnet", // or "mainnet" signer, // Keplr or a wrapped HD wallet address: account.address, // sender bech32 address gasPrice: "0.025ulume", }); ``` The signer must provide three methods. `signDirect` and `signAmino` sign transactions. `signArbitrary` implements ADR-036, the Cosmos standard for arbitrary message signing, which Cascade uses to authenticate with SuperNodes. Keplr covers all three in the browser. In Node.js you build a compatible signer from a mnemonic, as shown below. ## Wallet options Browser wallets must know the Lumera chain before they can sign for it. Register it once with `experimentalSuggestChain` using this config. The config uses coin type 60 with the Ethereum key features because testnet derives keys the Ethereum way since the `v1.20.0` upgrade. ```ts chain-config.ts theme={null} export const CHAIN_ID = "lumera-testnet-2"; export const LUMERA_CHAIN_INFO = { chainId: CHAIN_ID, chainName: "Lumera Testnet", rpc: "https://rpc.testnet.lumera.io", rest: "https://lcd.testnet.lumera.io", bip44: { coinType: 60 }, bech32Config: { bech32PrefixAccAddr: "lumera", bech32PrefixAccPub: "lumerapub", bech32PrefixValAddr: "lumeravaloper", bech32PrefixValPub: "lumeravaloperpub", bech32PrefixConsAddr: "lumeravalcons", bech32PrefixConsPub: "lumeravalconspub", }, currencies: [ { coinDenom: "LUME", coinMinimalDenom: "ulume", coinDecimals: 6 }, ], feeCurrencies: [ { coinDenom: "LUME", coinMinimalDenom: "ulume", coinDecimals: 6, gasPriceStep: { low: 0.025, average: 0.03, high: 0.04 }, }, ], stakeCurrency: { coinDenom: "LUME", coinMinimalDenom: "ulume", coinDecimals: 6, }, features: ["stargate", "ibc-transfer", "eth-address-gen", "eth-key-sign"], }; ``` `getKeplrSigner` returns a signer with `signDirect`, `signAmino`, and `signArbitrary` already wired up. ```ts keplr-client.ts theme={null} import { createLumeraClient, getKeplrSigner, isKeplrAvailable, } from "@lumera-protocol/sdk-js"; import { CHAIN_ID, LUMERA_CHAIN_INFO } from "./chain-config"; if (!isKeplrAvailable()) { throw new Error("Please install the Keplr extension"); } // Register the Lumera chain, then request wallet access await window.keplr.experimentalSuggestChain(LUMERA_CHAIN_INFO); const signer = await getKeplrSigner(CHAIN_ID); const [account] = await signer.getAccounts(); const client = await createLumeraClient({ preset: "testnet", signer, address: account.address, gasPrice: "0.025ulume", }); ``` Also install `@cosmjs/amino`. A plain `DirectSecp256k1HdWallet` signs transactions but not arbitrary data, so wrap it together with an Amino wallet to add ADR-036 support. ```ts node-client.ts theme={null} import { createLumeraClient } from "@lumera-protocol/sdk-js"; import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import { Secp256k1HdWallet, makeSignDoc as makeAminoSignDoc, } from "@cosmjs/amino"; const mnemonic = process.env.LUMERA_MNEMONIC!; const directWallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "lumera", }); const aminoWallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "lumera", }); const [account] = await directWallet.getAccounts(); const signer = { getAccounts: () => directWallet.getAccounts(), signDirect: (addr: string, doc: any) => directWallet.signDirect(addr, doc), signAmino: (addr: string, doc: any) => aminoWallet.signAmino(addr, doc), async signArbitrary(chainId: string, signerAddress: string, data: string) { const signDoc = makeAminoSignDoc( [ { type: "sign/MsgSignData", value: { signer: signerAddress, data: Buffer.from(data).toString("base64"), }, }, ], { gas: "0", amount: [] }, "", "", 0, 0 ); const { signature } = await aminoWallet.signAmino(signerAddress, signDoc); return { signed: data, signature: signature.signature, pub_key: signature.pub_key, }; }, }; const client = await createLumeraClient({ preset: "testnet", signer: signer as any, address: account.address, gasPrice: "0.025ulume", }); ``` Never hardcode a mnemonic. Load it from an environment variable or a secrets manager. ## Upload a file An upload runs in three steps. `prepareFile` hashes the file. `registerAction` registers the action on chain and escrows the storage fee. `sendFileToSupernodes` sends the file to a SuperNode and polls until processing completes. An action is the on-chain record of a storage request. The fee is paid in LUME, so fund your testnet account from the [faucet](/faucet) first. ```ts upload.ts theme={null} const data = new TextEncoder().encode("Hello, Lumera!"); // Expiration, 25 hours from now as a Unix timestamp const expirationTime = String(Math.floor(Date.now() / 1000) + 25 * 60 * 60); const uploader = client.Cascade.uploader; const prepared = await uploader.prepareFile(data); const { actionId, authSignature } = await uploader.registerAction(prepared, { fileName: "hello.txt", isPublic: true, expirationTime, }); await uploader.sendFileToSupernodes(actionId, authSignature, prepared.fileBytes); console.log("Stored with action ID:", actionId); ``` `actionId` is the permanent on-chain reference to your file. Save it. You need it for every download. `uploadFile` runs the same three steps in one call, but it resolves to the SN-API task, not the action ID. ## Download a file `download` signs the action ID with ADR-036 to authenticate, then returns a `ReadableStream` of the file bytes. ```ts download.ts theme={null} // actionId comes from registerAction in the upload step const stream = await client.Cascade.downloader.download(actionId); const reader = stream.getReader(); const chunks: Uint8Array[] = []; while (true) { const { done, value } = await reader.read(); if (done) break; if (value) chunks.push(value); } const size = chunks.reduce((total, chunk) => total + chunk.length, 0); const bytes = new Uint8Array(size); let offset = 0; for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.length; } console.log(new TextDecoder().decode(bytes)); ``` ## Check action status Query the action to follow its progress on chain. ```ts status.ts theme={null} const action = await client.Blockchain.Action.getAction(actionId); console.log("State:", action.state); ``` The state starts at `PENDING` after registration. It moves to `PROCESSING` while SuperNodes encode and distribute the file, then ends at `DONE`. An upload still unfinalized at its `expirationTime` ends at `EXPIRED` and the escrowed fee is refunded. ## Next steps Every method, option, and status value in the SDK. Wire the SDK into a browser upload flow. What happens between PENDING and DONE. # JavaScript SDK reference Source: https://docs.lumera.io/sdk/javascript-reference Every method, option, and status value in the Lumera JavaScript SDK. This page lists the public surface of [`@lumera-protocol/sdk-js`](https://www.npmjs.com/package/@lumera-protocol/sdk-js). For a guided start see the [JavaScript SDK page](/sdk/javascript). ## createLumeraClient The factory function that builds a configured client. ```ts theme={null} import { createLumeraClient } from "@lumera-protocol/sdk-js"; const client = await createLumeraClient({ preset: "testnet", signer: wallet, address: "lumera1...", gasPrice: "0.025ulume", }); ``` | Field | Type | Required | Description | | - | - | - | - | | `preset` | `"testnet"` or `"mainnet"` | No | Fills in chain ID and endpoints for the chosen network | | `rpcUrl` | `string` | No | Custom CometBFT RPC endpoint, overrides the preset | | `lcdUrl` | `string` | No | Custom LCD or REST endpoint, overrides the preset | | `chainId` | `string` | No | Custom chain ID, overrides the preset | | `snapiUrl` | `string` | No | Custom SN-API gateway endpoint, overrides the preset | | `signer` | `OfflineSigner` | Yes | CosmJS offline signer used for transaction signing | | `address` | `string` | Yes | Bech32 address of the signing account | | `gasPrice` | `string` | No | Gas price string. Defaults to `"0.025ulume"` | | `http.timeout` | `number` | No | Request timeout in milliseconds. Defaults to `30000` | | `http.maxRetries` | `number` | No | Maximum retry attempts. Defaults to `3` | All four endpoint fields sit at the top level of the config. Use a preset for the standard networks, or set them individually to point at your own infrastructure. ```ts theme={null} const client = await createLumeraClient({ chainId: "lumera-testnet-2", rpcUrl: "https://rpc.testnet.lumera.io", lcdUrl: "https://lcd.testnet.lumera.io", snapiUrl: "https://snapi.testnet.lumera.io", signer: wallet, address: "lumera1...", }); ``` Returns `Promise`. The signer needs `signDirect`, `signAmino`, and `signArbitrary` for the full feature set. See [wallet options](/sdk/javascript#wallet-options). ## Cascade uploader ### uploadFile Runs the whole upload in one call. Prepare, register on chain, and send to SuperNodes. ```ts theme={null} const result = await client.Cascade.uploader.uploadFile(fileBytes, { fileName: "document.pdf", isPublic: true, expirationTime: "1742553600", taskOptions: { pollInterval: 2000, timeout: 300000 }, }); ``` | Option | Type | Description | | - | - | - | | `fileName` | `string` | Filename metadata | | `isPublic` | `boolean` | Whether the file downloads without the owner signature | | `expirationTime` | `string` | Unix timestamp. Unfinalized uploads past this time refund the fee | | `taskOptions.pollInterval` | `number` | Milliseconds between status polls, default 2000 | | `taskOptions.timeout` | `number` | Milliseconds before timeout, default 300000 | Resolves to the completed SN-API `Task`, not the action ID. The `Task` type declares only `taskId`, `status`, and `progress`. When you need the action ID, run the lower level steps below. `registerAction` returns it as `actionId`. ### Lower level upload steps Use these when you need control over individual phases. | Method | Purpose | | - | - | | `prepareFile(file)` | Convert `Uint8Array`, `File`, or `Blob` to bytes and compute the BLAKE3 hash | | `registerAction({ fileBytes, dataHash }, options)` | Generate the layout, sign it, and broadcast the registration transaction. Returns `{ actionId, authSignature }` | | `makeAuthSignature(actionId, dataHash)` | Create an auth signature for an existing action | | `sendFileToSupernodes(actionId, authSignature, fileBytes, options)` | Send the file to the SN-API gateway after registration | `sendFileToSupernodes` retries up to 5 times with 3 second delays to absorb SN-API indexing lag. ## Cascade downloader | Method | Purpose | | - | - | | `download(actionId, taskOptions?)` | Download by action ID. Returns `Promise>` | | `downloadFile({ actionId, taskOptions })` | Same download with an explicit options object | | `downloadPrivate(actionId)` | Download with the owner ADR-036 signature always applied | Every download authenticates with an ADR-036 signature over the action ID. The [download lifecycle page](/cascade/concepts/download-lifecycle) shows how to consume the stream. ## Blockchain queries | Method | Returns | | - | - | | `Blockchain.getChainId()` | The connected chain ID | | `Blockchain.Action.getParams()` | Current action parameters such as `max_raptor_q_symbols` | | `Blockchain.Action.getAction(actionId)` | The action record with `creator`, `actionID`, `actionType`, `metadata`, and `state` | | `Blockchain.Action.getActionFee(dataSizeKb)` | The storage fee in `ulume` for that size in KB, rounded up from bytes | | `Blockchain.Supernode.listSupernodes()` | All registered SuperNodes | | `Blockchain.Tx.simulate(address, msgs)` | Gas estimate for a set of messages | | `Blockchain.Tx.broadcast(signedTx)` | Broadcast result for a signed transaction | ```ts theme={null} const dataSizeKb = Math.ceil(fileSizeBytes / 1024); const { amount } = await client.Blockchain.Action.getActionFee(dataSizeKb); ``` Chain parameter queries are cached for 5 minutes. ## Wallet helpers | Helper | Purpose | | - | - | | `getKeplrSigner(chainId)` | Signer from the Keplr extension with `signDirect`, `signAmino`, and `signArbitrary` | | `isKeplrAvailable()` | Whether the Keplr extension is present | Keplr is the only browser wallet with an exported helper. ## LEP-1 helpers For deterministic layout and ID work that must match on-chain validation. ```ts theme={null} import { createSingleBlockLayout, generateIds, buildIndexFile, } from "@lumera-protocol/sdk-js"; const layoutBytes = await createSingleBlockLayout(fileBytes); const layoutIds = await generateIds(layoutFileB64, layoutSignatureB64, rq_ids_ic, rq_ids_max); const index = buildIndexFile(layoutIds, layoutSignatureB64); ``` The [erasure coding page](/cascade/concepts/erasure-coding) explains the derivation scheme these helpers implement. ## Task status values The SDK reports task progress with these statuses. | Status | Category | Meaning | | - | - | - | | `sdk:completed` | Success | Operation completed | | `sdk:upload_completed` | Success | Upload confirmed | | `sdk:download_completed` | Success | Download ready | | `sdk:failed` | Failure | Generic failure | | `sdk:supernodes_unavailable` | Failure | No SuperNode accepted the task | | `sdk:registration_failure` | Failure | On-chain registration failed | | `sdk:upload_failed` | Failure | File transfer failed | | `sdk:processing_failed` | Failure | SN-API processing failed | | `sdk:processing_timeout` | Failure | Processing exceeded the timeout | | `sdk:download_failure` | Failure | Download reconstruction failed | On-chain action states are separate. They move from `PENDING` to `PROCESSING` to `DONE`, `FAILED`, or `EXPIRED`. See [error handling](/cascade/error-handling) for recovery patterns. ## Next steps Install, connect a wallet, and run your first upload. Handle every failure mode with working code. # lumerad CLI reference Source: https://docs.lumera.io/sdk/lumerad-cli Use the lumerad command line tool to query chain state, submit transactions, manage keys, and work with Lumera modules. `lumerad` is the Lumera blockchain node binary and the primary command-line tool for interacting with the Lumera Protocol. You use it to run a full node, query any on-chain state, submit transactions, manage cryptographic keys, and work with Lumera-specific modules such as actions (Cascade/Sense), claims, and the SuperNode registry. It is built on the Cosmos SDK and follows the same command conventions as other Cosmos chains. Existing Cosmos familiarity transfers directly. The current release is `v1.20.1-hotfix` (July 14, 2026). Version `v1.20.0` was the Aurora Convergence EVM upgrade. *** ## Installation ### Build from source ```shell theme={null} git clone https://github.com/LumeraProtocol/lumera cd lumera ``` ```shell theme={null} make build ``` The compiled binary is placed at `build/lumerad`. You can also run `make build-debug` to include debug symbols. ```shell theme={null} sudo mv build/lumerad /usr/local/bin/ ``` ```shell theme={null} lumerad version ``` **Prerequisites.** You need Go 1.26+ (see `go.mod` for the exact version) and `make`. The `libwasmvm` shared library is built automatically during `make build`. You can also source it from the [CosmWasm releases page](https://github.com/CosmWasm/wasmvm/releases). *** ## Key management Before you can sign transactions, you need at least one key in your local keyring. ```shell theme={null} lumerad keys add # Generate a new key and display its mnemonic lumerad keys list # List all keys in the keyring lumerad keys show # Show a key's details, address, and public key lumerad keys import # Import a key from a mnemonic phrase lumerad keys delete # Permanently delete a key from the keyring ``` When you run `lumerad keys add`, the mnemonic is displayed **once** and never stored. Write it down and keep it in a safe place. It is the only way to recover your key if you lose access to the keyring files. *** ## Query commands Query commands read on-chain state without submitting any transactions. They are safe to run at any time and do not require a signing key. ```shell theme={null} # List all active validators lumerad query staking validators # Show delegations for a specific address lumerad query staking delegations
# Show token balances for a specific address lumerad query bank balances
``` ```shell theme={null} # List all Cascade/Sense actions lumerad query action list-actions # Fetch a specific action by ID lumerad query action action ``` ```shell theme={null} # Show the claim record for a legacy address lumerad query claim claim-record # List claimed records for a vested term (0 if not vested) lumerad query claim list-claimed ``` ```shell theme={null} # Query the connected node's sync status and block height lumerad status ``` *** ## Transaction commands Transaction commands broadcast signed messages to the chain. Every transaction command requires `--from` (the signing key) and `--chain-id`. Fees are charged in `ulume` (1 LUME = 1,000,000 ulume). ```shell theme={null} # Delegate tokens to a validator lumerad tx staking delegate ulume \ --from \ --chain-id lumera-mainnet-1 # Unbond tokens from a validator (subject to unbonding period) lumerad tx staking unbond ulume \ --from \ --chain-id lumera-mainnet-1 ``` ```shell theme={null} # Vote on an active governance proposal lumerad tx gov vote yes \ --from \ --chain-id lumera-mainnet-1 # Valid vote options: yes | no | abstain | no_with_veto ``` ```shell theme={null} # Claim LUME to a new address, signed with the legacy key lumerad tx claim claim \ --from \ --chain-id lumera-mainnet-1 # Same claim into a vesting tier lumerad tx claim delayed-claim \ --from \ --chain-id lumera-mainnet-1 ``` *** ## Global flags These flags apply to every `lumerad` command. You can set frequently-used flags as environment variables (e.g., `LUMERA_CHAIN_ID`) or in `~/.lumera/config/client.toml`. | Flag | Description | | - | - | | `--chain-id` | Chain identifier, `lumera-mainnet-1` for mainnet or `lumera-testnet-2` for testnet | | `--from` | Key name or bech32 address used to sign the transaction | | `--fees` | Transaction fee in `ulume` (e.g., `5000ulume`) | | `--gas` | Gas limit as an integer, or `"auto"` to estimate automatically | | `--gas-adjustment` | Multiplier applied to the estimated gas when using `--gas auto` (default `1.3`) | | `--node` | Tendermint RPC endpoint (default: `tcp://localhost:26657`) | | `--output` | Output format: `text` (default) or `json` | | `--broadcast-mode` | How to wait for the result: `sync`, `async`, or `block` | | `--keyring-backend` | Keyring backend: `os`, `file`, or `test` | Add `--output json` to any query command and pipe the result to `jq` for easy scripting. ```shell theme={null} lumerad query bank balances lumera1abc... --output json | jq '.balances' ``` For transactions, combine `--broadcast-mode block` with `--output json` to get the full transaction result in a single call. The result includes events and the transaction hash. *** ## Common workflows ```shell theme={null} # 1. Find your address lumerad keys show mykey --bech acc # 2. Check your balance lumerad query bank balances $(lumerad keys show mykey -a) # 3. Pick a validator lumerad query staking validators --output json | jq '.validators[] | {moniker: .description.moniker, address: .operator_address}' # 4. Delegate lumerad tx staking delegate lumeravaloper1xyz... 1000000ulume \ --from mykey \ --chain-id lumera-mainnet-1 \ --fees 5000ulume \ --gas auto \ --gas-adjustment 1.4 ``` ```shell theme={null} lumerad query action action --output json | jq '.action.state' ``` A finalized Cascade action has `state: "ACTION_STATE_DONE"`. ```shell theme={null} lumerad status --node tcp://rpc.mainnet.lumera.io:26657 lumerad query bank balances lumera1abc... --node tcp://rpc.mainnet.lumera.io:26657 ``` Set `--node` once in your client config to avoid repeating it. ```shell theme={null} lumerad config node tcp://rpc.mainnet.lumera.io:26657 ``` *** ## Additional build targets The Makefile exposes several targets useful during development. ```shell theme={null} make build-debug # Build with debug symbols make build-proto # Regenerate protobuf files from .proto sources make lint # Run golangci-lint make unit-tests # Run unit test suite make integration-tests # Run integration tests ``` *** ## Further reading The upstream documentation for the SDK that powers `lumerad`. Consensus engine and RPC reference. Cross-chain messaging used by Lumera. Genesis files, seeds, and peer lists for mainnet and testnet. # rq-go RaptorQ library Source: https://docs.lumera.io/sdk/raptorq Use the rq-go library to encode files into fault-tolerant RaptorQ symbols and reconstruct them even with missing data. `rq-go` is a Go library that wraps the RaptorQ Forward Error Correction (FEC) algorithm. Lumera's Cascade service uses it to split files into redundant, independently-retrievable symbols before distributing them across the SuperNode P2P network. Because RaptorQ is an erasure code, the original file can be reconstructed even if a subset of the symbols is lost or unavailable. This makes Cascade storage resilient to node churn and partial network failures. Most developers interact with Cascade through the [SuperNode gRPC API](/api/grpc/cascade-service) or the `lumerad` CLI rather than calling `rq-go` directly. This page documents the library for developers who need low-level encoding control or who are building tooling on top of Cascade's storage layer. *** ## Platform support `rq-go` ships pre-built static libraries for the following targets. No external shared libraries or C toolchains are required at runtime. The final binary is fully self-contained. | Platform | Architecture | | - | - | | Linux | amd64 | | Linux | arm64 (including Raspberry Pi) | | macOS | amd64 (Intel) | | macOS | arm64 (Apple Silicon) | *** ## Installation Add `rq-go` to your Go module. ```shell theme={null} go get lumera.build/rq-go@v0.3.0 ``` The module path is `lumera.build/rq-go` from v0.3.0 on. Earlier versions were published as `github.com/LumeraProtocol/rq-go`; switch the import path when you upgrade. Then import it in your source files. ```go theme={null} import "lumera.build/rq-go" ``` *** ## Quick start The example below shows the complete encode and decode round trip using the library's default configuration. ```go raptorq_example.go theme={null} package main import ( "fmt" "os" raptorq "lumera.build/rq-go" ) func main() { // Create a processor with default settings. processor, err := raptorq.NewDefaultRaptorQProcessor() if err != nil { panic(err) } defer processor.Free() // Always free resources when done. // Choose an optimal block size for the file being encoded. fileInfo, err := os.Stat("large_file.dat") if err != nil { panic(err) } blockSize := processor.GetRecommendedBlockSize(uint64(fileInfo.Size())) // Encode the file into RaptorQ symbols. result, err := processor.EncodeFile("large_file.dat", "symbols/", blockSize) if err != nil { panic(err) } fmt.Printf("Encoded into %d symbols\n", result.TotalSymbolsCount) fmt.Printf("Layout file: %s\n", result.LayoutFilePath) // Decode symbols back to the original file. err = processor.DecodeSymbols( "symbols/", "recovered.dat", "symbols/_raptorq_layout.json", ) if err != nil { panic(err) } fmt.Println("File recovered successfully!") } ``` Always call `processor.Free()` (or defer it immediately after creation) to release the memory held by the underlying C library. Forgetting to do so will cause a memory leak, especially in long-running services. *** ## Custom configuration Use `NewRaptorQProcessor` when you need to tune symbol size, redundancy, memory limits, or concurrency. ```go theme={null} // NewRaptorQProcessor(symbolSize, redundancyFactor, maxMemoryMB, concurrencyLimit) processor, err := raptorq.NewRaptorQProcessor(65535, 4, 4096, 2) if err != nil { panic(err) } defer processor.Free() ``` ### Configuration parameters | Parameter | Default | Description | | - | - | - | | `symbolSize` | `65535` (64 KB − 1 B) | Size of each encoded symbol in bytes. Smaller symbols increase parallelism but raise overhead. | | `redundancyFactor` | `4` | Repair symbols generated per source symbol. Higher values improve loss tolerance at the cost of storage. | | `maxMemoryMB` | `16384` (16 GB) | Hard cap on memory the processor may allocate, in MB. | | `concurrencyLimit` | `4` | Maximum number of blocks encoded or decoded concurrently. | *** ## API reference ### `NewDefaultRaptorQProcessor() (*RaptorQProcessor, error)` Creates a processor with default settings (see table above). Use this for most applications. ### `NewRaptorQProcessor(symbolSize, redundancyFactor, maxMemoryMB, concurrencyLimit uint32) (*RaptorQProcessor, error)` Creates a processor with explicit parameters. ### `processor.GetRecommendedBlockSize(fileSize uint64) uint64` Returns the recommended block size (in bytes) for the given file, taking into account the processor's memory limit and efficiency heuristics. Using this value avoids excessive memory consumption for large files. ### `processor.EncodeFile(inputPath, outputDir string, blockSize uint64) (*EncodeResult, error)` Encodes the file at `inputPath` into RaptorQ symbols written to `outputDir`. The layout metadata file (`_raptorq_layout.json`) is also written to `outputDir`. Returns an `EncodeResult` with two fields. * `TotalSymbolsCount` is the total number of symbols generated across all blocks. * `LayoutFilePath` is the absolute path to the generated layout file. ### `processor.DecodeSymbols(symbolsDir, outputPath, layoutPath string) error` Reconstructs the original file from the symbols in `symbolsDir`, guided by the layout file at `layoutPath`, and writes the output to `outputPath`. Reconstruction succeeds even when some symbols are missing, as long as enough symbols survive for each block. ### `processor.CreateMetadata(inputPath, layoutPath string, blockSize uint64) (*EncodeResult, error)` Generates the layout file without writing any symbol files. Use this to plan storage requirements or generate symbol identifiers ahead of time. ### `processor.Free()` Releases all memory held by the processor. Must be called when the processor is no longer needed. *** ## Block processing and memory management `rq-go` processes files in blocks to bound peak memory usage. The file is divided into blocks of at most `blockSize` bytes. Each block is encoded separately. Only one block's working memory is live at a time, so peak usage is proportional to `blockSize` rather than to the total file size. Each block produces a set of source symbols plus repair symbols (determined by `redundancyFactor`). Symbols are written to the output directory as individual files named by their content hash. A single `_raptorq_layout.json` file records the encoder parameters, block boundaries, symbol identifiers, and block hashes needed for decoding. Use `GetRecommendedBlockSize` to let the library select a block size that balances memory efficiency against encoding overhead. *** ## Metadata file format The `_raptorq_layout.json` file produced by `EncodeFile` is required for decoding. Keep it alongside the symbols (or store it separately and pass its path to `DecodeSymbols`). ```json theme={null} { "blocks": [ { "block_id": 0, "encoder_parameters": [0, 0, 25, 240, 160, 0, 195, 80, 1, 0, 1, 8], "original_offset": 0, "size": 1700000, "symbols": [ "9yCaAXSexMsaWDP6pzK4wZ4w9Hqrr6QPjJZ86wJMGoq9", "3Q4MtczkeZzWbECcA8eUeMaQ14cGHF4PpgeYo33cMtYD", "G6okoLMA1wGtVZwieSykR9bvLSw49iwYZux2byJDrbDF", "AJ9fp8Ydqo1aaVHzjHowajJA4ELwfetpQAMUT47GZays" ], "hash": "9gD64LFuoQPYJoWBQmnG2TdPErWwni7Bhrpn6ae74rk7" }, { "block_id": 1, "encoder_parameters": [0, 0, 25, 240, 160, 0, 195, 80, 1, 0, 1, 8], "original_offset": 1700000, "size": 1700000, "symbols": [ "CxFNCbQhtWLzXwpGCE8L1m67WEV85zuTpTtYyRm6nDQF", "8NVZfQzFDsXwQgEbuNxzyo9D18da9qEHfDpou7mCzg72", "2hsAk5xWZCJ6d3xnnEaPu6TXsVwN6vfVhLDbKo7fsGVd", "96KaGntmMeqzL9uPxpKce2PMV9BiUXovMiDUBw1t3Mhz", "3kMQUazSrgfpxxkw3yY9zfh8amHXCeD7ZsmaDCu2szdB" ], "hash": "9LvSmppDKePbZnY6PLX8hyEY8gcskVJKwzmgn7zyPNyC" } ] } ``` | Field | Type | Description | | - | - | - | | `block_id` | integer | Zero-based block index | | `encoder_parameters` | array of integers | 12-byte RaptorQ encoder parameter block needed for decoding | | `original_offset` | integer | Byte offset of this block within the original file | | `size` | integer | Size of this block in bytes | | `symbols` | array of strings | Base58-encoded content hashes that identify each symbol file | | `hash` | string | Base58-encoded hash of the block data, used for integrity verification | If the file fits within a single block, the layout contains exactly one entry with `block_id: 0`. The decoder handles single-block and multi-block layouts identically. *** ## Usage in Lumera `rq-go` is used internally by the Lumera SuperNode's Cascade service. When you call [`CascadeService/Register`](/api/grpc/cascade-service), the SuperNode encodes your file with this library, distributes the resulting symbols across the P2P network, and records the symbol IDs on-chain. You do not need to use `rq-go` directly unless you are building custom tooling or integrating at a lower level than the gRPC API. For most integration work, prefer the [SuperNode gRPC API](/api/grpc/cascade-service) or the `lumerad` CLI over calling `rq-go` directly. The SuperNode handles encoding parameters, P2P distribution, and on-chain finalization for you. # Rust SDK Source: https://docs.lumera.io/sdk/rust Build server-side Rust applications and CLI tools that store and retrieve files on Cascade. The Rust SDK is the official SDK for server-side applications, CLI tools, and system-level integrations with Lumera Protocol. It talks to the chain over gRPC and routes file transfers through SN-API, the REST gateway in front of SuperNodes. Because file transfers travel over HTTPS to SN-API, the SDK works from any machine with ordinary outbound internet access. The [Go SDK](/sdk/go) instead streams files to SuperNodes directly over gRPC on port 4444, which requires direct network reachability to that port. ## Install ```bash theme={null} # Initialize a project if you don't have one yet cargo init . # Add the SDK and async runtime cargo add lumera-sdk-rs cargo add tokio --features full ``` The SDK needs stable Rust with edition 2021 or later. ## Architecture The SDK is organized into three core modules. | Module | Purpose | | - | - | | `chain` | On-chain operations. Action parameters, fee lookups, transaction build, sign, and broadcast, gas simulation. | | `snapi` | SN-API interaction. Upload, download, status polling, file retrieval. | | `cascade` | High-level orchestration. Deterministic payload and ID generation, ticket registration, upload and download workflows. | ## Create a client Configure the SDK with explicit values or with environment variables. ```rust theme={null} use lumera_sdk_rs::chain::ChainConfig; use lumera_sdk_rs::{CascadeConfig, CascadeSdk}; let chain_cfg = ChainConfig::new( "lumera-testnet-2", "https://grpc.testnet.lumera.io", "https://rpc.testnet.lumera.io", "https://lcd.testnet.lumera.io", "0.025ulume", ); let cfg = CascadeConfig::new(chain_cfg, "https://snapi.testnet.lumera.io"); let sdk = CascadeSdk::new(cfg); ``` Or load the configuration from the environment. ```rust theme={null} use lumera_sdk_rs::{CascadeSdk, SdkSettings}; // Load .env file if present let _ = dotenvy::dotenv(); let settings = SdkSettings::from_env(); let sdk = CascadeSdk::new(settings.to_cascade_config()); ``` ### Environment variables The built-in defaults assume a local devnet. For testnet, override them in a `.env` file or your shell. | Variable | Testnet value | Purpose | | - | - | - | | `LUMERA_CHAIN_ID` | `lumera-testnet-2` | Chain identifier | | `LUMERA_GRPC` | `https://grpc.testnet.lumera.io` | gRPC endpoint | | `LUMERA_RPC` | `https://rpc.testnet.lumera.io` | CometBFT RPC endpoint | | `LUMERA_REST` | `https://lcd.testnet.lumera.io` | REST LCD endpoint | | `SNAPI_BASE` | `https://snapi.testnet.lumera.io` | SN-API base URL | | `LUMERA_GAS_PRICE` | `0.025ulume` | Gas price | ## Derive signing keys Upload and download both need a `SigningIdentity` derived from your mnemonic. It carries the chain signing key for transactions and the arbitrary signing key for ADR-036 authentication with SN-API. ```rust theme={null} use lumera_sdk_rs::SigningIdentity; let identity = SigningIdentity::from_mnemonic( &mnemonic, "lumera", "m/44'/118'/0'/0/0", )?; ``` Load the mnemonic from an environment variable or a secrets manager. Never hardcode it or commit it to version control. ## Upload a file Uploads happen in two steps. Register the action on chain, then stream the file to SN-API. ```rust theme={null} use lumera_sdk_rs::RegisterTicketRequest; use std::path::PathBuf; let file_path = PathBuf::from("./my-document.pdf"); // 1. Register the on-chain action let registered = sdk.register_ticket( &identity.chain_signing_key, &identity.arbitrary_signing_key, &identity.address, &file_path, RegisterTicketRequest { file_name: "my-document.pdf".to_string(), is_public: true, expiration_time: expiration.to_string(), // Unix timestamp }, ).await?; // 2. Upload the file to SN-API let task = sdk.upload_via_snapi( ®istered.action_id, ®istered.auth_signature, &file_path, ).await?; // 3. Poll the upload status let status = sdk.snapi.upload_status(&task).await?; println!("Stored with action ID: {}", registered.action_id); ``` `register_ticket` escrows the storage fee and returns the `action_id`, the permanent on-chain reference to your file. SuperNodes then encode the data and distribute it across the network. [How Cascade works](/cascade/how-cascade-works) explains what happens behind each step. ## Download a file ```rust theme={null} // 1. Request a download task with your arbitrary signing key let down_task = sdk.request_download( ®istered.action_id, &identity.arbitrary_signing_key, ).await?; // 2. Stream the file bytes from SN-API let bytes = sdk.snapi.download_file(&down_task).await?; std::fs::write("./downloaded-file.pdf", &bytes)?; ``` ## Key dependencies | Crate | Role | | - | - | | `cosmrs` | Cosmos SDK signing and gRPC | | `reqwest` | HTTP client with rustls | | `tokio` | Async runtime | | `blake3` | File integrity verification | | `prost` | Protobuf encoding | ## Next steps The storage flow behind register, upload, and download. The REST endpoints the SDK calls under the hood. The gRPC alternative for backends with direct SuperNode access. # Rust SDK reference Source: https://docs.lumera.io/sdk/rust-reference Modules, settings, client surfaces, and helpers in the Lumera Rust SDK. This page lists the public surface of the Rust SDK, `lumera-sdk-rs`. For a guided start see the [Rust SDK page](/sdk/rust). The crate is [`lumera-sdk-rs`](https://crates.io/crates/lumera-sdk-rs) on crates.io and the library name is `lumera_sdk_rs`. Add it with `cargo add lumera-sdk-rs`. Release notes and source tarballs are in [Lumera releases](https://github.com/LumeraProtocol/releases/releases?q=sdk-rs). The crate is pre-1.0. Pin a version (for example `lumera-sdk-rs = "=0.1.0"`) if you need a stable surface. ## Crate layout The crate exposes seven modules. Three of them carry the client surfaces you will use most. | Module | Purpose | | - | - | | `chain` | Action parameters, fee lookups, transaction build, sign, broadcast, and gas simulation | | `snapi` | SN-API calls for upload, download, status polling, and file retrieval | | `cascade` | High-level orchestration over `chain` and `snapi` | | `config` | Settings loading from environment, `.env`, TOML, and JSON | | `keys` | Mnemonic derivation and address validation | | `crypto` | Raw message signing and ADR-036 sign bytes | | `error` | The single `SdkError` type | These items are re-exported at the crate root. ```rust theme={null} pub use cascade::{CascadeConfig, CascadeSdk, RegisterTicketRequest}; pub use config::SdkSettings; pub use keys::SigningIdentity; ``` ## Settings `SdkSettings` holds the six values every client needs. | Field | Type | Default | | - | - | - | | `chain_id` | `String` | `lumera-devnet` | | `grpc_endpoint` | `String` | `http://127.0.0.1:9090` | | `rpc_endpoint` | `String` | `http://127.0.0.1:26657` | | `rest_endpoint` | `String` | `http://127.0.0.1:1317` | | `gas_price` | `String` | `0.025ulume` | | `snapi_base` | `String` | `http://127.0.0.1:8080` | The defaults point at a local devnet. Override them for testnet or mainnet. ### Loading settings | Method | Behavior | | - | - | | `SdkSettings::default()` | Local devnet values | | `SdkSettings::from_env()` | Defaults with environment overrides applied | | `SdkSettings::from_env_file(path)` | Loads a `.env` file, then applies environment overrides | | `SdkSettings::from_file(path)` | Reads `.toml` or `.json`, then applies environment overrides | | `settings.to_cascade_config()` | Converts settings into a `CascadeConfig` | Environment variables win over file values, which makes deployment-time overrides straightforward. Any other file extension returns `SdkError::InvalidInput`. ### Environment variables | Variable | Field | | - | - | | `LUMERA_CHAIN_ID` | `chain_id` | | `LUMERA_GRPC` | `grpc_endpoint` | | `LUMERA_RPC` | `rpc_endpoint` | | `LUMERA_REST` | `rest_endpoint` | | `LUMERA_GAS_PRICE` | `gas_price` | | `SNAPI_BASE` | `snapi_base` | ## Cascade surface `CascadeSdk` owns a `ChainClient` and an `SnApiClient` as public fields, so you can drop to either layer directly. ```rust theme={null} use lumera_sdk_rs::{CascadeConfig, CascadeSdk}; let sdk = CascadeSdk::new(CascadeConfig::new(chain_cfg, "https://snapi.testnet.lumera.io")); ``` ### Configuration `CascadeConfig` carries a `ChainConfig` plus the SN-API base URL. | Item | Signature | | - | - | | `CascadeConfig::new` | `(chain: ChainConfig, snapi_base: impl Into) -> Self` | | `CascadeConfig::with_snapi_base` | `(self, snapi_base: impl Into) -> Self` | ### Workflow methods | Method | Signature | | - | - | | `register_ticket` | `(&self, chain_signing_key, arbitrary_signing_key, creator_addr: &str, file_path: &Path, req: RegisterTicketRequest) -> Result` | | `upload_via_snapi` | `(&self, action_id: &str, signature: &str, file_path: &Path) -> Result` | | `request_download` | `(&self, action_id: &str, signing_key: &k256::ecdsa::SigningKey) -> Result` | `register_ticket` does the full registration in one call. It reads the file, hashes it, builds the RaptorQ layout, signs it, computes layout IDs, looks up the fee, and submits the action. Both upload and download return a task ID you poll through `snapi`. ### Deterministic helpers These are associated functions, so call them on the type rather than an instance. | Function | Signature | | - | - | | `compute_data_hash_b64` | `(file_bytes: &[u8]) -> String` | | `create_layout_b64` | `(file_path: &Path) -> Result` | | `generate_ids` | `(base: &str, ic: u32, max: u32) -> Result, SdkError>` | | `build_index_file` | `(layout_ids: Vec, layout_signature: String) -> IndexFile` | | `canonical_json_bytes` | `(v: &T) -> Result, SdkError>` | | `random_ic` | `(max: u32) -> u32` | `compute_data_hash_b64` is BLAKE3 over the file bytes, base64 encoded. `generate_ids` compresses with zstd level 3, hashes with BLAKE3, and encodes with base58, which is what keeps IDs byte-compatible with the SuperNode implementation. ### Cascade types | Type | Fields | | - | - | | `RegisterTicketRequest` | `file_name: String`, `is_public: bool`, `expiration_time: String` | | `RegisteredTicket` | `action_id: String`, `auth_signature: String`, `data_hash_b64: String`, `metadata_json: String` | | `IndexFile` | `layout_ids: Vec`, `layout_signature: String`, `version: i32` | ## Chain surface `ChainClient::new(cfg: ChainConfig)` builds the client. `ChainConfig` has the same five chain fields as `SdkSettings` minus `snapi_base`, plus a builder for each one (`with_chain_id`, `with_grpc_endpoint`, `with_rpc_endpoint`, `with_rest_endpoint`, `with_gas_price`). ### Queries | Method | Signature | | - | - | | `get_action_params` | `(&self) -> Result` | | `get_action_fee_amount` | `(&self, file_size_kbs: u64) -> Result` | | `get_account_info` | `(&self, address: &str) -> Result` | | `get_tx` | `(&self, tx_hash: &str) -> Result, SdkError>` | | `get_tx_event_attributes` | `(&self, tx_hash: &str) -> Result>, SdkError>` | | `calculate_fee_amount` | `(&self, gas_limit: u64) -> Result` | ### Transactions | Method | Signature | | - | - | | `register_action` | `(&self, signing_key, tx: RequestActionTxInput) -> Result` | | `request_action_tx` | `(&self, signing_key, tx: RequestActionTxInput, memo: impl Into) -> Result` | | `build_signed_tx` | `(&self, signing_key, creator: &str, msgs: Vec, memo: impl Into, gas_limit: u64) -> Result` | | `build_signed_tx_with_simulation` | `(&self, signing_key, creator: &str, msgs: Vec, memo: impl Into, fallback_gas_limit: u64, gas_adjustment: f64) -> Result<(cosmrs::tx::Raw, u64), SdkError>` | | `broadcast_signed_tx` | `(&self, tx_raw: &cosmrs::tx::Raw, mode: BroadcastMode) -> Result` | | `send_any_msgs` | `(&self, signing_key, creator: &str, msgs: Vec, memo: impl Into, gas_limit: u64, mode: BroadcastMode) -> Result` | | `simulate_gas_for_tx` | `(&self, tx_raw: &cosmrs::tx::Raw) -> Result` | Every `signing_key` above is a `&cosmrs::crypto::secp256k1::SigningKey`. `send_any_msgs` lets you submit arbitrary Cosmos messages, so the SDK is not limited to Cascade actions. `BroadcastMode` is `Async`, `Sync`, or `Commit`. ### Waiting | Method | Signature | | - | - | | `wait_for_tx_confirmation` | `(&self, tx_hash: &str, timeout_secs: u64) -> Result` | | `wait_for_event_attribute` | `(&self, tx_hash: &str, event_type: &str, attr_key: &str, timeout_secs: u64) -> Result` | ### Chain types | Type | Fields | | - | - | | `ChainConfig` | `chain_id`, `grpc_endpoint`, `rpc_endpoint`, `rest_endpoint`, `gas_price`, all `String` | | `ActionParams` | `max_raptor_q_symbols: u32`, `svc_challenge_count: u32`, `svc_min_chunks_for_challenge: u32`, `base_action_fee_denom: String` | | `TxResult` | `tx_hash: String`, `action_id: String` | | `RequestActionSubmitResult` | `tx_hash: String`, `action_id: String` | | `AccountInfo` | `address: String`, `account_number: u64`, `sequence: u64` | | `TxConfirmationStatus` | `tx_hash: String`, `height: i64`, `code: u32`, `raw_log: String` | | `BroadcastTxResult` | `tx_hash: String`, `check_tx_code: Option`, `deliver_tx_code: Option`, `log: String` | | `EventAttribute` | `event_type: String`, `key: String`, `value: String` | | `RequestActionTxInput` | `creator`, `action_type`, `metadata`, `price`, `expiration_time`, `file_size_kbs`, all `String`, plus `app_pubkey: Vec` | ### Log helpers | Function | Signature | | - | - | | `extract_action_id_from_log` | `(log: &str) -> Option` | | `extract_event_attribute_from_log` | `(log: &str, event_type: &str, attr_key: &str) -> Option` | `extract_action_id_from_log` is a shorthand that looks for the `action_id` attribute on the `action_registered` event. ## SN-API surface `SnApiClient::new(base: String)` builds the client, and `base` is public. | Method | Signature | Endpoint | | - | - | - | | `start_cascade` | `(&self, action_id: &str, signature: &str, file_path: &Path) -> Result` | `POST /api/v1/actions/cascade` | | `start_cascade_bytes` | `(&self, action_id: &str, signature: &str, file_name: &str, file_bytes: Vec) -> Result` | `POST /api/v1/actions/cascade` | | `upload_status` | `(&self, task_id: &str) -> Result` | `GET /api/v1/actions/cascade/tasks/{task_id}/status` | | `request_download` | `(&self, action_id: &str, signature: &str) -> Result` | `POST /api/v1/actions/cascade/{action_id}/downloads` | | `download_status` | `(&self, task_id: &str) -> Result` | `GET /api/v1/downloads/cascade/{task_id}/status` | | `download_file` | `(&self, task_id: &str) -> Result, SdkError>` | `GET /api/v1/downloads/cascade/{task_id}/file` | `start_cascade` reads the file and delegates to `start_cascade_bytes`, so use the bytes form when the data is already in memory. `request_download` retries transient server errors, because a download can race with finalization right after an upload completes. Status methods return raw `serde_json::Value` rather than typed structs. See the [SN-API reference](/api/sn-api) for the endpoint payloads. ## Signing identity `SigningIdentity` derives both signing keys the SDK needs from one mnemonic. | Field | Type | | - | - | | `chain_signing_key` | `cosmrs::crypto::secp256k1::SigningKey` | | `arbitrary_signing_key` | `k256::ecdsa::SigningKey` | | `address` | `String` | | `hrp` | `String` | Two keys exist because chain transactions and arbitrary payload signatures use different libraries. | Method | Signature | | - | - | | `SigningIdentity::from_mnemonic` | `(mnemonic: &str, hrp: &str, derivation_path: &str) -> Result` | | `validate_address` | `(&self, expected_address: &str) -> Result<(), SdkError>` | | `SigningIdentity::validate_chain_prefix` | `(expected_address: &str, expected_hrp: &str) -> Result<(), SdkError>` | | `derive_signing_keys_from_mnemonic` | `(mnemonic: &str) -> Result<(secp256k1::SigningKey, k256::ecdsa::SigningKey), SdkError>` | `derive_signing_keys_from_mnemonic` is a module-level convenience that assumes the `lumera` prefix and the `m/44'/118'/0'/0/0` path. ## Crypto helpers | Function | Signature | | - | - | | `sign_raw_message_b64` | `(sk: &k256::ecdsa::SigningKey, message: &str) -> String` | | `make_adr36_sign_bytes` | `(signer: &str, message: &str) -> Result, SdkError>` | `sign_raw_message_b64` signs the raw bytes and base64 encodes the signature. `make_adr36_sign_bytes` builds the ADR-036 `MsgSignData` document used for offline signature verification. See [LumeraID](/intelligence-layer/identity/lumeraid) for how ADR-036 signatures gate access to private objects. ## Errors Every fallible call returns `Result`. | Variant | Meaning | | - | - | | `SdkError::Http` | Transport or SN-API failure | | `SdkError::Serialization` | Encode or decode failure | | `SdkError::Crypto` | Key derivation or signing failure | | `SdkError::Chain` | Chain query or broadcast failure | | `SdkError::InvalidInput` | Bad argument, unreadable file, or unsupported config extension | ## Examples in the repo | Example | Shows | | - | - | | `examples/golden_devnet.rs` | Register a ticket, upload, request a download, and verify the hash | | `examples/custom_config.rs` | Explicit `ChainConfig` and `CascadeConfig` construction | | `examples/from_env_settings.rs` | Loading `SdkSettings` from the environment | | `examples/ui_server.rs` | An Axum server exposing the workflow over REST | ## Next steps The guided walkthrough for installing and using the SDK. The storage flow behind register, upload, and download. The REST endpoints the `snapi` module calls. The equivalent surface in Go. # Connect MetaMask to Lumera Source: https://docs.lumera.io/smart-contracts/connect-metamask Add the Lumera EVM network to MetaMask and work with both address formats. MetaMask talks to Lumera through the Ethereum JSON-RPC endpoint that every EVM enabled node serves. This page gives you the network values, a manual setup walkthrough, account import for migrated keys, and fixes for the most common connection errors. The EVM is live on testnet (`lumera-testnet-2`) only. Mainnet gains the EVM with its upgrade, so point MetaMask at testnet for now. See the [EVM upgrade page](/upgrades/evm-upgrade) for what changes. ## Use the EVM endpoint, not the Cosmos endpoints The MetaMask RPC URL is not the same as the Keplr RPC URL. Keplr is a Cosmos wallet and talks to the LCD and CometBFT endpoints. MetaMask talks only to the EVM JSON-RPC endpoint, a separate service on a different port and URL. Pasting a Cosmos URL into MetaMask is the most common reason a connection fails with "could not fetch chain ID". | Wallet | Protocol | Testnet URL | | - | - | - | | Keplr | Cosmos LCD/REST | `https://lumera-testnet-api.polkachu.com` | | Keplr | CometBFT RPC | `https://lumera-testnet-rpc.polkachu.com` | | MetaMask | EVM JSON-RPC | `https://evm-rpc.testnet.lumera.io` | Only the EVM JSON-RPC row belongs in a MetaMask network configuration. The Cosmos endpoints never answer `eth_chainId`, so MetaMask cannot use them. ## Network values Use these values to add Lumera testnet to MetaMask. | Field | Value | | - | - | | Network name | A clear label such as `Lumera Testnet EVM` | | RPC URL | `https://evm-rpc.testnet.lumera.io` | | Chain ID | `76857769` | | Currency symbol | `LUME` | | Block explorer URL | Leave blank | The EVM chain ID `76857769` is the same on every Lumera network. JSON-RPC responses return it as hex `0x494c1a9`. The mainnet EVM endpoint is `https://evm-rpc.lumera.io` and starts answering once mainnet completes its EVM upgrade. If you run a node yourself, MetaMask on the same machine can use `http://localhost:8545`. Verify an endpoint before adding it. ```bash theme={null} curl -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}' \ https://evm-rpc.testnet.lumera.io ``` The response must include the chain ID in hex. ```json theme={null} {"jsonrpc":"2.0","id":1,"result":"0x494c1a9"} ``` ## Add the network manually Open MetaMask and click the network name in the top left corner. Choose "Add a custom network". Older versions call it "Add network manually". Fill in the fields from the table above and save the network. Select the network in the selector. MetaMask fetches the chain ID from the RPC URL to confirm it. If MetaMask says it cannot fetch the chain ID, the RPC URL is not an EVM JSON-RPC endpoint or the endpoint is unreachable from your browser. See [troubleshooting](#troubleshooting) below. The Lumera Portal can configure the network for you. When you connect MetaMask on the [testnet Portal](https://portal.testnet.lumera.io/lumera-testnet-2), it checks `eth_chainId`, switches with `wallet_switchEthereumChain`, and only shows the "Add network" popup (`wallet_addEthereumChain`) if MetaMask does not know the chain yet. If no MetaMask popup appears, click the MetaMask icon in your browser toolbar. Chrome sometimes suppresses it. ## One account, two addresses Lumera uses Ethereum style keys (`eth_secp256k1`, coin type 60). The same account has two representations. | Representation | Format | Used by | | - | - | - | | Cosmos address | `lumera1...` | Keplr, `lumerad`, Cosmos endpoints | | Ethereum address | `0x...` | MetaMask, EVM tooling, JSON-RPC | Both point to the same funds and the same state. Apps like the Lumera Portal show both forms after you connect. MetaMask may label your native balance as ETH on custom networks. The amount is still your LUME balance. The EVM side reports the 18 decimal denom `alume`, where 1 ulume equals 10^12 alume, so the values line up exactly. ## Import a migrated account Accounts created before a network's EVM upgrade use coin type 118 and must be migrated on chain before they work in MetaMask. The migration produces a matching `0x...` and `lumera1...` pair on a coin type 60 key. See the [EVM upgrade page](/upgrades/evm-upgrade) for the migration itself, then import the key. In MetaMask, open the account selector and choose "Add wallet". Choose "Import a wallet" for a 12, 18, or 24 word Secret Recovery Phrase, or "Import an account" for a single private key. Enter the secret for the migrated key. Open the account menu, choose "Rename", and give the account a clear label so you can find it later. Only import a Secret Recovery Phrase or private key you own and control. Never paste a seed phrase into a website. Enter it only in the MetaMask extension's own import screen. ## Troubleshooting ### MetaMask cannot fetch the chain ID Test the RPC URL directly. ```bash theme={null} curl -H 'content-type: application/json' \ --data '{"jsonrpc":"2.0","id":1,"method":"eth_chainId","params":[]}' \ ``` The response must include `"result":"0x494c1a9"`. If it returns `Method not found`, the URL points at CometBFT RPC instead of EVM JSON-RPC. Watch for these common mistakes. * An LCD URL such as `https://lumera-testnet-api.polkachu.com` is Cosmos REST, not EVM JSON-RPC. * A CometBFT URL such as `https://lumera-testnet-rpc.polkachu.com` is consensus RPC, not EVM JSON-RPC. * `http://localhost:8545` only works when MetaMask runs on the same machine as the node. ### Requests fail in the browser but curl works The endpoint likely has a DNS, TLS, or CORS problem. MetaMask rejects invalid HTTPS certificates, and browsers enforce CORS preflight checks that curl skips. If you operate the endpoint, check those three layers from a machine outside the server. ## Running your own public endpoint Exposing a node's EVM JSON-RPC over HTTPS needs a reverse proxy with TLS, CORS headers, and rate limiting. That setup is an operator task and lives in the [MetaMask configuration guide on GitHub](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/user-guides/metamask-configuration.md), including nginx examples for hostname based and path based proxies plus WebSocket support. ## Next steps Deploy your first Solidity contract with Remix and MetaMask. Fund your account with testnet tokens for gas. Migrate a legacy account to the EVM key format. # Deploy a contract with Remix Source: https://docs.lumera.io/smart-contracts/deploy-with-remix Compile, deploy, and call a Solidity contract on Lumera with Remix and MetaMask. [Remix IDE](https://remix.ethereum.org) compiles and deploys Solidity from the browser with no local toolchain. This walkthrough deploys a small counter contract on Lumera and exercises reads, writes, events, and reverts. The EVM is live on testnet (`lumera-testnet-2`) only. Mainnet gains the EVM with its [upgrade](/upgrades/evm-upgrade), so deploy to testnet for now. ## Prerequisites * The MetaMask browser extension, configured with the Lumera testnet network. Follow [Connect MetaMask](/smart-contracts/connect-metamask) first. * Testnet LUME in your MetaMask account for gas. Get some from the [faucet](/faucet). MetaMask must use the EVM JSON-RPC URL `https://evm-rpc.testnet.lumera.io` with chain ID `76857769` and currency symbol `LUME`. A Cosmos LCD or CometBFT RPC URL cannot answer `eth_chainId` and will not work. ## Deploy the counter contract Open [Remix IDE](https://remix.ethereum.org). In the File Explorer panel on the left, create a new file called `Counter.sol` and paste this code. ```solidity Counter.sol theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /// @title Counter - A simple counter contract for testing Lumera EVM /// @notice Demonstrates basic state reads and writes on Lumera contract Counter { uint256 private _count; address public owner; event CountChanged(address indexed caller, uint256 newCount); constructor(uint256 initialCount) { _count = initialCount; owner = msg.sender; } /// @notice Returns the current count function get() public view returns (uint256) { return _count; } /// @notice Increments the counter by 1 function increase() public { _count += 1; emit CountChanged(msg.sender, _count); } /// @notice Decrements the counter by 1 (reverts on underflow) function decrease() public { require(_count > 0, "Counter: cannot decrease below zero"); _count -= 1; emit CountChanged(msg.sender, _count); } /// @notice Sets the counter to an arbitrary value /// @param newCount The new counter value function set(uint256 newCount) public { _count = newCount; emit CountChanged(msg.sender, _count); } } ``` Open the Solidity Compiler tab (second icon in the left sidebar). Set the compiler version to `0.8.20` or later to match the pragma, then click "Compile Counter.sol". A green checkmark appears next to the file name when compilation succeeds. Open the Deploy and Run Transactions tab (third icon in the left sidebar). In the Environment dropdown, select "Injected Provider - MetaMask". MetaMask prompts you to connect. Pick the account you want to use and click "Connect". Verify three things before deploying. * The Account field shows your MetaMask address. * The Balance shows your LUME balance. * The network indicator shows `Custom (76857769)`, the Lumera EVM chain ID. Select `Counter` in the Contract dropdown. Enter `0` (or any initial count) as the constructor argument next to the Deploy button, then click "Deploy". MetaMask opens a contract creation transaction. Review the gas estimate and click "Confirm". When the transaction confirms, the contract appears under Deployed Contracts at the bottom of the panel. ## Interact with the contract Expand the deployed contract to see its functions. Remix color codes them. Blue buttons are read-only `view` functions with no gas cost and no MetaMask popup. Orange buttons change state, cost gas, and trigger a MetaMask confirmation. Click "get". The result appears below the button. ```text theme={null} 0: uint256: 0 ``` Click "increase" and confirm the transaction in MetaMask. Click "get" again to verify the count is now `1`. Enter a value such as `42` in the input next to "set", click "set", and confirm in MetaMask. "get" now returns `42`. Click "decrease" and confirm in MetaMask. "get" returns `41`. Set the counter to `0`, then click "decrease". The transaction reverts with `Counter: cannot decrease below zero`. MetaMask may warn about the likely failure before you confirm. The contract records the deployer as `owner`. Click the blue "owner" button, the auto generated getter for the public state variable. It returns your MetaMask address. ## Inspect transactions ### In Remix Each transaction appears in the Remix terminal at the bottom of the screen. Click an entry to expand its details. * Transaction hash, click to copy * From and to addresses * Gas used * Decoded input showing the function called and its arguments * Logs showing the emitted `CountChanged` events ### Over JSON-RPC Query the transaction receipt from the node. ```bash theme={null} # Replace TX_HASH with the transaction hash from Remix curl -s -X POST https://evm-rpc.testnet.lumera.io \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["TX_HASH"],"id":1}' | jq '.' ``` The `CountChanged` event fires on every state change. Count the logs for your contract. ```bash theme={null} # Replace CONTRACT_ADDRESS with the deployed contract address curl -s -X POST https://evm-rpc.testnet.lumera.io \ -H "Content-Type: application/json" \ -d '{ "jsonrpc":"2.0", "method":"eth_getLogs", "params":[{"address":"CONTRACT_ADDRESS","fromBlock":"0x0","toBlock":"latest"}], "id":1 }' | jq '.result | length' ``` ## Troubleshooting ### MetaMask shows the wrong chain ID Confirm the MetaMask network uses chain ID `76857769`. If you run your own node and upgraded it from a pre-EVM binary, the config migration adds the `[evm]` section to `app.toml` automatically on first startup. The node configuration details live in the [EVM user guides on GitHub](https://github.com/LumeraProtocol/lumera/tree/master/docs/evm-integration/user-guides). ### Transaction fails with "nonce too low" MetaMask may cache nonces. Open MetaMask settings, go to Advanced, and use "Clear activity tab data" to reset the nonce cache for the current network. ### Transaction pending indefinitely Check that the node is producing blocks. ```bash theme={null} curl -s -X POST https://evm-rpc.testnet.lumera.io \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' | jq '.result' ``` If the block number is not advancing, the node is stalled or consensus is not running. ### Gas estimation fails The Lumera EVM uses an EIP-1559 fee market. If estimation fails, set the gas parameters manually in MetaMask's advanced transaction settings. ### "Internal JSON-RPC error" on deploy Check the Remix console for the full error. Common causes include the following. * Insufficient LUME balance for gas * Contract code exceeds the block gas limit * Constructor arguments missing or of the wrong type ## Going further Deploy an OpenZeppelin ERC-20 template the same way to create a token on Lumera. For production workflows, point Hardhat or Foundry at the same JSON-RPC endpoint. Contracts can also call native chain features like staking, governance, and Cascade through precompiles. ## Next steps Call native Lumera modules from Solidity at fixed addresses. Explore every JSON-RPC method the node supports. Endpoints and methods for scripting against the EVM. # Explore the JSON-RPC catalog Source: https://docs.lumera.io/smart-contracts/openrpc-playground Discover every JSON-RPC method a Lumera node supports through the OpenRPC spec. Lumera publishes a machine readable API catalog in the [OpenRPC](https://open-rpc.org/) format. Wallets, developer tools, and code generators can discover every JSON-RPC method the node supports, including parameters, return types, and usage examples. This page shows how to fetch the spec and browse it interactively. The EVM and its JSON-RPC surface are live on testnet (`lumera-testnet-2`) only. Mainnet gains them with its [EVM upgrade](/upgrades/evm-upgrade). ## Two ways to fetch the spec | Method | Endpoint | Default port | Protocol | Use case | | - | - | - | - | - | | JSON-RPC | `rpc_discover` or `rpc.discover` | 8545 (EVM JSON-RPC) | POST | Programmatic discovery from apps, scripts, or the playground | | HTTP | `/openrpc.json` | 1317 (Cosmos REST) | GET or POST | Browser access, curl, CI pipelines, static documentation | Both return the same embedded spec of about 743 methods. The spec is regenerated on every build from the actual Go RPC implementation, so it never drifts from the running code. ## Quick start Query the public testnet endpoint with `rpc_discover`. ```bash theme={null} curl -s -X POST https://evm-rpc.testnet.lumera.io \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"rpc_discover","params":[],"id":1}' | jq '.result.info' ``` Expected output. ```json theme={null} { "title": "Lumera Cosmos EVM JSON-RPC API", "version": "cosmos/evm v0.6.0", "description": "Auto-generated method catalog from Cosmos EVM JSON-RPC namespace implementations." } ``` On your own node, the Cosmos REST server also serves the spec over plain HTTP. ```bash theme={null} curl -s http://localhost:1317/openrpc.json | jq '.info' ``` The HTTP endpoint is served by the Cosmos REST API server on port 1317, not the EVM JSON-RPC server on port 8545. The REST server needs `api.enable = true` and the JSON-RPC server needs `json-rpc.enable = true` in `app.toml`. ## Use the OpenRPC playground The [OpenRPC Playground](https://playground.open-rpc.org) is a browser based explorer that renders the spec as a searchable method list with live request execution. Pass the spec location in the `url` query parameter. Point it at a node's REST port, which serves `/openrpc.json`. ```text theme={null} https://playground.open-rpc.org/?url=http://localhost:1317/openrpc.json ``` Or point it directly at the JSON-RPC port, which answers both discovery names. ```text theme={null} https://playground.open-rpc.org/?url=http://localhost:8545 ``` Inside the playground you can do the following. * Browse the left panel, which groups methods by namespace (`eth`, `net`, `web3`, `debug`, `txpool`, `rpc`) * Click a method to see its parameters, return type, and examples * Click "Try It" to execute the method against the connected node * Read results inline with syntax highlighting The playground loads the spec with a GET to `/openrpc.json`, then sends "Try It" requests as POSTs back to the same URL. Lumera proxies `POST /openrpc.json` to the internal JSON-RPC server and rewrites the OpenRPC style `rpc.discover` name to the native `rpc_discover` for compatibility. Direct requests to the JSON-RPC port accept both names. Mainnet builds refuse to start with the `debug`, `personal`, or `admin` namespaces enabled. Testnet allows them, so plan tracing dependent workflows accordingly. ## CORS for browser access The `/openrpc.json` endpoint and the WebSocket server share one CORS origin list, configured in `app.toml`. ```toml theme={null} [json-rpc] ws-origins = ["127.0.0.1", "localhost"] ``` | Setting | Effect | | - | - | | `["127.0.0.1", "localhost"]` (default) | Works from a local browser only | | `["*"]` | Allows any origin, acceptable for local development or testnet | | `["https://playground.open-rpc.org"]` | Allows the hosted playground specifically | Restrict origins to specific domains on production endpoints. ## Configuration requirements For discovery to work on your own node, `app.toml` needs the JSON-RPC server enabled with the `rpc` namespace in the API list. ```toml theme={null} [json-rpc] enable = true # The "rpc" namespace must be in the API list. api = "eth,net,web3,rpc" ``` The `rpc` namespace is included by default and re-added during config migration. If you customized the `api` list, make sure `rpc` is still present. The HTTP endpoint additionally requires the REST server. ```toml theme={null} [api] enable = true ``` ## Useful queries ```bash theme={null} # List all available methods curl -s -X POST https://evm-rpc.testnet.lumera.io \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"rpc_discover","params":[],"id":1}' \ | jq '[.result.methods[].name] | sort' # Count methods by namespace curl -s -X POST https://evm-rpc.testnet.lumera.io \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"rpc_discover","params":[],"id":1}' \ | jq '[.result.methods[].name] | group_by(split("_")[0]) | map({namespace: .[0] | split("_")[0], count: length})' # Get details for a specific method curl -s -X POST https://evm-rpc.testnet.lumera.io \ -H "Content-Type: application/json" \ -d '{"jsonrpc":"2.0","method":"rpc_discover","params":[],"id":1}' \ | jq '.result.methods[] | select(.name == "eth_sendRawTransaction")' ``` ## Regenerating the spec This part only matters if you work on the `lumerad` codebase itself. The spec is embedded in the binary at build time. After adding or changing JSON-RPC methods, regenerate it with `make openrpc`. The spec also regenerates automatically as a dependency of `make build`, so a fresh build always serves a catalog that matches its compiled RPC surface. ## Next steps Use the RPC surface interactively by deploying a Solidity contract. Endpoints and methods for scripting against the EVM. # Smart contracts on Lumera Source: https://docs.lumera.io/smart-contracts/overview Deploy Solidity contracts on Lumera with standard Ethereum tooling and reach native modules through precompiles. Lumera Protocol runs three execution environments side by side. Native Cosmos SDK modules power Cascade and staking. CosmWasm runs Rust smart contracts. The v1.20.0 Aurora Convergence upgrade added a full EVM that runs Solidity and Vyper contracts. The EVM is live on testnet (`lumera-testnet-2`). Mainnet still runs `v1.12.0` and gains EVM support with its upgrade, so build and test against testnet for now. This page gives you the complete picture of the EVM support. The rest of this section shows each piece in practice, from wallet setup to precompile calls. Three execution environments on one chain Three execution environments on one chain ## What the EVM support includes Lumera integrates Cosmos EVM v0.6.0 directly into the chain. It is not a sidechain or a bridge. On EVM enabled networks it includes the following. * Ethereum JSON-RPC and WebSocket endpoints, enabled by default on every upgraded node * An EVM indexer and transaction tracing * OpenRPC discovery through `rpc_discover` or the `/openrpc.json` path on the API server * An EIP-1559 fee market with dynamic base fees * Phase 1 of a bidirectional CosmWasm to EVM bridge Standard toolchains work without changes. Hardhat, Foundry, Remix, Ethers, viem, and MetaMask all target Lumera over JSON-RPC. ## Accounts Lumera uses Ethereum style key derivation (`eth_secp256k1`, BIP44 coin type 60). One account has two representations of the same key. | Representation | Format | Used by | | - | - | - | | Cosmos address | `lumera1...` | Keplr, `lumerad`, Cosmos endpoints | | Ethereum address | `0x...` | MetaMask, EVM tooling, JSON-RPC | Both point to the same funds and the same state. Accounts created before a network's v1.20.0 upgrade need a one time migration. See the [EVM upgrade page](/upgrades/evm-upgrade). ## Token precision The native token keeps its 6 decimal base denom `ulume`. The EVM side exposes an 18 decimal denom called `alume`, where 1 ulume equals 10^12 alume. The `x/precisebank` module bridges the two without creating a second supply. Balances always reconcile between Cosmos and EVM views. ## Precompiles Precompiles are contracts at fixed addresses that expose native chain features to Solidity. Lumera ships the standard Cosmos EVM set plus three custom precompiles for its own modules. | Precompile | Address | What it exposes | | - | - | - | | Action | `0x0901` | Register and query Cascade and Sense actions from contracts | | SuperNode | `0x0902` | SuperNode registration state and queries | | Wasm | `0x0903` | Call CosmWasm contracts from the EVM | The standard set covers bank, staking, distribution, governance, slashing, ICS20 transfers, Bech32 conversion, and P256 verification. Full details live in the [precompiles overview](/smart-contracts/precompiles/overview). New to the EVM side? Start by [connecting MetaMask](/smart-contracts/connect-metamask), then [deploy your first contract with Remix](/smart-contracts/deploy-with-remix). When you are ready to script against the node, explore the full method catalog in the [OpenRPC playground](/smart-contracts/openrpc-playground). ## Where to build first Cascade storage is the core product of the chain. If your app needs permanent storage, start with the [Cascade section](/cascade/how-cascade-works) and the SDKs. Reach for the EVM when you need Solidity contracts, MetaMask users, or existing Ethereum tooling. The [Action precompile](/smart-contracts/precompiles/action) lets a contract drive Cascade directly. ## Next steps Add the Lumera EVM network to MetaMask and verify the endpoint. Compile and deploy a Solidity contract from the browser. Call native Lumera modules from Solidity at fixed addresses. # The Action precompile Source: https://docs.lumera.io/smart-contracts/precompiles/action Request and query Cascade and Sense actions from Solidity contracts. The Action precompile exposes the `x/action` module to the EVM at a single fixed address. Solidity contracts can request, finalize, approve, and query Cascade storage and Sense analysis actions without leaving the EVM execution context. The EVM is live on testnet (`lumera-testnet-2`) only. Mainnet runs `v1.12.0` and gains the EVM with its upgrade. ## Address ```text theme={null} 0x0000000000000000000000000000000000000901 ``` Lumera custom precompiles start at `0x0900`. Everything below that range belongs to the Ethereum and Cosmos EVM standard sets. See the [precompiles overview](/smart-contracts/precompiles/overview) for the full address map. ## Design The precompile mixes typed and generic methods. Typed methods carry action-specific metadata, which gives Solidity compile-time safety for fields that differ between Cascade and Sense. Generic methods share one signature regardless of action type. | Category | Methods | | - | - | | Typed (Cascade) | `requestCascade`, `finalizeCascade` | | Typed (Sense) | `requestSense`, `finalizeSense` | | Generic | `approveAction`, `getAction`, `getActionFee`, `getParams`, `getActionsByState`, `getActionsByCreator`, `getActionsBySuperNode` | ## Action lifecycle ```text theme={null} Request (Pending) -> Processing -> Finalize (Done) -> Approve (Approved) -> Rejected / Failed / Expired ``` The `state` field in query results uses these values. | State | Value | Description | | - | - | - | | Pending | 1 | Newly created, awaiting SuperNode processing | | Processing | 2 | SuperNodes are working on the action | | Done | 3 | Finalized by a SuperNode, awaiting creator approval | | Approved | 4 | Creator approved the result | | Rejected | 5 | Creator rejected the result | | Failed | 6 | Processing failed | | Expired | 7 | Exceeded the expiration time | The `actionType` field uses `1` for Sense and `2` for Cascade. ## Solidity interface The interface below comes from [`IAction.sol`](https://github.com/LumeraProtocol/lumera/blob/master/precompiles/solidity/contracts/interfaces/IAction.sol) in the lumera repo. ```solidity IAction.sol theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /// @notice Precompile at 0x0000000000000000000000000000000000000901 interface IAction { /// @notice LEP 5 availability commitment for Cascade storage verification. struct AvailabilityCommitment { string commitmentType; // e.g. "merkle_blake3" uint8 hashAlgo; // 0 = unspecified, 1 = BLAKE3, 2 = SHA256 uint32 chunkSize; // bytes per chunk uint64 totalSize; // total file size in bytes uint32 numChunks; // number of chunks bytes root; // Merkle root hash uint32[] challengeIndices; // chunk indices the SuperNode must prove } /// @notice LEP 5 Merkle inclusion proof for a single challenged chunk. struct ChunkProof { uint32 chunkIndex; // which chunk this proves bytes leafHash; // hash of the chunk data bytes[] pathHashes; // sibling hashes along the Merkle path bool[] pathDirections; // true = right sibling, false = left } /// @notice Represents a single action on the Lumera chain. struct ActionInfo { string actionId; address creator; uint8 actionType; // 1 = Sense, 2 = Cascade uint8 state; // see the state table above string metadata; // JSON metadata (type-specific) uint256 price; // fee paid in ulume int64 expirationTime; int64 blockHeight; address[] superNodes; } event ActionRequested( string indexed actionId, address indexed creator, uint8 actionType, uint256 price ); event ActionApproved( string indexed actionId, address indexed creator ); /// @notice Request a Cascade storage action. function requestCascade( string calldata dataHash, string calldata fileName, uint64 rqIdsIc, string calldata signatures, uint256 price, int64 expirationTime, uint64 fileSizeKbs, AvailabilityCommitment calldata commitment // pass an empty root to skip ) external returns (string memory actionId); /// @notice Request a Sense analysis action. function requestSense( string calldata dataHash, uint64 ddAndFingerprintsIc, uint256 price, int64 expirationTime, uint64 fileSizeKbs ) external returns (string memory actionId); /// @notice Finalize a Cascade action with storage proofs. function finalizeCascade( string calldata actionId, string[] calldata rqIdsIds, ChunkProof[] calldata chunkProofs // pass empty for pre-LEP5 ) external returns (bool success); /// @notice Finalize a Sense analysis action with results. function finalizeSense( string calldata actionId, string[] calldata ddAndFingerprintsIds, string calldata signatures ) external returns (bool success); /// @notice Approve a finalized action (creator only). function approveAction( string calldata actionId ) external returns (bool success); /// @notice Get details of a specific action by ID. function getAction( string calldata actionId ) external view returns (ActionInfo memory action); /// @notice Calculate the fee for an action of the given data size. function getActionFee( uint64 dataSizeKbs ) external view returns (uint256 baseFee, uint256 perKbFee, uint256 totalFee); /// @notice List actions created by a specific address. function getActionsByCreator( address creator, uint64 offset, uint64 limit ) external view returns (ActionInfo[] memory actions, uint64 total); /// @notice List actions filtered by state. function getActionsByState( uint8 state, uint64 offset, uint64 limit ) external view returns (ActionInfo[] memory actions, uint64 total); /// @notice List actions assigned to a specific SuperNode. function getActionsBySuperNode( address superNode, uint64 offset, uint64 limit ) external view returns (ActionInfo[] memory actions, uint64 total); /// @notice Get the action module parameters. function getParams() external view returns ( uint256 baseActionFee, uint256 feePerKbyte, uint64 maxActionsPerBlock, uint64 minSuperNodes, int64 expirationDuration, string memory superNodeFeeShare, string memory foundationFeeShare, uint32 svcChallengeCount, uint32 svcMinChunksForChallenge ); } ``` ## Request a Cascade action from a contract A minimal storage client queries the fee, requests the action, and tracks the returned `actionId`. ```solidity CascadeStorageClient.sol theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "./IAction.sol"; contract CascadeStorageClient { IAction constant ACTION = IAction(0x0000000000000000000000000000000000000901); mapping(string => string) public uploads; event UploadRequested(string indexed dataHash, string actionId, uint256 totalFee); function uploadFile( string calldata dataHash, string calldata fileName, uint64 rqIdsIc, string calldata signatures, uint64 fileSizeKbs ) external { // 1. Query the fee to determine the price (, , uint256 totalFee) = ACTION.getActionFee(fileSizeKbs); // 2. Set expiration to 1 hour from now int64 expiration = int64(int256(block.timestamp)) + 3600; // 3. Request the Cascade action. An empty commitment skips LEP 5. IAction.AvailabilityCommitment memory commitment; string memory actionId = ACTION.requestCascade( dataHash, fileName, rqIdsIc, signatures, totalFee, expiration, fileSizeKbs, commitment ); uploads[dataHash] = actionId; emit UploadRequested(dataHash, actionId, totalFee); } function checkUploadState(string calldata dataHash) external view returns (uint8 state) { IAction.ActionInfo memory info = ACTION.getAction(uploads[dataHash]); return info.state; } function approveUpload(string calldata dataHash) external { ACTION.approveAction(uploads[dataHash]); } } ``` Requesting the action registers it on chain. The file itself still travels to SuperNodes off chain, the same way as in the normal [upload lifecycle](/cascade/concepts/upload-lifecycle). `finalizeCascade` and `finalizeSense` are called by SuperNodes when processing completes, not by your app. ## Fees `getActionFee` returns the base fee, the per-kilobyte rate, and the total, all in `ulume`. The current testnet parameters are 10,000 `ulume` base plus 10 `ulume` per KB. Governance can change them, so query the fee on chain instead of hardcoding values. ## Call it from JavaScript Read methods work through `eth_call` with no deployed contract. ```typescript query-action.ts theme={null} import { ethers } from "ethers"; const ACTION_ADDRESS = "0x0000000000000000000000000000000000000901"; const ACTION_ABI = [ "function getActionFee(uint64 dataSizeKbs) view returns (uint256 baseFee, uint256 perKbFee, uint256 totalFee)", "function getAction(string actionId) view returns (tuple(string actionId, address creator, uint8 actionType, uint8 state, string metadata, uint256 price, int64 expirationTime, int64 blockHeight, address[] superNodes))", "event ActionRequested(string indexed actionId, address indexed creator, uint8 actionType, uint256 price)", ]; // Point at your node's EVM JSON-RPC endpoint (port 8545) const provider = new ethers.JsonRpcProvider("http://localhost:8545"); const action = new ethers.Contract(ACTION_ADDRESS, ACTION_ABI, provider); // Query the fee for 100 KB const [baseFee, perKbFee, totalFee] = await action.getActionFee(100n); console.log(`Total fee for 100 KB: ${totalFee} ulume`); // Watch for new actions action.on("ActionRequested", (actionId, creator, actionType, price) => { console.log(`New action ${actionId} by ${creator}, type=${actionType}, price=${price}`); }); ``` Transaction methods work the same way with a signer attached. ## Behavior notes * **Address translation.** The precompile converts your EVM `0x...` caller address to `lumera1...` before it reaches the message server. Addresses in returned `ActionInfo` structs come back converted to `0x...` form. * **Metadata bridging.** Typed Solidity arguments become a JSON metadata string inside the precompile, then flow through `MsgRequestAction` to the keeper exactly like a Cosmos transaction. * **Pagination.** List queries cap at 100 results per call. A larger `limit` is silently capped, so page with `offset`. * **Events.** The precompile emits `ActionRequested`, `ActionFinalized`, and `ActionApproved` as EVM logs, so standard event subscriptions work. * **Gas.** Calls meter gas from the underlying Cosmos gas usage converted to EVM gas units. The Go implementation lives in [precompiles/action](https://github.com/LumeraProtocol/lumera/tree/master/precompiles/action), with a full reference in the [action precompile doc](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/precompiles/action-precompile.md). ## Next steps The full storage flow behind every Cascade action. Read the state of the nodes that process your actions. Staking, governance, and IBC from Solidity. # The CosmWasm to EVM bridge Source: https://docs.lumera.io/smart-contracts/precompiles/cosmwasm-bridge Call CosmWasm contracts from Solidity and the reverse through the Wasm precompile. Lumera Protocol runs CosmWasm and an EVM on the same chain. The Wasm precompile at `0x0903` and a companion plugin in the wasm keeper form a bidirectional bridge between the two runtimes. Solidity contracts can execute and query CosmWasm contracts, and CosmWasm contracts can call and query EVM contracts. This page covers phase 1 of the bridge, which is what ships today. The EVM is live on testnet (`lumera-testnet-2`) only. Mainnet runs `v1.12.0` and gains the EVM with its upgrade. ## The two directions | Direction | Mechanism | Entry point | | - | - | - | | EVM to CosmWasm | Static precompile (`IWasm`) | `0x0000000000000000000000000000000000000903` | | CosmWasm to EVM | Custom message and query handlers | JSON `Custom` envelope in `CosmosMsg` and `QueryRequest` | Both directions share a reentrancy guard and execute as the calling contract, not as the outer user. ## What phase 1 covers Phase 1 supports non-payable calls in both directions. * From the EVM you get `execute`, `query`, `contractInfo`, and `rawQuery` against any CosmWasm contract. * From CosmWasm you get `evm_call` messages and queries plus an `evm_account` query. * No funds move across the boundary. `execute` is non-payable and `evm_call` carries no value field. * No instantiation. You cannot deploy a CosmWasm contract from the EVM or an EVM contract from CosmWasm. * The reentrancy guard enforces a maximum call depth of 1, so a call cannot cross the boundary twice (no EVM to Wasm to EVM chains). * Every CosmWasm to EVM call is capped at 3,000,000 gas or the remaining gas, whichever is smaller. The [wasm precompile doc](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/precompiles/wasm-precompile.md) in the lumera repo tracks the phase roadmap beyond this. ## EVM to CosmWasm The interface below comes from [`IWasm.sol`](https://github.com/LumeraProtocol/lumera/blob/master/precompiles/solidity/contracts/interfaces/IWasm.sol) in the lumera repo. ```solidity IWasm.sol theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /// @notice Precompile at 0x0000000000000000000000000000000000000903 /// @dev Phase 1: non-payable execute, query, contractInfo, rawQuery. interface IWasm { /// @notice Emitted when a CosmWasm contract is successfully executed. event WasmExecuted( address indexed caller, string contractAddr, bytes response ); /// @notice Execute a CosmWasm contract (non-payable, no funds transfer). /// @param contractAddr The bech32 address of the target CosmWasm contract. /// @param msg The JSON-encoded execute message. function execute( string calldata contractAddr, bytes calldata msg ) external returns (bytes memory response); /// @notice Query a CosmWasm contract (read-only). function query( string calldata contractAddr, bytes calldata msg ) external view returns (bytes memory response); /// @notice Get metadata about a CosmWasm contract. function contractInfo( string calldata contractAddr ) external view returns ( uint64 codeId, string memory creator, string memory admin, string memory label ); /// @notice Query a raw storage key from a CosmWasm contract. function rawQuery( string calldata contractAddr, bytes calldata key ) external view returns (bytes memory value); } ``` | Method | Type | Description | | - | - | - | | `execute` | Transaction | Execute a CosmWasm contract. The caller address is converted to bech32. Non-payable in phase 1. | | `query` | View | Smart query a CosmWasm contract. Returns raw JSON bytes. | | `contractInfo` | View | Contract metadata. Code ID, creator, admin, and label. | | `rawQuery` | View | Read a raw storage key from the contract's KV store. | ### Example ```solidity WasmCaller.sol theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "./interfaces/IWasm.sol"; contract WasmCaller { IWasm constant WASM = IWasm(0x0000000000000000000000000000000000000903); // Query a CosmWasm counter contract function getCount(string calldata wasmContract) external view returns (bytes memory) { bytes memory queryMsg = '{"get_count":{}}'; return WASM.query(wasmContract, queryMsg); } // Execute a CosmWasm counter contract function increment(string calldata wasmContract) external returns (bytes memory) { bytes memory execMsg = '{"increment":{}}'; return WASM.execute(wasmContract, execMsg); } // Check that a contract exists function checkContract(string calldata wasmContract) external view returns (uint64 codeId) { (codeId, , , ) = WASM.contractInfo(wasmContract); } } ``` Messages and responses are raw JSON bytes. Your Solidity code passes the exact execute or query message the CosmWasm contract expects and parses the JSON response itself or hands it back to an off-chain caller. ## CosmWasm to EVM CosmWasm contracts reach the EVM through the standard `Custom` envelope. No precompile address is involved in this direction. To call an EVM contract, send a `CosmosMsg::Custom` with an `evm_call` payload. ```json theme={null} { "evm_call": { "contract": "0x1234abcd...", "calldata": "0xa9059cbb000000..." } } ``` The `contract` field is the hex EVM contract address. The `calldata` field is hex-encoded EVM calldata, meaning the function selector plus ABI-encoded arguments. Phase 1 has no `value` field. To read EVM state, send a `QueryRequest::Custom` with the same `evm_call` shape. It behaves like `eth_call` and returns the hex-encoded return data. ```json theme={null} {"result": "0x"} ``` An `evm_account` query returns basic account info for any EVM address. ```json theme={null} {"balance": "", "nonce": 0, "is_contract": false} ``` ## Behavior you should know * **Sender identity.** Cross-runtime calls execute as the calling contract, never as the transaction origin. An EVM contract calling `execute` appears to the CosmWasm side as its own address converted to bech32. A CosmWasm contract calling `evm_call` appears to the EVM side as its own address, so `msg.sender` in the target contract is the wasm contract. Proxy and delegatecall patterns do not propagate across the runtime boundary. * **Atomicity.** Failures revert atomically. An EVM to Wasm call snapshots both the multistore and the EVM state journal, and both revert together on failure. A Wasm to EVM call runs in a cache context that the dispatcher discards on failure. * **Gas.** Both runtimes settle in Cosmos SDK gas. An EVM to Wasm call deducts the consumed Cosmos gas from the calling contract's gas. A Wasm to EVM call charges the EVM gas used back to the wasm context, capped at 3,000,000 per call. * **Events.** Successful `execute` calls emit a `WasmExecuted` EVM log with the caller, the target contract address, and the raw response. The implementation lives in [precompiles/wasm](https://github.com/LumeraProtocol/lumera/tree/master/precompiles/wasm), [precompiles/crossruntime](https://github.com/LumeraProtocol/lumera/tree/master/precompiles/crossruntime), and `app/wasm_evm_plugin.go` in the lumera repo. ## Next steps The full address map and when to use each precompile. Drive Cascade and Sense actions from Solidity. Deploy and test a contract against Lumera testnet. # EVM precompiles on Lumera Source: https://docs.lumera.io/smart-contracts/precompiles/overview Native chain features exposed to Solidity contracts at fixed addresses. Precompiles are contracts at fixed addresses that the chain implements natively instead of in EVM bytecode. Your Solidity code calls them like any other contract, and the chain runs the matching module logic directly. There is nothing to deploy, and the addresses never change. Lumera Protocol ships 11 static precompiles. Eight come from the standard Cosmos EVM v0.6.0 set. Three are custom precompiles that expose Lumera's own modules. The EVM is live on testnet (`lumera-testnet-2`) only. Mainnet runs `v1.12.0` and gains the EVM with its upgrade. ## Address map Lumera follows a simple address convention. | Range | Contents | | - | - | | `0x01` to `0x0a` | Ethereum standard precompiles | | `0x0100` to `0x0806` | Cosmos EVM standard precompiles | | `0x0900` and up | Lumera custom precompiles | ### Custom precompiles | Precompile | Address | Module | | - | - | - | | [Action](/smart-contracts/precompiles/action) | `0x0000000000000000000000000000000000000901` | `x/action` | | [SuperNode](/smart-contracts/precompiles/supernode) | `0x0000000000000000000000000000000000000902` | `x/supernode` | | [Wasm](/smart-contracts/precompiles/cosmwasm-bridge) | `0x0000000000000000000000000000000000000903` | CosmWasm (wasmd) | ### Standard precompiles | Precompile | Address | Purpose | | - | - | - | | P256 | `0x...0100` | NIST P-256 signature verification (EIP-7212) | | Bech32 | `0x...0400` | Hex and Bech32 address conversion | | Staking | `0x...0800` | Delegate, undelegate, redelegate, manage validators | | Distribution | `0x...0801` | Claim rewards, set withdraw address, fund community pool | | ICS20 | `0x...0802` | IBC fungible token transfers | | Bank | `0x...0804` | Query balances and supply (read-only) | | Gov | `0x...0805` | Submit proposals, vote, query governance state | | Slashing | `0x...0806` | Unjail validators, query signing info | The Vesting precompile (`0x...0803`) is excluded because the current Cosmos EVM default registry does not provide an implementation for it. Full signatures for the whole set live on the [standard precompiles page](/smart-contracts/precompiles/standard). ## When to use a precompile Reach for a precompile whenever your contract needs a native chain feature. * Register and track Cascade storage or Sense analysis actions from contract logic ([Action](/smart-contracts/precompiles/action)) * Read SuperNode registration state, rankings, and hardware metrics on chain ([SuperNode](/smart-contracts/precompiles/supernode)) * Execute or query a CosmWasm contract from Solidity ([Wasm](/smart-contracts/precompiles/cosmwasm-bridge)) * Stake, vote, claim rewards, or send IBC transfers from a contract ([standard set](/smart-contracts/precompiles/standard)) You can also call any precompile straight from JavaScript over JSON-RPC. Read methods work through `eth_call` with no deployed contract at all. ## Calling a precompile Import the interface and point it at the fixed address. ```solidity MyDApp.sol theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "./interfaces/IAction.sol"; import "./interfaces/ISupernode.sol"; contract MyDApp { IAction constant ACTION = IAction(0x0000000000000000000000000000000000000901); ISupernode constant SUPERNODE = ISupernode(0x0000000000000000000000000000000000000902); function getStorageCost(uint64 fileSizeKbs) external view returns (uint256) { (, , uint256 totalFee) = ACTION.getActionFee(fileSizeKbs); return totalFee; } function totalSuperNodes() external view returns (uint64 total) { (, total) = SUPERNODE.listSuperNodes(0, 1); } } ``` The interfaces (`IAction.sol`, `ISupernode.sol`, `IWasm.sol`) live in [precompiles/solidity](https://github.com/LumeraProtocol/lumera/tree/master/precompiles/solidity) in the lumera repo, together with example contracts and a Hardhat test project. They are plain type-safe wrappers around calls to the fixed addresses. ## Gas Precompile calls consume gas like any other EVM call. The custom precompiles meter gas from the underlying Cosmos gas usage, converted to EVM gas units. `view` methods cost nothing when called through `eth_call`. The P256 precompile has a fixed cost of 3,450 gas. ## Safety rails Precompile addresses cannot receive plain token transfers. The chain blocks them through the module account block list and a bank send restriction, so funds cannot be lost by sending tokens to a precompile address by accident. ## Next steps Staking, governance, IBC transfers, and more with full signatures. Drive Cascade and Sense actions from your contracts. Call CosmWasm contracts from Solidity and the reverse. # Standard Cosmos EVM precompiles Source: https://docs.lumera.io/smart-contracts/precompiles/standard Eight precompiles that expose core Cosmos modules to Solidity. Lumera Protocol enables eight standard precompiles from the Cosmos EVM v0.6.0 framework. They expose core Cosmos SDK modules to Solidity, so contracts can stake, vote, claim rewards, convert addresses, and move tokens over IBC without leaving the EVM. The set is registered in [`app/evm/precompiles.go`](https://github.com/LumeraProtocol/lumera/blob/master/app/evm/precompiles.go) and uses the upstream implementations from the [cosmos/evm](https://github.com/cosmos/evm) repo. The EVM is live on testnet (`lumera-testnet-2`) only. Mainnet runs `v1.12.0` and gains the EVM with its upgrade. The Vesting precompile (`0x0000000000000000000000000000000000000803`) is intentionally excluded. The current Cosmos EVM default registry does not provide an implementation for it. ## Shared types Precompiles that accept pagination or return coins share these Solidity types. ```solidity Types.sol theme={null} struct Coin { string denom; uint256 amount; } struct DecCoin { string denom; uint256 amount; uint8 precision; } struct PageRequest { bytes key; uint64 offset; uint64 limit; bool countTotal; bool reverse; } struct PageResponse { bytes nextKey; uint64 total; } struct Height { uint64 revisionNumber; uint64 revisionHeight; } ``` ## P256 | | | | - | - | | Address | `0x0000000000000000000000000000000000000100` | | Gas | Fixed 3,450 | Verifies NIST P-256 (secp256r1) signatures per [EIP-7212](https://eips.ethereum.org/EIPS/eip-7212). This curve backs WebAuthn passkeys, hardware security modules, and mobile secure enclaves, so contracts can verify signatures from those devices on chain. There is no Solidity interface. Call it with a raw `STATICCALL` and exactly 160 bytes of input. | Offset | Size | Field | | - | - | - | | 0 | 32 | Message hash | | 32 | 32 | Signature `r` | | 64 | 32 | Signature `s` | | 96 | 32 | Public key `x` | | 128 | 32 | Public key `y` | It returns `uint256(1)` as 32 bytes on success and empty data on failure. ```solidity theme={null} function verifyP256( bytes32 hash, bytes32 r, bytes32 s, bytes32 x, bytes32 y ) external view returns (bool) { bytes memory input = abi.encodePacked(hash, r, s, x, y); (bool ok, bytes memory result) = address(0x100).staticcall(input); return ok && result.length == 32 && abi.decode(result, (uint256)) == 1; } ``` ## Bech32 | | | | - | - | | Address | `0x0000000000000000000000000000000000000400` | | Interface | `Bech32I.sol` | Converts addresses between hex (EIP-55) and Bech32 (Cosmos) formats. Use it whenever a contract needs to hand a `lumera1...` address to a Cosmos-native module or display one to users. ```solidity Bech32I.sol theme={null} interface Bech32I { /// @dev Convert hex address to bech32 format. function hexToBech32( address addr, string memory prefix ) external returns (string memory bech32Address); /// @dev Convert bech32 address to hex format. function bech32ToHex( string memory bech32Address ) external returns (address addr); } ``` ```solidity theme={null} Bech32I constant BECH32 = Bech32I(0x0000000000000000000000000000000000000400); // Convert the caller's EVM address to a Lumera bech32 address string memory lumeraAddr = BECH32.hexToBech32(msg.sender, "lumera"); // Convert a Lumera bech32 address back to hex address evmAddr = BECH32.bech32ToHex("lumera1abc..."); ``` ## Staking | | | | - | - | | Address | `0x0000000000000000000000000000000000000800` | | Interface | `StakingI.sol` | Full EVM interface to the Cosmos SDK staking module. Contracts can create validators, manage delegations, and read staking state. Transaction methods. | Method | Description | | - | - | | `createValidator(Description, CommissionRates, uint256, address, string, uint256)` | Create a new validator | | `editValidator(Description, address, int256, int256)` | Modify validator description or commission | | `delegate(address, string, uint256)` | Delegate tokens to a validator | | `undelegate(address, string, uint256)` | Start undelegation, returns `completionTime` | | `redelegate(address, string, string, uint256)` | Move delegation between validators | | `cancelUnbondingDelegation(address, string, uint256, uint256)` | Cancel an in-progress undelegation | Query methods. | Method | Description | | - | - | | `delegation(address, string)` | Delegation shares and balance | | `unbondingDelegation(address, string)` | Unbonding delegation entries | | `validator(address)` | Validator info | | `validators(string, PageRequest)` | List validators by status | | `redelegation(address, string, string)` | A specific redelegation | | `redelegations(address, string, string, PageRequest)` | List redelegations with pagination | It emits the events `CreateValidator`, `Delegate`, `Unbond`, `Redelegate`, `CancelUnbondingDelegation`, and `EditValidator`. ```solidity theme={null} StakingI constant STAKING = StakingI(0x0000000000000000000000000000000000000800); // Delegate 100 LUME to a validator STAKING.delegate( msg.sender, "lumeravaloper1abc...", 100 * 1e18 // amount in alume (18 decimals) ); ``` ## Distribution | | | | - | - | | Address | `0x0000000000000000000000000000000000000801` | | Interface | `DistributionI.sol` | Handles staking reward distribution. Contracts can claim rewards, set withdrawal addresses, and read reward balances. Transaction methods. | Method | Description | | - | - | | `claimRewards(address, uint32)` | Claim rewards from up to N validators | | `setWithdrawAddress(address, string)` | Set a custom withdrawal address | | `withdrawDelegatorRewards(address, string)` | Withdraw rewards from one validator | | `withdrawValidatorCommission(string)` | Withdraw validator commission | | `fundCommunityPool(address, Coin[])` | Contribute to the community pool | | `depositValidatorRewardsPool(address, string, Coin[])` | Deposit to a validator rewards pool | Query methods. | Method | Description | | - | - | | `validatorDistributionInfo(string)` | Validator commission and self-bond rewards | | `validatorOutstandingRewards(string)` | Outstanding rewards for a validator | | `validatorCommission(string)` | Accumulated commission | | `validatorSlashes(string, uint64, uint64, PageRequest)` | Slash events in a height range | | `delegationRewards(address, string)` | Rewards for a specific delegation | | `delegationTotalRewards(address)` | Total rewards across all delegations | | `delegatorValidators(address)` | Validators the account delegates to | | `delegatorWithdrawAddress(address)` | Current withdrawal address | | `communityPool()` | Community pool balance | ```solidity theme={null} DistributionI constant DISTRIBUTION = DistributionI(0x0000000000000000000000000000000000000801); // Claim rewards from up to 10 validators DISTRIBUTION.claimRewards(msg.sender, 10); // Check rewards for a specific delegation DecCoin[] memory rewards = DISTRIBUTION.delegationRewards( msg.sender, "lumeravaloper1abc..." ); ``` ## ICS20 | | | | - | - | | Address | `0x0000000000000000000000000000000000000802` | | Interface | `ICS20I.sol` | Sends IBC fungible token transfers directly from Solidity. This is the primary way EVM contracts move tokens across chains. The single transaction method is `transfer(string, string, string, uint256, address, string, Height, uint64, string)` with these parameters. | Parameter | Meaning | | - | - | | `sourcePort`, `sourceChannel` | IBC routing, for example `"transfer"` and `"channel-0"` | | `denom`, `amount` | Token denomination and amount | | `sender`, `receiver` | Hex sender and bech32 receiver | | `timeoutHeight`, `timeoutTimestamp` | Timeout configuration, 0 disables | | `memo` | Optional IBC memo for forwarding or wasm hooks | Query methods are `denom(string)`, `denoms(PageRequest)`, and `denomHash(string)` for denomination traces. It emits an `IBCTransfer` event. ```solidity theme={null} ICS20I constant ICS20 = ICS20I(0x0000000000000000000000000000000000000802); // Send 50 LUME to Osmosis ICS20.transfer( "transfer", "channel-0", "ulume", 50_000_000, // 50 LUME in ulume msg.sender, "osmo1receiver...", Height(0, 0), // no height timeout uint64(block.timestamp + 600) * 1_000_000_000, // 10 min timeout "" // no memo ); ``` ## Bank | | | | - | - | | Address | `0x0000000000000000000000000000000000000804` | | Interface | `IBank.sol` | Read-only queries for native token balances and supply. It exposes no transaction methods. Token transfers use the normal EVM transfer path or the other precompiles. ```solidity IBank.sol theme={null} struct Balance { address contractAddress; // ERC20 contract address uint256 amount; } interface IBank { function balances(address account) external view returns (Balance[] memory); function totalSupply() external view returns (Balance[] memory); function supplyOf(address erc20Address) external view returns (uint256); } ``` ## Gov | | | | - | - | | Address | `0x0000000000000000000000000000000000000805` | | Interface | `IGov.sol` | Full EVM interface to the Cosmos SDK governance module. Contracts can submit proposals, deposit, vote, and read governance state. Transaction methods. | Method | Description | | - | - | | `submitProposal(address, bytes, Coin[])` | Submit a proposal (protoJSON body) | | `cancelProposal(address, uint64)` | Cancel an active proposal | | `deposit(address, uint64, Coin[])` | Deposit funds to a proposal | | `vote(address, uint64, VoteOption, string)` | Vote on a proposal | | `voteWeighted(address, uint64, WeightedVoteOption[], string)` | Weighted or split vote | The `VoteOption` enum values are `Unspecified(0)`, `Yes(1)`, `Abstain(2)`, `No(3)`, and `NoWithVeto(4)`. Query methods. | Method | Description | | - | - | | `getVote(uint64, address)` | A voter's vote on a proposal | | `getVotes(uint64, PageRequest)` | All votes for a proposal | | `getDeposit(uint64, address)` | Deposit info for a depositor | | `getDeposits(uint64, PageRequest)` | All deposits for a proposal | | `getTallyResult(uint64)` | Voting tally | | `getProposal(uint64)` | Proposal details | | `getProposals(uint32, address, address, PageRequest)` | List proposals with filters | | `getParams()` | Governance parameters | | `getConstitution()` | On-chain constitution text | ```solidity theme={null} IGov constant GOV = IGov(0x0000000000000000000000000000000000000805); // Vote Yes on proposal #1 GOV.vote(msg.sender, 1, VoteOption.Yes, "Voting from EVM"); ``` ## Slashing | | | | - | - | | Address | `0x0000000000000000000000000000000000000806` | | Interface | `ISlashing.sol` | Manages validator jail status. A validator can unjail itself from the EVM, and any contract can read signing info. ```solidity ISlashing.sol theme={null} struct SigningInfo { address validatorAddress; int64 startHeight; int64 indexOffset; int64 jailedUntil; bool tombstoned; int64 missedBlocksCounter; } interface ISlashing { function unjail(address validatorAddress) external returns (bool success); function getSigningInfo(address consAddress) external view returns (SigningInfo memory); function getSigningInfos(PageRequest calldata pagination) external view returns (SigningInfo[] memory, PageResponse memory); function getParams() external view returns (Params memory); } ``` ## Address summary | Precompile | Address | Type | | - | - | - | | P256 | `0x...0100` | Cryptographic | | Bech32 | `0x...0400` | Utility | | Staking | `0x...0800` | Module | | Distribution | `0x...0801` | Module | | ICS20 | `0x...0802` | IBC | | Vesting | `0x...0803` | Excluded | | Bank | `0x...0804` | Module | | Gov | `0x...0805` | Module | | Slashing | `0x...0806` | Module | The full upstream reference with every interface file lives in the [cosmos/evm precompiles source](https://github.com/cosmos/evm) and the [Lumera standard precompiles doc](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/precompiles/standard-precompiles.md). ## Next steps Drive Cascade and Sense actions from your contracts. Read SuperNode state and metrics on chain. Deploy and test a contract against Lumera testnet. # The SuperNode precompile Source: https://docs.lumera.io/smart-contracts/precompiles/supernode Read SuperNode registration state and metrics from Solidity contracts. The SuperNode precompile exposes the `x/supernode` module to the EVM at a single fixed address. Contracts can look up registered SuperNodes, list them with pagination, rank them for a block, and read their reported hardware metrics and module parameters. The EVM is live on testnet (`lumera-testnet-2`) only. Mainnet runs `v1.12.0` and gains the EVM with its upgrade. ## Address ```text theme={null} 0x0000000000000000000000000000000000000902 ``` Lumera custom precompiles start at `0x0900`. See the [precompiles overview](/smart-contracts/precompiles/overview) for the full address map. ## SuperNode states The `currentState` field in query results uses these values. | State | Value | Description | | - | - | - | | Active | 1 | Operational and processing actions | | Disabled | 2 | Deregistered by the owner | | Stopped | 3 | Temporarily stopped by the owner, can restart | | Penalized | 4 | Slashed due to misbehavior evidence | | Postponed | 5 | Suspended due to metrics non-compliance | | Storage Full | 6 | Disk usage above the configured threshold | ## Design notes * **Validator addresses stay strings.** A `lumeravaloper...` address has no meaningful 20-byte EVM representation, so the ABI uses `string` for validator and account addresses instead of a lossy `address` mapping. * **Metrics are integers.** The module stores hardware metrics as floats. Solidity has no floating-point type, so the precompile rounds them to `uint32` and `uint64` values. Percentages are whole numbers, so 45 means 45 percent. * **Latest state wins.** The module stores state history. Queries return the latest state entry and the block height where it changed. ## Solidity interface The interface below comes from [`ISupernode.sol`](https://github.com/LumeraProtocol/lumera/blob/master/precompiles/solidity/contracts/interfaces/ISupernode.sol) in the lumera repo. ```solidity ISupernode.sol theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; /// @notice Precompile at 0x0000000000000000000000000000000000000902 interface ISupernode { /// @notice On-chain information about a registered SuperNode. struct SuperNodeInfo { string validatorAddress; // Bech32 lumeravaloper... address string supernodeAccount; // Bech32 lumera... account address uint8 currentState; // see the state table above int64 stateHeight; // block height of last state transition string ipAddress; // current IP address string p2pPort; // P2P listening port string note; // operator-set note uint64 evidenceCount; // number of misbehavior evidence records } /// @notice Hardware metrics reported by a SuperNode. struct MetricsReport { uint32 versionMajor; uint32 versionMinor; uint32 versionPatch; uint32 cpuCoresTotal; uint64 cpuUsagePercent; uint64 memTotalGb; uint64 memUsagePercent; uint64 memFreeGb; uint64 diskTotalGb; uint64 diskUsagePercent; uint64 diskFreeGb; uint64 uptimeSeconds; uint32 peersCount; } /// @notice Get SuperNode info by validator address. function getSuperNode( string calldata validatorAddress ) external view returns (SuperNodeInfo memory info); /// @notice Get SuperNode info by its operator account address. function getSuperNodeByAccount( string calldata supernodeAddress ) external view returns (SuperNodeInfo memory info); /// @notice List all registered SuperNodes with pagination. /// @param limit Max results to return (capped at 100) function listSuperNodes( uint64 offset, uint64 limit ) external view returns (SuperNodeInfo[] memory nodes, uint64 total); /// @notice Get top SuperNodes for a block by XOR-distance ranking. /// @param state Filter by state (0 = all states) function getTopSuperNodesForBlock( int32 blockHeight, int32 limit, uint8 state ) external view returns (SuperNodeInfo[] memory nodes); /// @notice Get the latest metrics for a SuperNode. function getMetrics( string calldata validatorAddress ) external view returns ( MetricsReport memory metrics, uint64 reportCount, int64 lastReportHeight ); /// @notice Get the SuperNode module parameters. function getParams() external view returns ( uint256 minimumStake, uint64 reportingThreshold, uint64 slashingThreshold, string memory minSupernodeVersion, uint64 minCpuCores, uint64 minMemGb, uint64 minStorageGb ); } ``` The precompile also implements six transaction handlers (`registerSupernode`, `deregisterSupernode`, `startSupernode`, `stopSupernode`, `updateSupernode`, `reportMetrics`) and emits `SupernodeRegistered`, `SupernodeDeregistered`, and `SupernodeStateChanged` events. The shipped interface exposes queries only, because operators manage SuperNodes with Cosmos SDK transactions through `lumerad` and `sn-manager`, not through the EVM. See the [registration guide](/supernodes/register). ## Read SuperNode state from a contract ```solidity SupernodeDashboard.sol theme={null} // SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "./ISupernode.sol"; contract SupernodeDashboard { ISupernode constant SN = ISupernode(0x0000000000000000000000000000000000000902); /// @notice Total number of registered SuperNodes. function totalSupernodes() external view returns (uint64) { (, uint64 total) = SN.listSuperNodes(0, 1); return total; } /// @notice Top N active SuperNodes for a given block. function topForBlock(int32 blockHeight, int32 count) external view returns (ISupernode.SuperNodeInfo[] memory) { return SN.getTopSuperNodesForBlock(blockHeight, count, 1); // 1 = Active } /// @notice Whether a SuperNode has ever reported metrics. function isHealthy(string calldata validatorAddress) external view returns (bool hasReported, uint64 reportCount) { (, reportCount, ) = SN.getMetrics(validatorAddress); hasReported = reportCount > 0; } /// @notice Minimum stake required to register. function minimumStake() external view returns (uint256) { (uint256 stake, , , , , , ) = SN.getParams(); return stake; } } ``` `getParams` returns the minimum stake, metric reporting thresholds, and minimum hardware requirements. Governance can change these parameters, so query them on chain instead of hardcoding values. ## Call it from JavaScript ```typescript query-supernodes.ts theme={null} import { ethers } from "ethers"; const SN_ADDRESS = "0x0000000000000000000000000000000000000902"; const SN_ABI = [ "function getParams() view returns (uint256 minimumStake, uint64 reportingThreshold, uint64 slashingThreshold, string minSupernodeVersion, uint64 minCpuCores, uint64 minMemGb, uint64 minStorageGb)", "function listSuperNodes(uint64 offset, uint64 limit) view returns (tuple(string validatorAddress, string supernodeAccount, uint8 currentState, int64 stateHeight, string ipAddress, string p2pPort, string note, uint64 evidenceCount)[], uint64 total)", ]; // Point at your node's EVM JSON-RPC endpoint (port 8545) const provider = new ethers.JsonRpcProvider("http://localhost:8545"); const supernode = new ethers.Contract(SN_ADDRESS, SN_ABI, provider); const params = await supernode.getParams(); console.log(`Min stake: ${params.minimumStake} ulume`); console.log(`Min version: ${params.minSupernodeVersion}`); const [nodes, total] = await supernode.listSuperNodes(0n, 10n); console.log(`Total SuperNodes: ${total}`); for (const node of nodes) { console.log(`${node.validatorAddress} state=${node.currentState}`); } ``` ## Behavior notes * **Pagination.** `listSuperNodes` caps at 100 results per call. A larger `limit` is silently capped, so page with `offset`. * **Gas.** Calls meter gas from the underlying Cosmos gas usage converted to EVM gas units. Reads through `eth_call` are free. The Go implementation lives in [precompiles/supernode](https://github.com/LumeraProtocol/lumera/tree/master/precompiles/supernode), with a full reference in the [supernode precompile doc](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/precompiles/supernode-precompile.md). ## Next steps What SuperNodes do and how they earn fees. Request the actions that SuperNodes process. Call CosmWasm contracts from Solidity. # Compliance and Enforcement Source: https://docs.lumera.io/supernodes/enforcement The states a SuperNode can occupy, how storage claims are challenged, and what happens when a node fails. A SuperNode earns by serving actions and retaining data. This page covers what the Protocol checks, what it does when a node fails a check, and what an operator controls. Two enforcement systems apply to the same machine and are frequently confused. | System | Scope | Penalty | | - | - | - | | `x/slashing` | Your **validator's** consensus duties, including downtime and double signing | Slashed stake and jailing | | `x/audit` and `x/supernode` | Your **SuperNode's** service obligations, including storage retention | Exclusion from work and loss of payouts | Consensus faults cost stake. Service faults cost income. Because a SuperNode must stay bound to an active validator, a jailed or unbonded validator also removes its SuperNode from the ranked list. ## Node states | State | New storage assignments | Compute work | Everlight payouts | | - | - | - | - | | `ACTIVE` | Eligible | Eligible | Paid | | `STORAGE_FULL` | Excluded | Eligible | Paid for held data | | `POSTPONED` | Excluded | Excluded | Not eligible | `STORAGE_FULL` exists because a full disk is not misbehavior. A node that has filled its allocation still holds its Cascade data and can still serve compute. It keeps earning on what it holds and stays eligible for Sense and inference work. Only new storage stops. The transitions are strict, and the more restrictive state always wins. ```text theme={null} ACTIVE ........ storage full, nothing else failing ..... STORAGE_FULL ACTIVE ........ any other violation ................... POSTPONED STORAGE_FULL .. storage freed ......................... ACTIVE STORAGE_FULL .. another violation appears ............. POSTPONED ``` A node enters `STORAGE_FULL` when `disk_usage_percent` exceeds `max_storage_usage_percent` and nothing else is failing. Both are governable parameters. ## Epoch reports `x/audit` collects a report from each SuperNode every epoch. Reports carry the service metrics defined by LEP-4, including `cascade_kademlia_db_bytes`, the volume of Cascade data a node holds. These reports feed two systems. They drive rank, which determines which nodes receive actions, and they set Everlight payout weight. Reported volume is self-declared, so it passes through guardrails before it becomes payout weight. A smoothing window averages reports across periods, a growth cap limits how quickly a node's reported share can rise, and new nodes ramp to full weight gradually. Freshness is also required. A stale report makes a node ineligible for that distribution. ## Storage challenges Self-reported volume establishes how much a node claims to hold. Challenges establish whether it actually holds it. Challenges run on an epoch cycle over the SuperNode mesh on port 4445. Challenger, recipient, and observer roles are all selected deterministically from on-chain state, so no node chooses who audits it. The challenged node returns a hash computed over a byte range of a stored artifact, observers independently verify the response, and the outcome is accepted on observer quorum. Results are batched into the epoch health report rather than submitted as individual transactions. Repair already exists alongside this. It is gated by request, verify, and commit, and reconstructed content is validated against the expected hash before it is persisted, so a repair cannot silently write wrong data. ## The direction of enforcement LEP-6, Storage-Truth Enforcement and Ticket-Driven Self-Healing, is approved and changes how challenge evidence is used. Operators should understand it because it changes what gets you penalized. The runtime ships in SuperNode v2.6.0 and later, behind two gates. Locally, the `storage_challenge.lep6` and `self_healing` blocks must be enabled in `config.yml`; a missing block defaults to disabled. On chain, the `x/audit` parameter `storage_truth_enforcement_mode` must be above `UNSPECIFIED`, which otherwise disables every LEP-6 service. The modes are `UNSPECIFIED`, `SHADOW`, `SOFT`, and `FULL`. The coverage model changes. One third of active SuperNodes are challenged per epoch, deterministically selected, and each challenged node receives one compound challenge containing a recent-ticket and an old-ticket subchallenge. Testing both separately answers two different questions, namely whether a node ingests new assignments and whether it silently drops cold data later. Byte sampling changes. Instead of a fixed leading kilobyte, challenges sample four deterministic 256-byte ranges, which removes any benefit from retaining only the start of a file. Most importantly, scoring separates into three independent signals. | Score | Drives | Notes | | - | - | - | | Node suspicion | Audit enforcement against an operator | Decays over epochs. Escalates on repeated distinct failures | | Ticket deterioration | Repair scheduling for a piece of data | Not a penalty. A ticket can deteriorate without any single node being at fault | | Reporter reliability | Trust in what a node reports about others | Penalizes an operator whose reports diverge from network consensus | Separating node suspicion from ticket deterioration is the design point. Whether to penalize an operator and whether to repair data are different questions, and they no longer share a signal. Node suspicion maps to graduated action, from elevated challenge rate through watch and probation to postponement. Recovery requires both score decay and a run of clean passes. The reporter score matters for honest operators too. Reporting failures that rechecks overturn, or persistently reporting worse outcomes than the rest of the network sees, reduces the weight of your reports and can make you temporarily ineligible as a challenger. Track status in the [LEP repository](https://github.com/LumeraProtocol/lumera/tree/master/docs/leps). ## What an operator controls 1. **Stay bound to a bonded validator.** Under LEP-3 the combined validator self-delegation and SuperNode account delegation must meet `minimum_stake_for_sn`. Check it with `lumerad query supernode params`. 2. **Keep disk headroom.** `STORAGE_FULL` protects revenue on data you already hold, but only `ACTIVE` nodes receive new assignments that grow it. 3. **Keep reports fresh.** A missed or stale report forfeits a distribution regardless of how much you retain. 4. **Actually retain the data.** Challenges sample randomly across recent and old assignments. Partial retention is caught probabilistically and the evidence accumulates. 5. **Stay current.** Enforcement behavior arrives through governance-approved upgrades. Keep the node on current releases with `sn-manager`. 6. **Opt in to LEP-6.** A config carried over from v2.5.x has no `storage_challenge.lep6` or `self_healing` block, and a missing block defaults to disabled. Add both before the chain moves past `SHADOW`. Monitoring, upgrades, and troubleshooting. Payout eligibility and the STORAGE\_FULL state. What your node is storing and proving. Stake requirements and registration. # SuperNode node setup Source: https://docs.lumera.io/supernodes/node-setup Install sn-manager on a dedicated server, open the firewall, and initialize your SuperNode. This page prepares a dedicated server, installs `sn-manager`, and initializes the SuperNode. `sn-manager` is the SuperNode process manager. It downloads the `supernode` binary, keeps it updated, and supervises the running process. The walkthrough uses testnet values (`lumera-testnet-2`). For mainnet, use `lumera-mainnet-1` and mainnet endpoints. ## Step 1. Prepare the server SSH into your **SuperNode server** (not the validator) and install the required packages. ```bash theme={null} sudo apt update && sudo apt upgrade -y sudo apt install -y curl wget jq ufw lz4 tar golang-go ``` **Never run a SuperNode and validator on the same server.** They must be on separate machines. The setup script will refuse to run if it detects a validator service on the same host. ## Step 2. Configure the firewall Open the ports required by `sn-manager`. ```bash theme={null} sudo ufw allow 22/tcp comment "SSH" sudo ufw allow 4444/tcp comment "SuperNode gRPC" sudo ufw allow 4445/tcp comment "SuperNode P2P" sudo ufw allow 8002/tcp comment "SuperNode REST" sudo ufw --force enable sudo ufw status ``` You should see all four ports listed as ALLOW. ## Step 3. Choose a Lumera gRPC endpoint The SuperNode needs a gRPC endpoint to query the chain and broadcast its on-chain messages. You have two options. ### Option 1. Use a public testnet gRPC endpoint (recommended) The simplest and most common setup points `sn-manager` at a public testnet-2 gRPC endpoint. You do not need to touch your validator, open any ports on it, or edit `app.toml`. Pick one of these endpoints, or find more on the [networks page](/networks). * `grpc.testnet.lumera.io:443` (official) * `https://lumera-testnet-grpc.linknode.org` (AstroStake) * `lumera-testnet-grpc.stakerhouse.com:443` (StakerHouse) * `https://grpc-t.lumera.nodestake.org/` (Nodestake) Note the endpoint you pick. You will pass it as `--lumera-grpc` in Step 5. ### Option 2. Self-host your validator's gRPC (advanced) If you prefer to route SuperNode traffic through your own validator instead of a third-party endpoint, you can expose the validator's built-in gRPC port. This gives you sovereignty and lower latency, but it increases the validator's attack surface and requires firewall rules in two places. Only follow this option if you have a specific reason to avoid public endpoints. **Bind gRPC to all interfaces.** SSH into your **validator server** and run the following. ```bash theme={null} # Check current gRPC config grep -A 5 "^\[grpc\]" ~/.lumera/config/app.toml # Change localhost to 0.0.0.0 sed -i 's|address = "localhost:9090"|address = "0.0.0.0:9090"|' ~/.lumera/config/app.toml # Restart the validator sudo systemctl restart lumera ``` **Restrict access to the SuperNode IP.** Do not expose port 9090 publicly. ```bash theme={null} sudo ufw allow from to any port 9090 proto tcp comment "SuperNode gRPC" ``` Replace `` with your SuperNode server's public IP address. You must also open the port in your cloud provider's firewall (Azure NSG, AWS Security Group, GCP VPC firewall, DigitalOcean Cloud Firewall). ufw alone is not enough. **Verify the listener.** On the validator, run this check. ```bash theme={null} sudo ss -tlnp | grep 9090 ``` You should see `*:9090` or `0.0.0.0:9090`. A line containing `127.0.0.1:9090` means the `app.toml` edit did not apply. Re-check the file and restart `lumera` again. **Verify reachability from the SuperNode.** ```bash theme={null} curl -v telnet://:9090 --max-time 3 2>&1 | head -5 ``` You should see `Connected to port 9090`. A timeout usually means the cloud firewall is blocking. `ufw` is not the only layer. You will pass `:9090` as `--lumera-grpc` in Step 5. ## Step 4. Install sn-manager Download and install the latest `sn-manager` release. Installing it under your home directory lets it update itself. On **mainnet**, download the latest release: ```bash theme={null} curl -L https://github.com/LumeraProtocol/supernode/releases/latest/download/supernode-linux-amd64.tar.gz | tar -xz ``` On **testnet**, download the newest `-testnet` release instead. `latest` is always the newest mainnet release. ```bash theme={null} TAG=$(curl -s "https://api.github.com/repos/LumeraProtocol/supernode/releases?per_page=30" | grep -o '"tag_name": *"[^"]*-testnet"' | head -1 | cut -d'"' -f4) curl -L "https://github.com/LumeraProtocol/supernode/releases/download/$TAG/supernode-linux-amd64.tar.gz" | tar -xz ``` Then install it: ```bash theme={null} mkdir -p ~/.sn-manager/bin install -D -m 0755 sn-manager ~/.sn-manager/bin/sn-manager ``` Add `sn-manager` to your PATH. ```bash theme={null} echo 'export PATH="$HOME/.sn-manager/bin:$PATH"' >> ~/.bashrc source ~/.bashrc sn-manager version ``` ## Step 5. Initialize the SuperNode Set a passphrase for the keyring and initialize. ```bash theme={null} export SUPERNODE_PASSPHRASE="your-secure-passphrase" sn-manager init -y \ --auto-upgrade \ --keyring-backend file \ --keyring-passphrase-env SUPERNODE_PASSPHRASE \ --key-name supernode \ --supernode-addr 0.0.0.0 \ --supernode-port 4444 \ --lumera-grpc https://lumera-testnet-grpc.linknode.org \ --chain-id lumera-testnet-2 ``` Use whichever public testnet gRPC endpoint you chose in Step 3. If you picked Option 2 (self-host) instead, replace the URL with `:9090`. `--keyring-passphrase-env` takes the name of an environment variable, not the passphrase itself. The passphrase must be at least 8 characters. It protects the SuperNode wallet key stored in the file-based keyring. Write it down now. You will enter the exact same value in the systemd service file in Step 6. **Save the mnemonic that is displayed immediately.** It is the only backup of your SuperNode wallet key. If you lose it, you lose access to the SuperNode account and any delegated funds. The output includes the SuperNode account address, called `SN_ACCOUNT`, which starts with `lumera1`. Note this address. You will need it for funding, delegation, and registration. ### Recover an existing key If you have a mnemonic from a previous installation, add the `--recover` and `--mnemonic` flags. ```bash theme={null} sn-manager init -y \ --auto-upgrade \ --keyring-backend file \ --keyring-passphrase-env SUPERNODE_PASSPHRASE \ --key-name supernode \ --supernode-addr 0.0.0.0 \ --supernode-port 4444 \ --lumera-grpc https://lumera-testnet-grpc.linknode.org \ --chain-id lumera-testnet-2 \ --recover --mnemonic "your 24 word mnemonic phrase here" ``` `--lumera-grpc` must point to a **testnet** gRPC endpoint. Use one of the public endpoints from Step 3, or `:9090` if you self-hosted. ## Step 6. Create the systemd service ```bash theme={null} sudo tee /etc/systemd/system/sn-manager.service > /dev/null < The `SUPERNODE_PASSPHRASE` in this service file must match the passphrase from Step 5 exactly, character for character, with no typos or trailing whitespace. Any mismatch causes the SuperNode process to loop on `incorrect passphrase` at startup and fail to load the keyring. This is the single most common post-install failure, so double-check the value before starting the service. Secure the service file (it contains your passphrase) and enable it. ```bash theme={null} sudo chmod 600 /etc/systemd/system/sn-manager.service sudo systemctl daemon-reload sudo systemctl enable sn-manager ``` The systemd service file stores your keyring passphrase in plaintext. It is protected by `chmod 600` (root-only read), but be aware of this when managing your server. ## Step 7. Start the SuperNode ```bash theme={null} sudo systemctl start sn-manager ``` ### Check the service status ```bash theme={null} sudo systemctl status sn-manager ``` ### Watch the logs ```bash theme={null} sudo journalctl -fu sn-manager ``` ### Run a health check ```bash theme={null} sn-manager status sn-manager check ``` The health check may not pass until the SuperNode is registered on chain (next page). This is expected. ## Step 8. Install the lumerad CLI The registration flow on the next page signs a `staking delegate` transaction with the SuperNode key. That key lives in `~/.supernode/keys/` on **this host**, so the delegation must be signed on this same machine. You only need the `lumerad` **CLI binary** here, not a full chain node. Do **not** run `lumerad init` or `lumerad start` on this host. Doing so would spin up a second, unrelated chain node in `~/.lumera/` that has nothing to do with your SuperNode. You install the binary only to run `lumerad tx` and `lumerad keys` subcommands against the SuperNode keyring. ### Build lumerad from source Clone the Lumera repository, check out the release tag matching your network, and build the binary into `~/go/bin/`. ```bash theme={null} git clone https://github.com/LumeraProtocol/lumera.git ~/lumera-src cd ~/lumera-src git checkout v1.20.2 # testnet tag, use the current release tag if newer go build -o ~/go/bin/lumerad ./cmd/lumera ``` The build needs the Go version pinned in the repo's `go.mod` (currently Go 1.26.2). The Go from `apt` may be older. If the build fails on the Go version, install a newer Go from [https://go.dev/dl/](https://go.dev/dl/) first. ### Add lumerad to your PATH By default `~/go/bin` is not on your shell PATH, so `lumerad` returns `command not found` even after a successful build. Add it. ```bash theme={null} echo 'export PATH="$HOME/go/bin:$PATH"' >> ~/.bashrc source ~/.bashrc lumerad version ``` You should see a version string printed. Your SuperNode host is now ready to sign the delegation transaction in [Register your SuperNode](/supernodes/register). Every `lumerad` command you run on the SuperNode host **must** include `--keyring-backend file` and `--keyring-dir ~/.supernode/keys`. The SuperNode key lives at `~/.supernode/keys/` and uses the file keyring backend, and neither is a `lumerad` default. Transaction commands (`lumerad tx ...`) also need `--node ` to point the CLI at a CometBFT RPC for testnet-2, since the SuperNode host has no local chain node. Use a public testnet endpoint such as `https://lumera-testnet-rpc.polkachu.com/`, or `tcp://:26657` if you prefer to broadcast through your own validator (which requires opening port 26657). Verify the setup by printing the SuperNode key's address. It should match the `SN_ACCOUNT` you noted in Step 5. ```bash theme={null} lumerad keys show supernode -a --keyring-backend file --keyring-dir ~/.supernode/keys ``` You will be prompted for the keyring passphrase from Step 5. A successful output is a single `lumera1...` line. ## Alternative: run without sn-manager `sn-manager` installs the SuperNode and keeps it updated safely. If you would rather manage the `supernode` binary yourself, replace Steps 4 to 7 with the following. Updates are then yours to do by hand. Install the binary. On testnet, replace `latest/download` with `download/$TAG`, with `TAG` set as in Step 4. ```bash theme={null} sudo curl -L -o /usr/local/bin/supernode \ https://github.com/LumeraProtocol/supernode/releases/latest/download/supernode-linux-amd64 sudo chmod +x /usr/local/bin/supernode supernode version ``` Initialize it with the keyring and network settings from Step 5. Add `--recover` to restore an existing key from its mnemonic. ```bash theme={null} export SUPERNODE_PASSPHRASE="your-secure-passphrase" supernode init \ --keyring-backend file \ --keyring-passphrase-env SUPERNODE_PASSPHRASE \ --key-name supernode \ --supernode-addr 0.0.0.0 \ --supernode-port 4444 \ --lumera-grpc https://lumera-testnet-grpc.linknode.org \ --chain-id lumera-testnet-2 ``` Instead of `--keyring-passphrase-env`, you can pass `--keyring-passphrase` or `--keyring-passphrase-file`. Run it as a systemd service. ```bash theme={null} sudo tee /etc/systemd/system/supernode.service > /dev/null < ## Next steps With `sn-manager` running and `lumerad` installed, proceed to fund, delegate, and register your SuperNode on chain. Fund the SuperNode account, delegate stake, and register on chain. Day to day commands, monitoring, and troubleshooting. # SuperNode operations and troubleshooting Source: https://docs.lumera.io/supernodes/operations Day to day SuperNode operations, monitoring, upgrades, configuration reference, and troubleshooting. This page is the operating reference for a running SuperNode. It covers daily commands, on-chain lifecycle transactions, upgrades, the configuration file, monitoring, and troubleshooting. For first time installation and registration, start with [node setup](/supernodes/node-setup). ## Quick reference ```bash theme={null} # View logs sudo journalctl -fu sn-manager # Restart the SuperNode sudo systemctl restart sn-manager # Stop the SuperNode sudo systemctl stop sn-manager # Check service status sudo systemctl status sn-manager # Health check sn-manager status sn-manager check ``` ## SuperNode states The on-chain state of your SuperNode determines whether it is actively serving the network. | State | Meaning | Action | | - | - | - | | `ACTIVE` | Operating normally | None | | `DISABLED` | Deregistered | Can re-register | | `STOPPED` | Manually stopped via transaction | Start with the `start-supernode` tx | | `PENALIZED` | Penalized for violations | Cannot re-register | | `POSTPONED` | Non-compliant metrics | Recovers automatically when compliant | | `STORAGE_FULL` | Disk usage above the configured threshold | Free disk space to return to `ACTIVE` | `STORAGE_FULL` node stops receiving new Cascade storage assignments but stays eligible for Sense and Agent duties, and keeps earning Everlight payouts for data it already holds. See [Everlight for SuperNode operators](/cascade/everlight/supernode-operators). ## On-chain metrics (LEP-4) SuperNodes must self-report metrics about every 400 blocks. If metrics are not reported within the grace period of about 100 blocks, the SuperNode moves to the `POSTPONED` state. This reporting is handled automatically by `sn-manager`, so no manual action is needed. Reported metrics include the version, CPU, RAM, and disk usage, port status, uptime, and peer count. ## Lifecycle operations ### Check SuperNode status ```bash theme={null} # On-chain (run on validator host) lumerad query supernode get-supernode $(lumerad keys show validator --bech val -a) # Local health (run on SuperNode host) sn-manager status sn-manager check ``` ### Update the SuperNode endpoint If your SuperNode's IP changes, update it on chain. Run on your **validator host**. ```bash theme={null} VALOPER=$(lumerad keys show validator --bech val -a) lumerad tx supernode update-supernode \ "$VALOPER" "NEW_IP:4444" "endpoint update" "" \ --from validator \ --chain-id lumera-testnet-2 \ --gas auto --gas-adjustment 1.3 \ --fees 10000ulume -y ``` ### Stop the SuperNode To gracefully stop your SuperNode, submit the stop transaction first, then stop the service. ```bash theme={null} # On validator host, notify the chain lumerad tx supernode stop-supernode $(lumerad keys show validator --bech val -a) "maintenance" \ --from validator \ --chain-id lumera-testnet-2 \ --gas auto --gas-adjustment 1.3 \ --fees 10000ulume -y # On SuperNode host, stop the service sudo systemctl stop sn-manager ``` ### Restart the SuperNode Start the service first, then submit the on-chain start transaction. ```bash theme={null} # On SuperNode host sudo systemctl start sn-manager # On validator host lumerad tx supernode start-supernode $(lumerad keys show validator --bech val -a) \ --from validator \ --chain-id lumera-testnet-2 \ --gas auto --gas-adjustment 1.3 \ --fees 10000ulume -y ``` ### Deregister the SuperNode To permanently remove your SuperNode from the network, run this on the **validator host**. ```bash theme={null} lumerad tx supernode deregister-supernode $(lumerad keys show validator --bech val -a) \ --from validator \ --chain-id lumera-testnet-2 \ --gas auto --gas-adjustment 1.3 \ --fees 10000ulume -y ``` This moves the SuperNode to the `DISABLED` state. You can re-register later if needed. ### List all SuperNodes ```bash theme={null} lumerad query supernode list-supernodes ``` ## Upgrading sn-manager `sn-manager` includes a built-in version manager. ```bash theme={null} # List available versions sn-manager ls-remote # Download a specific version sn-manager get v2.6.1-testnet # Switch to the new version sn-manager use v2.6.1-testnet # Restart the service sudo systemctl restart sn-manager # Verify sn-manager version ``` If you initialized with `--auto-upgrade`, `sn-manager` upgrades itself and the `supernode` binary automatically. It checks every 10 minutes and applies updates within the same major version. On a testnet chain ID it tracks releases tagged `-testnet`. On mainnet it tracks stable tags. You can still switch versions manually at any time. ## Configuration reference All runtime parameters live in `~/.supernode/config.yml`. Path values that do not start with `/` resolve relative to the base directory. The block below is what `supernode init` writes on v2.6.x. Optional fields are omitted from it and listed in the tables that follow. ```yaml config.yml theme={null} supernode: key_name: "supernode" identity: "lumera1..." host: "0.0.0.0" port: 4444 keyring: backend: "file" dir: "keys" passphrase_plain: "" passphrase_env: "SUPERNODE_PASSPHRASE" passphrase_file: "" p2p: port: 4445 data_dir: "data/p2p" lumera: grpc_addr: "https://lumera-testnet-grpc.linknode.org" chain_id: "lumera-testnet-2" raptorq: files_dir: "raptorq_files" ``` Restart the SuperNode after any configuration change. Changes only apply on process start. ### supernode section | Field | Description | | - | - | | `key_name` | Keyring key used to sign transactions | | `identity` | The `lumera1...` address derived from the key. Written by `init`. Must match your registered `SN_ACCOUNT` | | `host` | Bind address for the gRPC service. `ip_address` is accepted as an alias for older configs | | `port` | gRPC service port, default `4444` | | `gateway_port` | Optional. HTTP gateway port for `SupernodeService`, default `8002` | | `evm_key_name` | Optional. Name of the `eth_secp256k1` key used for one-time legacy account migration. Remove it after migrating | ### keyring section | Field | Description | | - | - | | `backend` | `os`, `file`, or `test`. Use `file` on servers, never `test` in production | | `dir` | Keyring directory for the `file` backend, default `keys` | | `passphrase_plain` | Plain text passphrase. Testing only, readable by anyone who can read the file | | `passphrase_env` | Name of the environment variable holding the passphrase. Preferred on servers | | `passphrase_file` | Path to a passphrase file with `600` permissions | If `passphrase_plain` is set it takes precedence over the other two. Leave it empty in production. ### p2p section | Field | Description | | - | - | | `port` | P2P listen port. Keep the default `4445`, the network expects it | | `data_dir` | Where DHT records and peer metadata live, default `data/p2p` | | `bootstrap_nodes` | Optional. Explicit peers to dial on start. Leave unset to use the network defaults | ### lumera section | Field | Description | | - | - | | `grpc_addr` | The Lumera gRPC endpoint. A public endpoint or `:9090` when self-hosting | | `chain_id` | `lumera-testnet-2` or `lumera-mainnet-1`, must match the network | | `gas_adjustment` | Optional. Multiplier on simulated gas, default `1.3` | | `gas_adjustment_multiplier` | Optional. Per-retry multiplier after an out-of-gas error, default `1.3` | | `gas_adjustment_max_attempts` | Optional. Retries on out of gas, default `3`, capped at `10` | | `gas_padding` | Optional. Flat gas added to the estimate, default `50000` | | `gas_price` | Optional. `0.025` or `0.025ulume`, default `0.025` | | `fee_denom` | Optional. Fee coin denom, default `ulume` | ### raptorq section | Field | Description | | - | - | | `files_dir` | Where RaptorQ encoded symbols are stored, default `raptorq_files` | Cascade encode and decode are I/O heavy. If the server has a dedicated NVMe volume, point `files_dir` at an absolute path on it. ### log section | Field | Description | | - | - | | `level` | Log verbosity, for example `info` or `debug` | ### storage\_challenge section | Field | Description | | - | - | | `enabled` | Runs the storage challenge service | | `poll_interval_ms` | Optional. Dispatcher tick cadence | | `submit_evidence` | Optional. Submits challenge evidence on chain | | `lep6` | Optional. LEP-6 compound challenges. Requires v2.6.0 or later | The `lep6` block takes `enabled` (default `true` when the block is present), `max_concurrent_targets` (default `4`), `recipient_read_timeout`, and a nested `recheck` block with `enabled`, `lookback_epochs`, `max_per_tick`, `tick_interval_ms`, `max_failure_attempts_per_ticket`, and `failure_backoff_ttl_ms`. ### self\_healing section Requires v2.6.0 or later. A missing block defaults to disabled. | Field | Description | | - | - | | `enabled` | Runs the self-healing dispatcher and transport server | | `poll_interval_ms` | Optional. Dispatcher tick cadence, default `30000` | | `max_concurrent_reconstructs` | Optional. Reconstruct workers, default `2` | | `max_concurrent_verifications` | Optional. Verification workers, default `4` | | `max_concurrent_publishes` | Optional. Publish-to-DHT workers, default `2` | | `staging_dir` | Optional. Staging root, default `~/.supernode/heal-staging` | | `verifier_fetch_timeout_ms` | Optional. Caps one fetch from the healer, default `60000` | | `verifier_fetch_attempts` | Optional. Fetch attempts, default `3` | | `verifier_backoff_base_ms` | Optional. Backoff base between attempts, default `2000` | | `audit_query_timeout_ms` | Optional. Caps audit status and params calls, default `10000` | Both blocks are also gated on chain: while the `x/audit` parameter `storage_truth_enforcement_mode` is `UNSPECIFIED`, every LEP-6 service stays off regardless of local config. See [compliance and enforcement](/supernodes/enforcement). ## Monitoring The `supernode` process exposes a gRPC status service. `SupernodeService.GetStatus` returns the version, uptime, CPU, memory and storage usage, peer counts, registered services, and your rank. Wire it into your monitoring stack and alert on low peers, low disk, and unexpected restarts, the same signals you watch for a validator. The [SupernodeService gRPC reference](/api/grpc/supernode-service) documents the full response. Also watch the on-chain state from the validator host. ```bash theme={null} lumerad query supernode get-supernode ``` A healthy record shows `state: SUPERNODE_STATE_ACTIVE`. Any other state means your SuperNode is not being selected for actions and is not earning. ## Backup Always back up these files from your SuperNode host. ```bash theme={null} mkdir -p ~/sn-backup cp ~/.supernode/config.yml ~/sn-backup/ cp -r ~/.supernode/keys/ ~/sn-backup/keys/ ``` **Also save your mnemonic offline.** The key files and config can be recreated from the mnemonic, but the mnemonic cannot be recovered from the key files. ## Troubleshooting | Issue | Cause | Solution | | - | - | - | | SuperNode shows `POSTPONED` | Metrics reporting failed | Check logs with `journalctl -u sn-manager -f` and verify ports with `ufw status`. The service auto-recovers when compliant | | Registration fails with "insufficient stake" | Combined stake below minimum | Check both sources with `lumerad query staking delegation ` and your validator self-delegation. The combined total must meet `minimum_stake_for_sn` (check with `lumerad query supernode params`) | | `SigningInfo not found` after registration | Chain has not processed yet | Wait a few blocks and query again | | sn-manager will not start | Passphrase or config issue | Verify `SUPERNODE_PASSPHRASE` in the service file and inspect `~/.supernode/config.yml` | | Ports appear closed in metrics | Firewall or cloud security group | Verify `ufw status` and cloud provider firewall rules for 4444, 4445, and 8002, and that the registered endpoint matches your public IP and port 4444. Test externally with `nc -zv 4444` | | Eligibility fails although `SN_ACCOUNT` was funded | Zero validator self-delegation, `SN_ACCOUNT` funds not delegated, or a different `SN_ACCOUNT` registered than the one that delegated | Keep a non-zero self-delegation, delegate from `SN_ACCOUNT` ([register, Step 2](/supernodes/register#step-2-delegate-from-the-supernode-account-to-the-validator)), and compare `identity` in `~/.supernode/config.yml` with the registered account | | `sn-manager` auto-update fails | The install is not writable by the service user | Install under `~/.sn-manager/bin`, remove any `/usr/local/bin/sn-manager` copy, and check that `type -a sn-manager` lists only the home install | | Service stays down after `sn-manager stop` | `sn-manager stop` exits cleanly, so systemd does not restart it | Start it again with `sudo systemctl start sn-manager` | | Cannot connect to validator gRPC | gRPC bound to localhost | On the validator, set `address = "0.0.0.0:9090"` in `~/.lumera/config/app.toml` and run `ufw allow from to any port 9090` | ## Useful links * [Automated setup script](https://github.com/kaleababayneh/lumera-validator) * [SuperNode releases](https://github.com/LumeraProtocol/supernode/releases) * [sn-manager README](https://github.com/LumeraProtocol/supernode/blob/master/sn-manager/README.md) * [SuperNode operator guide](https://github.com/LumeraProtocol/lumera-networks/blob/master/docs/SUPERNODE_GUIDE.md) * [Lumera Portal (testnet)](https://portal.testnet.lumera.io/lumera-testnet-2) * [Discord](https://discord.com/invite/lumeraprotocol) ## Next steps How storage retention payouts work for your node. The status and metrics API your monitoring can poll. # Run a SuperNode on Lumera Source: https://docs.lumera.io/supernodes/overview Pair a dedicated SuperNode server with your validator to serve Cascade, Sense, and AI workloads for extra rewards. A SuperNode is a compute-intensive node that extends a Lumera Protocol validator with specialized services. Validators handle consensus and block production. SuperNodes handle the heavy lifting of Cascade storage, Sense verification, and AI inference. Every SuperNode is paired with a validator but must run on a separate server. The SuperNode keeps its own account, keys, and stake delegation, and talks to the Lumera chain over gRPC. A full [video walkthrough of the setup](https://www.youtube.com/watch?v=5BW3NH0qXbU) is available on YouTube. Existing operators. The `v1.20.0` EVM upgrade changes account key derivation. On networks that have upgraded (testnet today), the daemon can migrate your account once `evm_key_name` is set in `config.yml`, or you can migrate first yourself. See the [EVM upgrade page](/upgrades/evm-upgrade). ## Services and rewards **Cascade** is permanent, redundant file storage. Your SuperNode encodes files with RaptorQ erasure coding, splits them into redundant symbols, and distributes the symbols across the P2P network. The original file can be rebuilt even when a subset of symbols is lost. **Sense** protects content integrity. Participating SuperNodes independently evaluate submitted media for near duplicates and reach a consensus backed verdict. The protocol selects a set of top ranked SuperNodes for each action, based on stake and performance. If your SuperNode is selected and fulfills the action correctly, you receive a share of the action fee. This is proof of service income, separate from validator staking rewards. Higher stake and reliable uptime improve your rank and your earning opportunities. ## Architecture The SuperNode and its validator are two independent hosts that meet on chain. The validator produces blocks through its normal consensus P2P on port 26656. The SuperNode separately talks to the Lumera chain over gRPC to read state and broadcast its own messages. Supernode Architecture Supernode Architecture What ties the two hosts together is a pair of on-chain records. * **`register-supernode(VALOPER, SN_ENDPOINT, SN_ACCOUNT)`**. Signed by the validator operator key, this message binds a specific SuperNode endpoint and account to a specific validator. Only that validator's operator can register, update, or retire the SuperNode. * **Stake delegation from `SN_ACCOUNT` to `VALOPER`**. Under LEP-3, the combined total of validator self-delegation and this SN account delegation must meet the network's minimum stake. Together those two records form the entire SuperNode and validator relationship. SuperNodes also form a peer-to-peer mesh with other SuperNodes over a Kademlia DHT on port 4445, used for data replication and storage challenge proofs across the network. ## Key information | Item | Testnet | Mainnet | | - | - | - | | Chain ID | `lumera-testnet-2` | `lumera-mainnet-1` | | Binary | `sn-manager` ([releases](https://github.com/LumeraProtocol/supernode/releases)) | `sn-manager` ([releases](https://github.com/LumeraProtocol/supernode/releases)) | | Config directory | `~/.supernode` | `~/.supernode` | | Min combined stake | 10,000 LUME (self-delegation + SN account) | 25,000 LUME (self-delegation + SN account) | | SuperNode explorer | [portal.testnet.lumera.io/supernodes](https://portal.testnet.lumera.io/lumera-testnet-2/supernodes) | [portal.lumera.io/supernodes](https://portal.lumera.io/lumera-mainnet-1/supernodes) | | SuperNode API | [list\_super\_nodes](https://lcd.testnet.lumera.io/LumeraProtocol/lumera/supernode/v1/list_super_nodes) | [list\_super\_nodes](https://lcd.lumera.io/LumeraProtocol/lumera/supernode/v1/list_super_nodes) | | Explorer | [portal.testnet.lumera.io](https://portal.testnet.lumera.io/lumera-testnet-2) | [portal.lumera.io](https://portal.lumera.io) | ## Staking requirements (LEP-3) SuperNode registration requires a minimum combined stake from two sources, enforced on chain through the `minimum_stake_for_sn` parameter. | Network | Minimum stake | | - | - | | Mainnet (`lumera-mainnet-1`) | 25,000 LUME (25,000,000,000 `ulume`) | | Testnet (`lumera-testnet-2`) | 10,000 LUME (10,000,000,000 `ulume`) | The stake can come from two sources combined. Both count toward the minimum. 1. **Validator self-delegation**, which must be non-zero. 2. **Delegation from the SuperNode account (`SN_ACCOUNT`)** to your validator. For example, 8,000 LUME self-delegated plus 2,000 LUME from `SN_ACCOUNT` gives 10,000 LUME total, which meets the testnet requirement. You can verify the current on-chain requirement at any time. ```bash theme={null} # Via CLI lumerad query supernode params # Via REST API, mainnet curl -s https://lcd.lumera.io/LumeraProtocol/lumera/supernode/v1/params | jq '.params.minimum_stake_for_sn' # Via REST API, testnet curl -s https://lcd.testnet.lumera.io/LumeraProtocol/lumera/supernode/v1/params | jq '.params.minimum_stake_for_sn' ``` This parameter is governable and can change through a governance proposal. Always check the current value before registering. ## Hardware requirements SuperNodes are more resource-intensive than validators because of the compute workloads they handle. | Component | Minimum | Recommended | | - | - | - | | CPU | 8 cores (x86\_64) | 16+ cores | | RAM | 16 GB | 64 GB | | Storage | 1 TB NVMe SSD | 4 TB NVMe SSD | | Network | 1 Gbps | 5 Gbps | | OS | Ubuntu 22.04+ | Ubuntu 22.04+ | ARM processors are not supported. HDDs are not suitable. NVMe SSDs are required for the I/O demands of storage and compute tasks. ## Required ports ### SuperNode host | Port | Protocol | Purpose | | - | - | - | | 4444 | TCP | SuperNode gRPC API | | 4445 | TCP | P2P communication | | 8002 | TCP | REST gateway | ### Validator host | Port | Protocol | Purpose | | - | - | - | | 26656 | TCP | CometBFT P2P (public, standard validator requirement) | | 9090 | TCP | gRPC, only needed for the self-hosted endpoint option | ## Prerequisites Before setting up a SuperNode, you need the following. * A running Lumera validator in a bonded state. See the [testnet](/validators/overview) or [mainnet](/validators/overview) validator guide. * A **separate** server meeting the hardware requirements above. * Access to the validator operator key for registration transactions. * Testnet LUME tokens from the [testnet faucet](/faucet), the [AstroStake faucet](https://faucet.astrostake.xyz/lumera), or [Discord](https://discord.com/invite/lumeraprotocol). ## Automated setup If you prefer a one-command setup, use the tested setup script that handles all steps automatically. ```bash theme={null} wget -O setup-supernode.sh https://raw.githubusercontent.com/kaleababayneh/lumera-validator/main/setup-supernode.sh chmod +x setup-supernode.sh ./setup-supernode.sh ``` The script is interactive. It pauses for key creation and gRPC configuration, then prints the registration command to run on your validator host. See the [source and full guide](https://github.com/kaleababayneh/lumera-validator) for details. For a manual step-by-step walkthrough, continue with the pages below. ## Next steps Install sn-manager and initialize the SuperNode. Fund, delegate, and register on chain. Monitor, upgrade, and troubleshoot your node. # Register your SuperNode Source: https://docs.lumera.io/supernodes/register Fund the SuperNode account, delegate stake, and register the SuperNode on chain. With the SuperNode running, three transactions bind it to your validator. You fund the SuperNode account, delegate stake to your validator, then register the SuperNode with the validator operator key. This page walks through each transaction and the checks that confirm success. ## Prerequisites Before registering, make sure of the following. * Your SuperNode is running and the `sn-manager` service is active. See [node setup](/supernodes/node-setup). * You have the SuperNode account address `SN_ACCOUNT`, which starts with `lumera1`. * You have your validator operator address `VALOPER`, which starts with `lumeravaloper1`. * Your validator is bonded and synced. ## Step 1. Fund the SuperNode account The SuperNode account needs LUME tokens to delegate to your validator. There are several ways to fund it. ### Find your SuperNode account address If you did not note the `SN_ACCOUNT` address when you ran `sn-manager init`, you can retrieve it at any time from the **SuperNode server** in one of two ways. **Read it from the SuperNode config file.** No `lumerad` call needed. ```bash theme={null} cat ~/.supernode/config.yml | grep -i -A1 -E 'address|key' ``` You will see output similar to this. ```text theme={null} key_name: supernode identity: lumera1hl2tutczuuu6ngap57r79mzv9jy8n26xcu7tsx -- keyring: backend: file dir: keys passphrase_env: SUPERNODE_PASSPHRASE ``` The value next to `identity:` (starts with `lumera1...`) is your `SN_ACCOUNT`. **Or use the `lumerad` CLI** installed in [node setup, Step 8](/supernodes/node-setup#step-8-install-the-lumerad-cli). ```bash theme={null} # Print just the bech32 address of the supernode key lumerad keys show supernode -a --keyring-backend file --keyring-dir ~/.supernode/keys # Or list all keys in the SuperNode keyring lumerad keys list --keyring-backend file --keyring-dir ~/.supernode/keys ``` You will be prompted for the keyring passphrase. The address starts with `lumera1`. This is your `SN_ACCOUNT`, and it is what you pass to every command below. Every `lumerad` command you run on the SuperNode host **must** include **both** `--keyring-backend file` **and** `--keyring-dir ~/.supernode/keys`. The SuperNode stores its keyring at `~/.supernode/keys/`, not in `lumerad`'s default `~/.lumera/keyring-os/` location. ### Option A. Transfer from your validator wallet On your **validator host**, send tokens to the SuperNode account. ```bash theme={null} lumerad tx bank send validator ulume \ --chain-id lumera-testnet-2 \ --gas auto --gas-adjustment 1.3 \ --fees 10000ulume -y ``` Replace `` with your SuperNode's address and `` with the amount in ulume. The combined stake from your validator self-delegation and this SN account delegation must meet the on-chain minimum, currently 10,000 LUME on testnet-2. ### Option B. Testnet faucet or Discord Request testnet tokens for your `SN_ACCOUNT` address from the [testnet faucet](/faucet), the [AstroStake faucet](https://faucet.astrostake.xyz/lumera), or [Discord](https://discord.com/invite/lumeraprotocol). ### Option C. Foundation-funded SN account (vesting) The Lumera foundation can fund your SuperNode account. It does not delegate for you. It sends funds, and you delegate them in Step 2. 1. Give the foundation a **fresh** `SN_ACCOUNT` address, from a newly created key. That is either the key `sn-manager init` created in [node setup, Step 5](/supernodes/node-setup#step-5-initialize-the-supernode), or a new key you created elsewhere and [recovered on the SuperNode host](/supernodes/node-setup#recover-an-existing-key). 2. The foundation sends the funds as a vesting account, either temporarily locked (delayed vesting, which unlocks after a delay or over time) or permanently locked (permanent vesting, which never unlocks). 3. You delegate from `SN_ACCOUNT` to your validator in Step 2. Delegated vesting funds count toward the LEP-3 minimum. Keep your validator's own self-delegation above zero. The eligibility check requires some self-delegation even when the SN account's delegation covers the minimum. Funds sitting in `SN_ACCOUNT` without a delegation do not count. Treat the `SN_ACCOUNT` key as critical: if you lose it, you can no longer manage its delegation, even while the funds are still vesting. SuperNode registration uses a **LEP-3 dual-source stake** model. The minimum is the combined total of your validator self-delegation plus the SN account delegation. On testnet-2 this is currently **10,000 LUME**. Check the current value with `lumerad query supernode params`. This parameter is governable and can change. ## Step 2. Delegate from the SuperNode account to the validator This links the SuperNode's stake to your validator. The delegation from the SN account counts toward the combined minimum stake alongside your validator self-delegation. ### Find your validator operator address On your **validator host**, retrieve the `VALOPER` address (starts with `lumeravaloper1...`) from the validator key. ```bash theme={null} lumerad keys show validator --bech val -a ``` This prints the bech32 operator address, which is what you pass as `` in both Step 2 and Step 3. Note it down. You will need it twice. ### Run the delegation This must run on the **SuperNode host**, where the `supernode` key lives in `~/.supernode/keys/`. It requires `lumerad` on that host, which you installed in [node setup, Step 8](/supernodes/node-setup#step-8-install-the-lumerad-cli). ```bash theme={null} VALOPER="lumeravaloper1..." # paste the address from the command above lumerad tx staking delegate "$VALOPER" 10000000000ulume \ --from supernode \ --keyring-backend file \ --keyring-dir ~/.supernode/keys \ --chain-id lumera-testnet-2 \ --node https://lumera-testnet-rpc.polkachu.com/ \ --gas auto --gas-adjustment 1.3 \ --fees 10000ulume -y ``` You will be prompted for the SuperNode keyring passphrase. A successful broadcast prints `code: 0` and a `txhash`. The flags above are all mandatory on the SuperNode host and cannot be omitted. * `--keyring-backend file` and `--keyring-dir ~/.supernode/keys` tell `lumerad` where the SuperNode key actually lives, not in its default `~/.lumera/keyring-os/`. * `--node https://lumera-testnet-rpc.polkachu.com/` points the CLI at a public testnet-2 CometBFT RPC endpoint, because the SuperNode host has no local chain node to broadcast through. * `--from supernode` selects the SuperNode key inside that keyring as the signer. If you would rather broadcast through your own validator instead of a public endpoint, use `--node tcp://:26657` and open port `26657` on the validator from the SuperNode IP. Open it in **both** `ufw` and your cloud provider firewall, the same way you opened `9090` in [node setup, Step 3](/supernodes/node-setup#step-3-choose-a-lumera-grpc-endpoint). Adjust the amount as needed. `10000000000ulume` equals 10,000 LUME. If your validator already has substantial self-delegation, you may need less from the SN account. Only the combined total must meet the minimum. ### Verify the delegation Still on the **SuperNode host**, pass the same `--node` you used for the delegation. Otherwise `lumerad` tries its default `tcp://localhost:26657` and fails with a connection error. ```bash theme={null} lumerad query staking delegations \ --node https://lumera-testnet-rpc.polkachu.com/ ``` You should see a delegation entry pointing to your validator operator address. ## Step 3. Register the SuperNode on chain **This command must run on your validator host**, signed by the validator operator key. It cannot be run from the SuperNode server. SSH into your **validator server** and run the registration. ```bash theme={null} VALOPER=$(lumerad keys show validator --bech val -a) SN_ENDPOINT=":4444" SN_ACCOUNT="lumera1..." # SuperNode account from node setup lumerad tx supernode register-supernode \ "$VALOPER" \ "$SN_ENDPOINT" \ "$SN_ACCOUNT" \ --from validator \ --chain-id lumera-testnet-2 \ --gas auto --gas-adjustment 1.3 \ --fees 10000ulume -y ``` Replace `` with your SuperNode server's public IP and set `SN_ACCOUNT` to the address from [node setup, Step 5](/supernodes/node-setup#step-5-initialize-the-supernode). A successful response shows `code: 0`. ## Step 4. Verify registration ### Check on-chain status On your **validator host**, query the SuperNode record. ```bash theme={null} lumerad query supernode get-supernode "$VALOPER" ``` You should see `state: SUPERNODE_STATE_ACTIVE`. ### Check SuperNode health On your **SuperNode host**, run the health checks. ```bash theme={null} sn-manager status sn-manager check ``` ### Verify port connectivity From any external machine, test that the SuperNode ports are reachable. ```bash theme={null} nc -zv 4444 nc -zv 4445 nc -zv 8002 ``` ## Verification checklist Run these checks to confirm everything is working. Pay attention to which host each command belongs on. On the **validator host**, which has the local chain node and the `validator` key. ```bash theme={null} # SuperNode registered? (state should be SUPERNODE_STATE_ACTIVE) lumerad query supernode get-supernode $(lumerad keys show validator --bech val -a) ``` On the **SuperNode host**, where a remote `--node` is required since there is no local chain node. ```bash theme={null} # sn-manager running? sudo systemctl status sn-manager # Delegation in place? lumerad query staking delegations \ --node https://lumera-testnet-rpc.polkachu.com/ # Ports listening? ss -tlnp | grep -E '4444|4445|8002' # Health check passing? sn-manager check ``` If the on-chain query returns `SigningInfo not found` right after registration, wait a few blocks for the chain to pick it up. ## Next steps Monitor, upgrade, and troubleshoot your running SuperNode. Architecture, staking model, and requirements at a glance. # LUME Token Source: https://docs.lumera.io/token The native asset of Lumera, used for protocol services, network security, settlement, and governance. LUME is the native asset of the Lumera Protocol. It is used to pay for transactions and protocol services, secure consensus through staking, coordinate economic activity across the network, and govern protocol parameters. Because transaction fees, protocol-service payments, staking collateral, and governance participation are all denominated in LUME, the asset is embedded directly within the operation and security of the network rather than existing alongside it. ## Protocol Utility **Transactions and protocol services.** LUME pays gas on every transaction and settles protocol-service requests across the network. When an Action requires payment, the applicable fee is quoted in LUME, escrowed on-chain, and released according to the service's completion and settlement rules. **Economic coordination.** LUME provides a common settlement asset across applications, validators, SuperNodes, and protocol services. This allows participants to request work, provide services, post economic commitments, and settle activity through a shared protocol-native asset. **Security and staking.** Validators and delegators stake LUME to secure consensus. Validators operate infrastructure, propose and validate blocks, and earn security issuance and transaction fees. Delegators contribute stake without operating a validator and earn pro-rata rewards net of validator commission. **SuperNode alignment.** SuperNodes operate alongside eligible validators and provide the decentralized services applications consume. Validator eligibility and self-stake requirements tie protocol-service capacity to operators with real economic commitment to the network. **Governance.** Staked LUME governs protocol parameters, economic settings, software upgrades, community-pool expenditures, and the evolution of protocol services. See [Governance](/governance). ## Supply and Issuance LUME issuance is adaptive rather than fixed. The protocol recalculates the issuance rate each block to steer the proportion of bonded supply toward a target staking ratio. When the bonded ratio falls below the target, issuance increases incrementally to strengthen the incentive to stake and secure the network. When the bonded ratio exceeds the target, issuance decreases to limit unnecessary dilution and preserve additional liquid supply. Changes are bounded and gradual, preventing abrupt adjustments to network economics. ### Initial Protocol Parameters | Parameter | Value | | - | - | | Initial supply | 250,000,000 LUME | | Target bonded ratio | 67% | | Initial issuance rate | 13.75% | | Maximum issuance rate | 20% | | Minimum issuance rate | 5% | | Issuance adjustment rate | 7.5% | These parameters are governable and may change through on-chain governance. Current values can be queried directly from the network's mint and staking modules. ### Deflationary Mechanics Two mechanisms offset growth in circulating supply: * **Transaction-fee burn.** A portion of every transaction fee is permanently removed from supply. As transaction activity increases, the amount of LUME burned increases with it, creating a direct link between network usage and token scarcity. * **Slashing.** Validators and delegators may forfeit staked LUME for objectively provable consensus failures. Where slashed LUME is burned under the applicable protocol rules, the penalty permanently removes that amount from supply. A separate portion of security issuance is directed to the community pool for governance-approved protocol development and ecosystem initiatives. Exact economic flows are described in [Protocol Economics](/protocol-revenue). ## Staking and Network Security Validators and delegators stake LUME to secure consensus and participate in the network's economic security. Validators are expected to maintain availability, correctly sign blocks, and comply with consensus rules. Provable failures may result in jailing, loss of rewards, or slashing of bonded stake. | Condition | Potential consequence | | - | - | | Downtime | Temporary jailing, missed rewards, and any applicable downtime penalty | | Double signing | Significant slashing and removal from the active validator set | | Other provable consensus violations | Penalties determined by the applicable protocol and governance rules | Delegators share the economic consequences of the validator they select. Validator selection should therefore account for operational reliability, security practices, commission, governance participation, and long-term performance—not advertised yield alone. ## LUME and Network Usage LUME's utility expands with the services available through the protocol. Applications use it to pay for transactions and decentralized services; validators and delegators stake it to secure consensus; SuperNodes earn it for performing verifiable work; and holders use it to govern the economic parameters under which the network operates. As Lumera supports a broader ecosystem of decentralized intelligent services, LUME remains the common asset connecting service demand, operator compensation, network security, and governance through one economic framework. Understand issuance, transaction fees, service revenue, burns, and Everlight. Learn how LUME holders govern protocol parameters and network upgrades. Stake LUME, secure consensus, and earn network rewards. # v1.20.0 EVM upgrade Source: https://docs.lumera.io/upgrades/evm-upgrade The Aurora Convergence release added full EVM support and changed key derivation, and its mainnet date is to be announced. The v1.20.0 release, named Aurora Convergence, added full EVM compatibility to Lumera Protocol. It shipped on June 25, 2026 and is live on testnet (`lumera-testnet-2` runs the `v1.20.2` line). Mainnet still runs `v1.12.0`, so the mainnet upgrade is ahead. This page explains what changed and what users and operators need to do when their network upgrades. The mainnet upgrade has been rescheduled, and the new date is to be announced. It was planned for block `6,715,000`. Mainnet passed that height on `v1.12.0` without upgrading, and no upgrade plan is on chain yet. Check [network status and upgrades](/networks-status) for the live versions and upgrade plan. ## What changed * Cosmos EVM v0.6.0 runs Solidity contracts natively alongside Cosmos SDK modules and CosmWasm * New modules `x/vm`, `x/feemarket`, `x/precisebank`, `x/erc20`, and `x/evmigration` * Key derivation moved from `secp256k1` (coin type 118) to `eth_secp256k1` (coin type 60) * An 18 decimal EVM denom `alume` bridges to the native 6 decimal `ulume` * Ethereum JSON-RPC, WebSocket, indexer, tracing, and OpenRPC discovery are on by default * An EIP-1559 fee market and EVM precompiles for native modules, including Action and SuperNode Both execution environments run on the same chain and share the same state and the same security. Nothing on the Cosmos side is removed. IBC, validators, SuperNodes, governance, and bech32 addresses continue to work as before. See the [smart contracts overview](/smart-contracts/overview) for the developer view. ## Release timeline | Release | Date | Notes | | - | - | - | | `v1.20.0` | June 25, 2026 | Aurora Convergence, the EVM upgrade | | `v1.20.1` | July 6, 2026 | Fixes on top of v1.20.0 | | `v1.20.1-hotfix` | July 14, 2026 | Fixes on top of v1.20.1 | | `v1.20.2` | August 2026 | Upgrade applied on testnet. | ## Why account keys changed The chain switched key types at the upgrade. Ethereum tooling derives addresses differently from Cosmos, so supporting MetaMask and standard EVM wallets required moving from BIP44 coin type 118 to coin type 60. The practical effect is that the same mnemonic now derives a different Lumera address. Everything you hold stays at the old address until you migrate it. The `x/evmigration` module moves that state in one atomic, fee-free transaction carrying two proofs, an ADR-036 signature from the legacy key and an EIP-191 signature from the new key. After migration one account works in both wallets. Keplr shows the bech32 form starting `lumera1`. MetaMask shows the hex form starting `0x`. Both point at the same funds. Migration is irreversible and time-limited. The window closes three calendar months after your network upgrades. Delegations and unbonding left at a legacy address become permanently unrecoverable after that. See [EVM migration](/evm-migration) for the deadline and the steps. ## For node operators The release adds several EVM sections to `app.toml`, covering `[evm]`, `[evm.mempool]`, `[json-rpc]`, and rate limiting. Nodes on v1.20.0 and later fill in missing sections automatically on first start. Mainnet rejects the `debug`, `personal`, and `admin` JSON-RPC namespaces. Public RPC operators should review the [node config guide](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/user-guides/node-evm-config-guide.md) before the upgrade height, and the [tuning guide](https://github.com/LumeraProtocol/lumera/blob/master/docs/evm-integration/user-guides/tune-guide.md) for parameter sizing. Validators should stage the new binary in Cosmovisor ahead of the upgrade height. See [validator operations](/validators/mainnet/operations) for the procedure, including the `libwasmvm` step that catches operators out on every release. ## Next steps What to do with your account, and by when. What you can build now that the EVM is live. Stage the binary and prepare for the upgrade height. # Create a mainnet validator Source: https://docs.lumera.io/validators/mainnet/create-validator Create a secured wallet, fund it, and register your validator on lumera-mainnet-1. Your node must be running and fully synced before you register. Check it first. ```bash theme={null} lumerad status 2>&1 | jq '.sync_info | {latest_block_height, catching_up}' # catching_up must be false ``` If you have not completed [node setup](/validators/mainnet/node-setup), do that first. ## Step 1. Set the keyring backend On a headless server the default `os` keyring has no daemon to talk to. It will appear to hang. On mainnet use the `file` backend, which encrypts keys on disk behind a passphrase. ```bash theme={null} lumerad config set client keyring-backend file --skip-validate lumerad config set client chain-id lumera-mainnet-1 --skip-validate ``` Do not use the `test` backend on mainnet. It stores keys in plaintext. Anyone who reads the filesystem takes your funds. That includes a backup, a snapshot, or a compromised process. The `file` backend prompts for a passphrase on every signing operation. That friction is the point. For larger stake, keep the operator key off the validator. Generate and sign from a separate secured machine, or use a Ledger hardware wallet with `--ledger`. The operator key only signs occasional transactions. It does not have to live beside the node. ## Step 2. Create a wallet ```bash theme={null} lumerad keys add validator ``` You are prompted to set a keyring passphrase. Then you see your address and a 24-word mnemonic. ``` - address: lumera1abc... name: validator **Important** write this mnemonic phrase in a safe place. word1 word2 word3 ... word24 ``` Record the 24-word mnemonic offline, now. It is shown exactly once. Write it on paper or store it in a hardware backup. Never put it in a file on the server, a password manager sync, or a screenshot. Whoever holds this phrase controls your stake. You have two addresses. They are two encodings of the same key. ```bash theme={null} # Account address. Receives tokens. lumerad keys show validator -a # lumera1... # Operator address. Identifies the validator in staking commands. lumerad keys show validator --bech val -a # lumeravaloper1... ``` Staking queries use the `lumeravaloper1...` form. ## Step 3. Fund the wallet There is no mainnet faucet. Transfer LUME to the account address from Step 2 from an exchange or an existing wallet. Confirm it arrived. ```bash theme={null} lumerad query bank balances $(lumerad keys show validator -a) ``` The protocol minimum is 1 LUME, which is 1,000,000 ulume, plus fees. The active set is capped at 50 validators and ranked by total stake. To actually enter it you need enough stake to displace the smallest bonded validator. Check the current floor before you commit. ```bash theme={null} lumerad query staking validators --status BOND_STATUS_BONDED -o json \ | jq -r '[.validators[].tokens | tonumber] | sort | .[0:5]' ``` Send a small test transfer first and confirm it lands before you move your full stake. An address typo is unrecoverable. ## Step 4. Create the validator Cosmos SDK v0.50 takes a JSON file rather than individual CLI flags. Build it. ```bash theme={null} cat > ~/validator.json < `commission-max-rate` and `commission-max-change-rate` can never be changed after creation. Delegators evaluate both. Setting the max rate too low limits you forever. Setting it too high deters delegation. Decide deliberately before you submit. Older guides show `--amount` and `--pubkey` flags on `create-validator`. Those were removed in Cosmos SDK v0.50. They now fail. The JSON file above is the current form. Submit the transaction. ```bash theme={null} lumerad tx staking create-validator ~/validator.json \ --from=validator \ --chain-id=lumera-mainnet-1 \ --gas=auto \ --gas-adjustment=1.3 \ --fees=10000ulume \ -y ``` A successful broadcast returns `code: 0`. Any non-zero `code` means the transaction was rejected. The `raw_log` field explains why. The commonly cited `--fees=5000ulume` is often rejected with `insufficient fees`. Use `10000ulume` or higher. You can also let the node compute the fee. Replace `--fees` with `--gas-prices=0.025ulume`. ## Step 5. Verify ```bash theme={null} lumerad query staking validator $(lumerad keys show validator --bech val -a) ``` | Status | Meaning | | - | - | | `BOND_STATUS_BONDED` | In the active set and signing blocks | | `BOND_STATUS_UNBONDED` | Registered but not in the active set yet | | `BOND_STATUS_UNBONDING` | Leaving the active set | A new validator normally starts unbonded. The active set is capped at 50 and ranked by total stake. You stay unbonded until your delegation is large enough to displace the smallest bonded validator. Confirm the registered consensus key is the one your node actually signs with. If these differ, the validator will never sign a block no matter what its status says. ```bash theme={null} lumerad query staking validator $(lumerad keys show validator --bech val -a) -o json | jq -r .consensus_pubkey.key lumerad comet show-validator | jq -r .key # The two must be identical ``` Once bonded, check that you are signing. ```bash theme={null} lumerad query slashing signing-info $(lumerad comet show-validator) ``` `missed_blocks_counter` should stay near zero. `tombstoned` must be `false`. Watch the first hour closely. If `missed_blocks_counter` climbs steadily, your node is not signing. Sustained downtime leads to jailing and a slash of your stake. ## Verification checklist ```bash theme={null} # Node healthy and synced systemctl is-active lumera lumerad status 2>&1 | jq -r .sync_info.catching_up # false # Validator registered lumerad query staking validator $(lumerad keys show validator --bech val -a) \ | jq '{moniker: .description.moniker, status, tokens, jailed}' # Signing, once bonded lumerad query slashing signing-info $(lumerad comet show-validator) \ | jq '{missed_blocks_counter, tombstoned, jailed_until}' # Balance left for fees lumerad query bank balances $(lumerad keys show validator -a) ``` ## Back up everything Before you walk away, make sure you have all three stored offline and test-restored. 1. The 24-word mnemonic from Step 2 2. `~/.lumera/config/priv_validator_key.json` 3. `~/.lumera/config/node_key.json` Never restore `priv_validator_key.json` onto a second running machine. Two nodes signing with the same consensus key is double-signing. The network detects it, permanently tombstones the validator, and slashes the stake. When you migrate servers, confirm the old node is fully stopped and its key removed before the new one starts. ## Next steps Day-to-day commands, upgrades, monitoring, and troubleshooting. Provide Cascade and Sense services for extra rewards. Rehearse the full setup on testnet first. # Mainnet node setup Source: https://docs.lumera.io/validators/mainnet/node-setup Install dependencies and lumerad, then sync your Lumera mainnet node from a snapshot. This guide takes a fresh Ubuntu server to a fully synced mainnet node running under systemd. Follow the steps in order. When the node reports `catching_up: false`, continue to [create validator](/validators/mainnet/create-validator). ## Step 1. Install dependencies SSH into your server, update the system, and install the required packages. ```bash theme={null} sudo apt update && sudo apt upgrade -y sudo apt install build-essential jq curl git wget lz4 unzip gpg -y ``` ## Step 2. Install Go Use the version pinned in the chain's [`go.mod`](https://github.com/LumeraProtocol/lumera/blob/master/go.mod), currently Go 1.26.2. ```bash theme={null} GO_VERSION=1.26.2 curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" -o /tmp/go.tar.gz sudo rm -rf /usr/local/go sudo tar -C /usr/local -xzf /tmp/go.tar.gz echo 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin' >> ~/.profile source ~/.profile go version # go version go1.26.2 linux/amd64 ``` ## Step 3. Install lumerad Lumera publishes a prebuilt `linux/amd64` binary for every release, so there is nothing to compile. Run the version mainnet is on, currently v1.12.0. ```bash theme={null} # what mainnet is running right now curl -fsSL https://lumera-rpc.polkachu.com/abci_info | jq -r .result.response.version # 1.12.0 LUMERA_VERSION=v1.12.0 mkdir -p ~/lumera-install && cd ~/lumera-install curl -fsSL -O "https://github.com/LumeraProtocol/lumera/releases/download/${LUMERA_VERSION}/release_checksum" # the archive is named differently across releases, so take the name from the checksum ASSET=$(awk '{print $2}' release_checksum) curl -fsSL -O "https://github.com/LumeraProtocol/lumera/releases/download/${LUMERA_VERSION}/${ASSET}" ``` Verify the download before you install it. Never skip this on mainnet. ```bash theme={null} sha256sum -c release_checksum # Expected: : OK ``` If this prints `FAILED`, stop. Delete the files and download them again. Do not install an unverified binary on a machine that will hold a consensus key. Extract the archive and run the bundled installer. It copies `lumerad` into `/usr/local/bin`, installs `libwasmvm.x86_64.so` into `/usr/lib`, and refreshes the linker cache. ```bash theme={null} tar -xzf "$ASSET" sudo ./install.sh ``` Confirm the binary runs. The version must match what the chain reports above. ```bash theme={null} lumerad version # 1.12.0 ``` ## Step 4. Initialize the node Pick a moniker. This is the public name for your validator. Replace `my-validator` with your own name, then initialize. ```bash theme={null} MONIKER="my-validator" lumerad init "$MONIKER" --chain-id lumera-mainnet-1 ``` This creates `~/.lumera` with a `config/` directory. It generates your node key and consensus key, and writes default `config.toml`, `app.toml`, and `client.toml`. Back up your keys right away. ```bash theme={null} mkdir -p ~/lumera-key-backup chmod 700 ~/lumera-key-backup cp ~/.lumera/config/priv_validator_key.json ~/lumera-key-backup/ cp ~/.lumera/config/node_key.json ~/lumera-key-backup/ chmod 600 ~/lumera-key-backup/*.json chmod 600 ~/.lumera/config/priv_validator_key.json ~/.lumera/config/node_key.json ``` `priv_validator_key.json` is your validator identity. Anyone who holds it can double-sign on your behalf. That is punished by permanent tombstoning and a slash of your stake. Copy these files off the server to encrypted offline storage now. Verify you can restore them. Never run two nodes with the same key. ## Step 5. Download and verify genesis Download the mainnet genesis file and check its hash. ```bash theme={null} GENESIS_URL="https://raw.githubusercontent.com/LumeraProtocol/lumera-networks/master/mainnet/genesis.json" GENESIS_SHA256="a95f833cfead372d1b8f98b79412ee7d9d59a513152b2a75ba7fd4001aa22430" curl -fsSL -o ~/.lumera/config/genesis.json "$GENESIS_URL" echo "$GENESIS_SHA256 $HOME/.lumera/config/genesis.json" | sha256sum -c - # Expected: ...genesis.json: OK ``` Confirm the chain ID inside the file. ```bash theme={null} jq -r .chain_id ~/.lumera/config/genesis.json # Expected: lumera-mainnet-1 ``` ## Step 6. Configure the node Set the [seed nodes](https://github.com/cosmos/chain-registry/blob/master/lumera/chain.json) and the minimum gas price. ```bash theme={null} SEEDS="10ed1e176d874c8bb3c7c065685d2da6a4b86475@seed-lumera.ibs.team:16656,\ df949a46ae6529ae1e09b034b49716468d5cc7e9@seeds.stakerhouse.com:12756,\ ade4d8bc8cbe014af6ebdf3cb7b1e9ad36f412c0@seeds.polkachu.com:30756" sed -i "s|^seeds *=.*|seeds = \"$SEEDS\"|" "$HOME/.lumera/config/config.toml" sed -i "s|^minimum-gas-prices *=.*|minimum-gas-prices = \"0.025ulume\"|" "$HOME/.lumera/config/app.toml" ``` Load a maintained [address book](https://www.polkachu.com/addrbooks/lumera) so a new node has peers to dial if the seeds are unreachable. ```bash theme={null} curl -fsSL -o "$HOME/.lumera/config/addrbook.json" \ https://snapshots.polkachu.com/addrbook/lumera/addrbook.json ``` Enable pruning so disk usage stays under control. Skip this only if you specifically need an archive node. ```bash theme={null} sed -i \ -e 's|^pruning *=.*|pruning = "custom"|' \ -e 's|^pruning-keep-recent *=.*|pruning-keep-recent = "100"|' \ -e 's|^pruning-interval *=.*|pruning-interval = "10"|' \ "$HOME/.lumera/config/app.toml" sed -i 's|^indexer *=.*|indexer = "null"|' "$HOME/.lumera/config/config.toml" ``` | Strategy | Keeps | Disk usage | | - | - | - | | `default` | Last 362,880 states | Medium | | `nothing` | Everything (full archive) | Very high | | `everything` | Last 2 states only | Lowest | | `custom` | Whatever you set above | Configurable | `indexer = "null"` turns off the transaction indexer and saves disk. Leave it as `"kv"` if you need to query historical transactions by hash from this node. Turn on Prometheus metrics. ```bash theme={null} sed -i 's|^prometheus *= *false|prometheus = true|' "$HOME/.lumera/config/config.toml" ``` Bind the RPC to localhost. A validator only needs the P2P port reachable. ```bash theme={null} sed -i "/^\[rpc\]/,/^\[/{ s|^laddr *=.*|laddr = \"tcp://127.0.0.1:26657\"| }" "$HOME/.lumera/config/config.toml" # [rpc] should be 127.0.0.1:26657, [p2p] still 0.0.0.0:26656 grep -n "^laddr" "$HOME/.lumera/config/config.toml" ``` `lumerad` can listen on the ports below. Everything except 26656 should be firewalled or bound to `127.0.0.1`. | Port | Service | Config key | | - | - | - | | 26656 | P2P (must be reachable) | `config.toml` `[p2p].laddr` | | 26657 | CometBFT RPC | `config.toml` `[rpc].laddr` | | 1317 | REST API (LCD) | `app.toml` `[api].address` | | 9090 | gRPC | `app.toml` `[grpc].address` | | 8545 | EVM JSON-RPC (HTTP) | `app.toml` `[json-rpc].address` | | 8546 | EVM JSON-RPC (WebSocket) | `app.toml` `[json-rpc].ws-address` | Do not open 26657 (RPC), 1317 (LCD), 9090 (gRPC), or 8545 and 8546 (EVM JSON-RPC) on a validator. Exposing RPC publicly is a common cause of DDoS-induced downtime and jailing. Run a separate full node if you need to serve those endpoints. In a sentry setup, the validator should accept inbound connections only from its sentries. ## Step 7. Harden the server ### Firewall Allow SSH and the P2P port. Deny everything else inbound. ```bash theme={null} sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow ssh sudo ufw allow 26656/tcp comment "Lumera P2P" sudo ufw --force enable sudo ufw status ``` ### Dedicated user Run the node as its own user rather than `root`. ```bash theme={null} sudo useradd -m -s /bin/bash validator sudo usermod -aG sudo validator sudo passwd validator # Give it your SSH key sudo rsync --archive --chown=validator:validator ~/.ssh /home/validator/ ``` Confirm `ssh validator@YOUR_SERVER_IP` and `sudo -v` both work from a second terminal now. The next section disables root SSH, so if this account is not reachable you lose access to the server. Move the node directory from Step 4 across, so `~/.lumera` resolves to the same data before and after the switch. ```bash theme={null} sudo rsync -a ~/.lumera ~/lumera-key-backup /home/validator/ sudo chown -R validator:validator /home/validator/.lumera /home/validator/lumera-key-backup # Drop the originals only once the consensus key is confirmed identical sudo diff ~/.lumera/config/priv_validator_key.json \ /home/validator/.lumera/config/priv_validator_key.json \ && sudo rm -rf ~/.lumera ~/lumera-key-backup ``` Switch users. Steps 8 to 10 and the whole of [create validator](/validators/mainnet/create-validator) run as `validator`, and `$HOME` must already be `/home/validator` when you write the systemd unit in Step 9. ```bash theme={null} su - validator ``` Do the move before Step 8. Migrating afterwards means shifting the whole synced database instead of a few config files. ### SSH Harden SSH on a machine that holds a consensus key. Disable password logins and root SSH. Drop-ins in `/etc/ssh/sshd_config.d/` override the main file. Check for conflicting values first. ```bash theme={null} grep -rn -iE "permitrootlogin|passwordauthentication" /etc/ssh/sshd_config.d/ 2>/dev/null ``` ```bash theme={null} sudo sed -i \ -e 's|^#\?PermitRootLogin.*|PermitRootLogin no|' \ -e 's|^#\?PasswordAuthentication.*|PasswordAuthentication no|' \ -e 's|^#\?MaxAuthTries.*|MaxAuthTries 3|' \ /etc/ssh/sshd_config # Ubuntu 24.04+ activates SSH through a socket unit. Restart whichever one is in use. if systemctl is-active --quiet ssh.socket; then sudo systemctl restart ssh.socket else sudo systemctl restart ssh fi ``` Verify the settings actually took effect. ```bash theme={null} sudo sshd -T | grep -iE "^permitrootlogin|^passwordauthentication|^maxauthtries" # Expected: permitrootlogin no / passwordauthentication no / maxauthtries 3 ``` Keep your current session open and confirm you can log in from a second terminal before closing it. The new config applies to the next connection. Locking yourself out of a validator means downtime and eventual jailing. For sentry nodes, consensus key protection, and more hardening, see [validator security and monitoring](/validators/security). ## Step 8. Sync the chain Pick one of the two options below. The snapshot is fastest and is the recommended path. ### Option A. Snapshot (recommended) Copy the current URL from [Polkachu's Lumera snapshots](https://www.polkachu.com/tendermint_snapshots/lumera), currently about 11 GB. ```bash theme={null} # example, the height changes daily SNAP_URL="https://snapshots.polkachu.com/snapshots/lumera/lumera_6167759.tar.lz4" ``` Reset local state, then stream the snapshot straight into the data directory. ```bash theme={null} lumerad comet unsafe-reset-all --home "$HOME/.lumera" --keep-addr-book curl -o - -L "$SNAP_URL" | lz4 -c -d - | tar -x -C "$HOME/.lumera" ``` ### Option B. State sync State sync fetches a verified state snapshot over the P2P layer instead of downloading a database. It uses no third-party storage. It depends on the RPC provider having snapshots enabled. ```bash theme={null} SNAP_RPC="https://lumera-rpc.polkachu.com:443" LATEST_HEIGHT=$(curl -fsSL "$SNAP_RPC/block" | jq -r .result.block.header.height) TRUST_HEIGHT=$((LATEST_HEIGHT - 2000)) TRUST_HASH=$(curl -fsSL "$SNAP_RPC/block?height=$TRUST_HEIGHT" | jq -r .result.block_id.hash) echo "trust_height=$TRUST_HEIGHT trust_hash=$TRUST_HASH" sed -i "/^\[statesync\]/,/^\[/{ s|^enable *=.*|enable = true| s|^rpc_servers *=.*|rpc_servers = \"$SNAP_RPC,$SNAP_RPC\"| s|^trust_height *=.*|trust_height = $TRUST_HEIGHT| s|^trust_hash *=.*|trust_hash = \"$TRUST_HASH\"| s|^trust_period *=.*|trust_period = \"168h0m0s\"| }" "$HOME/.lumera/config/config.toml" lumerad comet unsafe-reset-all --home "$HOME/.lumera" --keep-addr-book ``` The edit is scoped to the `[statesync]` section. Confirm the result before you start. ```bash theme={null} sed -n '/\[statesync\]/,/^\[/p' "$HOME/.lumera/config/config.toml" | head -20 ``` If state sync stalls at "discovering snapshots" for more than a few minutes, no peer is serving snapshots. Fall back to Option A. Do not try to replay mainnet from block 1 with a single current binary. The chain has passed through many governance upgrades. Replaying historical blocks needs the binary that was live at each height. Use a snapshot or state sync. ## Step 9. Create the systemd service Create the service unit. `$USER` and `which lumerad` resolve on their own, so you can paste this as is. ```bash theme={null} sudo tee /etc/systemd/system/lumera.service > /dev/null < Before your first upgrade, switch this unit to run under Cosmovisor so binary swaps happen automatically at the upgrade height. See the chain upgrades section of [operations](/validators/mainnet/operations). ## Step 10. Start and verify Start the node and watch the logs. ```bash theme={null} sudo systemctl start lumera sudo journalctl -fu lumera ``` You should see blocks being committed. ``` INF committed state height=6124300 module=state ``` Press `Ctrl+C` to stop tailing. This does not stop the node. Wait 30 seconds to a minute, then check the node's sync status. ```bash theme={null} lumerad status 2>&1 | jq '.sync_info | {latest_block_height, catching_up}' ``` Wait until `catching_up` is `false`. Confirm you have peers. ```bash theme={null} curl -fsSL localhost:26657/net_info | jq -r '.result.n_peers' ``` ## Verification checklist Everything below should pass before you create your validator. ```bash theme={null} lumerad version # prints a version systemctl is-active lumera # active lumerad status 2>&1 | jq -r .sync_info.catching_up # false lumerad status 2>&1 | jq -r .node_info.network # lumera-mainnet-1 lumerad status 2>&1 | jq -r '.node_info.id' # node ID curl -fsSL localhost:26657/net_info | jq -r .result.n_peers # 1 or more sudo ufw status | grep 26656 # ALLOW ``` ## Next steps Once `catching_up` is `false`, register your validator. Create a secured wallet, fund it, and submit the validator transaction. Day-to-day commands, upgrades, monitoring, and troubleshooting. # Mainnet validator operations Source: https://docs.lumera.io/validators/mainnet/operations Day-to-day operations, chain upgrades, monitoring, and troubleshooting for a Lumera mainnet validator. Double-signing is unrecoverable. If two processes ever sign at the same height with your consensus key, the network permanently tombstones your validator and slashes your stake. Before you start any node, confirm the previous one is fully stopped. This is the single most expensive mistake a validator can make. ## Quick reference ```bash theme={null} sudo journalctl -fu lumera # tail logs sudo systemctl restart lumera # restart sudo systemctl stop lumera # stop sudo systemctl status lumera # service state lumerad status 2>&1 | jq '.sync_info | {latest_block_height, catching_up}' lumerad query bank balances $(lumerad keys show validator -a) ``` Two addresses come up in almost every command. Set them once per shell session. ```bash theme={null} VAL=$(lumerad keys show validator --bech val -a) # lumeravaloper1... ACC=$(lumerad keys show validator -a) # lumera1... ``` If you open a new terminal later, run those two lines again before using `$VAL` or `$ACC`. ## Staking operations Delegate more stake. ```bash theme={null} lumerad tx staking delegate "$VAL" 1000000ulume \ --from=validator --chain-id=lumera-mainnet-1 \ --gas=auto --gas-adjustment=1.3 --fees=10000ulume -y ``` Withdraw rewards and commission. ```bash theme={null} lumerad tx distribution withdraw-rewards "$VAL" \ --from=validator --commission \ --chain-id=lumera-mainnet-1 \ --gas=auto --gas-adjustment=1.3 --fees=10000ulume -y ``` Check what is claimable first. ```bash theme={null} lumerad query distribution commission "$VAL" lumerad query distribution rewards "$ACC" "$VAL" ``` Always leave enough LUME in the account to cover future fees. A validator that cannot pay for an `unjail` transaction stays jailed. Edit validator metadata. Omit any flag you do not want to change. ```bash theme={null} lumerad tx staking edit-validator \ --new-moniker="my-validator" \ --website="https://example.com" \ --identity="" \ --security-contact="security@example.com" \ --details="Professional Lumera validator" \ --from=validator --chain-id=lumera-mainnet-1 \ --gas=auto --gas-adjustment=1.3 --fees=10000ulume -y ``` To change only commission. ```bash theme={null} lumerad tx staking edit-validator --commission-rate="0.08" \ --from=validator --chain-id=lumera-mainnet-1 \ --gas=auto --gas-adjustment=1.3 --fees=10000ulume -y ``` Setting a Keybase `identity` makes your logo appear in explorers and delegation dashboards. It is worth doing before you solicit delegation. Commission changes are capped by `commission-max-change-rate` and allowed once per 24 hours. Unjail after downtime. ```bash theme={null} lumerad query slashing signing-info $(lumerad comet show-validator) lumerad tx slashing unjail \ --from=validator --chain-id=lumera-mainnet-1 \ --gas=auto --gas-adjustment=1.3 --fees=10000ulume -y ``` Check the `tombstoned` field before you unjail. Tombstoning results from double-signing and is permanent. The validator can never rejoin the active set, and unjail will fail. Fix the root cause before you unjail after downtime, or you will simply be jailed again. ## Governance Validators are expected to vote. Delegators who do not vote inherit your vote. Abstaining silently moves their stake too. ```bash theme={null} lumerad query gov proposals --proposal-status voting-period lumerad query gov proposal lumerad tx gov vote yes \ --from=validator --chain-id=lumera-mainnet-1 \ --gas=auto --gas-adjustment=1.3 --fees=10000ulume -y ``` Valid options are `yes`, `no`, `abstain`, and `no_with_veto`. ## Chain upgrades Upgrades pass through governance and halt the chain at a set height until validators run the new binary. Missing one means downtime, and eventually a jail and slash. Watch for pending upgrades. ```bash theme={null} lumerad query upgrade plan # shows the scheduled plan, if any lumerad query gov proposals --proposal-status voting-period ``` Note the plan `name` and `height`. Both matter below. ### Option A. Cosmovisor (strongly recommended on mainnet) Cosmovisor swaps the binary automatically at the upgrade height without causing any downtime. Install Cosmovisor. It is a prebuilt binary, so no Go is required. ```bash theme={null} COSMOVISOR_VERSION=v1.7.1 cd ~ && curl -fsSL -O "https://github.com/cosmos/cosmos-sdk/releases/download/cosmovisor%2F${COSMOVISOR_VERSION}/cosmovisor-${COSMOVISOR_VERSION}-linux-amd64.tar.gz" tar -xzf "cosmovisor-${COSMOVISOR_VERSION}-linux-amd64.tar.gz" cosmovisor sudo mv cosmovisor /usr/local/bin/ cosmovisor version ``` Initialize the layout with your current binary as the genesis version. ```bash theme={null} export DAEMON_NAME=lumerad export DAEMON_HOME=$HOME/.lumera cosmovisor init "$(which lumerad)" ls "$HOME/.lumera/cosmovisor/genesis/bin" # should contain lumerad ``` Point systemd at Cosmovisor. ```bash theme={null} sudo tee /etc/systemd/system/lumera.service > /dev/null < `DAEMON_ALLOW_DOWNLOAD_BINARIES=false` is deliberate. Letting a node download and run a binary named in an on-chain proposal is a serious security risk. Always stage binaries yourself and verify the checksum. Also note that each Lumera release ships an updated `libwasmvm.x86_64.so` and Cosmovisor swaps only `lumerad`. Copy the new shared library into `/usr/lib` as shown above. Skipping this is a common cause of a node failing to restart right after an upgrade, exactly when you can least afford it. ### Option B. Manual swap If you are not using Cosmovisor, you must be present when the chain halts at the upgrade height. ```bash theme={null} sudo systemctl stop lumera # Confirm it is really stopped before you continue systemctl is-active lumera # should print: inactive cd ~/lumera-install UPGRADE_NAME="v1.20.1" # from: lumerad query upgrade plan curl -fsSL -O "https://github.com/LumeraProtocol/lumera/releases/download/${UPGRADE_NAME}/release_checksum" ASSET=$(awk '{print $2}' release_checksum) curl -fsSL -O "https://github.com/LumeraProtocol/lumera/releases/download/${UPGRADE_NAME}/${ASSET}" sha256sum -c release_checksum tar -xzf "$ASSET" sudo ./install.sh lumerad version sudo systemctl start lumera sudo journalctl -fu lumera ``` Always stop the node before you replace the binary. Never start a second instance while the first is running. Two processes using the same `priv_validator_key.json` cause double-signing and permanent tombstoning. ## Monitoring Health checks. ```bash theme={null} # Sync state lumerad status 2>&1 | jq '.sync_info | {latest_block_height, catching_up}' # Peers curl -fsSL localhost:26657/net_info | jq -r .result.n_peers # Missed blocks lumerad query slashing signing-info $(lumerad comet show-validator) \ | jq '{missed_blocks_counter, tombstoned}' # Disk df -h "$HOME/.lumera" ``` Compare against the live chain. ```bash theme={null} echo "local : $(lumerad status 2>&1 | jq -r .sync_info.latest_block_height)" echo "chain : $(curl -fsSL https://lumera-rpc.polkachu.com/status | jq -r .result.sync_info.latest_block_height)" ``` What to alert on. | Metric | Alert threshold | | - | - | | Block signing rate | Below 95 percent in the signing window | | `catching_up` | `true` for more than 5 minutes | | Peer count | Below 5 | | Free disk | Below 20 percent | | `missed_blocks_counter` | Any sustained increase | | Block time lag | More than 30 seconds behind wall clock | | Process restarts | Any unexpected restart | | Pending upgrade plan | Any non-empty `query upgrade plan` | With `prometheus = true` in `config.toml`, metrics are served on `localhost:26660/metrics`. Scrape them with Prometheus and build alerts in Grafana. Purpose-built options include [tenderduty](https://github.com/blockpane/tenderduty) and [cosmos-validator-watcher](https://github.com/kilnfi/cosmos-validator-watcher). Both page you on missed blocks directly. For a node\_exporter, Prometheus, and alert-rule starting point, see [validator security and monitoring](/validators/security#metrics-and-alerts). On mainnet, treat alerting as mandatory rather than optional. At minimum, page a human on missed blocks. Catching downtime in minutes instead of hours is the difference between a warning and a slash. Routine maintenance. | Cadence | Task | | - | - | | Daily | Check signing rate, sync status, disk headroom | | Weekly | Apply OS security patches. Verify key backups are intact | | Monthly | Test restoring keys from backup. Review commission and metadata | | Per release | Stage the new binary in Cosmovisor before the upgrade height | ## Troubleshooting | Symptom | Cause and fix | | - | - | | `error while loading shared libraries: libwasmvm.x86_64.so` | The CosmWasm library is missing or stale. Copy `libwasmvm.x86_64.so` from the extracted release to `/usr/lib` and run `sudo ldconfig`. | | `upgrade plan "vX.Y.Z" is scheduled at height N but not registered in this binary` | Your binary is older than the chain. Install the latest release. This panic happens before the SDK reads `--unsafe-skip-upgrades`, so that flag will not get you past it. | | Keyring command hangs on a headless server | The `os` backend has no daemon. Use `lumerad config set client keyring-backend file --skip-validate`. | | `insufficient fees; got 5000ulume` | Raise the fee to `--fees=10000ulume`, or use `--gas-prices=0.025ulume`. | | `create-validator` rejects `--amount` or `--pubkey` | Cosmos SDK v0.50 requires a JSON file. See [create validator](/validators/mainnet/create-validator). | | Zero peers right after start | Normal for 2 to 3 minutes. If it persists, read the logs before changing your firewall. | | `Couldn't connect to any seeds` or `Error dialing seed ... connection refused` | The seed node is down. `connection refused` is the remote host rejecting you, so this is not your firewall. Stop the node, run `curl -fsSL -o ~/.lumera/config/addrbook.json https://snapshots.polkachu.com/addrbook/lumera/addrbook.json`, then start it. A running node rewrites `addrbook.json` from memory, so it has to be stopped first. | | Node stuck at `catching_up: true` | Usually disk I/O or too few peers. Check `df -h`, peer count, and consider re-syncing from a fresh snapshot. | | State sync stuck on "discovering snapshots" | No peer is serving snapshots. Fall back to the snapshot method in [node setup](/validators/mainnet/node-setup). | | Validator shows `BOND_STATUS_UNBONDED` | Expected until your stake is large enough to enter the capped active set. It is not an error. | | Jailed after a restart | Confirm the node resumed signing, then submit `tx slashing unjail`. Check `tombstoned` first. | ## Re-syncing from scratch If state becomes corrupted, wipe and re-sync. Your keys are untouched by this. ```bash theme={null} sudo systemctl stop lumera lumerad comet unsafe-reset-all --home "$HOME/.lumera" --keep-addr-book SNAP_URL=$(curl -fsSL https://www.polkachu.com/tendermint_snapshots/lumera \ | grep -oE 'https://snapshots\.polkachu\.com/snapshots/lumera/lumera_[0-9]+\.tar\.lz4' | head -1) curl -o - -L "$SNAP_URL" | lz4 -c -d - | tar -x -C "$HOME/.lumera" sudo systemctl start lumera ``` `unsafe-reset-all` deletes chain data and resets the private validator state file. It never touches `priv_validator_key.json` or `node_key.json`. Your validator identity survives. ## Migrating to a new server The dangerous part is the window where both machines could sign. 1. Set up the new server completely and sync it, without copying the consensus key. 2. Stop the old node with `sudo systemctl stop lumera` and confirm with `systemctl is-active lumera`. 3. Confirm the old node has stopped producing signatures in the explorer. 4. Only then copy `priv_validator_key.json` to the new server and start it. 5. Delete the key from the old server and disable its service with `sudo systemctl disable --now lumera`. Never shortcut step 3. Missing a few blocks during a clean cutover costs almost nothing. Double-signing during a sloppy one ends the validator permanently. ## Useful links * [Lumera releases](https://github.com/LumeraProtocol/lumera/releases) * [Network configurations](https://github.com/LumeraProtocol/lumera-networks) * [Validator security and monitoring](/validators/security) * [SuperNode operator guide](https://github.com/LumeraProtocol/lumera-networks/blob/master/docs/SUPERNODE_GUIDE.md) * [Mainnet explorer](https://portal.lumera.io) * [Mainnet snapshots](https://www.polkachu.com/tendermint_snapshots/lumera) * [Discord](https://discord.com/invite/lumeraprotocol) ## Next steps Provide Cascade and Sense services alongside your validator. Rehearse upgrades and operations on testnet. # Run a validator on Lumera Source: https://docs.lumera.io/validators/overview Run a validator on Lumera mainnet or testnet and help secure the network. Lumera Protocol is a Cosmos SDK Layer 1 blockchain secured by CometBFT consensus. Validators produce blocks, vote on governance proposals, and maintain the integrity of the network. In return they earn staking rewards and commission from their delegators. Validators who additionally register as SuperNodes can also provide Cascade storage services and earn fees from file storage operations. This guide covers the base validator setup. See the [SuperNode overview](/supernodes/overview) for upgrading to a SuperNode. ## Choose a network Run on testnet first. Testnet lets you rehearse the full setup with tokens that carry no value. Move to mainnet once you are confident in your process. | Network | Chain ID | Use it for | | - | - | - | | Mainnet | `lumera-mainnet-1` | Production validation with real LUME at stake | | Testnet | `lumera-testnet-2` | Practice runs and upgrade rehearsals | ## Network information | Item | Value | | - | - | | Chain ID | `lumera-mainnet-1` | | Denom | `ulume` (6 decimals, 1 LUME = 1,000,000 ulume) | | Min gas price | `0.025ulume` | | Seed node | `10ed1e176d874c8bb3c7c065685d2da6a4b86475@seed-lumera.ibs.team:16656` | | Genesis file | [mainnet/genesis.json](https://raw.githubusercontent.com/LumeraProtocol/lumera-networks/master/mainnet/genesis.json) | | Public RPC | `https://rpc.lumera.io` | | Public API (LCD) | `https://lcd.lumera.io` | | Public gRPC | `https://grpc.lumera.io` | | SN-API | `https://snapi.lumera.io` | | Snapshot | [Polkachu snapshots](https://www.polkachu.com/tendermint_snapshots/lumera) (about 11 GB, daily) | | Explorer | [portal.lumera.io](https://portal.lumera.io) | | Item | Value | | - | - | | Chain ID | `lumera-testnet-2` | | Denom | `ulume` (6 decimals, 1 LUME = 1,000,000 ulume) | | Min gas price | `0.025ulume` | | Peers | No seed node: download the address book from `https://snapshots.lumera.io/testnet/addrbook.json` | | Genesis file | [testnet-2/genesis.json](https://raw.githubusercontent.com/LumeraProtocol/lumera-networks/master/testnet-2/genesis.json) | | Public RPC | `https://lumera-testnet-rpc.polkachu.com` | | Public API (LCD) | `https://lumera-testnet-api.polkachu.com` | | Public gRPC | [polkachu.com/testnet\_public\_grpc](https://www.polkachu.com/testnet_public_grpc) | | SN-API | `https://snapi.testnet.lumera.io` | | Snapshot | [Polkachu testnet snapshots](https://www.polkachu.com/testnets/lumera/snapshots) (about 55 GB) | | Explorer | [portal.testnet.lumera.io](https://portal.testnet.lumera.io/lumera-testnet-2) | | Faucet | [faucet.astrostake.xyz/lumera](https://faucet.astrostake.xyz/lumera) | ## Binary version Run the version your chosen network is currently on. ```bash theme={null} curl -fsSL https://lumera-rpc.polkachu.com/abci_info | jq -r .result.response.version # 1.12.0 ``` ```bash theme={null} curl -fsSL https://lumera-testnet-rpc.polkachu.com/abci_info | jq -r .result.response.version ``` Releases ship a prebuilt `linux/amd64` binary together with the CosmWasm shared library and an installer, so you do not need Go or a compiler. ARM (including Apple Silicon) is not supported for production nodes. ## Hardware requirements These are the specifications published in the [lumera-networks repository](https://github.com/LumeraProtocol/lumera-networks). Testnet runs on lighter hardware than mainnet. | Component | Minimum | Recommended | | - | - | - | | CPU | 8 cores (x86\_64) | 16 cores | | RAM | 32 GB | 64 GB | | Storage | 2 TB NVMe SSD | 4 TB NVMe SSD | | Network | 1 Gbps dedicated | 5 Gbps+ | | OS | Ubuntu 22.04 LTS or later | Ubuntu 24.04 LTS or later | Use NVMe SSDs. Mechanical drives cannot sustain a live CometBFT node, and SATA SSDs stall under load. | Component | Minimum | Recommended | | - | - | - | | CPU | 4 vCPU (x86\_64) | 8+ cores | | RAM | 8 GB (+ 4 GB swap) | 16 to 32 GB | | Storage | 250 GB NVMe/SSD | 500 GB+ NVMe | | Network | 1 Gbps | 5 Gbps | | OS | Ubuntu 22.04 LTS or later | Ubuntu 24.04 LTS or later | Testnet chain data is already about 55 GB and grows continuously. Start at 250 GB and enable pruning. HDDs are not viable. SATA SSDs may stall under load. ## What you need Both networks need a VPS or bare-metal server running Ubuntu 22.04 LTS or later on x86\_64. The funding requirement differs. At least 1 LUME (1,000,000 ulume) for minimum self-delegation and fees. You will need a significant amount of LUME to enter the active set, which is capped at 50 validators. Testnet LUME from the [faucet](https://faucet.astrostake.xyz/lumera) or the [Lumera Discord](https://discord.com/invite/lumeraprotocol). ## Next steps The guide runs in three stages. Follow them in order for your chosen network. Install `lumerad`, sync the node, and run it under systemd. Create a secured wallet, fund it, and submit the validator transaction. Day-to-day commands, chain upgrades, monitoring, and troubleshooting. Install `lumerad`, sync the node, and run it under systemd. Create a wallet, fund it from the faucet, and submit the validator transaction. Day-to-day commands, chain upgrades, monitoring, and troubleshooting. # Validator security and monitoring Source: https://docs.lumera.io/validators/security Sentry nodes, consensus key protection, extra host hardening, and Prometheus alerts for a Lumera validator. Node setup already covers the firewall, a dedicated user, and SSH hardening ([mainnet](/validators/mainnet/node-setup#step-7-harden-the-server), [testnet](/validators/testnet/node-setup#step-7-harden-the-server)). This page adds the practices from the Lumera validator operations manual that go further: a sentry layer in front of the validator, protection for the consensus key, more host hardening, and metrics alerts. They apply to mainnet and testnet alike. On mainnet, treat them as the baseline. ## Sentry node architecture A sentry layer keeps the validator off the public internet. The validator talks only to a few full nodes you run, the sentries, and the sentries talk to the rest of the network. A DDoS attack then hits a replaceable full node instead of costing you missed blocks. Each sentry is an ordinary full node, set up like the validator (same `lumerad`, genesis, and sync), on its own server. Run at least two. If every sentry is down, the validator has no peers. Find each node's ID on that node. ```bash theme={null} lumerad comet show-node-id ``` On each **sentry**, set these in `~/.lumera/config/config.toml`. ```toml theme={null} pex = true persistent_peers = "@:26656" private_peer_ids = "" ``` On the **validator**, set these in `~/.lumera/config/config.toml`. ```toml theme={null} pex = false persistent_peers = "@:26656,@:26656" private_peer_ids = "" ``` `private_peer_ids` keeps the sentries from gossiping the validator's address. With `pex = false`, the validator dials only the peers listed. Then let only the sentries reach the validator's P2P port, and restart. ```bash theme={null} sudo ufw delete allow 26656/tcp sudo ufw allow from to any port 26656 proto tcp sudo ufw allow from to any port 26656 proto tcp sudo ufw status sudo systemctl restart lumera ``` ## Protect the consensus key The validator signs blocks with `~/.lumera/config/priv_validator_key.json`. The node's P2P identity is `~/.lumera/config/node_key.json`. * Back up both files offline, encrypted. Never keep the only copy on the server. * Never run the same `priv_validator_key.json` on two machines at once. Two signers double-sign, and double signing is slashed and tombstoned permanently. * When you restore or move a validator, stop the old node and make sure it can't start again before you start the new one. See [migrating to a new server](/validators/mainnet/operations#migrating-to-a-new-server). * Protect the operator key's keyring with a strong passphrase. The operations manual recommends keeping the consensus key in a hardware security module: a YubiHSM 2, or a Ledger Nano S or X with the Tendermint app. Follow the device's own documentation to generate the key and connect it to the node. ## Host hardening beyond node setup Install security updates automatically. ```bash theme={null} sudo apt install -y unattended-upgrades sudo dpkg-reconfigure -plow unattended-upgrades ``` Ban addresses that keep failing SSH logins. ```bash theme={null} sudo apt install -y fail2ban sudo systemctl enable --now fail2ban ``` Lock the root password. Root SSH is already off after node setup. ```bash theme={null} sudo passwd -l root ``` Before locking root, confirm from a second terminal that your own user can still log in and run `sudo -v`. ## Metrics and alerts The operations pages list what to alert on ([mainnet](/validators/mainnet/operations#monitoring), [testnet](/validators/testnet/operations#monitoring)). This is a minimal Prometheus setup to start from. Turn on the node's metrics: set `prometheus = true` in the `[instrumentation]` section of `~/.lumera/config/config.toml` and restart. Metrics are then served on `localhost:26660/metrics`. Add host metrics with node\_exporter. Check the [node\_exporter releases](https://github.com/prometheus/node_exporter/releases) for the current version. ```bash theme={null} wget https://github.com/prometheus/node_exporter/releases/download/v1.7.0/node_exporter-1.7.0.linux-amd64.tar.gz tar xvf node_exporter-1.7.0.linux-amd64.tar.gz sudo mv node_exporter-1.7.0.linux-amd64/node_exporter /usr/local/bin/ sudo tee /etc/systemd/system/node_exporter.service > /dev/null < Keep ports 26660 and 9100 closed to the internet. Scrape them from Prometheus on the same host or over a private network. Scrape both in Prometheus. ```yaml theme={null} # /etc/prometheus/prometheus.yml global: scrape_interval: 15s rule_files: - alerts.yml # the rules below; the path is relative to this file scrape_configs: - job_name: 'validator' static_configs: - targets: ['localhost:26660'] - job_name: 'node' static_configs: - targets: ['localhost:9100'] ``` Two alert rules to start with. ```yaml theme={null} # /etc/prometheus/alerts.yml groups: - name: validator rules: - alert: ValidatorDown expr: up == 0 for: 5m labels: severity: critical - alert: BlocksMissed expr: cometbft_consensus_validator_missed_blocks > 10 for: 10m labels: severity: warning ``` Metric names start with the `namespace` set in the `[instrumentation]` section, `cometbft` by default. List what your node exports with `curl -s localhost:26660/metrics | grep missed` and adjust the rule to match. ## Next steps Upgrades, monitoring, troubleshooting, and migration on mainnet. The same procedures on testnet. # Create a testnet validator Source: https://docs.lumera.io/validators/testnet/create-validator Create a wallet, fund it from the faucet, and register your validator on lumera-testnet-2. Your node must be running and fully synced before you register. Check it first. ```bash theme={null} lumerad status 2>&1 | jq '.sync_info | {latest_block_height, catching_up}' # catching_up must be false ``` If you have not completed [node setup](/validators/testnet/node-setup), do that first. ## Step 1. Set the keyring backend On a headless server the default `os` keyring has no daemon to talk to. It will appear to hang. Set the backend to `test` so keys are stored as files on disk. ```bash theme={null} lumerad config set client keyring-backend test --skip-validate lumerad config set client chain-id lumera-testnet-2 --skip-validate ``` The `test` backend stores keys unencrypted on disk. That is an acceptable trade-off for testnet. On mainnet, use `file`, which is passphrase protected, or a hardware signer. See the [mainnet guide](/validators/mainnet/create-validator). ## Step 2. Create a wallet ```bash theme={null} lumerad keys add validator ``` The output includes your address and a 24-word mnemonic. ``` - address: lumera1abc... name: validator **Important** write this mnemonic phrase in a safe place. word1 word2 word3 ... word24 ``` Record the 24-word mnemonic offline, now. It is shown exactly once. Write it on paper or store it in a hardware backup. Never put it in a file on the server, a password manager sync, or a screenshot. Whoever holds this phrase controls your stake. You have two addresses. They are two encodings of the same key. ```bash theme={null} # Account address. Receives tokens. lumerad keys show validator -a # lumera1... # Operator address. Identifies the validator in staking commands. lumerad keys show validator --bech val -a # lumeravaloper1... ``` Staking queries use the `lumeravaloper1...` form. ## Step 3. Fund the wallet Request testnet LUME. There are two options. 1. The faucet at [faucet.astrostake.xyz/lumera](https://faucet.astrostake.xyz/lumera) 2. The [Lumera Discord](https://discord.com/invite/lumeraprotocol) if the faucet is empty Confirm it arrived. ```bash theme={null} lumerad query bank balances $(lumerad keys show validator -a) ``` You need at least 1 LUME, which is 1,000,000 ulume, for the minimum self-delegation. Request a little extra for fees. ## Step 4. Create the validator Cosmos SDK v0.50 takes a JSON file rather than individual CLI flags. Build it. ```bash theme={null} cat > ~/validator.json < `commission-max-rate` and `commission-max-change-rate` can never be changed after creation. Delegators evaluate both. Setting the max rate too low limits you forever. Setting it too high deters delegation. Decide deliberately before you submit. Older guides show `--amount` and `--pubkey` flags on `create-validator`. Those were removed in Cosmos SDK v0.50. They now fail. The JSON file above is the current form. Submit the transaction. ```bash theme={null} lumerad tx staking create-validator ~/validator.json \ --from=validator \ --chain-id=lumera-testnet-2 \ --gas=auto \ --gas-adjustment=1.3 \ --fees=10000ulume \ -y ``` A successful broadcast returns `code: 0`. Any non-zero `code` means the transaction was rejected. The `raw_log` field explains why. The commonly cited `--fees=5000ulume` is often rejected with `insufficient fees`. Use `10000ulume` or higher. You can also let the node compute the fee. Replace `--fees` with `--gas-prices=0.025ulume`. ## Step 5. Verify ```bash theme={null} lumerad query staking validator $(lumerad keys show validator --bech val -a) ``` | Status | Meaning | | - | - | | `BOND_STATUS_BONDED` | In the active set and signing blocks | | `BOND_STATUS_UNBONDED` | Registered but not in the active set yet | | `BOND_STATUS_UNBONDING` | Leaving the active set | A new validator normally starts unbonded. The active set is capped and ranked by total stake. You stay unbonded until your delegation is large enough to displace the smallest bonded validator. Confirm the registered consensus key is the one your node actually signs with. If these differ, the validator will never sign a block no matter what its status says. ```bash theme={null} lumerad query staking validator $(lumerad keys show validator --bech val -a) -o json | jq -r .consensus_pubkey.key lumerad comet show-validator | jq -r .key # The two must be identical ``` Once bonded, check that you are signing. ```bash theme={null} lumerad query slashing signing-info $(lumerad comet show-validator) ``` `missed_blocks_counter` should stay near zero. `tombstoned` must be `false`. Watch the first hour closely. If `missed_blocks_counter` climbs steadily, your node is not signing. Sustained downtime leads to jailing and a slash of your stake. ## Verification checklist ```bash theme={null} # Node healthy and synced systemctl is-active lumera lumerad status 2>&1 | jq -r .sync_info.catching_up # false # Validator registered lumerad query staking validator $(lumerad keys show validator --bech val -a) \ | jq '{moniker: .description.moniker, status, tokens, jailed}' # Signing, once bonded lumerad query slashing signing-info $(lumerad comet show-validator) \ | jq '{missed_blocks_counter, tombstoned, jailed_until}' # Balance left for fees lumerad query bank balances $(lumerad keys show validator -a) ``` ## Back up everything Before you walk away, make sure you have all three stored offline and test-restored. 1. The 24-word mnemonic from Step 2 2. `~/.lumera/config/priv_validator_key.json` 3. `~/.lumera/config/node_key.json` Never restore `priv_validator_key.json` onto a second running machine. Two nodes signing with the same consensus key is double-signing. The network detects it, permanently tombstones the validator, and slashes the stake. When you migrate servers, confirm the old node is fully stopped and its key removed before the new one starts. ## Next steps Day-to-day commands, upgrades, monitoring, and troubleshooting. Provide Cascade and Sense services for extra rewards. Take your validator to production. # Testnet node setup Source: https://docs.lumera.io/validators/testnet/node-setup Install dependencies and lumerad, then sync your Lumera testnet node from a snapshot. This guide takes a fresh Ubuntu server to a fully synced testnet node running under systemd. Follow the steps in order. When the node reports `catching_up: false`, continue to [create validator](/validators/testnet/create-validator). ## Step 1. Install dependencies SSH into your server, update the system, and install the required packages. ```bash theme={null} sudo apt update && sudo apt upgrade -y sudo apt install build-essential jq curl git wget lz4 unzip gpg -y ``` ## Step 2. Install Go Use the version pinned in the chain's [`go.mod`](https://github.com/LumeraProtocol/lumera/blob/master/go.mod), currently Go 1.26.2. ```bash theme={null} GO_VERSION=1.26.2 curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz" -o /tmp/go.tar.gz sudo rm -rf /usr/local/go sudo tar -C /usr/local -xzf /tmp/go.tar.gz echo 'export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin' >> ~/.profile source ~/.profile go version # go version go1.26.2 linux/amd64 ``` ## Step 3. Install lumerad Lumera publishes a prebuilt `linux/amd64` binary for every release, so there is nothing to compile. Run the version testnet is on. Testnet is on the `v1.20.2` line; a binary older than that halts at the v1.20.2 upgrade height and cannot sync to the tip. ```bash theme={null} # what testnet is running right now curl -fsSL https://lumera-testnet-rpc.polkachu.com/abci_info | jq -r .result.response.version # use the tag matching the line above LUMERA_VERSION=v1.20.2 mkdir -p ~/lumera-install && cd ~/lumera-install curl -fsSL -O "https://github.com/LumeraProtocol/lumera/releases/download/${LUMERA_VERSION}/release_checksum" # the archive is named differently across releases, so take the name from the checksum ASSET=$(awk '{print $2}' release_checksum) curl -fsSL -O "https://github.com/LumeraProtocol/lumera/releases/download/${LUMERA_VERSION}/${ASSET}" ``` Verify the download before you install it. ```bash theme={null} sha256sum -c release_checksum # Expected: : OK ``` If this prints `FAILED`, stop. Delete the files and download them again. Do not install an unverified binary on a machine that will hold a consensus key. Extract the archive and run the bundled installer. It copies `lumerad` into `/usr/local/bin`, installs `libwasmvm.x86_64.so` into `/usr/lib`, and refreshes the linker cache. ```bash theme={null} tar -xzf "$ASSET" sudo ./install.sh ``` Confirm the binary runs. The version must match what the chain reports above. ```bash theme={null} lumerad version ``` ## Step 4. Initialize the node Pick a moniker. This is the public name for your validator. Replace `my-validator` with your own name, then initialize. ```bash theme={null} MONIKER="my-validator" lumerad init "$MONIKER" --chain-id lumera-testnet-2 ``` This creates `~/.lumera` with a `config/` directory. It generates your node key and consensus key, and writes default `config.toml`, `app.toml`, and `client.toml`. Back up your keys right away. ```bash theme={null} mkdir -p ~/lumera-key-backup chmod 700 ~/lumera-key-backup cp ~/.lumera/config/priv_validator_key.json ~/lumera-key-backup/ cp ~/.lumera/config/node_key.json ~/lumera-key-backup/ chmod 600 ~/lumera-key-backup/*.json chmod 600 ~/.lumera/config/priv_validator_key.json ~/.lumera/config/node_key.json ``` `priv_validator_key.json` is your validator identity. Anyone who holds it can double-sign on your behalf. That is punished by permanent tombstoning and a slash of your stake. Copy these files off the server to encrypted offline storage now. Verify you can restore them. Never run two nodes with the same key. ## Step 5. Download and verify genesis Download the testnet genesis file and check its hash. ```bash theme={null} GENESIS_URL="https://raw.githubusercontent.com/LumeraProtocol/lumera-networks/master/testnet-2/genesis.json" GENESIS_SHA256="8d30d41d2711b43d1f27f49816c68c6bafb799f0f08fb7e4a6676c4158150031" curl -fsSL -o ~/.lumera/config/genesis.json "$GENESIS_URL" echo "$GENESIS_SHA256 $HOME/.lumera/config/genesis.json" | sha256sum -c - # Expected: ...genesis.json: OK ``` Confirm the chain ID inside the file. ```bash theme={null} jq -r .chain_id ~/.lumera/config/genesis.json # Expected: lumera-testnet-2 ``` ## Step 6. Configure the node Set the minimum gas price. ```bash theme={null} sed -i "s|^minimum-gas-prices *=.*|minimum-gas-prices = \"0.025ulume\"|" "$HOME/.lumera/config/app.toml" ``` Testnet has no seed node. Load the address book that Lumera's snapshot service keeps current, so a new node has peers to dial: ```bash theme={null} curl -fsSL -o "$HOME/.lumera/config/addrbook.json" \ https://snapshots.lumera.io/testnet/addrbook.json ``` Polkachu publishes another one at `https://snapshots.polkachu.com/testnet-addrbook/lumera/addrbook.json`. Enable pruning so disk usage stays under control. Skip this only if you specifically need an archive node. ```bash theme={null} sed -i \ -e 's|^pruning *=.*|pruning = "custom"|' \ -e 's|^pruning-keep-recent *=.*|pruning-keep-recent = "100"|' \ -e 's|^pruning-interval *=.*|pruning-interval = "10"|' \ "$HOME/.lumera/config/app.toml" sed -i 's|^indexer *=.*|indexer = "null"|' "$HOME/.lumera/config/config.toml" ``` | Strategy | Keeps | Disk usage | | - | - | - | | `default` | Last 362,880 states | Medium | | `nothing` | Everything (full archive) | Very high | | `everything` | Last 2 states only | Lowest | | `custom` | Whatever you set above | Configurable | `indexer = "null"` turns off the transaction indexer and saves disk. Leave it as `"kv"` if you need to query historical transactions by hash from this node. Turn on Prometheus metrics. ```bash theme={null} sed -i 's|^prometheus *= *false|prometheus = true|' "$HOME/.lumera/config/config.toml" ``` Bind the RPC to localhost. A validator only needs the P2P port reachable. ```bash theme={null} sed -i "/^\[rpc\]/,/^\[/{ s|^laddr *=.*|laddr = \"tcp://127.0.0.1:26657\"| }" "$HOME/.lumera/config/config.toml" # [rpc] should be 127.0.0.1:26657, [p2p] still 0.0.0.0:26656 grep -n "^laddr" "$HOME/.lumera/config/config.toml" ``` `lumerad` can listen on the ports below. Everything except 26656 should be firewalled or bound to `127.0.0.1`. | Port | Service | Config key | | - | - | - | | 26656 | P2P (must be reachable) | `config.toml` `[p2p].laddr` | | 26657 | CometBFT RPC | `config.toml` `[rpc].laddr` | | 1317 | REST API (LCD) | `app.toml` `[api].address` | | 9090 | gRPC | `app.toml` `[grpc].address` | | 8545 | EVM JSON-RPC (HTTP) | `app.toml` `[json-rpc].address` | | 8546 | EVM JSON-RPC (WebSocket) | `app.toml` `[json-rpc].ws-address` | ## Step 7. Harden the server ### Firewall Allow SSH and the P2P port. Deny everything else inbound. ```bash theme={null} sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow ssh sudo ufw allow 26656/tcp comment "Lumera P2P" sudo ufw --force enable sudo ufw status ``` ### Dedicated user Run the node as its own user rather than `root`. ```bash theme={null} sudo useradd -m -s /bin/bash validator sudo usermod -aG sudo validator sudo passwd validator # Give it your SSH key sudo rsync --archive --chown=validator:validator ~/.ssh /home/validator/ ``` Confirm `ssh validator@YOUR_SERVER_IP` and `sudo -v` both work from a second terminal now. The next section disables root SSH, so if this account is not reachable you lose access to the server. Move the node directory from Step 4 across, so `~/.lumera` resolves to the same data before and after the switch. ```bash theme={null} sudo rsync -a ~/.lumera ~/lumera-key-backup /home/validator/ sudo chown -R validator:validator /home/validator/.lumera /home/validator/lumera-key-backup # Drop the originals only once the consensus key is confirmed identical sudo diff ~/.lumera/config/priv_validator_key.json \ /home/validator/.lumera/config/priv_validator_key.json \ && sudo rm -rf ~/.lumera ~/lumera-key-backup ``` Switch users. Steps 8 to 10 and the whole of [create validator](/validators/testnet/create-validator) run as `validator`, and `$HOME` must already be `/home/validator` when you write the systemd unit in Step 9. ```bash theme={null} su - validator ``` Do the move before Step 8. Migrating afterwards means shifting the whole synced database instead of a few config files. ### SSH Harden SSH on a machine that holds a consensus key. Disable password logins and root SSH. This locks `root` out of SSH, so do not run it until the user above exists and you have logged in as it. Drop-ins in `/etc/ssh/sshd_config.d/` override the main file. Check for conflicting values first. ```bash theme={null} grep -rn -iE "permitrootlogin|passwordauthentication" /etc/ssh/sshd_config.d/ 2>/dev/null ``` ```bash theme={null} sudo sed -i \ -e 's|^#\?PermitRootLogin.*|PermitRootLogin no|' \ -e 's|^#\?PasswordAuthentication.*|PasswordAuthentication no|' \ -e 's|^#\?MaxAuthTries.*|MaxAuthTries 3|' \ /etc/ssh/sshd_config # Ubuntu 24.04+ activates SSH through a socket unit. Restart whichever one is in use. if systemctl is-active --quiet ssh.socket; then sudo systemctl restart ssh.socket else sudo systemctl restart ssh fi ``` Verify the settings actually took effect. ```bash theme={null} sudo sshd -T | grep -iE "^permitrootlogin|^passwordauthentication|^maxauthtries" # Expected: permitrootlogin no / passwordauthentication no / maxauthtries 3 ``` Keep your current session open and confirm you can log in from a second terminal before closing it. The new config applies to the next connection. Locking yourself out of a validator means downtime and eventual jailing. Also keep RPC, LCD, gRPC, and the EVM JSON-RPC ports closed on a validator. Exposing RPC publicly is a common cause of DDoS-induced downtime and jailing. For sentry nodes, consensus key protection, and more hardening, see [validator security and monitoring](/validators/security). ## Step 8. Sync the chain Pick one of the two options below. The snapshot is fastest and is the recommended path. ### Option A. Snapshot (recommended) Copy the current URL from [Polkachu's Lumera testnet snapshots](https://www.polkachu.com/testnets/lumera/snapshots), currently about 55 GB. ```bash theme={null} # example, the height changes daily SNAP_URL="https://snapshots.polkachu.com/testnet-snapshots/lumera/lumera_5875101.tar.lz4" ``` Reset local state, then stream the snapshot straight into the data directory. ```bash theme={null} lumerad comet unsafe-reset-all --home "$HOME/.lumera" --keep-addr-book curl -o - -L "$SNAP_URL" | lz4 -c -d - | tar -x -C "$HOME/.lumera" ``` ### Option B. State sync State sync fetches a verified state snapshot over the P2P layer instead of downloading a database. It uses no third-party storage. It depends on the RPC provider having snapshots enabled. ```bash theme={null} SNAP_RPC="https://lumera-testnet-rpc.polkachu.com:443" LATEST_HEIGHT=$(curl -fsSL "$SNAP_RPC/block" | jq -r .result.block.header.height) TRUST_HEIGHT=$((LATEST_HEIGHT - 2000)) TRUST_HASH=$(curl -fsSL "$SNAP_RPC/block?height=$TRUST_HEIGHT" | jq -r .result.block_id.hash) echo "trust_height=$TRUST_HEIGHT trust_hash=$TRUST_HASH" sed -i "/^\[statesync\]/,/^\[/{ s|^enable *=.*|enable = true| s|^rpc_servers *=.*|rpc_servers = \"$SNAP_RPC,$SNAP_RPC\"| s|^trust_height *=.*|trust_height = $TRUST_HEIGHT| s|^trust_hash *=.*|trust_hash = \"$TRUST_HASH\"| s|^trust_period *=.*|trust_period = \"168h0m0s\"| }" "$HOME/.lumera/config/config.toml" lumerad comet unsafe-reset-all --home "$HOME/.lumera" --keep-addr-book ``` The edit is scoped to the `[statesync]` section. Confirm the result before you start. ```bash theme={null} sed -n '/\[statesync\]/,/^\[/p' "$HOME/.lumera/config/config.toml" | head -20 ``` If state sync stalls at "discovering snapshots" for more than a few minutes, no peer is serving snapshots. Fall back to Option A. Do not try to replay the chain from block 1 with a single current binary. The chain has passed through many governance upgrades. Replaying historical blocks needs the binary that was live at each height. Use a snapshot or state sync. ## Step 9. Create the systemd service Create the service unit. `$USER` and `which lumerad` resolve on their own, so you can paste this as is. ```bash theme={null} sudo tee /etc/systemd/system/lumera.service > /dev/null < Before your first upgrade, switch this unit to run under Cosmovisor so binary swaps happen automatically at the upgrade height. See the chain upgrades section of [operations](/validators/testnet/operations). ## Step 10. Start and verify Start the node and watch the logs. ```bash theme={null} sudo systemctl start lumera sudo journalctl -fu lumera ``` You should see blocks being committed. ``` INF committed state height=5830900 module=state ``` Press `Ctrl+C` to stop tailing. This does not stop the node. Wait a minute or two, then check the sync status. ```bash theme={null} lumerad status 2>&1 | jq '.sync_info | {latest_block_height, catching_up}' ``` Wait until `catching_up` is `false`. Compare your height against the live chain. ```bash theme={null} echo "local : $(lumerad status 2>&1 | jq -r .sync_info.latest_block_height)" echo "chain : $(curl -fsSL https://lumera-testnet-rpc.polkachu.com/status | jq -r .result.sync_info.latest_block_height)" ``` Confirm you have peers. ```bash theme={null} lumerad status 2>&1 | jq '.node_info.id' curl -fsSL localhost:26657/net_info | jq -r '.result.n_peers' ``` If zero peers persists, the address book is probably stale. Stop the node, download a current one (`https://snapshots.lumera.io/testnet/addrbook.json`), then start it again. If `config.toml` still lists `seeds` from an older setup, clear them: testnet has no seed node, and `config.toml` is only read at startup. ## Verification checklist Everything below should pass before you create your validator. ```bash theme={null} lumerad version # prints a version systemctl is-active lumera # active lumerad status 2>&1 | jq -r .sync_info.catching_up # false lumerad status 2>&1 | jq -r .node_info.network # lumera-testnet-2 curl -fsSL localhost:26657/net_info | jq -r .result.n_peers # 1 or more sudo ufw status | grep 26656 # ALLOW ``` ## Next steps Once `catching_up` is `false`, register your validator. Create a wallet, fund it from the faucet, and register. Day-to-day commands, upgrades, monitoring, and troubleshooting. # Testnet validator operations Source: https://docs.lumera.io/validators/testnet/operations Day-to-day operations, chain upgrades, monitoring, and troubleshooting for a Lumera testnet validator. Double-signing is unrecoverable. If two processes ever sign at the same height with your consensus key, the network permanently tombstones your validator and slashes your stake. Before you start any node, confirm the previous one is fully stopped. This is the single most expensive mistake a validator can make. ## Quick reference ```bash theme={null} sudo journalctl -fu lumera # tail logs sudo systemctl restart lumera # restart sudo systemctl stop lumera # stop sudo systemctl status lumera # service state lumerad status 2>&1 | jq '.sync_info | {latest_block_height, catching_up}' lumerad query bank balances $(lumerad keys show validator -a) ``` Two addresses come up in almost every command. Set them once per shell session. ```bash theme={null} VAL=$(lumerad keys show validator --bech val -a) # lumeravaloper1... ACC=$(lumerad keys show validator -a) # lumera1... ``` If you open a new terminal later, run those two lines again before using `$VAL` or `$ACC`. ## Staking operations Delegate more stake. ```bash theme={null} lumerad tx staking delegate "$VAL" 1000000ulume \ --from=validator --chain-id=lumera-testnet-2 \ --gas=auto --gas-adjustment=1.3 --fees=10000ulume -y ``` Withdraw rewards and commission. ```bash theme={null} lumerad tx distribution withdraw-rewards "$VAL" \ --from=validator --commission \ --chain-id=lumera-testnet-2 \ --gas=auto --gas-adjustment=1.3 --fees=10000ulume -y ``` Check what is claimable first. ```bash theme={null} lumerad query distribution commission "$VAL" lumerad query distribution rewards "$ACC" "$VAL" ``` Always leave enough LUME in the account to cover future fees. A validator that cannot pay for an `unjail` transaction stays jailed. Edit validator metadata. Omit any flag you do not want to change. ```bash theme={null} lumerad tx staking edit-validator \ --new-moniker="my-validator" \ --website="https://example.com" \ --identity="" \ --security-contact="security@example.com" \ --details="Professional Lumera validator" \ --from=validator --chain-id=lumera-testnet-2 \ --gas=auto --gas-adjustment=1.3 --fees=10000ulume -y ``` To change only commission. ```bash theme={null} lumerad tx staking edit-validator --commission-rate="0.08" \ --from=validator --chain-id=lumera-testnet-2 \ --gas=auto --gas-adjustment=1.3 --fees=10000ulume -y ``` Setting a Keybase `identity` makes your logo appear in explorers and delegation dashboards. Commission changes are capped by `commission-max-change-rate` and allowed once per 24 hours. Unjail after downtime. ```bash theme={null} lumerad query slashing signing-info $(lumerad comet show-validator) lumerad tx slashing unjail \ --from=validator --chain-id=lumera-testnet-2 \ --gas=auto --gas-adjustment=1.3 --fees=10000ulume -y ``` Check the `tombstoned` field before you unjail. Tombstoning results from double-signing and is permanent. The validator can never rejoin the active set, and unjail will fail. Fix the root cause before you unjail after downtime, or you will simply be jailed again. ## Governance Validators are expected to vote. Delegators who do not vote inherit your vote. Abstaining silently moves their stake too. ```bash theme={null} lumerad query gov proposals --proposal-status voting-period lumerad query gov proposal lumerad tx gov vote yes \ --from=validator --chain-id=lumera-testnet-2 \ --gas=auto --gas-adjustment=1.3 --fees=10000ulume -y ``` Valid options are `yes`, `no`, `abstain`, and `no_with_veto`. ## Chain upgrades Upgrades pass through governance and halt the chain at a set height until validators run the new binary. Missing one means downtime, and eventually a jail and slash. Testnet upgrades come more often than mainnet ones, which makes this the perfect place to practice the flow. Watch for pending upgrades. ```bash theme={null} lumerad query upgrade plan # shows the scheduled plan, if any lumerad query gov proposals --proposal-status voting-period ``` Note the plan `name` and `height`. Both matter below. ### Option A. Cosmovisor (recommended) Cosmovisor swaps the binary automatically at the upgrade height without causing any downtime. Install Cosmovisor. It is a prebuilt binary, so no Go is required. ```bash theme={null} COSMOVISOR_VERSION=v1.7.1 cd ~ && curl -fsSL -O "https://github.com/cosmos/cosmos-sdk/releases/download/cosmovisor%2F${COSMOVISOR_VERSION}/cosmovisor-${COSMOVISOR_VERSION}-linux-amd64.tar.gz" tar -xzf "cosmovisor-${COSMOVISOR_VERSION}-linux-amd64.tar.gz" cosmovisor sudo mv cosmovisor /usr/local/bin/ cosmovisor version ``` Initialize the layout with your current binary as the genesis version. ```bash theme={null} export DAEMON_NAME=lumerad export DAEMON_HOME=$HOME/.lumera cosmovisor init "$(which lumerad)" ls "$HOME/.lumera/cosmovisor/genesis/bin" # should contain lumerad ``` Point systemd at Cosmovisor. ```bash theme={null} sudo tee /etc/systemd/system/lumera.service > /dev/null < `DAEMON_ALLOW_DOWNLOAD_BINARIES=false` is deliberate. Letting a node download and run a binary named in an on-chain proposal is a serious security risk. Always stage binaries yourself and verify the checksum. Also note that each Lumera release ships an updated `libwasmvm.x86_64.so` and Cosmovisor swaps only `lumerad`. Copy the new shared library into `/usr/lib` as shown above. Skipping this is a common cause of a node failing to restart right after an upgrade. ### Option B. Manual swap If you are not using Cosmovisor, you must be present when the chain halts at the upgrade height. ```bash theme={null} sudo systemctl stop lumera # Confirm it is really stopped before you continue systemctl is-active lumera # should print: inactive cd ~/lumera-install UPGRADE_NAME="v1.20.1" # from: lumerad query upgrade plan curl -fsSL -O "https://github.com/LumeraProtocol/lumera/releases/download/${UPGRADE_NAME}/release_checksum" ASSET=$(awk '{print $2}' release_checksum) curl -fsSL -O "https://github.com/LumeraProtocol/lumera/releases/download/${UPGRADE_NAME}/${ASSET}" sha256sum -c release_checksum tar -xzf "$ASSET" sudo ./install.sh lumerad version sudo systemctl start lumera sudo journalctl -fu lumera ``` Always stop the node before you replace the binary. Never start a second instance while the first is running. Two processes using the same `priv_validator_key.json` cause double-signing and permanent tombstoning. ## Monitoring Health checks. ```bash theme={null} # Sync state lumerad status 2>&1 | jq '.sync_info | {latest_block_height, catching_up}' # Peers curl -fsSL localhost:26657/net_info | jq -r .result.n_peers # Missed blocks lumerad query slashing signing-info $(lumerad comet show-validator) \ | jq '{missed_blocks_counter, tombstoned}' # Disk df -h "$HOME/.lumera" ``` Compare against the live chain. ```bash theme={null} echo "local : $(lumerad status 2>&1 | jq -r .sync_info.latest_block_height)" echo "chain : $(curl -fsSL https://lumera-testnet-rpc.polkachu.com/status | jq -r .result.sync_info.latest_block_height)" ``` What to alert on. | Metric | Alert threshold | | - | - | | Block signing rate | Below 95 percent in the signing window | | `catching_up` | `true` for more than 5 minutes | | Peer count | Below 5 | | Free disk | Below 20 percent | | `missed_blocks_counter` | Any sustained increase | | Block time lag | More than 30 seconds behind wall clock | | Process restarts | Any unexpected restart | | Pending upgrade plan | Any non-empty `query upgrade plan` | With `prometheus = true` in `config.toml`, metrics are served on `localhost:26660/metrics`. Scrape them with Prometheus and build alerts in Grafana. Purpose-built options include [tenderduty](https://github.com/blockpane/tenderduty) and [cosmos-validator-watcher](https://github.com/kilnfi/cosmos-validator-watcher). Both page you on missed blocks directly. For a node\_exporter, Prometheus, and alert-rule starting point, see [validator security and monitoring](/validators/security#metrics-and-alerts). Treat alerting as important even on testnet. At minimum, page a human on missed blocks. Practicing the alert flow here means it already works when you move to mainnet. Routine maintenance. | Cadence | Task | | - | - | | Daily | Check signing rate, sync status, disk headroom | | Weekly | Apply OS security patches. Verify key backups are intact | | Monthly | Test restoring keys from backup. Review commission and metadata | | Per release | Stage the new binary in Cosmovisor before the upgrade height | ## Troubleshooting | Symptom | Cause and fix | | - | - | | `error while loading shared libraries: libwasmvm.x86_64.so` | The CosmWasm library is missing or stale. Copy `libwasmvm.x86_64.so` from the extracted release to `/usr/lib` and run `sudo ldconfig`. | | `upgrade plan "vX.Y.Z" is scheduled at height N but not registered in this binary` | Your binary is older than the chain. Install the latest release. This panic happens before the SDK reads `--unsafe-skip-upgrades`, so that flag will not get you past it. | | Keyring command hangs on a headless server | The `os` backend has no daemon. Use `lumerad config set client keyring-backend test --skip-validate`. | | `insufficient fees; got 5000ulume` | Raise the fee to `--fees=10000ulume`, or use `--gas-prices=0.025ulume`. | | `create-validator` rejects `--amount` or `--pubkey` | Cosmos SDK v0.50 requires a JSON file. See [create validator](/validators/testnet/create-validator). | | Zero peers right after start | Normal for 2 to 3 minutes. If it persists, read the logs before changing your firewall. | | `Couldn't connect to any seeds` or `Error dialing seed ... connection refused` | The seed node is down. `connection refused` is the remote host rejecting you, so this is not your firewall. Stop the node, run `curl -fsSL -o ~/.lumera/config/addrbook.json https://snapshots.polkachu.com/testnet-addrbook/lumera/addrbook.json`, then start it. A running node rewrites `addrbook.json` from memory, so it has to be stopped first. | | Node stuck at `catching_up: true` | Usually disk I/O or too few peers. Check `df -h`, peer count, and consider re-syncing from a fresh snapshot. | | State sync stuck on "discovering snapshots" | No peer is serving snapshots. Fall back to the snapshot method in [node setup](/validators/testnet/node-setup). | | Validator shows `BOND_STATUS_UNBONDED` | Expected until your stake is large enough to enter the capped active set. It is not an error. | | Jailed after a restart | Confirm the node resumed signing, then submit `tx slashing unjail`. Check `tombstoned` first. | ## Re-syncing from scratch If state becomes corrupted, wipe and re-sync. Your keys are untouched by this. ```bash theme={null} sudo systemctl stop lumera lumerad comet unsafe-reset-all --home "$HOME/.lumera" --keep-addr-book SNAP_URL=$(curl -fsSL https://www.polkachu.com/testnets/lumera/snapshots \ | grep -oE 'https://snapshots\.polkachu\.com/testnet-snapshots/lumera/lumera_[0-9]+\.tar\.lz4' | head -1) curl -o - -L "$SNAP_URL" | lz4 -c -d - | tar -x -C "$HOME/.lumera" sudo systemctl start lumera ``` `unsafe-reset-all` deletes chain data and resets the private validator state file. It never touches `priv_validator_key.json` or `node_key.json`. Your validator identity survives. ## Migrating to a new server The dangerous part is the window where both machines could sign. 1. Set up the new server completely and sync it, without copying the consensus key. 2. Stop the old node with `sudo systemctl stop lumera` and confirm with `systemctl is-active lumera`. 3. Confirm the old node has stopped producing signatures in the explorer. 4. Only then copy `priv_validator_key.json` to the new server and start it. 5. Delete the key from the old server and disable its service with `sudo systemctl disable --now lumera`. Never shortcut step 3. Missing a few blocks during a clean cutover costs almost nothing. Double-signing during a sloppy one ends the validator permanently. ## Useful links * [Lumera releases](https://github.com/LumeraProtocol/lumera/releases) * [Network configurations](https://github.com/LumeraProtocol/lumera-networks) * [Validator security and monitoring](/validators/security) * [SuperNode operator guide](https://github.com/LumeraProtocol/lumera-networks/blob/master/docs/SUPERNODE_GUIDE.md) * [Testnet explorer](https://portal.testnet.lumera.io/lumera-testnet-2) * [Testnet snapshots](https://www.polkachu.com/testnets/lumera/snapshots) * [Discord](https://discord.com/invite/lumeraprotocol) ## Next steps Provide Cascade and Sense services alongside your validator. Take everything you practiced here to production.