Try   HackMD

Keys API Service

Intro

Simple Lido keys and validators HTTP API. It will be possible to launch it on someone's machine and fetch actual Lido keys from all sources. API will also provide HTTP methods for Automating Validator Exits.

Glossary

  • NodeOperatorRegistry is a Lido contract that manages a сurated set of node operators, stores their signing keys;
  • Module is a contract in charge of different validators subsets;
  • Module state is a keys, operators and other data for some specific EL block;
  • Possible validators subsets are Curated (modified version of NodeOperatorRegistry), Community, DVT, Offchain or L2 upgrade for Curated subset;
  • Module type is a value from Staking Module that help to match Module with our code base;
  • StakingRouter is a new contract that is responsible for modules management, reserve allocation and rewards distribution.

Problem statement

Lido node operators keys and validators are used in a lot of services: Oracle, Council Daemon, MEV Monitoring, BalVal, Node Operators Widget. Also Node Operators need fetching keys for automating exits. Currently Lido supports only Curated validators subset handled by NodeOperatorRegistry contract. But in the future validators set can be expanded with DVT and Community validators. The StakingRouter contract was proposed for implementing modular approach of managing different subsets of validators. It will support any number of Curated, Community and DVT modules. These modules could store keys on-chain, off-chain, on L2 or in any other way. Services will have to support fetching keys from all these modules.

Assumptions

  1. StakingRouter will be implemented;
  2. StakingRouter could support any number Curated, Community, DVT, off-chain and L2 Curated modules;
  3. Every module will have an L1 contract;
  4. That L1 contract address will not be changed;
  5. Every module can be uniquely identified by it's contract address;
  6. Every module will have a type and function to get it;
  7. Every module will have keysOpIndex or other value to understand that keys list was changed (will be renamed to nonce);
  8. Curated module will have operator id;
  9. Community/DVT modules will have some group id for keys to determine ownership of keys;
  10. Used keys will not be removed from any module storage.

Requirements

  1. Keys API should keep all data of modules in a consistent state. In other words Keys API should keep state of all modules identical (blockHash in metadata of each module should be equal);
  2. Keys API will return it's latest fetched EL state. It will NOT return any arbitrary state for arbitrary blockNumber. Keys API is NOT an indexer.

Technical solution

Overview

Keys API service will work with all StakingRouter modules. We chose modular way of implementation. For each type of module we will have separate library. Modules data will be stored in separate tables too. For example, we will have CuratedKey and CommunityKey tables. Every library will contain its own {ModuleName}Meta table with EL data. Meta will include keyOpIndex (nonce) value. API will run cron job to update keys in db for all modules to latest EL block. At the moment API supports only NodeOperatorRegistry keys.

API will have endpoint for Automating Validator Exits. Information about validators state will be fetched from beacon node and stored in database. We will implement this algorithm as ValidatorState library.

In the next sections we will describe technical details of each part.

Modules libraries interface

In this section we will describe general interface of libraries for StakingRouter modules. Each library will contain methods for fetching keys and other data from modules storages, methods for writing and reading data to the database.

// information that shows data relevance interface BlockMetadata { blockNumber: number; blockHash: string; timestamp: number; } // information from staking router module interface ModuleMetadata { // address of module stakingModuleAddress: string; // module type type: string; // unique id of the module moduleId: number; // will be renamed as a nonce keysOpIndex: number; // reward fee of the module moduleFee: number; // treasury fee treasuryFee: number; // target percent of total keys in protocol, in BP targetShare: number; // module status if module can not accept the deposits or can participate in further reward distribution status: number; // name of module name: string; // block.timestamp of the last deposit of the module lastDepositAt: number; // block.number of the last deposit of the module lastDepositBlock: number; } interface Metadata { blockMetadata: BlockMetadata; moduleMetadata: ModuleMetadata; } interface Key { index: number; used: boolean; operatorIndex: number; depositSignature: string; key: string; } interface Operator { index: number; active: boolean; name: string; rewardAddress: string; stakingLimit: number; stoppedValidators: number; totalSigningKeys: number; usedSigningKeys: number; } interface Data<Op extends Operator = Operator,PKey extends Key = Key> { keys: PKey[], operators: Op[], meta: Metadata } interface AbstractModule<Op extends Operator = Operator,PKey extends Key = Key> { stakingModuleAddress: string; type: string; keysOpIndex: number; // method for updating data in database from contract update(blockHash: string): Promise<Metadata>; // methods for reading data from database getKeysFromStorage(): Promise<{keys: PKey[]; meta: Metadata}>; getOperatorsFromStorage(): Promise<{operators: Op[]; meta: Metadata}>; getKeysAndOperatorsFromStorage(): Promise<{keys: PKey[]; operators: Op[]; meta: Metadata}>; getMetadataFromStorage(): Promise<Metadata>; }

