GenLayer Node API

GenLayer Node API

The GenLayer Node provides a JSON-RPC API (opens in a new tab) for interacting with it. This API allows you to execute contract calls, retrieve transaction information, and perform various blockchain operations.

GenLayer Methods

gen_call

Executes a new message call immediately without creating a transaction on the blockchain. This method supports read operations, write operations, and contract deployments.

Method: gen_call

Parameters:

  • request (object, required): The contract call request parameters
    • from (string, required): The address making the call
    • to (string, required): The contract address to call
    • data (string, required): The encoded function call data
    • type (string, required): The type of call ("read", "write", or "deploy")
    • blockNumber (string, optional): The block number to query at (hex-encoded). If not provided, uses latest block
    • status (string, optional): The transaction status filter ("finalized" or "decided"). If not provided, uses decided state
    • value (string, optional): The value to send with the call (hex-encoded)
    • leader_public_data (string or null, optional): Hex-encoded data returned by a previous leader execution. Its encoding is defined by the GenVM specification. When provided, GenVM runs in validator mode; when omitted or null, GenVM runs in leader mode

Returns: A structured JSON object containing execution results and debug information

Response Fields:

  • data (string): Hex-encoded return value from contract execution
  • leaderPublicData (string): Hex-encoded leader data whose encoding is defined by the GenVM specification. Populated in leader mode; empty in validator mode
  • status (object): Execution status
    • code (number): Status code (0=success, 1=user error, 2=vm error, 3=internal error)
    • message (string): Human-readable status message
  • stdout (string): Standard output captured during execution
  • stderr (string): Standard error captured during execution
  • logs (array): GenVM log entries, each as a JSON object with fields like level, message, target, file, ts
  • events (array): Contract events emitted during execution
    • topics (array of strings): Event topics as hex-encoded strings
    • data (string): Event data as hex-encoded string
  • messages (array): Messages emitted by the contract
    • messageType (number): Type identifier for the message
    • recipient (string): Recipient address
    • value (string): Value in hex format
    • data (string): Message data as hex-encoded string
    • onAcceptance (boolean): Whether message is sent on acceptance. Always false for an external message (messageType 0): external messages on acceptance are not supported yet
    • saltNonce (string): Salt nonce in hex format
  • nondetDisagreementCallNo (number or null): When running in validator mode (with leader_public_data), indicates which run_nondet call the validator disagreed on. Null when there is no disagreement or when running in leader mode
  • syncedBlock (string): Hex-encoded block number that GenVM has synced to. Indicates data freshness — responses are based on state up to this block

Example - Deploy Call:

{
  "jsonrpc": "2.0",
  "method": "gen_call",
  "params": [
    {
      "type": "deploy",
      "data": "0xf9023db90223232076302e312e300a23207b2022446570656e6473223a202270792d67656e6c617965723a3135716669766a7679383038303072683939387063786d64326d38766131777132717a71687a3835306e3867676372346939713022207d0a0a66726f6d2067656e6c6179657220696d706f7274202a0a0a0a2320636f6e747261637420636c6173730a636c6173732053746f7261676528676c2e436f6e7472616374293a0a2020202073746f726167653a207374720a0a202020202320636f6e7374727563746f720a20202020646566205f5f696e69745f5f2873656c662c20696e697469616c5f73746f726167653a20737472293a0a202020202020202073656c662e73746f72616765203d20696e697469616c5f73746f726167650a0a20202020232072656164206d6574686f6473206d75737420626520616e6e6f7461746564207769746820766965770a2020202040676c2e7075626c69632e766965770a20202020646566206765745f73746f726167652873656c6629202d3e207374723a0a202020202020202072657475726e2073656c662e73746f726167650a0a2020202023207772697465206d6574686f640a2020202040676c2e7075626c69632e77726974650a20202020646566207570646174655f73746f726167652873656c662c206e65775f73746f726167653a2073747229202d3e204e6f6e653a0a202020202020202073656c662e73746f72616765203d206e65775f73746f726167650a950e04617267730d6c696e697469616c2076616c756500",
      "from": "0xf6A60752C24Cd3BFcAf8d387a5EB62d3746F44EE",
      "to": "0x0000000000000000000000000000000000000000",
      "status": "finalized"
    }
  ],
  "id": 1
}

Example Response (Deploy Success):

{
  "jsonrpc": "2.0",
  "result": {
    "data": "00",
    "leaderPublicData": "0e076e645f6f75747305",
    "status": {
      "code": 0,
      "message": "success"
    },
    "stdout": "",
    "stderr": "",
    "logs": [
      {
        "file": "common/src/lib.rs:111",
        "level": "info",
        "message": "logging initialized",
        "target": "genvm_common",
        "ts": "1770221913210",
        "version": "v0.2.11-aarch64-macos-release"
      },
      {
        "file": "src/exe/run.rs:150",
        "genvm_id": "1",
        "level": "info",
        "message": "genvm id",
        "target": "genvm::exe::run",
        "ts": "1770221913212"
      },
      {
        "file": "src/lib.rs:299",
        "level": "info",
        "message": "metrics",
        "metrics": {
          "gvm": {
            "host": { "time": 654 },
            "llm_module": { "calls": 0, "time": 0 },
            "supervisor": { "compilation_time": 0, "compiled_modules": 0, "precompile_hits": 2 },
            "web_module": { "calls": 0, "time": 0 }
          },
          "llm": null,
          "web": null
        },
        "target": "genvm",
        "ts": "1770221913590"
      }
    ],
    "events": [],
    "messages": [],
    "nondetDisagreementCallNo": null
  },
  "id": 1
}

Example - Read Call (Latest Decided State):

{
  "jsonrpc": "2.0",
  "method": "gen_call",
  "params": [
    {
      "type": "read",
      "data": "0xd8960e066d6574686f646c6765745f686176655f636f696e00",
      "from": "0xf6A60752C24Cd3BFcAf8d387a5EB62d3746F44EE",
      "to": "0xCe5712E63fd5475288aB1B7c0a368B9417357b81",
      "gas": "0x5208",
      "value": "0x0"
    }
  ],
  "id": 1
}

Example - Read Call with Specific Block and Status:

{
  "jsonrpc": "2.0",
  "method": "gen_call",
  "params": [
    {
      "type": "read",
      "data": "0xd8960e066d6574686f646c6765745f686176655f636f696e00",
      "from": "0xf6A60752C24Cd3BFcAf8d387a5EB62d3746F44EE",
      "to": "0xCe5712E63fd5475288aB1B7c0a368B9417357b81",
      "blockNumber": "0x151ec5",
      "status": "decided"
    }
  ],
  "id": 1
}

Example - Write Call:

{
  "jsonrpc": "2.0",
  "method": "gen_call",
  "params": [
    {
      "from": "0x742d35Cc6634C0532925a3b8D4C9db96c4b4d8b6",
      "to":   "0x742d35Cc6634C0532925a3b8D4C9db96c4b4d8b6",
      "data": "0xa9059cbb000000000000000000000000742d35cc6634c0532925a3b8d4c9db96c4b4d8b6",
      "type": "write",
      "gas":  "0x5208",
      "value":"0x0"
    }
  ],
  "id": 1
}

Example Response with Messages (Router Forward Call):

When a contract emits internal messages (e.g., a router contract forwarding calls), the response includes the messages array:

{
  "jsonrpc": "2.0",
  "result": {
    "data": "00",
    "leaderPublicData": "0e076e645f6f75747305",
    "status": {
      "code": 0,
      "message": "success"
    },
    "stdout": "",
    "stderr": "",
    "logs": [
      {
        "file": "src/lib.rs:299",
        "level": "info",
        "message": "metrics",
        "target": "genvm",
        "ts": "1770225770000"
      }
    ],
    "events": [],
    "messages": [
      {
        "messageType": 1,
        "recipient": "0xc346f80c86adf1d71accfc4ef6efe7482b25be40",
        "value": "0x0",
        "data": "0xf86eb86a16046172677331d4023078393164463338654243623930384335643635...",
        "onAcceptance": false,
        "saltNonce": "0x0"
      },
      {
        "messageType": 1,
        "recipient": "0xad26513a4315b0ea8a5c800322b35092a83dfd3d",
        "value": "0x0",
        "data": "0xf86eb86a16046172677331d4023078333738653339374533316346383538333630...",
        "onAcceptance": false,
        "saltNonce": "0x0"
      }
    ],
    "nondetDisagreementCallNo": null
  },
  "id": 1
}

Notes:

  • The blockNumber parameter can be either hex-encoded with a 0x prefix (e.g., "0x151ec5") or decimal (e.g., "1384133")
  • Valid status values are "decided" (default) and "finalized"
  • When both parameters are omitted, the call executes against the latest decided state
  • For read and write type calls, if the contract doesn't exist at the specified block/status combination, an error is returned with code -32001 (Resource not found) and message "contract not found at address 0x..."
  • For deploy type calls, the status and blockNumber parameters are not used for contract state validation since no contract exists yet

gen_getContractSchema

Retrieve the schema/interface of a GenLayer contract to understand its available methods and properties.

Method: gen_getContractSchema

Parameters:

  • request (object, required): The contract schema request
    • code (string, required): Base64-encoded contract code

Returns: Contract schema object with methods and properties

{
  "ctor": { // constructor info
    "kwparams": {}, // dict
    "params": [["value", "type"]] // list of tuples
  },
  "methods": { // dict from method name to method info
    "method_name": {
      "kwparams": {}, // dict
      "params": [["value", "type"]], // list of tuples
      "payable": false, // bool
      "readonly": false, // bool
      "ret": "null" // string
    },
  }
}

Example:

{
  "jsonrpc": "2.0",
  "method": "gen_getContractSchema",
  "params": [
    {
      "code": "IyB7ICJEZXBlbmRzIjogInB5LWdlbmxheWVyOnRlc3QiIH0KCmZyb20gZ2VubGF5ZXIgaW1wb3J0ICoKCgojIGNvbnRyYWN0IGNsYXNzCmNsYXNzIFN0b3JhZ2UoZ2wuQ29udHJhY3QpOgogICAgc3RvcmFnZTogc3RyCgogICAgIyBjb25zdHJ1Y3RvcgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGluaXRpYWxfc3RvcmFnZTogc3RyKToKICAgICAgICBzZWxmLnN0b3JhZ2UgPSBpbml0aWFsX3N0b3JhZ2UKCiAgICAjIHJlYWQgbWV0aG9kcyBtdXN0IGJlIGFubm90YXRlZCB3aXRoIHZpZXcKICAgIEBnbC5wdWJsaWMudmlldwogICAgZGVmIGdldF9zdG9yYWdlKHNlbGYpIC0+IHN0cjoKICAgICAgICByZXR1cm4gc2VsZi5zdG9yYWdlCgogICAgIyB3cml0ZSBtZXRob2QKICAgIEBnbC5wdWJsaWMud3JpdGUKICAgIGRlZiB1cGRhdGVfc3RvcmFnZShzZWxmLCBuZXdfc3RvcmFnZTogc3RyKSAtPiBOb25lOgogICAgICAgIHNlbGYuc3RvcmFnZSA9IG5ld19zdG9yYWdlCg=="
    }
  ],
  "id": 1
}

Response:

{
  "jsonrpc": "2.0",
  "result": {
    "ctor": {
      "kwparams": {},
      "params": [["initial_storage", "string"]]
    },
    "methods": {
      "get_storage": {
        "kwparams": {},
        "params": [],
        "readonly": true,
        "ret": "string"
      },
      "update_storage": {
        "kwparams": {},
        "params": [["new_storage", "string"]],
        "payable": false,
        "readonly": false,
        "ret": "null"
      }
    }
  },
  "id": 1
}

gen_getContractState

Retrieves the current state of a GenLayer intelligent contract at a specific block height and transaction status.

