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.
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;Curated
(modified version of NodeOperatorRegistry
), Community
, DVT
, Offchain
or L2
upgrade for Curated subset;StakingRouter
is a new contract that is responsible for modules management, reserve allocation and rewards distribution.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.
StakingRouter
will be implemented;StakingRouter
could support any number Curated
, Community
, DVT
, off-chain and L2 Curated
modules;L1
contract;L1
contract address will not be changed;keysOpIndex
or other value to understand that keys list was changed (will be renamed to nonce
);operator id
;Community
/DVT
modules will have some group id for keys to determine ownership of keys;blockHash
in metadata of each module should be equal);EL
state. It will NOT return any arbitrary state for arbitrary blockNumber
. Keys API is NOT an indexer.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.
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>;
}
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();
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
.
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 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.
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.
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;
}
Algorithm of fetching validators, update and store in database:
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"
}
}
}
}
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.
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
.
Fetch all validators for slot_root
received on 1 step by request.
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 };
}
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
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
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
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) {}
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
GET /v1/status
class Response {
// keys api version
appVersion: string;
chainId: number;
elBlockSnapshot: ELBlockSnapshot;
clBlockSnapshot: CLBlockSnapshot;
}