Updating keys in Keys API service

As was mentioned in requirements section Keys API service should keep modules data in storage in consistent state. To fulfill this condition we will fetch data from modules storages for the same blockHash and write it in database in transaction.

// some function that returns all modules async function getModules(): Promise<AbstractModule[]> { ... } // somewhere in cron jobs const modules = await getModules(); const block = await provider.getBlock('latest'); const blockHash = block.hash; // get all keys, operators, meta entityManager.transactional(async () => { const metadatas = Promise.all(modules.map(async (module) => { return await module.update(blockHash); })); if (!checkMetadatas(metadatas)) { throw new Error('Inconsistent metadatas'); } }); // reading keys somewhere in HTTP controller const keys = await module.getKeysFromStorage();

Curated module library

As described earlier, at the moment Lido stores keys on-chain in NodeOperatorRegistry contract. We have already implemented Registry library for fetching keys and operators from this contract, store in database and update database state to latest EL block.

DB Models

Registry library contains RegistryKey, RegistryOperator, RegistryMeta tables.

interface RegistryKey { index: number; key: string; depositSignature: string; used: boolean; operatorIndex: number; } interface RegistryOperator { index: number; active: boolean; name: string; rewardAddress: string; stackingLimit: number; stoppedValidators: number; totalSigningKeys: number; usedSigningKeys: number; } interface RegistryMeta { blockNumber: number; blockHash: string; timestamp: number; keysOpIndex: number; }

Updating keys

Updating keys in Registry library is based on comparing meta information in contract and database. We read blockHash,blockNumber values of latest block from EL and fetch keyOpIndex from NodeOperatorRegistry by blockHash. If current blockNumber is less than previous or keyOpIndex wasn't changed, keys in database will not be updated. Otherwise, keys and operators will be updated in library's database.

async update(blockHashOrBlockTag: string) { const prevMeta: RegistryMeta = await getMetaDataFromStorage(); const currMeta = await getMetaDataFromContract(blockHashOrBlockTag); if (prevMeta.blockNumber > currMeta.blockNumber) { return; } if (prevMeta.keyOpIndex === currMeta.keyOpIndex) { return; } updateOperatorsAndKeysInDB(currMeta.blockHash); } async getMetaDataFromContract(blockHashOrBlockTag): Promise<RegistryMeta> { const block = await provider.getBlock(blockHashOrBlockTag); const blockHash = block.hash; const blockTag = { blockHash }; const keysOpIndex = await registryContract.getKeysOpIndex({ blockTag }); return { blockNumber: block.number, blockHash, keysOpIndex, timestamp: block.timestamp, }; }

The same algorithm we consider to have in other modules libraries.

Validator state library

Keys API will also provide endpoints for working with validators. This library will fetch keys from beacon node, store data in database and support it in actual state.

DB Models

Library will contain Validator, Meta tables.

enum VALIDATOR_STATUS { PENDING_INITIALIZED = 'pending_initialized', PENDING_QUEUED = 'pending_queued', ACTIVE_ONGOING = 'active_ongoing', ACTIVE_EXITING = 'active_exiting' ACTIVE_SLASHED = 'active_slashed', EXITED_UNSLASHED = 'exited_unslashed', EXITED_SLASHED = 'exited_slashed', WITHDRAWAL_POSSIBLE = 'withdrawal_possible', WITHDRAWAL_DONE = 'withdrawal_done' } interface Validator { id: number; status: VALIDATOR_STATUS; key: string; } interface Meta { slot: integer; slotRoot: string; // execution layer data blockNumber: integer; blockHash: string; timestamp: integer; }

Validators approximate update algorithm

Algorithm of fetching validators, update and store in database:

  1. Fetch CL finalized block by request to Beacon node to get root and slot of last finalized block