Method: gen_getContractState

Parameters:

  • request (object, required): The contract state request parameters
    • address (string, required): The contract address to query
    • blockNumber (string, optional): The block number to query at (hex-encoded). If not provided, uses latest block
    • status (string, optional): The transaction status filter ("finalized" or "decided"). If not provided, uses decided state

Returns: Hex-encoded contract state data

Limits:

LimitDefaultConfig key
Contract bytes read per call16 MBnode.rpc.contract_read_max_bytes

This method reads the contract's whole storage, so its cost grows with the contract. The limit stops the read once the contract exceeds it and returns an error, rather than returning part of the state as if it were all of it. Operators may reduce it; 0 or less disables it.

Example Request - Latest Decided State:

{
  "jsonrpc": "2.0",
  "method": "gen_getContractState",
  "params": [
    {
      "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f"
    }
  ],
  "id": 1
}

Example Request - Specific Block and Status:

{
  "jsonrpc": "2.0",
  "method": "gen_getContractState",
  "params": [
    {
      "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f",
      "blockNumber": "0x151ec5",
      "status": "decided"
    }
  ],
  "id": 1
}

Example Response:

{
  "jsonrpc": "2.0",
  "result": "0xd9960e0773746f726167656c6e757064617465642074657374207374",
  "id": 1
}

Notes:

  • The blockNumber parameter should be hex-encoded with a 0x prefix
  • Valid status values are "decided" (default) and "finalized"
  • The returned state is hex-encoded binary data with a 0x prefix
  • Empty contract state returns "0x"
  • If the contract doesn't exist at the specified block/status, an error will be returned

cURL Example:

# Get latest decided state
curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_getContractState",
    "params": [{
      "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f"
    }],
    "id": 1
  }'
 
# Get state at specific block with decided status
curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_getContractState",
    "params": [{
      "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f",
      "blockNumber": "0x151ec5",
      "status": "decided"
    }],
    "id": 1
  }'

gen_getContractCode

Retrieves the code of a deployed GenLayer intelligent contract at a specific block height and transaction status.

Method: gen_getContractCode

Parameters:

  • request (object, required): The contract code request parameters
    • address (string, required): The contract address to query
    • blockNumber (string, optional): The block number to query at (hex-encoded). If not provided, uses latest block
    • status (string, optional): The transaction status filter ("finalized" or "decided"). If not provided, uses decided state

Returns: Base64-encoded contract code

Limits:

LimitDefaultConfig key
Contract bytes read per call16 MBnode.rpc.contract_read_max_bytes

The node reads the contract's code slot directly, so the cost of a call is proportional to the code it returns rather than to everything the contract stores. The limit is a backstop above that: a contract whose declared code length exceeds it is refused before the bytes are read. Operators may reduce it; 0 or less disables it.

Example Request - Latest Decided State:

{
  "jsonrpc": "2.0",
  "method": "gen_getContractCode",
  "params": [
    {
      "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f"
    }
  ],
  "id": 1
}

Example Request - Specific Block and Status:

{
  "jsonrpc": "2.0",
  "method": "gen_getContractCode",
  "params": [
    {
      "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f",
      "blockNumber": "0x151ec5",
      "status": "finalized"
    }
  ],
  "id": 1
}

Example Response:

{
  "jsonrpc": "2.0",
  "result": "IyB7ICJEZXBlbmRzIjogInB5LWdlbmxheWVyOnRlc3QiIH0KCmZyb20gZ2VubGF5ZXIgaW1wb3J0ICoKCgpjbGFzcyBTdG9yYWdlKGdsLkNvbnRyYWN0KToKICAgIHN0b3JhZ2U6IHN0cgoKICAgIGRlZiBfX2luaXRfXyhzZWxmLCBpbml0aWFsX3N0b3JhZ2U6IHN0cik6CiAgICAgICAgc2VsZi5zdG9yYWdlID0gaW5pdGlhbF9zdG9yYWdlCgogICAgQGdsLnB1YmxpYy52aWV3CiAgICBkZWYgZ2V0X3N0b3JhZ2Uoc2VsZikgLT4gc3RyOgogICAgICAgIHJldHVybiBzZWxmLnN0b3JhZ2UKCiAgICBAZ2wucHVibGljLndyaXRlCiAgICBkZWYgdXBkYXRlX3N0b3JhZ2Uoc2VsZiwgbmV3X3N0b3JhZ2U6IHN0cikgLT4gTm9uZToKICAgICAgICBzZWxmLnN0b3JhZ2UgPSBuZXdfc3RvcmFnZQo=",
  "id": 1
}

Notes:

  • The blockNumber parameter should be hex-encoded with a 0x prefix
  • Valid status values are "decided" (default) and "finalized"
  • The returned code is base64-encoded
  • If the contract doesn't exist at the specified block/status, an error will be returned
  • If the address does not have code (not a contract), an error will be returned
  • If the contract's code exceeds node.rpc.contract_read_max_bytes, the call is refused rather than truncated, so a partial answer is never returned as a whole one

cURL Example:

# Get latest decided code
curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_getContractCode",
    "params": [{
      "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f"
    }],
    "id": 1
  }'
 
# Get code at specific block with finalized status
curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_getContractCode",
    "params": [{
      "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f",
      "blockNumber": "0x151ec5",
      "status": "finalized"
    }],
    "id": 1
  }'

gen_getTransactionReceipt

Retrieves the transaction receipt for a given transaction ID. This endpoint is a proxy to the underlying consensus layer's getTransactionAllData function, providing detailed information about a transaction that has been processed.

Method: gen_getTransactionReceipt

Parameters:

  • request (object, required):
    • txId (string, required): The 32-byte transaction hash as a hexadecimal string with a 0x prefix.

Returns:

A Transaction object which contains the details of the transaction. The structure is based on ITransactionsTransaction from the consensus contracts. The object has the following fields:

  • id (string): The transaction ID as a hex string.
  • txOrigin (address): The original address that initiated the transaction.
  • sender (address): The address of the sender.
  • recipient (address): The destination address of the transaction.
  • activator (address): The address of the validator that activated the transaction.
  • status (uint8): The current status of the transaction.
  • statusName (string): The ABI transaction status name for status (Uninitialized, Pending, Proposing, Committing, Revealing, Accepted, Undetermined, Finalized, Canceled, AppealRevealing, AppealCommitting, ValidatorsTimeout, LeaderTimeout, or LeaderRevealing).
  • previousStatus (null): Always null. The contract dropped this field from ITransactions.Transaction (26 → 25 tuple components), so the node has nothing to report. It is null rather than 0 because 0 decodes as Uninitialized, which would be a wrong answer rather than an absent one. The field is removed in a v2 receipt.
  • txSlot (uint256): The transaction slot number.
  • initialRotations (uint256): The initial number of rotations.
  • numOfInitialValidators (uint256): The number of initial validators.
  • randomSeed (string): The random seed used for the transaction as a hex string.
  • txExecutionHash (string): The execution hash of the transaction as a hex string.
  • txCallData (string): The transaction call data as a hex string.
  • eqBlocksOutputs (string): The equivalent blocks outputs as a hex string.
  • timestamps (object): Object containing various timestamps:
    • Created (uint256): When the transaction was created.
    • Pending (uint256): When the transaction became pending.
    • Activated (uint256): When the transaction was activated.
    • Proposed (uint256): When the transaction was proposed.
    • Committed (uint256): When the transaction was committed.
    • LastVote (uint256): When the last vote was cast.
    • AppealSubmitted (uint256): When an appeal was submitted.
    • LeaderRevealed (uint256): When the leader was revealed.
  • result (uint8): The result of the transaction execution.
  • txExecutionResult (uint8): The leader execution vote result (0 NotVoted, 1 FinishedWithReturn, 2 FinishedWithError, 3 Timeout, 4 NondetDisagree, 5 DeterministicViolation).
  • txExecutionResultName (string): The ABI vote type name for txExecutionResult.
  • epoch (uint256): The epoch the transaction belongs to.
  • resultHash (string): The transaction result hash as a hex string.
  • validUntil (uint256): The timestamp until which the transaction is valid.
  • fees (object|null): Fee accounting echo. null when fee accounting is disabled or the FeeManager binding/address is not available. All numeric values in this object are decimal strings.
    • deposit (string): Submission-time fee custody sum, from transactionFeesBudget(txId) + transactionDeveloperFeesBudget(txId).

    • userValue (string): User value carried by the transaction.

    • distribution (object): Effective fee distribution for the transaction:

      • leaderTimeunitsAllocation (string)
      • validatorTimeunitsAllocation (string)
      • appealRounds (string)
      • executionBudgetPerRound (string)
      • totalMessageFees (string)
      • rotations (string[])
      • maxPriceGenPerTimeUnit (string)
      • storageFeeMaxGasPrice (string)
      • receiptFeeMaxGasPrice (string)
    • locked (object|null): Activation-locked prices, or null before activation when no locked prices exist:

      • genPerTimeUnit (string)
      • storageUnitPrice (string)
      • receiptGasPrice (string)
    • consumed (object): On-chain consumption counters:

      • executionConsumed (string)
      • storageFeeUsed (string): Storage fee charged on-chain for the transaction, equal to the storage pages it consumed multiplied by the locked storageUnitPrice.
      • messageFeesConsumed (string)
      • messageFeesBudgetTotal (string)
    • hasAllocations (boolean): true when the chain pinned the transaction's message allocations: a pinned per-message fee allocation tree, or a closed transaction, whose allowed set is empty. false for an open bucket (global message-fee bucket) or fee-less transactions. It is also false when the tree cannot be read, so that a receipt query still answers; the node logs the read failure.

    • allocations (array): The pinned per-message fee allocation tree, one entry per locked allocation, internal and external (empty for an open bucket and for a closed transaction). Budget comes from the on-chain allocation tree; consumed is read from the separate allocationConsumed(txId, key) mapping, keyed by keccak256(abi.encode(messageType, recipient, callKey)). Each entry:

      • messageType (number): 0 = external, 1 = internal.
      • onAcceptance (boolean): Emission phase the allocation is pinned to (true = on acceptance, false = on finalization).
      • parentIndex (string): Index of the parent allocation in the flat tree, or the root sentinel (2^256 - 1) for a root-layer allocation.
      • recipient (string): Target contract address the allocation funds. There is no recipient wildcard: the zero address is a concrete recipient.
      • callKey (string): 32-byte call key (hex) the allocation matches, or the keccak256("") call-key wildcard.
      • budget (string): Maximum message-fee budget locked for this allocation.
      • consumed (string): Amount consumed against this allocation so far.
      • feeParams (string): ABI-encoded per-message fee params (hex), decoded per messageType.
    • allocationPolicy (string, optional): How the chain classifies the transaction's message allocations. It is read from the chain's own policy, not derived from hasAllocations and allocations; read it to tell a closed transaction from the other two.

      allocationPolicyhasAllocationsallocationsMeaning
      "open"false[]Nothing is pinned. Messages draw from the open bucket.
      "pinned"trueinternal and external nodesA message must match one of the listed allocations.
      "closed"true[]Allocations are pinned to an empty allowed set. The transaction runs and can fund a message from its own contract balance; the chain rejects every other message it emits.

      The field is omitted when the node could not read the allocation tree or the policy, or when the chain returns a policy this node version does not know; the node logs it. A receipt never states a policy it did not read.

      The values are read at the chain head. The chain deletes the allocation marker when it refunds a transaction's fee budget, so such a transaction reads "open" (and hasAllocations: false) whatever it was before. That applies to a cancelled transaction, to one that expired by idleness, and to one finalized through recovery; ordinary finalization keeps the policy. For those transactions "open" does not show that the transaction ran as an open bucket.

  • readStateBlockRanges (array): Array of block range objects, each containing:
    • ActivationBlock (uint256): The activation block number.
    • ProcessingBlock (uint256): The processing block number.
    • ProposalBlock (uint256): The proposal block number.
  • roundData (array): Array of round data objects containing:
    • round (uint256): The round number.
    • leaderIndex (uint256): Index of the round leader.
    • votesCommitted (uint256): Number of votes committed.
    • votesRevealed (uint256): Number of votes revealed.
    • appealBond (uint256): The appeal bond amount.
    • rotationsLeft (uint256): Number of rotations remaining.
    • result (uint256): The round result.
    • roundValidators (array): Array of validator addresses for this round.
    • validatorVotes (string): Base64-encoded validator votes.
    • validatorVotesHash (array): Array of vote hashes as hex strings.
    • validatorResultHash (array): Array of result hashes as hex strings.

