mirror of
https://github.com/lux-tel/websites.git
synced 2026-10-30 20:31:36 +00:00
462 lines
11 KiB
TypeScript
462 lines
11 KiB
TypeScript
type NetworkInfo = {
|
|
block_height: number;
|
|
block_timestamp: number;
|
|
block_top_hash: string;
|
|
hard_fork: number;
|
|
max_stakers: number;
|
|
min_operator_stake: number;
|
|
nettype: string;
|
|
staking_requirement: number;
|
|
version: string;
|
|
};
|
|
|
|
/** /info */
|
|
interface NetworkInfoResponse {
|
|
network: NetworkInfo;
|
|
t: number;
|
|
}
|
|
|
|
export enum NODE_STATE {
|
|
RUNNING = 'Running',
|
|
AWAITING_CONTRIBUTORS = 'Awaiting Contributors',
|
|
CANCELLED = 'Cancelled',
|
|
DECOMMISSIONED = 'Decommissioned',
|
|
DEREGISTERED = 'Deregistered',
|
|
AWAITING_EXIT = 'Awaiting Exit',
|
|
EXITED = 'Exited',
|
|
}
|
|
|
|
export interface LockedContribution {
|
|
amount: number;
|
|
key_image: string;
|
|
key_image_pub_key: string;
|
|
}
|
|
|
|
export interface Contributor {
|
|
address: string;
|
|
amount: bigint;
|
|
reserved: bigint;
|
|
locked_contributions: LockedContribution[];
|
|
}
|
|
|
|
export interface WalletInfo {
|
|
contract_claimed: bigint;
|
|
contract_rewards: bigint;
|
|
rewards: bigint;
|
|
}
|
|
|
|
export interface OpenNode {
|
|
bls_pubkey: string;
|
|
cancelled: boolean;
|
|
contract: string;
|
|
contributions: Array<Contributor>;
|
|
fee: number;
|
|
finalized: boolean;
|
|
service_node_pubkey: string;
|
|
service_node_signature: string;
|
|
total_contributions: number;
|
|
}
|
|
|
|
export interface GetOpenNodesResponse {
|
|
nodes: OpenNode[];
|
|
}
|
|
|
|
/** GET /stakes/<32 byte address> */
|
|
export type StakeContributor = {
|
|
address: string;
|
|
amount: number;
|
|
reserved: number;
|
|
};
|
|
|
|
export type Stake = {
|
|
contract_id: number;
|
|
contributors: Array<StakeContributor>;
|
|
deregistration_unlock_height: number | null;
|
|
earned_downtime_blocks: number;
|
|
exited: boolean;
|
|
last_reward_block_height: number | null;
|
|
last_uptime_proof: number | null;
|
|
liquidation_height: number | null;
|
|
operator_address: string;
|
|
operator_fee: number | null;
|
|
requested_unlock_height: number | null;
|
|
service_node_pubkey: string;
|
|
staked_balance: number | null;
|
|
state: NODE_STATE;
|
|
};
|
|
|
|
export interface GetStakesResponse {
|
|
network: NetworkInfo;
|
|
stakes: Array<Stake>;
|
|
historical_stakes: Array<Stake>;
|
|
wallet: WalletInfo;
|
|
error_stakes: Array<{ contract_id: number; error: string }>;
|
|
}
|
|
|
|
/** /store */
|
|
interface StoreRegistrationResponse {
|
|
success: boolean;
|
|
registration: {
|
|
type: 'solo' | 'contract';
|
|
operator: string;
|
|
contract?: string;
|
|
pubkey_ed25519: string;
|
|
pubkey_bls: string;
|
|
sig_ed25519: string;
|
|
sig_bls: string;
|
|
};
|
|
}
|
|
|
|
/** GET /nodes */
|
|
export interface GetNodesResponse {
|
|
network: NetworkInfo;
|
|
nodes: Stake[];
|
|
}
|
|
|
|
/** /registrations */
|
|
export interface Registration {
|
|
type: 'solo' | 'contract';
|
|
operator: string;
|
|
contract?: string;
|
|
pubkey_ed25519: string;
|
|
pubkey_bls: string;
|
|
sig_ed25519: string;
|
|
sig_bls: string;
|
|
timestamp: number;
|
|
}
|
|
|
|
export interface LoadRegistrationsResponse {
|
|
registrations: Registration[];
|
|
}
|
|
|
|
/** /validation */
|
|
interface ValidationError {
|
|
code: string;
|
|
error: string;
|
|
detail?: string;
|
|
}
|
|
|
|
interface ValidateRegistrationResponse {
|
|
success?: boolean;
|
|
error?: ValidationError;
|
|
remaining_contribution?: number;
|
|
remaining_spots?: number;
|
|
remaining_min_contribution?: number;
|
|
}
|
|
|
|
/** GET /exit/<32 byte pubkey> */
|
|
export interface GetNodeExitSignaturesResponse {
|
|
network: NetworkInfo;
|
|
result: BlsExitResponse;
|
|
}
|
|
|
|
/** GET /liquidation/<32 byte pubkey> */
|
|
export interface GetNodeLiquidationResponse {
|
|
network: NetworkInfo;
|
|
result: BlsLiquidationResponse;
|
|
}
|
|
|
|
/** GET /exit_liquidation_list */
|
|
export interface GetExitLiquidationListResponse {
|
|
network: NetworkInfo;
|
|
result: Array<Stake>;
|
|
}
|
|
|
|
/** POST /rewards */
|
|
|
|
export interface GetRewardsClaimSignatureResponse {
|
|
network: NetworkInfo;
|
|
result: BlsRewardsResponse;
|
|
}
|
|
|
|
export type BlsRewardsResponse = {
|
|
amount: number;
|
|
height: number;
|
|
msg_to_sign: string;
|
|
non_signer_indices: Array<number>;
|
|
signature: string;
|
|
};
|
|
|
|
export type BlsExitResponse = {
|
|
bls_pubkey: string;
|
|
msg_to_sign: string;
|
|
non_signer_indices: Array<number>;
|
|
signature: string;
|
|
timestamp: number;
|
|
};
|
|
|
|
export type BlsLiquidationResponse = BlsExitResponse;
|
|
|
|
/** Client types */
|
|
type HTTPMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
|
|
|
|
export interface RequestOptions {
|
|
endpoint: string;
|
|
method: HTTPMethod;
|
|
body?: BodyInit;
|
|
}
|
|
|
|
export interface StakingBackendResponse<T> {
|
|
data: T | null;
|
|
status: number;
|
|
statusText: string;
|
|
}
|
|
|
|
export type Logger = {
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: resolve proper type
|
|
debug: (data: any) => void;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: resolve proper type
|
|
error: (data: any) => void;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: resolve proper type
|
|
time: (data: any) => void;
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: resolve proper type
|
|
timeEnd: (data: any) => void;
|
|
};
|
|
|
|
export interface SSBClientConfig {
|
|
baseUrl: string;
|
|
logger?: Logger;
|
|
errorOn404?: boolean;
|
|
debug?: boolean;
|
|
}
|
|
|
|
/**
|
|
* Client for interacting with the Session Staking Backend API.
|
|
*/
|
|
export class SessionStakingClient {
|
|
private readonly baseUrl: string;
|
|
private readonly debug?: boolean;
|
|
private readonly logger: Logger = console;
|
|
private readonly errorOn404: boolean = false;
|
|
|
|
constructor(config: SSBClientConfig) {
|
|
const { baseUrl, debug, logger, errorOn404 } = config;
|
|
this.debug = debug;
|
|
|
|
if (this.debug) {
|
|
this.logger.debug('Initializing session staking backend client');
|
|
}
|
|
|
|
if (logger) {
|
|
this.logger = logger;
|
|
}
|
|
|
|
this.baseUrl = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl;
|
|
|
|
if (!this.baseUrl) {
|
|
throw new Error('baseUrl is required');
|
|
}
|
|
|
|
if (errorOn404) {
|
|
this.errorOn404 = errorOn404;
|
|
}
|
|
}
|
|
|
|
private async request<T>({
|
|
endpoint,
|
|
method,
|
|
body,
|
|
}: RequestOptions): Promise<StakingBackendResponse<T>> {
|
|
const url = `${this.baseUrl}${endpoint}`;
|
|
|
|
try {
|
|
if (this.debug) {
|
|
this.logger.time(url);
|
|
if (body) this.logger.debug(body);
|
|
}
|
|
const res = await fetch(url, {
|
|
method,
|
|
body,
|
|
});
|
|
|
|
let data: T | null = null;
|
|
if (!res.ok) {
|
|
const errorMessage = `Staking Backend request failed (${res.status}): ${res.statusText}`;
|
|
if (this.errorOn404) {
|
|
throw new Error(errorMessage);
|
|
} else {
|
|
this.logger.error(errorMessage);
|
|
data = null;
|
|
}
|
|
} else {
|
|
data = await res.json();
|
|
}
|
|
|
|
return { data, status: res.status, statusText: res.statusText };
|
|
} finally {
|
|
if (this.debug) this.logger.timeEnd(url);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieves general network information.
|
|
* @returns Network information.
|
|
*/
|
|
public async getNetworkInfo(): Promise<StakingBackendResponse<NetworkInfoResponse>> {
|
|
const options: RequestOptions = {
|
|
endpoint: `/info`,
|
|
method: 'GET',
|
|
};
|
|
return this.request<NetworkInfoResponse>(options);
|
|
}
|
|
|
|
public async getOpenNodes(): Promise<StakingBackendResponse<GetOpenNodesResponse>> {
|
|
const options: RequestOptions = {
|
|
endpoint: `/nodes/open`,
|
|
method: 'GET',
|
|
};
|
|
return this.request<GetOpenNodesResponse>(options);
|
|
}
|
|
|
|
/**
|
|
* Retrieves service nodes associated with the given wallet address.
|
|
* @param address Wallet address.
|
|
* @returns stakes, historical stakes, and wallet information.
|
|
*/
|
|
public async getStakesForWalletAddress({
|
|
address,
|
|
}: {
|
|
address: string;
|
|
}): Promise<StakingBackendResponse<GetStakesResponse>> {
|
|
const options: RequestOptions = {
|
|
endpoint: `/stakes/${address}`,
|
|
method: 'GET',
|
|
};
|
|
return this.request<GetStakesResponse>(options);
|
|
}
|
|
|
|
public async getRewardsClaimSignature({
|
|
address,
|
|
}: {
|
|
address: string;
|
|
}): Promise<StakingBackendResponse<GetRewardsClaimSignatureResponse>> {
|
|
const options: RequestOptions = {
|
|
endpoint: `/rewards/${address}`,
|
|
method: 'POST',
|
|
};
|
|
return this.request<GetRewardsClaimSignatureResponse>(options);
|
|
}
|
|
|
|
public async getNodeExitSignatures({
|
|
nodePubKey,
|
|
}: {
|
|
nodePubKey: string;
|
|
}): Promise<StakingBackendResponse<GetNodeExitSignaturesResponse>> {
|
|
const options: RequestOptions = {
|
|
endpoint: `/exit/${nodePubKey}`,
|
|
method: 'GET',
|
|
};
|
|
return this.request<GetNodeExitSignaturesResponse>(options);
|
|
}
|
|
|
|
public async getNodeLiquidation({
|
|
nodePubKey,
|
|
}: {
|
|
nodePubKey: string;
|
|
}): Promise<StakingBackendResponse<GetNodeLiquidationResponse>> {
|
|
const options: RequestOptions = {
|
|
endpoint: `/liquidation/${nodePubKey}`,
|
|
method: 'GET',
|
|
};
|
|
return this.request<GetNodeLiquidationResponse>(options);
|
|
}
|
|
|
|
public async exitLiquidationList() {
|
|
const options: RequestOptions = {
|
|
endpoint: `/exit_liquidation_list`,
|
|
method: 'GET',
|
|
};
|
|
return this.request<GetExitLiquidationListResponse>(options);
|
|
}
|
|
|
|
public async getNodes(): Promise<StakingBackendResponse<GetNodesResponse>> {
|
|
const options: RequestOptions = {
|
|
endpoint: `/nodes`,
|
|
method: 'GET',
|
|
};
|
|
return this.request<GetNodesResponse>(options);
|
|
}
|
|
|
|
/**
|
|
* Stores or replaces the registration details for a service node.
|
|
* @param snPubkey Service node public key (hex).
|
|
* @param queryParams Registration details.
|
|
* @returns Registration response.
|
|
*/
|
|
public async storeRegistration({
|
|
snPubkey,
|
|
queryParams,
|
|
}: {
|
|
snPubkey: string;
|
|
queryParams: BodyInit;
|
|
}): Promise<StakingBackendResponse<StoreRegistrationResponse>> {
|
|
const options: RequestOptions = {
|
|
endpoint: `/store/${snPubkey}`,
|
|
method: 'GET',
|
|
body: queryParams,
|
|
};
|
|
return this.request<StoreRegistrationResponse>(options);
|
|
}
|
|
|
|
/**
|
|
* Retrieves stored registrations for the given service node public key.
|
|
* @param snPubkey Service node public key (hex).
|
|
* @returns Registrations.
|
|
*/
|
|
public async loadRegistrations({
|
|
snPubkey,
|
|
}: {
|
|
snPubkey: string;
|
|
}): Promise<StakingBackendResponse<LoadRegistrationsResponse>> {
|
|
const options: RequestOptions = {
|
|
endpoint: `/registrations/${snPubkey}`,
|
|
method: 'GET',
|
|
};
|
|
return this.request<LoadRegistrationsResponse>(options);
|
|
}
|
|
|
|
/**
|
|
* Retrieves stored registrations associated with the given operator wallet.
|
|
* @param operator Operator wallet address (Ethereum).
|
|
* @returns Registrations.
|
|
*/
|
|
public async getOperatorRegistrations({
|
|
operator,
|
|
}: {
|
|
operator: string;
|
|
}): Promise<StakingBackendResponse<LoadRegistrationsResponse>> {
|
|
const options: RequestOptions = {
|
|
endpoint: `/registrations/${operator}`,
|
|
method: 'GET',
|
|
};
|
|
return this.request<LoadRegistrationsResponse>(options);
|
|
}
|
|
|
|
/**
|
|
* Validates a registration including fee, stakes, and reserved spot requirements.
|
|
* @param queryParams Registration details.
|
|
* @returns Validation response.
|
|
*/
|
|
public async validateRegistration({
|
|
queryParams,
|
|
}: {
|
|
queryParams: BodyInit;
|
|
}): Promise<StakingBackendResponse<ValidateRegistrationResponse>> {
|
|
const options: RequestOptions = {
|
|
endpoint: `/validate`,
|
|
method: 'GET',
|
|
body: queryParams,
|
|
};
|
|
return this.request<ValidateRegistrationResponse>(options);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new instance of the SessionStakingClient.
|
|
*
|
|
* @param config The configuration object for the SSB client.
|
|
* @returns A new instance of the SessionStakingClient.
|
|
*/
|
|
export const createSessionStakingClient = (config: SSBClientConfig): SessionStakingClient =>
|
|
new SessionStakingClient(config);
|