    ​​​​​​​​​{
    ​​​​​​​​​  "finalized": true
    ​​​​​​​​​  "data": {
    ​​​​​​​​​     "root": "string",
    ​​​​​​​​​     "header": {
    ​​​​​​​​​        "message": {
    ​​​​​​​​​           "slot": "number"
    ​​​​​​​​​        }
    ​​​​​​​​​     } 
    ​​​​​​​​​  }
    ​​​​​​​​​}
    
  2. Compare slot from Meta table and received slot on 1 step. If Meta is empty or slot number in database less than received slot on 1 step, go on 3 step. Otherwise stop algorithm execution.

  3. Fetch EL block information by request to Beacon node by slot root parameter. This information is needed to check modules' keys relevance compare to validators in API's database:

    ​​​​​​{
    ​​​​​​  "data": {
    ​​​​​​    "message": {
    ​​​​​​       "description": "The BeaconBlock object from the CL Bellatrix spec.",
    ​​​​​​       "body": {
    ​​​​​​         "execution_payload": {	
    ​​​​​​           "description": "The ExecutionPayload object from the CL Bellatrix spec.",
    ​​​​​​           "block_number": "number",
    ​​​​​​           "timestamp": "string"
    ​​​​​​           "block_hash": "string"
    ​​​​​​           
    ​​​​​​         }
    ​​​​​​       }
    ​​​​​​    }
    ​​​​​​  }
    ​​​​​​}
    

    Based on 10 assumption we need RegistryMeta.block_number be newer than or equal to ValidatorMeta.block_number.

  4. Fetch all validators for slot_root received on 1 step by request.

  5. Write Meta and Validators in transaction in database.

Cron job with this algorithm will be run every 10 seconds.

// validator's library part: interface ConsesusValidatorsAndMetadata { validators: Validator[]; meta: Meta; } class ValidatorsStateService { update() { const CONSENSUS_NODE = 'http://127.0.0.1:5050'; const BLOCK_ID = 'finalized'; const CONSENSUS_BLOCK_HEADERS_ENDPOINT = `${CONSENSUS_NODE}/eth/v1/beacon/headers/${BLOCK_ID}`; const headersResp = await fetch(CONSENSUS_BLOCK_HEADERS_ENDPOINT); const headersJson = await headersResp.json(); const { data: { root: currSlotRoot, header: { message: { slot: currSlot }, }, }, } = headersJson; // Get meta from database const { slot: prevSlot } = await getMetaDataFromStorage(); if ( prevSlot >= currSlot ) { // We have already had validators for this slot return; } const CONSENSUS_BLOCK_DETAILS_ENDPOINT = `${CONSENSUS_NODE}/eth/v2/beacon/blocks/${currSlotRoot}`; // Get EL block information // This information is needed to check RegistryKey data relevance compare to validators in API's database const blockDetailsResp = await fetch(CONSENSUS_BLOCK_DETAILS_ENDPOINT); const blockDetailsJson = await blockDetailsResp.json(); const { data: { message: { body: { execution_payload: { block_number, timestamp, block_hash }, }, }, }, } = blockDetailsJson; const CONSENSUS_STATE_VALIDATORS_ENDPOINT = `${CONSENSUS_NODE}/eth/v1/beacon/states/${currSlotRoot}/validators`; const validators = await fetch(CONSENSUS_STATE_VALIDATORS_ENDPOINT); const validatorsJson = await validators.json(); // save in database const clMeta = { root: currSlotRoot, slot: currSlot, blockNumber: block_number, timestamp, blockHash: block_hash }; storeValidatorsAndMetaTrans(validatorsJson, clMeta); } getValidators(pubkeys: string[]): Promise<ConsesusValidatorsAndMetadata> { ... } getLidoOldestValidators(pubkeys: string[], statuses: string[], validatorPercent: number): Promise<ConsesusValidatorsAndMetadata> { ... } } // Keys API // cron job const job = new CronJob( CronExpression.EVERY_10_SECONDS, () => validatorsStateService.update() ); job.start(); ... // HTTP request getLidoCuratedValidators(operatorId) { const { pubkeys, meta: elMeta } = await registryStorage.getKeysAndMeta({operatorIndex: operatorId, used: true}); const { validators, meta: clMeta} = await validatorsStateService.getValidators(pubkeys); // base on 10 assumption if (elMeta.blockNumber < clMeta.blockNumber ) { throw new InternalServerErrorException(); } return { validators }; }

API

/keys

GET /v1/keys

Endpoint returns list of keys for all modules.

Query:

  • used - filter for used/unused keys. Possible values: true/false;
  • operatorIndex - filter for keys of operator with index operatorIndex;
interface ELBlockSnapshot { blockNumber: number; blockHash: string; blockTimestamp: number; } interface Key { key: string; depositSignature: string; used: boolean; operatorIndex: number; } interface ModuleKey extends Key { moduleAddress: string; } class Response { data: ModuleKey[]; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; }

Example:

curl -X 'GET' \
  'http://localhost:3000/v1/keys' \
  -H 'accept: application/json'

Response of this endpoint could be very large but we can’t have a pagination here since data could be updated in the process.

If API returns 425 code, it means database is not ready for work

GET /v1/keys/{pubkey}

Return key by public key with basic fields. pubkey should be in lowercase.

class Response { data: ModuleKey[]; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; }

Example:

curl -X 'GET' \
  'http://localhost:3005/v1/keys/pubkey' \
  -H 'accept: application/json'

POST /v1/keys/find

Returns all keys found in db.

Request body:

interface RequestBody { // public keys in lowercase pubkeys: string[]; }

Response:

class Response { data: ModuleKey[]; meta: { elBlockSnapshot: ElBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; }

Example:

curl -X 'POST' \
  'http://localhost:3000/v1/keys/find' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{"pubkeys": ["pubkey0 "]}'

If API returns 425 code, it means database is not ready for work

/modules

GET /v1/modules

Endpoint returns list of staking router modules.

interface Module { nonce: number; type: string; // unique id of the module id: number; // address of module stakingModuleAddress: string; // rewarf fee of the module moduleFee: number; // treasury fee treasuryFee: number; // target percent of total keys in protocol, in BP targetShare: number; // module status if module can not accept the deposits or can participate in further reward distribution status: number; // name of module name: string; // block.timestamp of the last deposit of the module lastDepositAt: number; // block.number of the last deposit of the module lastDepositBlock: number; } interface ELBlockSnapshot { blockNumber: number, blockHash: string, blockTimestamp: number, } class Reponse { data: Module[]; elBlockSnapshot: ElBlockSnapshot; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; }

Example:

curl -X 'GET' \
  'http://localhost:3000/v1/modules' \
  -H 'accept: application/json'

If API returns 425 code, it means database is not ready for work

GET /v1/modules/{module_id}

module_id - staking router module contact address or id;

Endpoint return information about staking router module;

interface Module { nonce: number; type: string; /// @notice unique id of the module id: number; /// @notice address of module stakingModuleAddress: string; /// @notice rewarf fee of the module moduleFee: number; /// @notice treasury fee treasuryFee: number; /// @notice target percent of total keys in protocol, in BP targetShare: number; /// @notice module status if module can not accept the deposits or can participate in further reward distribution status: number; /// @notice name of module name: string; /// @notice block.timestamp of the last deposit of the module lastDepositAt: number; /// @notice block.number of the last deposit of the module lastDepositBlock: number; } interface ELBlockSnapshot { blockNumber: number; blockHash: string; blockTimestamp: number; } class Reponse { data: Module; elBlockSnapshot: ElBlockSnapshot; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundResponse implements HttpException { statusCode: number = 404; }

Example:

curl -X 'GET' \
  'http://localhost:3000/v1/modules/1' \
  -H 'accept: application/json'

If API returns 425 code, it means database is not ready for work

/modules/keys/

GET /v1/modules/keys/

Return keys for all modules grouped by staking router module.

Query:

  • used - filter for used/unused keys. Possible values: true/false;
  • operatorIndex - filter for keys of operator with index operatorIndex;
interface Module { // current KeyOpIndex nonce: number; // type of module type: string; /// @notice unique id of the module id: number; /// @notice address of module stakingModuleAddress: string; /// @notice rewarf fee of the module moduleFee: number; /// @notice treasury fee treasuryFee: number; /// @notice target percent of total keys in protocol, in BP targetShare: number; /// @notice module status if module can not accept the deposits or can participate in further reward distribution status: number; /// @notice name of module name: string; /// @notice block.timestamp of the last deposit of the module lastDepositAt: number; /// @notice block.number of the last deposit of the module lastDepositBlock: number; } class Response{ data: { keys: Key[]; module: Module; }[]; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; }

Example:

curl -X 'GET' \
  'http://localhost:3000/v1/modules/keys?used=true&operatorIndex=1' \
  -H 'accept: application/json'

If API returns 425 code, it means database is not ready for work

GET /v1/modules/{module_id}/keys

module_id - staking router module contact address or id;

Endpoint returns list of keys for module.

Query:

  • used - filter for used/unused keys. Possible values: true/false;
  • operatorIndex - filter for keys of operator with index operatorIndex;

Response:

Response depends on module type

interface Key { key: string; depositSignature: string; used: boolean; operatorIndex: number; } interface RegistryKey extends Key { index: number; } interface CommunityKey extends Key { } class Response { data: { keys: RegistryKey[] | CommunityKey[]; module: Module; }; meta: { elBlockSnapshot: ELBlockSnapshot } } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; }

Example:

curl -X 'GET' \
  'http://localhost:3000/v1/modules/1/keys?used=true&operatorIndex=1' \
  -H 'accept: application/json'

If API returns 425 code, it means database is not ready for work

POST /v1/modules/{module_id}/keys/find

module_id - staking router module contact address or id;

Returns all keys found in db.

Request body:


interface RequestBody {
  // public keys in lowercase
  pubkeys: string[];
}

Response:

interface Key { key: string; depositSignature: string; used: boolean; operatorIndex: number; } interface RegistryKey extends Key { index: number; } interface CommunityKey extends Key { } class Response { data: { keys: RegistryKey[] | CommunityKey[]; module: Module; }; meta: { elBlockSnapshot: ElBlockSnapshot } } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; }

Example:

curl -X 'POST' \
  'http://localhost:3000/v1/modules/1/keys/find' \
  -H 'accept: application/json' \
  -H 'Content-Type: application/json' \
  -d '{
  "pubkeys": [
    "pubkey"
  ]
}'

If API returns 425 code, it means database is not ready for work

/validators

GET /v1/modules/{module_id}/validators/validator-exits-to-prepare/{operator_id}

module_id - staking router module contact address or id;

This endpoint will return N of oldest lido validators for earliest epoch when voluntary exit can be processed for specific node operator and specific StakingRouter module. Node operator will use this validators list for preparing pre-sign exit messages. API will find used keys of node operator and find for these public keys N oldest validators in Validator table. We consider active validators (active_ongoing status) or validators in pending_initialized, pending_queued statuses. Module tables state fetched from EL should be newer than Validator table state fetched from CL. Otherwise API will return error on request.

Query:

Only one filter is available. If both parameters are provided, percent has a high priority.

  • percent - Percent of validators to exit. Default value is 10.
  • max_amount - Number of validators to exit. If validators number less than amount, return all validators.
interface Validator { validatorIndex: number; key: string; } interface CLBlockSnapshot { epoch: number; root: number; slot: number; blockNumber: number; timestamp: number; blockHash: string; }; class Response { data: Validator[]; meta: { clBlockSnapshot: CLBlockSnapshot; }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; } class InternalServerErrorExceptionNotActualData implements HttpException { statusCode: number = 500; message: string = 'Last Execution Layer block number in our database older than last Consensus Layer'; } class InternalServerErrorExceptionDisable implements HttpException { statusCode: number = 500; message: string = 'Validators Registry is disabled. Check environment variables'; }

Example:

curl -X 'GET' \
  'http://localhost:3000/v1/modules/1/validators/validator-exits-to-prepare/1?percent=10' \
  -H 'accept: application/json'

If API returns 425 code, it means database is not ready for work

GET /v1/modules/{module_id}/validators/generate-unsigned-exit-messages/{operator_id}

module_id - staking router module contact address or id;

Return unsigned exit messages for N oldest validators for earliest epoch when voluntary exit can be processed.

Query:

Only one filter is available. If both parameters are provided, percent has a high priority.