Example Request:

{
  "jsonrpc": "2.0",
  "method": "gen_getTransactionReceipt",
  "params": [
    {
      "txId": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49"
    }
  ],
  "id": 1
}

Example Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "id": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49",
    "txOrigin": "0x5b70759760a85d92bf972b67ee8558e8e8da87d4",
    "sender": "0x5b70759760a85d92bf972b67ee8558e8e8da87d4",
    "recipient": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f",
    "activator": "0xf27b07d36ff5b9dcd37fd3a09811d6456dd4483a",
    "status": 13,
    "statusName": "LeaderRevealing",
    "previousStatus": null,
    "txSlot": 1,
    "initialRotations": 1,
    "numOfInitialValidators": 5,
    "randomSeed": "0x3d0bdff63bce1cf84b155f1557bb15e303b903b1b559325b6492794a5e498954",
    "txExecutionHash": "0x76744dc491b1725eb2ba08cf516855c14aac310e36711368c04442fd39d61853",
    "txCallData": "f5b31604617267730da4017570646174656420746573742073746f72616765066d6574686f64747570646174655f73746f7261676500",
    "eqBlocksOutputs": "c0",
    "timestamps": {
      "Created": 1750774777,
      "Pending": 1750774777,
      "Activated": 1750774781,
      "Proposed": 1750774786,
      "Committed": 1750774791,
      "LastVote": 0,
      "AppealSubmitted": 0,
      "LeaderRevealed": 0
    },
    "result": 0,
    "txExecutionResult": 3,
    "txExecutionResultName": "Timeout",
    "epoch": 12,
    "resultHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
    "validUntil": 0,
    "fees": {
      "deposit": "1234500000000000",
      "userValue": "0",
      "distribution": {
        "leaderTimeunitsAllocation": "100",
        "validatorTimeunitsAllocation": "200",
        "appealRounds": "0",
        "executionBudgetPerRound": "20000000000000000",
        "totalMessageFees": "0",
        "rotations": ["0"],
        "maxPriceGenPerTimeUnit": "1200000000000000",
        "storageFeeMaxGasPrice": "12",
        "receiptFeeMaxGasPrice": "300000000"
      },
      "locked": {
        "genPerTimeUnit": "0",
        "storageUnitPrice": "10",
        "receiptGasPrice": "250000000"
      },
      "consumed": {
        "executionConsumed": "0",
        "storageFeeUsed": "250",
        "messageFeesConsumed": "0",
        "messageFeesBudgetTotal": "0"
      },
      "hasAllocations": false,
      "allocations": [],
      "allocationPolicy": "open"
    },
    "readStateBlockRanges": [
      {
        "ActivationBlock": 1377797,
        "ProcessingBlock": 1377797,
        "ProposalBlock": 1377798
      }
    ],
    "roundData": [
      {
        "round": 0,
        "leaderIndex": 3,
        "votesCommitted": 5,
        "votesRevealed": 0,
        "appealBond": 0,
        "rotationsLeft": 1,
        "result": 0,
        "roundValidators": [
          "0xa707a1d9dd946242f921f0ad62f61e1b7df30c85",
          "0xf27b07d36ff5b9dcd37fd3a09811d6456dd4483a",
          "0xf3738216c3208d77a9db79a1d31824e5133083c4",
          "0x56394354538bff1e0ee5021c2b6d3522fce30257",
          "0xcfdbac032f82f388d9eca76efa6610b98e7f59fe"
        ],
        "validatorVotes": "AAAAAAA=",
        "validatorVotesHash": [
          "54e3478b02dfb009a35d46b19ac7c967b65416430a33d3860e9301a0f2b19d57",
          "f74a48e7283c91757bf2c612e40f8a9eb69ea0f02cf5e6b89c3c9b6ad629a3c1",
          "2f51c3b1d9ff35140260fb42330fbed12303a9915b8da786c9b8ed060983fa6c",
          "730127e27167fcf2450fe506250b85d916fbf07dd874b7f0c69a2f8ceb9e44a6",
          "473d57953f7e7c1a6fb1d2335618f21088025e02bd9fa30d6f85f20a55c24d15"
        ],
        "validatorResultHash": [
          "0000000000000000000000000000000000000000000000000000000000000000",
          "0000000000000000000000000000000000000000000000000000000000000000",
          "0000000000000000000000000000000000000000000000000000000000000000",
          "0000000000000000000000000000000000000000000000000000000000000000",
          "0000000000000000000000000000000000000000000000000000000000000000"
        ]
      }
    ]
  }
}

gen_getTransactionStatus

Returns the transaction's current materialized consensus status. This is the lightweight polling surface: it does not project deadlines or turn protocol actions into synthetic statuses.

Method: gen_getTransactionStatus

Parameters:

  • request (object, required):
    • txId (string, required): The transaction hash (hex-encoded with 0x prefix)

Returns: Object with status string and numeric code

Response Fields:

FieldTypeDescription
statusstringHuman-readable status name (e.g., "Finalized", "Accepted", "Pending")
statusCodeuint8Numeric status code

Status Codes:

CodeStatus
0Uninitialized
1Pending
2Proposing
3Committing
4Revealing
5Accepted
6Undetermined
7Finalized
8Canceled
9AppealRevealing
10AppealCommitting
11ValidatorsTimeout
12LeaderTimeout
13LeaderRevealing

Example Request:

{
  "jsonrpc": "2.0",
  "method": "gen_getTransactionStatus",
  "params": [
    {
      "txId": "0x563f046c187d711127c51213ca62e2e4fee52009a98f0989a73a0a0382d21890"
    }
  ],
  "id": 1
}

Example Response:

{
  "jsonrpc": "2.0",
  "result": {
    "status": "Finalized",
    "statusCode": 7
  },
  "id": 1
}

Notes:

  • This endpoint reads the stored status directly. A deadline passing does not change this response until the corresponding protocol transition is materialized.
  • Use this for polling — it's cheaper than gen_getTransactionReceipt which returns the full receipt
  • Finalize is an action, not a status. Explorers and operator tooling that need projected state or recovery actions should use gen_getTransactionLifecycle.

gen_getTransactionLifecycle

Returns one advanced protocol lifecycle snapshot for a transaction. This endpoint is intended for explorers, operator tooling, and debugging; ordinary applications should prefer gen_getTransactionStatus or their SDK's simplified lifecycle model.

Method: gen_getTransactionLifecycle

Parameters:

  • request (object, required):
    • txId (string, required): The transaction hash (hex-encoded with 0x prefix)
    • timestamp (integer, optional): Unix timestamp used for projection. Defaults to the node clock.

Returns: The stored state, its projection at evaluatedAt, and the resolution kernel's next action from one contract read.

Response Fields:

FieldTypeDescription
storedStatusstringMaterialized on-chain status
storedStatusCodeuint8Materialized status code
projectedStatusstringStatus projected at evaluatedAt; projection does not write state
projectedStatusCodeuint8Projected status code
resolutionActionstringKernel action available from this snapshot, such as NoOp, ResolveAppeal, or Finalize
resolutionActionCodeuint8Resolution action code
resolutionSourcestringResolution-plan source/reason
resolutionSourceCodeuint8Resolution source code
decisionIdstring or nullDecimal decision identity used by decision-scoped protocol commands
decisionActivebooleanWhether decisionId still identifies the active decision
executionGenerationstring or nullCurrent execution generation as a decimal string from the same snapshot; independent of retained decision provenance. Null when unavailable in a synthetic response.
evaluatedAtintegerUnix timestamp used for the projection

Example Request:

{
  "jsonrpc": "2.0",
  "method": "gen_getTransactionLifecycle",
  "params": [
    {
      "txId": "0x563f046c187d711127c51213ca62e2e4fee52009a98f0989a73a0a0382d21890",
      "timestamp": 1787868000
    }
  ],
  "id": 1
}

Example Response:

{
  "jsonrpc": "2.0",
  "result": {
    "storedStatus": "Accepted",
    "storedStatusCode": 5,
    "projectedStatus": "Accepted",
    "projectedStatusCode": 5,
    "resolutionAction": "Finalize",
    "resolutionActionCode": 6,
    "resolutionSource": "FullReveal",
    "resolutionSourceCode": 6,
    "decisionId": "42",
    "decisionActive": true,
    "executionGeneration": "3",
    "evaluatedAt": 1787868000
  },
  "id": 1
}

Notes:

  • Every response field comes from one lifecycle read at one pinned head and one timestamp.
  • storedStatus is the protocol state. projectedStatus describes what the kernel computes at evaluatedAt; it is not independently materialized.
  • resolutionAction: "Finalize" is the authoritative finalization capability. There is deliberately no ReadyToFinalize status and no redundant canFinalize field.
  • A client must not submit a decision-scoped command unless decisionActive is true and decisionId is non-null.

gen_syncing

Returns the GenVM sync status, indicating how far the node's local GenVM state is behind the chain tip.

Method: gen_syncing

Parameters: None

Returns: Object with sync status information

Response Fields:

FieldTypeDescription
syncedBlockstringHex-encoded block number that GenVM has synced to
latestBlockstringHex-encoded latest block number on the chain
blocksBehindnumberNumber of blocks GenVM is behind the chain tip (0 = fully synced)

Example Request:

{
  "jsonrpc": "2.0",
  "method": "gen_syncing",
  "params": [],
  "id": 1
}

Example Response (Synced):

{
  "jsonrpc": "2.0",
  "result": {
    "syncedBlock": "0x151ec5",
    "latestBlock": "0x151ec5",
    "blocksBehind": 0
  },
  "id": 1
}

Example Response (Behind):

{
  "jsonrpc": "2.0",
  "result": {
    "syncedBlock": "0x151e00",
    "latestBlock": "0x151ec5",
    "blocksBehind": 197
  },
  "id": 1
}

Notes:

  • This endpoint always returns an object (never false like eth_syncing)
  • Use blocksBehind == 0 to determine if the node is fully synced
  • When the node is catching up, gen_call requests without an explicit blockNumber will serve data at the syncedBlock rather than waiting for full sync

cURL Example:

curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_syncing",
    "params": [],
    "id": 1
  }'

gen_estimateGas

Reserved for estimating gas for a GenLayer transaction. Not implemented — the method is registered and reachable, but every call returns an error.

Method: gen_estimateGas

Parameters: None currently read — any params supplied are ignored because the method returns before parsing them

Returns: Nothing. The call always fails with JSON-RPC error code -32004

Example Request:

{
  "jsonrpc": "2.0",
  "method": "gen_estimateGas",
  "params": [],
  "id": 1
}

Example Response:

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32004,
    "message": "method not implemented: gen_estimateGas"
  },
  "id": 1
}

Notes:

  • The method is registered in the genlayer endpoint group, so it is reachable and rate-limited like any other method — it just cannot succeed
  • The error code is -32004 ("method not supported"), not -32601 ("method not found"): the node recognises the method and is declining to serve it, rather than failing to route it
  • Disabling the genlayer group, or this method specifically via endpoints.methods, changes the response to the same -32004 code with a "method not supported" message
  • Do not use gas estimation to size a transaction budget today; set fees explicitly instead

cURL Example:

curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_estimateGas",
    "params": [],
    "id": 1
  }'

gen_getLogs

Queries GenLayer contract-emitted events, mirroring eth_getLogs semantics under the gen_ namespace. Events are produced by intelligent contracts (gl.chain.Event subclasses calling .emit()), persisted by the node while syncing accepted blocks, and indexed by contract address and topics.

Method: gen_getLogs

Parameters:

  • filter (object, required):
    • fromBlock (string, optional): First block of the range (inclusive), as a hex (0x…), decimal string, or "latest". Block numbers are rollup (L2) block numbers — the same numbering gen_syncing reports. Defaults to the latest GenVM-synced block.
    • toBlock (string, optional): Last block of the range (inclusive). Same format and default as fromBlock.
    • address (string | string[], optional): Restrict to events emitted by this contract address (or any of these addresses). Empty matches all addresses.
    • topics (array, optional): Positional topic filters, at most 4 entries. Each position is null (wildcard), a 32-byte hex topic, or an array of topics (OR within the position). Positions match eth_getLogs semantics: an event matches when every constrained position matches; an event with fewer topics than a constrained position does not match.
    • blockHash (string, optional): 32-byte rollup block hash selecting exactly one block (EIP-234). Mutually exclusive with fromBlock/toBlock. The node resolves the hash through the rollup RPC; an unknown hash returns error -32001.
    • status (string[], optional): Lifecycle stages to return, any of "accepted", "finalized", "cancelled". Filters on the emitting transaction's current stage, not on the stage it held at blockNumber. Defaults to ["accepted", "finalized"] — every event whose emitting round still stands. An unknown value returns error -32602.

Returns:

An array of log objects, ordered by block number, then transaction, then per-transaction event index:

  • address (string): Address of the emitting contract.
  • topics (string[]): The emitted topics, each a 32-byte hex string. topics[0] is the event signature topic: keccak256 of the UTF-8 string EventName(sortedIndexedFieldNames) — GenLayer hashes the sorted indexed field names without types, unlike Solidity's typed signature. Remaining topics are the indexed field values (calldata-encoded, right-padded to 32 bytes, or keccak-hashed when longer).
  • data (string): Hex of the event payload. Deviation from eth: this is the GenVM calldata encoding of the event's keyword-argument map, not ABI encoding — decode it with a GenLayer SDK calldata codec.
  • blockNumber (string): Rollup block number (hex) of the block whose sync recorded the event.
  • transactionHash (string): 32-byte GenLayer transaction ID.
  • logIndex (string): Deviation from eth: index of the event within its transaction (hex), not within the block.
  • round (number): GenLayer extra — the consensus round of the executed result that emitted the event.
  • status (string): GenLayer extra — the emitting transaction's current lifecycle stage: "accepted", "finalized", or "cancelled". Not the gen_getTransactionStatus vocabulary: that method's "Canceled" means a transaction cancelled before execution, whereas a "cancelled" log belongs to a transaction whose acceptance an appeal overturned and which is re-executed on a later round.
  • removed (boolean): Carries eth's meaning — the log is no longer part of the canonical set. true exactly when status is "cancelled".

blockHash and transactionIndex are omitted (not indexed node-side).

Semantics and limitations:

  • GenLayer events follow their transaction's lifecycle: they become emitted when the transaction is ACCEPTED, become FINALIZED with it, and can be CANCELLED by a successful appeal. Events from successful, accepted executions are recorded at acceptance time (a reverted transaction emits no logs, matching EVM semantics).
  • A log belongs to the block that emitted it. blockNumber is always the acceptance block and fromBlock/toBlock select on it, so a log's block is stable while its status moves. Each log is returned exactly once per query, whatever combination of statuses is requested — including when a transaction is accepted and finalized within the same block.
  • status is current, not as-of. The same query run twice can report a different status for a log (accepted → finalized) or stop returning it (accepted → cancelled), the same way gen_getTransactionStatus reports current state.
  • Default excludes cancelled logs. A query with no status returns ["accepted", "finalized"]. Events whose round was overturned on appeal are reachable with status: ["cancelled"], where they carry removed: true.
  • Retention. Operators bound the store with node.events.retention_blocks (default 1,000,000 blocks; 0 disables). Queries below the retention watermark return no logs.
  • Deviation from eth: an event emitted inside a write-enabled sandbox (allow_write_ops=True) whose failure the contract catches still surfaces on the successful transaction — GenVM sandboxes are permission boundaries, not transactional ones, so caught-failure branch events are part of the transaction's log set (EVM drops reverted-sub-call logs).
  • Coverage requires the transaction to have executed on an executor that reports events in its result (all executors bundled with the currently pinned GenVM manager do); a future pin bundling a pre-2025-12 executor would not surface events for transactions it executes.
  • Results reflect synced state: a transaction accepted moments ago becomes queryable once the sync loop writes its block (same trailing semantics as gen_getContractState at latest).

Limits:

LimitDefaultConfig key
Block range per query10,000 blocksnode.rpc.getlogs_max_block_range
Logs per response10,000 entriesnode.rpc.getlogs_max_results
Event retention1,000,000 blocksnode.events.retention_blocks

Both are node-configurable (operators may reduce them); exceeding either returns error -32005 (query exceeds limits: … — distinct from HTTP rate limiting, which uses the same code). The method carries a default rate limit of 2 requests per second per IP, also configurable.

Errors:

  • -32602: malformed filter (invalid hex, more than 4 topic positions, non-32-byte topic, blockHash combined with a block range, fromBlock greater than toBlock, unknown status value).
  • -32005: block range or result count over the configured cap.
  • -32001: blockHash unknown to the rollup.
  • -32000: internal failure (details are logged, not returned).

Example Request:

{
  "jsonrpc": "2.0",
  "method": "gen_getLogs",
  "params": [
    {
      "fromBlock": "0x14e300",
      "toBlock": "0x14e400",
      "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f",
      "topics": [
        "0x9f6dbb5f0f942152b8b254e69610e1c65e33a1bb61bd8f4bd88f0ec7bbe15c2f",
        null
      ]
    }
  ],
  "id": 1
}

Example Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": [
    {
      "address": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f",
      "topics": [
        "0x9f6dbb5f0f942152b8b254e69610e1c65e33a1bb61bd8f4bd88f0ec7bbe15c2f",
        "0x2a00000000000000000000000000000000000000000000000000000000000000"
      ],
      "data": "0x261674696d657374616d70014996d2c96e",
      "blockNumber": "0x14e309",
      "transactionHash": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49",
      "logIndex": "0x0",
      "round": 0,
      "status": "finalized",
      "removed": false
    }
  ]
}

gen_getTransactionData

Retrieves the on-chain transaction data for one round together with the contract execution outputs (returnData, leaderPublicData) recovered from the node's local trace store. If the node no longer holds the trace (restart, pruning), the on-chain fields are still returned with returnData: null — the transaction is never replayed.

Method: gen_getTransactionData

Parameters:

  • request (object, required):
    • txId (string, required): The 32-byte transaction hash as a hexadecimal string with a 0x prefix.
    • round (uint8, optional): The consensus round whose execution outputs to return. Defaults to the latest round.

Returns:

An object with the on-chain transaction fields for the selected round plus the locally stored execution outputs:

  • id (string): The transaction ID as a hex string.
  • sender (address): The address of the sender.
  • recipient (address): The destination address of the transaction.
  • activator (address): The address of the validator that activated the transaction.
  • status (uint8): The current status of the transaction.
  • statusName (string): The ABI transaction status name for status (Uninitialized, Pending, Proposing, Committing, Revealing, Accepted, Undetermined, Finalized, Canceled, AppealRevealing, AppealCommitting, ValidatorsTimeout, LeaderTimeout, or LeaderRevealing).
  • result (uint8): The consensus result of the transaction (0 Idle, 1 MajorityAgree, 2 MajorityDisagree, 3 MajorityTimeout, 4 DeterministicViolation, 5 NoMajority).
  • resultName (string): The ABI result name for result.
  • txExecutionResult (uint8): The leader execution vote result (0 NotVoted, 1 FinishedWithReturn, 2 FinishedWithError, 3 Timeout, 4 NondetDisagree, 5 DeterministicViolation).
  • txExecutionResultName (string): The ABI vote type name for txExecutionResult.
  • txCallData (string): The transaction call data as a hex string.
  • txExecutionHash (string): The execution hash of the transaction as a hex string.
  • eqBlocksOutputs (string): The equivalent blocks outputs as a hex string.
  • timestamps (object): Object containing various timestamps:
    • Created (uint256): When the transaction was created.
    • Pending (uint256): When the transaction became pending.
    • Activated (uint256): When the transaction was activated.
    • Proposed (uint256): When the transaction was proposed.
    • Committed (uint256): When the transaction was committed.
    • LastVote (uint256): When the last vote was cast.
    • AppealSubmitted (uint256): When an appeal was submitted.
    • LeaderRevealed (uint256): When the leader was revealed.
  • round (uint8): The round the execution outputs belong to (the requested round, or the latest round when omitted).
  • returnData (string|null): The contract's return data as a 0x-prefixed hex string, from the node's locally stored execution trace. null when the node holds no trace for the round — clients must handle null explicitly.
  • leaderPublicData (string): Leader data as a 0x-prefixed hex string from the stored trace. Its encoding is defined by the GenVM specification. 0x when no trace is available.

Example Request:

{
  "jsonrpc": "2.0",
  "method": "gen_getTransactionData",
  "params": [
    {
      "txId": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49"
    }
  ],
  "id": 1
}

Example Response:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "id": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49",
    "sender": "0x5b70759760a85d92bf972b67ee8558e8e8da87d4",
    "recipient": "0x73ca5a2b51edf506ceaf110a41780ec51294d89f",
    "activator": "0xf27b07d36ff5b9dcd37fd3a09811d6456dd4483a",
    "status": 7,
    "statusName": "Finalized",
    "result": 1,
    "resultName": "MajorityAgree",
    "txExecutionResult": 1,
    "txExecutionResultName": "FinishedWithReturn",
    "txCallData": "f5b31604617267730da4017570646174656420746573742073746f72616765066d6574686f64747570646174655f73746f7261676500",
    "txExecutionHash": "0x76744dc491b1725eb2ba08cf516855c14aac310e36711368c04442fd39d61853",
    "eqBlocksOutputs": "c0",
    "timestamps": {
      "Created": 1750774777,
      "Pending": 1750774777,
      "Activated": 1750774781,
      "Proposed": 1750774786,
      "Committed": 1750774791,
      "LastVote": 1750774795,
      "AppealSubmitted": 0,
      "LeaderRevealed": 0
    },
    "round": 0,
    "returnData": "0x00",
    "leaderPublicData": "0x0e076e645f6f7574730d1b01aabb"
  }
}

Notes:

  • Multi-round appeal data (roundData) and fee accounting stay on gen_getTransactionReceipt; debug output (stdout, stderr, genvm_log) and trace replay stay on gen_dbg_traceTransaction.
  • returnData and leaderPublicData come from the node's local trace store only — a node that never executed the transaction (or pruned the trace) serves returnData: null and leaderPublicData: "0x" without replaying.
  • Retention. returnData and leaderPublicData come from the trace store, which operators bound with node.trace.retention_blocks (default 1,000,000 blocks; 0 disables). Past the watermark this endpoint answers from chain data with returnData: null, exactly as it does for a transaction the node never executed.
  • The trace is persisted asynchronously after a transaction is accepted, so a query issued immediately after acceptance can still return returnData: null. Clients that need the execution output should poll until it appears (or fall back to gen_dbg_traceTransaction, which replays on demand).
  • An explicit round beyond the rounds the transaction has returns a -32001 error.
  • A malformed txId (not a 32-byte hex string) returns -32602; a transaction not found on-chain returns -32001.
  • This endpoint is in the genlayer group and is enabled by default.