  • percent - Percent of validators to exit. Default value is 10.
  • max_amount - Number of validators to exit. If validators number less than amount, return all validators.
interface ExitPresignMessage { validator_index: string; epoch: string; } interface CLBlockSnapshot { epoch: number; root: string; slot: number; blockNumber: number; timestamp: number; blockHash: string; }; class Response { data: ExitPresignMessage[]; meta: { clBlockSnapshot: CLBlockSnapshot; } } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; } class InternalServerErrorExceptionNotActualData implements HttpException { statusCode: number = 500; message: string = 'Last Execution Layer block number in our database older than last Consensus Layer'; } class InternalServerErrorExceptionDisable implements HttpException { statusCode: number = 500; message: string = 'Validators Registry is disabled. Check environment variables'; }

Example:

curl -X 'GET' \
  'http://localhost:3000/v1/modules/1/validators/generate-unsigned-exit-messages/1?percent=10' \
  -H 'accept: application/json'

If API returns 425 code, it means database is not ready for work

Approximate algorithm:

getExitsPresign(operatorId: number, messages: boolean) { const { keys: pubkeys, meta: elMeta } = await registryStorage.getKeysAndMeta({operatorIndex: operatorId, used: true}); const { validators, meta: clMeta } = await ValidatorsLibrary.getLidoOldestValidators(pubkeys, ['active_ongoing', 'pending_initialized', 'pending_queued'], 10) if (elMeta.blockNumber < clMeta.blockNumber ) { throw new InternalServerErrorException(); } if (messages) { return validators.map((v) => ({ validator_index: v.id, epoch: calculateEpoch(clMeta.slot) }) } return validators.map((v) => ({ validator_index: v.id, key: v.key})); } calculateEpoch(slot) {}

/operators

GET /v1/operators

List of operators grouped by staking router module

Query

interface Operator { index: number; active: boolean; } interface CuratedOperator extends Operator { name: string; rewardAddress: string; stakingLimit: number; stoppedValidators: number; totalSigningKeys: number; usedSigningKeys: number; } interface CommunityOperator extends Operator { } class Response { data: { operators: CuratedOperator[] | CommunityOperator[]; module: Module; }[]; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; }

Example:

curl -X 'GET' \
  'http://localhost:3000/v1/operators' \
  -H 'accept: application/json'

If API returns 425 code, it means database is not ready for work

GET /v1/modules/{module_id}/operators/

module_id - staking router module contact address or id;

List of SR module operators

class Response { data: { operators: CuratedOperator[] | CommunityOperator[]; module: Module; }; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; }

Example:

curl -X 'GET' \
  'http://localhost:3000/v1/modules/1/operators' \
  -H 'accept: application/json'

If API returns 425 code, it means database is not ready for work

GET /v1/modules/{module_id}/operators/{operator_id}

module_id - staking router module contact address or id;
operator_id - operator index;

List of SR module operators

class Response { data: { operators: CuratedOperator | CommunityOperator; module: Module; }; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; }

Example:

curl -X 'GET' \
  'http://localhost:3005/v1/modules/1/operators/1' \
  -H 'accept: application/json'

If API returns 425 code, it means database is not ready for work

GET /v1/modules/{module_id}/operators/keys

module_id - staking router module contact address or id;

Query:

  • used - filter for used/unused keys. Possible values: true/false;
  • operatorIndex - filter for keys of operator with index operatorIndex;
class Response { data: { keys: RegistryKey[] | CommunityKey[]; operators: CuratedOperator[] | CommunityOperator[]; module: Module; }; meta: { elBlockSnapshot: ELBlockSnapshot }; } interface HttpException { statusCode: number; message: string; } class TooEarlyResponse implements HttpException { statusCode: number = 425; message: string = "Too early response"; } class NotFoundException implements HttpException { statusCode: number = 404; }

Example:

curl -X 'GET' \
  'http://localhost:3005/v1/modules/1/operators/1' \
  -H 'accept: application/json'

If API returns 425 code, it means database is not ready for work

/status

GET /v1/status

class Response { // keys api version appVersion: string; chainId: number; elBlockSnapshot: ELBlockSnapshot; clBlockSnapshot: CLBlockSnapshot; }

Prometheus metrics

Roadmap

Future iterations

  • Make modules optional;
  • Research possibility of gRPC as a transport if Keys API is used as a microservice;
  • Implement absraction for staking router ;(possibility to have flexible list of modules, choose chain etc);
  • Use this service without db as CLI;