cURL Example:

# Latest round
curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_getTransactionData",
    "params": [{
      "txId": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49"
    }],
    "id": 1
  }'
 
# Specific round
curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_getTransactionData",
    "params": [{
      "txId": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49",
      "round": 1
    }],
    "id": 1
  }'

gen_protocolVersion

Reserved for reporting the GenLayer protocol version. Not implemented — the method is registered and reachable, but every call returns an error.

Method: gen_protocolVersion

Parameters: None currently read — any params supplied are ignored because the method returns before parsing them

Returns: Nothing. The call always fails with JSON-RPC error code -32004

Example Request:

{
  "jsonrpc": "2.0",
  "method": "gen_protocolVersion",
  "params": [],
  "id": 1
}

Example Response:

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32004,
    "message": "method not implemented: gen_protocolVersion"
  },
  "id": 1
}

Notes:

  • The method is registered in the genlayer endpoint group, so it is reachable and rate-limited like any other method — it just cannot succeed
  • The error code is -32004 ("method not supported"), not -32601 ("method not found"): the node recognises the method and is declining to serve it, rather than failing to route it
  • To identify what a node is running, use the ops server's GET /health, which reports node version and sync state
  • eth_protocolVersion is unaffected — it is proxied to the GenLayer chain like any other eth_* method

cURL Example:

curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_protocolVersion",
    "params": [],
    "id": 1
  }'

gen_subscribe

Opens a subscription to a stream of GenLayer consensus events over the node's inbound WebSocket endpoint. Notifications are pushed as they occur, removing the need to poll gen_getTransactionStatus.

Method: gen_subscribe

Transport: WebSocket only. This method is not available over HTTP — an HTTP POST cannot receive push notifications. See the WebSocket endpoint section for the URL.

Parameters:

  • stream (string, required): The event stream to subscribe to. One of transactions, leadership, epochs, newHeads.

Returns: A subscription id (UUID v4 string) used to correlate notifications and to cancel via gen_unsubscribe

Streams:

StreamEvent kinds delivered
transactionsNewTransaction, TransactionActivated, TransactionReceiptProposed, VoteCommitted, VoteRevealed, TransactionAccepted, TransactionFinalized, TransactionUndetermined
leadershipTransactionLeaderRotated, TransactionLeaderTimeout, TransactionLeaderChanged, TransactionLeaderRevealed, LeaderIdlenessProcessed, AppealStarted
epochsEpochAdvance, EpochFinalize, TimeoutsSet, SetEpochMinDuration
newHeadsNewHeader

Notification Fields:

FieldTypeDescription
kindstringThe event name, matching the consensus contract's ABI event (e.g. TransactionAccepted)
eventobjectThe decoded event payload. bytes32 values are 0x-prefixed hex; field names are camelCase
event.rawobjectThe originating chain log (block hash, transaction hash, topics), for reconciling against gen_getLogs

Example Request:

{
  "jsonrpc": "2.0",
  "method": "gen_subscribe",
  "params": [
    "transactions"
  ],
  "id": 1
}

Example Response:

{
  "jsonrpc": "2.0",
  "result": "b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33",
  "id": 1
}

Example Notification:

{
  "jsonrpc": "2.0",
  "method": "gen_subscription",
  "params": {
    "subscription": "b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33",
    "result": {
      "kind": "TransactionAccepted",
      "event": {
        "txId": "0x9c1f3a7b8d2e4f5061728394a5b6c7d8e9f0a1b2c3d4e5f60718293a4b5c6d7e",
        "raw": {
          "address": "0x1234567890abcdef1234567890abcdef12345678",
          "blockNumber": "0x151ec5",
          "transactionHash": "0x4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c"
        }
      }
    }
  }
}

Notes:

  • The subscription id is a UUID v4 generated by this node; it is never an upstream chain subscription id
  • Streams are coarse by design: one subscription covers a whole lifecycle, so following a transaction end to end costs one subscription rather than one per event kind
  • The kind values are the consensus contracts' ABI event names, which are stable across node releases. They are deliberately not the node's internal topic names
  • Every payload is derived from on-chain logs and contains no node-private data. The node's own GenVM sync position is not exposed here; use gen_syncing
  • newHeads is served for convenience and duplicates eth_subscribe("newHeads"), which is fed from the same upstream. Latency-sensitive clients should prefer eth_subscribe
  • Notifications are live only — there is no historical replay or backfill. Reconcile any gap with gen_getLogs
  • Server-side filtering (for example by txId) is not supported; subscribe to the stream and filter client-side
  • Subscribing to an unknown stream returns error code -32602 with the list of supported streams
  • Exceeding max_subs_per_conn returns error code -32000 ("subscription limit reached")
  • This method belongs to the genlayer endpoint group; disabling that group disables it on the WebSocket transport too
  • Standard clients can consume this with their generic subscription API, since notifications use the conventional gen_subscription envelope. In go-ethereum: client.Subscribe(ctx, "gen", ch, "transactions")

wscat Example:

wscat -c ws://localhost:9151/ws
> {"jsonrpc":"2.0","method":"gen_subscribe","params":["transactions"],"id":1}
< {"jsonrpc":"2.0","result":"b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33","id":1}
< {"jsonrpc":"2.0","method":"gen_subscription","params":{"subscription":"b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33","result":{"kind":"NewTransaction","event":{"txId":"0x9c1f..."}}}}

gen_unsubscribe

Cancels a subscription previously opened with gen_subscribe. The node stops sending notifications for that subscription and releases the resources backing it.

Method: gen_unsubscribe

Transport: WebSocket only, on the same connection that opened the subscription.

Parameters:

  • subscriptionId (string, required): The id returned by gen_subscribe

Returns: Boolean — true when a matching subscription was cancelled, false when the id is unknown on this connection

Example Request:

{
  "jsonrpc": "2.0",
  "method": "gen_unsubscribe",
  "params": [
    "b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33"
  ],
  "id": 2
}

Example Response:

{
  "jsonrpc": "2.0",
  "result": true,
  "id": 2
}

Notes:

  • An unknown subscription id is not an error: the response is result: false, matching the eth_unsubscribe convention
  • Subscriptions are scoped to the connection that created them. One connection cannot cancel another's subscription, and an id from a closed connection always returns false
  • Explicit unsubscription is optional for cleanup: closing the WebSocket cancels every subscription on that connection automatically
  • A notification already in flight when the cancellation is processed may still arrive; treat the true response as "no further events will be produced", not as a barrier
  • A missing or non-string parameter returns error code -32602

wscat Example:

wscat -c ws://localhost:9151/ws
> {"jsonrpc":"2.0","method":"gen_unsubscribe","params":["b3f1c2de-9a44-4c1e-8f77-2b0a5d6e1c33"],"id":2}
< {"jsonrpc":"2.0","result":true,"id":2}

Debug Methods

These methods are available for debugging and testing purposes during development.

Debug methods are off by default. To enable them, set genlayer_debug.enabled: true under node.rpc.endpoints in config.yaml. Enable them only on an RPC port that untrusted callers cannot reach:

node:
  rpc:
    endpoints:
      genlayer_debug:
        enabled: true

gen_dbg_ping

Simple connectivity test method that returns a "pong" response.

Method: gen_dbg_ping

Parameters: None

Returns: "pong" string

Example:

{
  "jsonrpc": "2.0",
  "method": "gen_dbg_ping",
  "params": [],
  "id": 1
}

Response:

{
  "jsonrpc": "2.0",
  "result": "pong",
  "id": 1
}

gen_dbg_trie

Get trie information for debugging node internal DB state.

Method: gen_dbg_trie

Parameters:

  • request (object, required): The trie debug request
    • txID (string, required): Transaction ID (hex-encoded)
    • round (integer, required): Round number

Returns: Trie debug information string

Example:

{
  "jsonrpc": "2.0",
  "method": "gen_dbg_trie",
  "params": [
    {
      "txID": "0x742d35Cc6634C0532925a3b8D4C9db96c4b4d8b6742d35Cc6634C0532925a3b8",
      "round": 0
    }
  ],
  "id": 1
}

Response:

{
  "jsonrpc": "2.0",
  "result": "graph TD\nnode0@{label: \"br  seq=1\"}\nnode1@{label: \"ext seq=1 0x00000000000000000000000000000000000000000000000000000000000000000000000\"}\nnode1 --\u003e data2@{label: \"seq=1\n[21 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]\"}\nnode0 --\u003e|0| node1\nnode3@{label: \"ext seq=1 0x72d46c3ada9f897c74d349bbfe0e450c798167c9f580f8daf85def57e96c3ea00000000\"}\nnode3 --\u003e data4@{label: \"seq=1\n[105 110 105 116 105 97 108 32 115 116 111 114 97 103 101 32 118 97 108 117 101 0 0 0 0 0 0 0 0 0 0 0]\"}\nnode0 --\u003e|3| node3\n",
  "id": 1
}

gen_dbg_traceTransaction

Retrieves the full execution trace for a transaction, including the return data, stdout/stderr, GenVM logs, and the exact error if execution failed.

Method: gen_dbg_traceTransaction

Parameters:

  • request (object, required):
    • txID (string, required): Transaction ID (hex-encoded with 0x prefix)
    • round (integer, optional): Consensus round number (default: 0)

Returns: Transaction execution trace object

Response Fields:

FieldTypeDescription
transaction_idstringTransaction hash
result_codenumber0=success, 1=user error, 2=VM error, 3=internal error
return_datastringHex-encoded contract return data
stdoutstringStandard output captured during execution
stderrstringStandard error captured during execution
genvm_logarrayDetailed GenVM execution log entries
storage_proofstringHex-encoded storage proof hash
run_timestringExecution duration
leader_public_datastringHex-encoded leader data; its encoding is defined by the GenVM specification
stored_atstring (optional)ISO 8601 timestamp of when the trace was stored; omitted when not available

Example:

{
  "jsonrpc": "2.0",
  "method": "gen_dbg_traceTransaction",
  "params": [{"txID": "0x563f046c187d711127c51213ca62e2e4fee52009a98f0989a73a0a0382d21890", "round": 0}],
  "id": 1
}

Response:

{
  "jsonrpc": "2.0",
  "result": {
    "transaction_id": "0x563f046c187d711127c51213ca62e2e4fee52009a98f0989a73a0a0382d21890",
    "result_code": 2,
    "return_data": "0x2e0464617461b402696e76616c69645f636f6e7472616374...",
    "stdout": "",
    "stderr": "",
    "genvm_log": [
      {
        "file": "src/lib.rs:82",
        "level": "error",
        "message": "vm error",
        "error": {"causes": ["VMError(invalid_contract absent_runner_comment)"]}
      }
    ],
    "storage_proof": "0x99ff0d9125e1fc9531a11262e15aeb2c60509a078c4cc4c64cefdfb06ff68647",
    "run_time": "0s",
    "leader_public_data": "0x0e076e645f6f75747305"
  },
  "id": 1
}

Notes:

  • If the trace isn't stored locally (e.g., the node restarted or data was pruned), the endpoint will replay the transaction automatically.
  • Retention. Operators bound the trace store with node.trace.retention_blocks (default 1,000,000 blocks; 0 disables). A trace below the retention watermark is gone, so this endpoint falls back to a live replay — reconstructed from the latest on-chain view, and not byte-identical to the stored artifact. A single trace larger than node.trace.max_trace_bytes (default 32 MB) is never stored, which produces the same fallback.
  • Field bound. The free-form execution-output fields (return_data, stdout, stderr, genvm_log) carry whatever the replayed contract emitted, so their size is contract-controlled. Each is capped at node.trace.max_debug_field_bytes (default 256 KB; 0 takes the default). When a field is cut, the truncation is marked in-band — a trailing ...[truncated: N of M bytes omitted from gen_dbg trace] on a string field, or a final {"truncated": "..."} entry on genvm_log — so the reply stays honest rather than silently short.
  • This endpoint is in the genlayer_debug group, which is disabled by default. Enable it deliberately, and only on an RPC bind that untrusted callers cannot reach: it replays a transaction through GenVM and returns internal execution output, so it is debug tooling, not a public API.
  • Use result_code to determine execution outcome: 0 = contract returned successfully, 1 = contract raised a user error, 2 = GenVM encountered an error, 3 = internal error.

Ethereum Compatibility

The GenLayer Node also supports Ethereum-compatible methods that are proxied to the underlying infrastructure. These methods follow the standard Ethereum JSON-RPC specification (opens in a new tab) and are prefixed with eth_.

Examples of supported Ethereum methods:

  • eth_blockNumber
  • eth_getBalance
  • eth_sendRawTransaction
  • eth_call
  • And other standard Ethereum JSON-RPC methods

zkSync Compatibility

zkSync-compatible (opens in a new tab) methods are also supported and proxied to the underlying infrastructure. These methods are prefixed with zks_.

The default config.yaml turns the zksync group off (node.rpc.endpoints.zksync.enabled: false). Set it to true to forward zks_* calls.

Usage Examples

cURL

# Test connectivity (debug method, needs genlayer_debug enabled)
curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_dbg_ping",
    "params": [],
    "id": 1
  }'
 
# Execute a contract call
curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_call",
    "params": [{
      "from": "0x742d35Cc6634C0532925a3b8D4C9db96c4b4d8b6",
      "to": "0x742d35Cc6634C0532925a3b8D4C9db96c4b4d8b6",
      "data": "0x70a08231000000000000000000000000742d35cc6634c0532925a3b8d4c9db96c4b4d8b6",
      "type": "read",
      "status": "decided"
    }],
    "id": 1
  }'
 
# Get contract schema
curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_getContractSchema",
    "params": [{
      "code": "IyB7ICJEZXBlbmRzIjogInB5LWdlbmxheWVyOnRlc3QiIH0KCmZyb20gZ2VubGF5ZXIgaW1wb3J0ICoKCgojIGNvbnRyYWN0IGNsYXNzCmNsYXNzIFN0b3JhZ2UoZ2wuQ29udHJhY3QpOgogICAgc3RvcmFnZTogc3RyCgogICAgIyBjb25zdHJ1Y3RvcgogICAgZGVmIF9faW5pdF9fKHNlbGYsIGluaXRpYWxfc3RvcmFnZTogc3RyKToKICAgICAgICBzZWxmLnN0b3JhZ2UgPSBpbml0aWFsX3N0b3JhZ2UKCiAgICAjIHJlYWQgbWV0aG9kcyBtdXN0IGJlIGFubm90YXRlZCB3aXRoIHZpZXcKICAgIEBnbC5wdWJsaWMudmlldwogICAgZGVmIGdldF9zdG9yYWdlKHNlbGYpIC0+IHN0cjoKICAgICAgICByZXR1cm4gc2VsZi5zdG9yYWdlCgogICAgIyB3cml0ZSBtZXRob2QKICAgIEBnbC5wdWJsaWMud3JpdGUKICAgIGRlZiB1cGRhdGVfc3RvcmFnZShzZWxmLCBuZXdfc3RvcmFnZTogc3RyKSAtPiBOb25lOgogICAgICAgIHNlbGYuc3RvcmFnZSA9IG5ld19zdG9yYWdlCg=="
    }],
    "id": 1
  }'
 
# Get debug trie information (debug method, needs genlayer_debug enabled)
curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_dbg_trie",
    "params": [{
      "txID": "0x742d35Cc6634C0532925a3b8D4C9db96c4b4d8b6742d35Cc6634C0532925a3b8",
      "round": 0
    }],
    "id": 1
  }'
 
# Get transaction receipt
curl -X POST http://localhost:9151 \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "gen_getTransactionReceipt",
    "params": [{
      "txId": "0x635060dd514082096d18c8eb64682cc6a944f9ce1ae6982febf7a71e9f656f49"
    }],
    "id": 1
  }'

Ops Methods

These methods provide operational endpoints for monitoring the GenLayer node.

balance

Returns the configured operator's account balance in multiple formats including hexadecimal, wei, and GEN units.

Method: GET /balance

Parameters: None

Returns: JSON object containing the operator's balance in three formats

Example Request:

curl -X GET http://localhost:9153/balance

Example Response:

{
  "balance_hex": "0x0de0b6b3a7640000",
  "balance_wei": "1000000000000000000",
  "balance_gen": "1.000"
}

Response Fields:

  • balance_hex (string): The balance encoded as hexadecimal with "0x" prefix
  • balance_wei (string): The balance in wei as a decimal string
  • balance_gen (string): The balance in GEN units formatted with 3 decimal places

Notes:

  • This endpoint is only available when the node is running in validator mode
  • The balance is always retrieved from the latest block
  • The operator address is configured at node startup
  • If the operator address is not configured, the zero address (0x0000...0000) is used
  • Balance conversions: 1 GEN = 1,000,000,000,000,000,000 wei (10^18 wei)
  • Returns HTTP 503 if the node is not in validator mode
  • Returns HTTP 500 if there's an error retrieving the balance

Example Response (Zero Balance):

{
  "balance_hex": "0x",
  "balance_wei": "0",
  "balance_gen": "0.000"
}

Example Response (Large Balance):

{
  "balance_hex": "0x3635c9adc5dea00000",
  "balance_wei": "1000000000000000000000",
  "balance_gen": "1000.000"
}

Error Response (Not in Validator Mode):

{
  "message": "Balance endpoint not available in full mode"
}

cURL Example with Headers:

curl -X GET http://localhost:9153/balance \
  -H "Accept: application/json" \
  -v

health

Returns the node's operational status and the typed condition catalog that explains it.

Method: GET /health

Parameters: None

Returns: JSON object containing the overall status, network/version information, validator identity (validator mode), and the full condition catalog

HTTP status: 503 when status is unavailable; 200 for every other state.

Example Request:

curl -X GET http://localhost:9153/health

Example Response:

{
  "status": "healthy",
  "network": "",
  "node_version": "v0.6.0",
  "protocol_version": "0.3.0",
  "genvm_version": "v0.0.18",
  "validator_address": "0x1234567890abcdef1234567890abcdef12345678",
  "validator_moniker": "my-validator",
  "conditions": {
    "chain.rpc":                { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
    "chain.websocket":          { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
    "chain.sync":               { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:02:14Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
    "consensus.ban":            { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
    "consensus.stake":          { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
    "consensus.operator_funds": { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
    "consensus.prime":          { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:04:41Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
    "genvm.llm":                { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" },
    "genvm.web":                { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:31Z", "updated_at": "2026-09-01T10:29:56Z", "last_ok": "2026-09-01T10:29:56Z" },
    "node.disk":                { "state": "healthy", "severity": "critical", "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" },
    "node.snapshot_age":        { "state": "healthy", "severity": "info",     "occurrences": 0, "consecutive_failures": 0, "since": "2026-09-01T08:00:03Z", "updated_at": "2026-09-01T10:31:12Z", "last_ok": "2026-09-01T10:31:12Z" }
  }
}

Response Fields:

  • status (string): the most urgent state across the catalog — unknown, healthy, suspect, warning, degraded, or unavailable
  • network (string): The network the node is connected to
  • node_version, protocol_version, genvm_version (string): build versions
  • validator_address (string, validator mode only): The validator's address
  • validator_moniker (string, validator mode only): The validator's on-chain moniker; reported alongside the address even when empty
  • conditions (object): the full catalog. Every entry is always present — this is not a list of what is broken.
    • state (string): current state
    • severity (string): the catalog's fixed class — info, warning, or critical. Not the state: a critical condition can be healthy.
    • occurrences (number): accepted failed observations since process start
    • consecutive_failures (number): current failure streak; zero after recovery
    • since, updated_at, last_ok (string, optional): ISO 8601 lifecycle timestamps
    • reason (string, present only when not healthy): stable, low-cardinality cause code
    • impact (string, present only when not healthy): what the fault does to this node
    • action (string, present only when not healthy): what the operator should do
    • details (object, present only when not healthy): typed fields explicitly allowlisted by the catalog entry. Raw provider payloads, prompts, credentials, URLs, and stack traces are rejected.

States

StateMeaningAlerted
unknownThe node never observed this condition.no
healthyThe last observation succeeded.no
suspectOne failure, not yet confirmed. It may clear by itself.no
warningThe capability works, and it is confirmed sub-optimal.yes
degradedThe failure is confirmed. The capability does not work.yes
unavailableThe node cannot do its duty at all.yes

Roll-up precedence, most urgent first: unavailable, degraded, warning, suspect, unknown, healthy.

unknown is not on that ladder. It is only ever the state a condition starts in, so it means "not observed yet", never "faulty". A GenVM module rests there until a transaction needs one; node.snapshot_age rests there on a node that takes no snapshots. Letting either gate the endpoint would report unknown for a node where nothing is wrong.

If no condition has been observed at all, status is unknown — nothing observed is not the same as nothing wrong.

Only unavailable returns 503.

A condition can never exceed the liveness cap its catalog entry declares. The GenVM conditions are capped at degraded, so a dead LLM provider returns 200: that node is still booted, synced and voting — it votes Timeout on the transactions it cannot execute, which is the designed behaviour rather than an outage. Failing the endpoint would pull a working validator out of a load balancer.

consensus.ban, consensus.stake, chain.rpc, chain.websocket and chain.sync are capped at unavailable, because each of them stops the node doing its job.

Example: the LLM provider is dead — HTTP 200

{
  "status": "degraded",
  "conditions": {
    "genvm.llm": {
      "state": "degraded",
      "severity": "critical",
      "reason": "llm_request_failed",
      "impact": "This validator cannot execute a transaction that needs an LLM. It votes Timeout, and the consensus contracts may penalize a Timeout vote.",
      "action": "Check the affected LLM provider service and payment or billing status, restore access, or configure a working fallback. If credentials are changed through .env, restart the node.",
      "occurrences": 2,
      "consecutive_failures": 2,
      "since": "2026-09-01T10:30:45Z",
      "updated_at": "2026-09-01T10:31:12Z",
      "last_ok": "2026-09-01T04:12:03Z"
    }
  }
}

Example: the validator is banned — HTTP 503

{
  "status": "unavailable",
  "conditions": {
    "consensus.ban": {
      "state": "unavailable",
      "severity": "critical",
      "reason": "validator_banned",
      "impact": "The consensus contracts do not select this validator for a round. The node earns nothing.",
      "action": "Wait for the stated epoch. If the ban is permanent, restake with a new validator.",
      "details": { "until_epoch": "4182", "permanent": "false" },
      "occurrences": 1,
      "consecutive_failures": 1,
      "since": "2026-09-01T09:14:02Z",
      "updated_at": "2026-09-01T10:31:12Z",
      "last_ok": "2026-09-01T09:13:52Z"
    }
  }
}

chain.sync — 503 only when the node stops syncing

Depth is not a fault. A node syncing from genesis is millions of blocks behind and perfectly healthy. What matters is whether the stored synced block advances.

ReasonStateHTTP
sync_laggingwarning200
sync_catching_updegraded200
sync_stalledunavailable503
sync_feed_frozenunavailable503

The node never skips a duty because it is behind. It votes Timeout only on a transaction that reads state it has not synced; a new contract deploy still runs.

cURL Example with Headers:

curl -X GET http://localhost:9153/health \
  -H "Accept: application/json" \
  -v

metrics

Exposes Prometheus-compatible metrics for monitoring node performance and health.

Method: GET /metrics

Parameters: None

Returns: Prometheus-format text response with all collected metrics

Example Request:

curl -X GET http://localhost:9153/metrics

Example Response:

# HELP genlayer_node_cpu_usage_percent Current CPU usage percentage (0-100)
# TYPE genlayer_node_cpu_usage_percent gauge
genlayer_node_cpu_usage_percent{component="node"} 15.5
genlayer_node_cpu_usage_percent{component="genvm-llm"} 45.2
genlayer_node_cpu_usage_percent{component="genvm-web"} 12.8
genlayer_node_cpu_usage_percent{component="webdriver"} 5.3

# HELP genlayer_node_memory_rss_bytes Resident Set Size (RSS) memory usage in bytes
# TYPE genlayer_node_memory_rss_bytes gauge
genlayer_node_memory_rss_bytes{component="node"} 524288000
genlayer_node_memory_rss_bytes{component="genvm-llm"} 2147483648

# HELP genlayer_node_network_rx_bytes_total Total bytes received across all network interfaces
# TYPE genlayer_node_network_rx_bytes_total counter
genlayer_node_network_rx_bytes_total{component="node"} 1073741824

Available Metrics

The node collects metrics for three main components:

  • Node - The main node process
  • GenVM - The GenVM modules (LLM and Web)
  • WebDriver - The WebDriver container

Operator Condition Metrics

  • genlayer_node_operator_condition_state{condition,state}: one-hot gauge for the current catalog state. All state series are prewarmed at process start.
  • genlayer_node_operator_condition_occurrences_total{condition,reason}: accepted failed observations by stable reason.
  • genlayer_node_operator_condition_transitions_total{condition,from,to}: lifecycle transitions, including recovery.
  • genlayer_node_operator_condition_last_success_timestamp_seconds{condition}: Unix timestamp of the last successful observation, or zero before one occurs.

condition, state, reason, from, and to come from the fixed startup catalog; upstream error text is never used as a label.

occurrences_total counts differently per condition kind, so the counter stays comparable: an observed condition (folded from duty traffic) counts every failed observation, while a polled condition counts only entry into a failing state. Counting every failed poll would scale the counter with the poll interval rather than the fault — a three-day ban at a ten-second poll would read as roughly 26,000 events.

CPU Metrics

genlayer_node_cpu_usage_percent

Current CPU usage percentage (0-100).

Type: Gauge

Labels:

  • component: One of node, genvm-llm, genvm-web, webdriver

Example:

genlayer_node_cpu_usage_percent{component="node"} 15.5
genlayer_node_cpu_usage_percent{component="genvm-llm"} 45.2
genlayer_node_cpu_usage_percent{component="genvm-web"} 12.8
genlayer_node_cpu_usage_percent{component="webdriver"} 5.3

Memory Metrics

genlayer_node_memory_rss_bytes

Resident Set Size (RSS) memory usage in bytes.

Type: Gauge

Labels:

  • component: One of node, genvm-llm, genvm-web, webdriver

Example:

genlayer_node_memory_rss_bytes{component="node"} 524288000
genlayer_node_memory_rss_bytes{component="genvm-llm"} 2147483648

genlayer_node_memory_vms_bytes

Virtual Memory Size (VMS) in bytes.

Type: Gauge

Labels:

  • component: One of node, genvm-llm, genvm-web, webdriver

Note: Only available for node and GenVM components.

genlayer_node_memory_usage_bytes

Current memory usage in bytes (WebDriver only).

Type: Gauge

Labels:

  • component: webdriver

genlayer_node_memory_limit_bytes

Memory limit in bytes (WebDriver only).

Type: Gauge

Labels:

  • component: webdriver

genlayer_node_memory_percent

Memory usage percentage.

Type: Gauge

Labels:

  • component: One of genvm-llm, genvm-web, node, webdriver
  • node: Node address (hex format)

genlayer_node_memory_peak_bytes

Peak memory usage in bytes (GenVM components only).

Type: Gauge

Labels:

  • component: One of genvm-llm, genvm-web
  • node: Node address (hex format)

Network Metrics

genlayer_node_network_rx_bytes_total

Total bytes received across all network interfaces.

Type: Counter

Labels:

  • component: One of node, genvm-llm, genvm-web, webdriver
  • node: Node address (hex format)

Example:

genlayer_node_network_rx_bytes_total{component="node",node="0x84b6cbd007511352d3fea26834c5c39a440903c4"} 96879942
genlayer_node_network_rx_bytes_total{component="webdriver",node="0x84b6cbd007511352d3fea26834c5c39a440903c4"} 84

genlayer_node_network_tx_bytes_total

Total bytes transmitted across all network interfaces.

Type: Counter

Labels:

  • component: One of node, genvm-llm, genvm-web, webdriver
  • node: Node address (hex format)

Disk Metrics

genlayer_node_disk_free_bytes

Available free disk space in bytes.

Type: Gauge

Labels:

  • component: node
  • directory: One of genlayer.db, keystore, logs
  • node: Node address (hex format)

genlayer_node_disk_total_bytes

Total disk space in bytes.

Type: Gauge

Labels:

  • component: node
  • directory: One of genlayer.db, keystore, logs
  • node: Node address (hex format)

genlayer_node_disk_usage_bytes

Used disk space in bytes.

Type: Gauge

Labels:

  • component: node
  • directory: One of genlayer.db, keystore, logs
  • node: Node address (hex format)

genlayer_node_disk_usage_percent

Used disk space as percentage.

Type: Gauge

Labels:

  • component: node
  • directory: One of genlayer.db, keystore, logs
  • node: Node address (hex format)

Node Status Metrics

genlayer_node_blocks_behind

Number of blocks behind the latest block.

Type: Gauge

Labels:

  • node: Node address (hex format)

genlayer_node_latest_block

Latest block number in the network.

Type: Gauge

Labels:

  • node: Node address (hex format)

genlayer_node_processing_block

Currently processing block number.

Type: Gauge

Labels:

  • node: Node address (hex format)

genlayer_node_synced_block

Last synced block number.

Type: Gauge

Labels:

  • node: Node address (hex format)

genlayer_node_synced

Node synchronization status (1 = synced, 0 = not synced).

Type: Gauge

Labels:

  • node: Node address (hex format)

genlayer_node_prime_blocked_insufficient_funds

Whether a mandatory validator prime is currently blocked by an unfunded operator wallet (1 = blocked, 0 = not blocked). While blocked the validator cannot pay for consensus duty transactions and is flagged idle each round — fund the operator wallet to recover.

Type: Gauge

Labels:

  • node: Node address (hex format)

genlayer_node_uptime_seconds

Node uptime in seconds.

Type: Gauge

Labels:

  • component: node
  • node: Node address (hex format)

Transaction Metrics

genlayer_node_transactions_accepted_synced_total

Total number of accepted and synced transactions.

Type: Gauge

Labels:

  • node: Node address (hex format)

genlayer_node_transactions_activated_total

Total number of activated transactions.

Type: Gauge

Labels:

  • node: Node address (hex format)

genlayer_node_transactions_leader_proposed_total

Total number of transactions proposed as leader.

Type: Gauge

Labels:

  • node: Node address (hex format)

genlayer_node_transactions_leader_revealed_total

Total number of transactions revealed as leader.

Type: Gauge

Labels:

  • node: Node address (hex format)

genlayer_node_transactions_validator_commit_total

Total number of validator commit votes.

Type: Gauge

Labels:

  • node: Node address (hex format)

genlayer_node_transactions_validator_reveal_total

Total number of validator reveal votes.

Type: Gauge

Labels:

  • node: Node address (hex format)

Gas Metrics (duty transactions)

Emitted inline from the shared consensus gas estimator and transactor, so they cover every duty method (finalizeTransaction, commitVote, revealVote, proposeReceiptWithFees, topUpAndSubmitAppeal, activateTransaction, validatorPrime, epochAdvance, ...). All are true counters.

genlayer_node_gas_estimation_total

Outcome of every duty gas estimation.

Type: Counter

Labels:

  • method: Contract method name
  • outcome: One of success (real RPC estimate), fallback_block_limit (estimation failed; submitted at full block gas limit), fallback_cache / fallback_cache_scaled (estimation failed; reused gas from the shared calldata-length-keyed cache, possibly learned from a different method)

genlayer_node_gas_estimation_capped_total

Estimation succeeded but the safety-multiplied estimate (or per-call reserve) exceeded the block gas limit and was silently capped — the safety margin is gone while everything still works.

Type: Counter

Labels:

  • method: Contract method name

genlayer_node_gas_shortfall_total

Confirmed on-chain out-of-gas: the transaction landed but ran out of gas mid-execution. Self-heals — the node retries with a larger gas limit — so a single occurrence is not actionable; a sustained rate signals systematic under-estimation (treat as a warning, not a page).

Type: Counter

Labels:

  • method: Contract method name
  • reason: One of typed_shortfall (InsufficientGasForInternalMessageCreation revert with exact gas requirement), out_of_gas (explicit OutOfGas revert), heuristic (undecoded revert that consumed gas above the exhaustion threshold)

genlayer_node_gas_block_limit_exhausted_total

Gas shortfall while the gas limit was already capped at the block limit: the work cannot fit in one block and retrying cannot help. Non-self-healing — any occurrence is actionable (page-worthy).

Type: Counter

Labels:

  • method: Contract method name

genlayer_node_gas_spent_wei_total

Cumulative wei actually billed for gas, from each mined duty receipt (gasUsed × effectiveGasPrice). Reverted attempts are billed too and are accounted per attempt; submissions that never land cost nothing. The reverted slice is the exact cost of failed work, and sum(genlayer_node_gas_spent_wei_total) is the operator's total billed duty gas.

Type: Counter

Labels:

  • method: Contract method name
  • outcome: One of success, reverted
  • price_source: effective (receipt's billed price) or fee_cap (submitted fee cap used as an upper bound when the RPC omits effectiveGasPrice)

Consensus Submission Metrics

genlayer_node_consensus_submit_total

Terminal outcome of every duty submission.

Type: Counter

Labels:

  • method: Contract method name
  • outcome: One of success (landed and executed), reverted (landed, contract rejected — gas still billed), failed (never landed: RPC/nonce/funds/authorization)

genlayer_node_consensus_revert_total

Decoded revert causes, one increment per observed revert (attempt-level).

Type: Counter

Labels:

  • method: Contract method name
  • error: Decoded custom error from the fee/gas allowlist (InsufficientFeesForRound, InsufficientFees, FailedFeeTransfer, FeeSettlementNotCompleted, MaxPriceExceeded, ExecutionBudgetExceeded, MessageBudgetExceeded, BudgetTooLow, RollupBudgetBelowFloor, InsufficientBondCustody, InvalidAppealBond, AppealBondTooLow, InsufficientGasForInternalMessageCreation, OutOfGas); anything else collapses to other

Fee Flow Metrics (v0.6 ConsensusMainWithFees)

Note: there are no dedicated appeal-bond metrics. No node runtime path submits appeals — appellants are external clients funding bonds from their own wallets. If a validator appeal duty ever ships, the method-labeled gas/submission metrics above cover it under method="topUpAndSubmitAppeal" with no code change.

genlayer_node_fees_operator_insufficient_funds_total

Duty submissions rejected because the operator account cannot cover value + gas. Complements the genlayer_node_prime_blocked_insufficient_funds gauge by leaving a trace of dips that recover between scrapes. A sustained increase means the operator wallet needs funding (page-worthy).

Type: Counter

genlayer_node_fees_settlement_events_total

Fee-settlement events decoded from this node's own mined finalize receipts. Finalize runs under a staggered lease, so each finalize is observed by exactly one node — aggregate fleet-wide with sum by (network). Use the severity label to alert on failures without enumerating event names: genlayer_node_fees_settlement_events_total{severity="alarm"} covers all three failure modes and any future one.

Type: Counter

Labels:

  • event: One of UnifiedFeesDistributed (fees distributed at settlement), FeesRefunded (surplus returned to the sender), FeeSettlementFailed (finalize succeeded but settlement failed — validators unpaid until retryFeeSettlement), CommitFinalFeesFailed (tx finalized without fee settlement), ValueWithdrawalFailed (a best-effort transfer — refund/appealer payout — failed; funds stay custodied pending recovery)
  • severity: alarm for the three failure events (unpaid or stuck-funds modes needing operator action) or info for the normal UnifiedFeesDistributed / FeesRefunded happy path

genlayer_node_fees_distributed_wei_total

Total wei moved at fee settlement across all recipients — committee payouts plus sender/contributor refunds plus appealer rewards — summed from UnifiedFeesDistributed amounts in this node's finalize receipts. It excludes amounts retained in staking custody (amountToStaking). This is a network "value settled" liveness signal, not per-node earnings — for a validator's own income use genlayer_node_fees_earned_wei_total.

Type: Counter

genlayer_node_fees_earned_wei_total

This node's validator earnings credited at prime, read from the ValidatorPrime event when a prime is effective. This is the accurate per-node income (unlike fees_distributed_wei_total, which is whole-committee plus refunds). Earnings settle into stake per epoch, so this series lags settlement by up to one prime interval. Pair type="fee_rewards" with genlayer_node_gas_spent_wei_total for operator net P&L (earned − gas_spent).

Type: Counter

Labels:

  • type: One of fee_rewards (fee-flow income), validator_rewards, delegator_rewards (staking-reward components)

genlayer_node_fees_penalties_wei_total

This node's fee penalties deducted at prime, read from the same ValidatorPrime event. Kept separate from fees_earned_wei_total so earnings stay gross income; subtract this for net.

Type: Counter

genlayer_node_external_message_delivery_total

External messages this node's own delivery calls (flushExternalMessages, flushInternalMessages, finalizeTransaction) resolved or paused, read from their mined receipts. A paused message was not called and not charged; the node retries it with a backoff (30 s doubling to 10 min) until the chain reports it issued. A paused_* series that keeps rising for one network means a message nobody can deliver.

Type: Counter

Labels:

  • outcome: delivered (ran and succeeded), call_failed (ran and failed; final), paused_gas (no room to forward its gas envelope), paused_value (its value was not yet available), cancelled_unfunded (its open-bucket envelope was never funded; value refunded), cancelled_ceiling (its gas limit is above the current ceiling; value refunded)

genlayer_node_external_message_reimbursed_wei_total

Wei the chain reimbursed this node for running external messages: gas used times the transaction's locked gas price. Pair it with gas_spent_wei_total for the net cost of delivery.

Type: Counter

Go Runtime Metrics

The endpoint also exposes standard Go runtime metrics including:

  • go_gc_duration_seconds - Garbage collection duration summary
  • go_goroutines - Number of active goroutines
  • go_memstats_* - Go memory statistics (heap, stack, GC)
  • go_threads - Number of OS threads

Process Metrics

Standard process metrics are also available:

  • process_cpu_seconds_total - Total CPU time
  • process_resident_memory_bytes - Resident memory size
  • process_virtual_memory_bytes - Virtual memory size
  • process_open_fds - Number of open file descriptors
  • process_network_receive_bytes_total - Network bytes received
  • process_network_transmit_bytes_total - Network bytes transmitted

Prometheus Handler Metrics

Metrics about the metrics endpoint itself:

  • promhttp_metric_handler_requests_total - Total scrape requests by status code
  • promhttp_metric_handler_requests_in_flight - Current scrapes being served

Configuration

Metrics collection can be configured in the node configuration file:

metrics:
  interval: "15s"        # Default interval for all collectors
  collectors:
    node:
      enabled: true      # Enable/disable node metrics
      interval: "30s"    # Optional: Override default interval
    genvm:
      enabled: true      # Enable/disable GenVM metrics
      # Uses default interval (15s)
    webdriver:
      enabled: true      # Enable/disable WebDriver metrics
      interval: "60s"    # Optional: Override default interval

Default Values

  • metrics.interval: 15s (applies to all collectors by default)
  • enabled: true (for each collector)
  • interval: Uses metrics.interval if not specified per collector

Configuration Priority

  1. Collector-specific interval (if specified)
  2. Global metrics.interval (if specified)
  3. System default (15s)

Troubleshooting

No Metrics for GenVM

GenVM processes are ephemeral and only run when executing smart contracts. If you don't see GenVM metrics, it's likely that no contracts are currently being executed.

WebDriver Metrics Missing

Ensure the WebDriver container is running:

docker ps | grep genlayer-node-webdriver

If not running, start it:

task docker:webdriver:docker-compose:up:detach

Metrics Not Updating

Check if metrics collection is enabled in your configuration and that the node has been restarted after configuration changes.

Performance Considerations

  • Collection intervals can be increased to reduce overhead further
  • Each collector runs independently and won't block others if one fails

snapshot

Produces a consistent snapshot of the node's state at the current block. Two modes selected by the request body: stream the snapshot back as a gzipped tar response (operator path), or write it to a destination directory on the node's filesystem (zero-downtime upgrade-controller path).

Method: POST /snapshot

Authentication: required. Send the operator token in either header:

Authorization: Bearer <token>
X-Admin-Auth: <token>

The token is the value of GENLAYERNODE_NODE_OPERATOR_TOKEN on the node process. When that variable is unset, the endpoint returns 401 to every caller regardless of header value (default-deny). The other ops routes (/metrics, /health, /balance) are unaffected and remain public.

Network exposure: the ops server is intended for in-host or in-cluster access (Prometheus scraping /metrics, k8s probes hitting /health). The endpoint may not be reachable from outside the host depending on the deployment's firewall. The recommended operator workflow is therefore one of:

  • SSH onto the node and call the endpoint via 127.0.0.1:9153, or
  • Open an SSH tunnel from the operator workstation to the node (ssh -L 9153:127.0.0.1:9153 <node>) and call localhost:9153 through it.

Parameters:

  • scope (string, optional): Which stores the archive carries — all (default), genvm or ops. An absent scope means all, so a caller written before the store split keeps its behaviour. An unrecognised value is rejected with 400.

    ScopeContentsPortable between nodes
    allboth stores, operation store firstNo
    genvmchain-derived history onlyYes
    opsthis node's consensus record onlyNo

    Only genvm may be given to another operator. all and ops carry the vote salts this node generated and the nonce ownership of its operator key; restoring either onto a different node would make that node act on votes it never cast and nonce slots it never signed. The node refuses to start against an operation store stamped with another operator, but that guard runs at startup, not at copy time — treat a non-portable archive accordingly.

    Every archive carries a snapshot.json manifest recording the format version, the scopes inside, the rollup block the GenVM store was captured at, and — for all and ops — the operator address that produced it.

  • destDir (string, optional): Absolute path on the node's filesystem, which must resolve to a location inside the configured snapshot directory (node.admin.snapshot_dir, default <datadir>/snapshots). When set, switches to write-to-path mode; when omitted or empty, the snapshot is streamed back as application/gzip. Paths outside the snapshot directory are rejected with 400.

Returns:

  • Stream mode (destDir omitted): gzipped tar archive in the response body, Content-Type: application/gzip.
  • Write-to-path mode (destDir set): empty body, 204 No Content.

Example Request — Stream Mode (operator):

On the node (after ssh <node>):

curl -X POST http://127.0.0.1:9153/snapshot \
  -H "X-Admin-Auth: $GENLAYERNODE_NODE_OPERATOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}' \
  -o snapshot.tar.gz

Or via SSH tunnel from a workstation (forwards 9153 to the node's loopback):

ssh -L 9153:127.0.0.1:9153 -N <node> &
curl -X POST http://localhost:9153/snapshot \
  -H "X-Admin-Auth: $GENLAYERNODE_NODE_OPERATOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}' \
  -o snapshot.tar.gz

The response body is a stream — curl can write it to a file (as above) or pipe it through any tool that reads stdin, without staging on local disk.

Example Response — Stream Mode:

HTTP/1.1 200 OK
Content-Type: application/gzip
Content-Disposition: attachment; filename="snapshot.tar.gz"

<gzipped tar archive of the snapshot's files>

Example Request — Write-to-Path Mode (upgrade controller):

curl -X POST http://127.0.0.1:9153/snapshot \
  -H "X-Admin-Auth: $GENLAYERNODE_NODE_OPERATOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"destDir": "/var/lib/genlayer/data/node/snapshots/bradbury-100"}'
 
Take only the portable half, to hand to another operator:
 
```bash
curl -sS -X POST http://localhost:9153/snapshot \
  -H "Authorization: Bearer $OPERATOR_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"scope": "genvm"}' -o genvm-snapshot.tar.gz

**Example Response — Write-to-Path Mode:**

HTTP/1.1 204 No Content


**HTTP Status Codes:**

- `200 OK`: Stream mode succeeded; body carries the gzipped tar archive.
- `204 No Content`: Write-to-path mode succeeded; snapshot is on disk at `destDir`.
- `400 Bad Request`: `destDir` is not absolute, resolves outside the configured snapshot directory, or already exists.
- `401 Unauthorized`: Missing, invalid, or empty operator token (also returned when `GENLAYERNODE_NODE_OPERATOR_TOKEN` is unset on the node).
- `500 Internal Server Error`: Snapshot creation failed (e.g., disk full, DB checkpoint error).

**Notes:**

- A snapshot may be several GB. In stream mode the connection stays open for the duration of the snapshot — configure reverse proxies / load balancers with adequate read timeouts and disable response buffering.
- In stream mode, a dropped connection leaves no on-disk artifact on the node; retry the request from scratch.
- In write-to-path mode, `destDir` must be an absolute path that does not yet exist and that resolves (after `filepath.Clean`) to a location inside the configured snapshot directory; the node creates it. Paths that escape the directory via `..` or point elsewhere on the filesystem are rejected with `400`. This bounds where an operator-token holder can write a checkpoint — still treat the operator token as a credential that can write under the node user's filesystem permissions within that directory.
- **Snapshot directory** (`node.admin.snapshot_dir`): bounds write-to-path mode. A relative value resolves under `datadir` (so the default `snapshots` is `<datadir>/snapshots`); an absolute value is used as-is, letting a node target a dedicated disk. The node creates the directory with `0700` permissions on first use if it does not exist.
- **Docker deploys**: in write-to-path mode, `destDir` (and the snapshot directory) is resolved inside the node container's filesystem, not on the host. The default `<datadir>/snapshots` falls under whatever volume the deployment mounts for `datadir`, so it is host-reachable wherever that volume maps. To use a different location, set `snapshot_dir` to a path that is itself volume-mapped identically on both sides, or stick with stream mode. Stream mode is unaffected — the response body is sent over HTTP regardless of where the node runs.
- The snapshot includes the full chain database (SSTables, MANIFEST, WAL) at the moment of capture. Treat the artifact as sensitive — anyone holding it can reconstruct the node's state.
- Token comparison is constant-time and length-padded so neither the value nor the length of the secret can be inferred from response timing.
- Rotation: update `GENLAYERNODE_NODE_OPERATOR_TOKEN` and restart the node. Tokens are read once at startup and not hot-reloaded.

**Forward-compatibility note:** when the zero-downtime upgrade controller (Phase 3) lands, `/snapshot` will additionally accept a second role (`upgrade`) so the controller can drive the snapshot + stop + start sequence with its own credential. The operator-token path documented here remains the same — no migration is required for operator tooling.

**cURL Example with Bearer header:**

```bash
curl -X POST http://127.0.0.1:9153/snapshot \
  -H "Authorization: Bearer $GENLAYERNODE_NODE_OPERATOR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{}' \
  -o snapshot.tar.gz