mirror of
https://github.com/lux-tel/websites.git
synced 2026-10-30 20:31:36 +00:00
465 lines
19 KiB
TypeScript
465 lines
19 KiB
TypeScript
import { isAddress } from 'viem';
|
|
import { z } from 'zod';
|
|
import { ARBITRUM_EVENT, CONTRIBUTION_CONTRACT_STATUS, EXIT_TYPE } from './enums';
|
|
import { isEd25519PublicKey } from './refine';
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
// //
|
|
// Base Schemas //
|
|
// //
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
export const ethereumAddressSchema = z.string().refine(isAddress);
|
|
export const ed25519PublicKeySchema = z.string().refine(isEd25519PublicKey);
|
|
export const nonSignerIndicesSchema = z.array(z.coerce.bigint());
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
// //
|
|
// Network Info //
|
|
// //
|
|
//////////////////////////////////////////////////////////////
|
|
export const networkInfoSchema = z.object({
|
|
/** The number of active (non-decommissioned) nodes on the network */
|
|
active_node_count: z.number(),
|
|
/** Hash of the latest appchain block */
|
|
block_hash: z.string(),
|
|
/** Height of the latest appchain block */
|
|
block_height: z.number(),
|
|
/** Timestamp (seconds) of the latest appchain block */
|
|
block_timestamp: z.number(),
|
|
/** Current hard fork */
|
|
hard_fork: z.number(),
|
|
/** Hash of the last immutable appchain block */
|
|
immutable_block_hash: z.string(),
|
|
/** Height of the last immutable appchain block */
|
|
immutable_block_height: z.number(),
|
|
/** Maximum number of stakers allowed in a single node */
|
|
max_stakers: z.number(),
|
|
/** Median operator fee of registered nodes */
|
|
median_operator_fee: z.number(),
|
|
/** Minimum operator contribution required to register a node */
|
|
min_operator_contribution: z.number(),
|
|
/** Network type eg: mainnet, testnet */
|
|
nettype: z.string(),
|
|
/** Number of nodes on the network @see {@link networkInfoSchema.active_node_count} for active nodes */
|
|
node_count: z.number(),
|
|
/** Target timestamp for the next block */
|
|
pulse_target_timestamp: z.number(),
|
|
/** Current staking requirement for a single node */
|
|
staking_requirement: z.number(),
|
|
/** Version of the node the backend is running */
|
|
version: z.string(),
|
|
});
|
|
|
|
export type NetworkInfo = z.infer<typeof networkInfoSchema>;
|
|
|
|
export const networkInfoResponseSchema = z.object({
|
|
network: networkInfoSchema,
|
|
t: z.number(),
|
|
});
|
|
|
|
export type NetworkInfoResponse = z.infer<typeof networkInfoResponseSchema>;
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
// //
|
|
// Added BLS Keys //
|
|
// //
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
export const addedBlsKeysSchema = z.record(z.string(), z.number());
|
|
|
|
export type AddedBlsKeys = z.infer<typeof addedBlsKeysSchema>;
|
|
|
|
export const nodesBlsKeysResponseSchema = z.object({
|
|
network: networkInfoSchema,
|
|
bls_keys: addedBlsKeysSchema,
|
|
t: z.number(),
|
|
});
|
|
|
|
export type NodesBlsKeysResponse = z.infer<typeof nodesBlsKeysResponseSchema>;
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
// //
|
|
// Arbitrum Events //
|
|
// //
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
export const arbitrumEventSchema = z.object({
|
|
/** Event data arguments, should be parseable as a JSON object */
|
|
args: z.unknown(),
|
|
/** Block this event was emitted in */
|
|
block: z.number(),
|
|
/** Index of this event in the block */
|
|
log_index: z.number(),
|
|
/** Main argument of the event. NOTE: This is super subjective and determined by the backend. It's the "most useful"
|
|
* argument, if the event is not emitted from a singleton contract it will be the contract's address, otherwise it
|
|
* will be something indexable or important. TODO (chore): document main_arg for each contract and event name */
|
|
main_arg: z.string().nullable(),
|
|
/** Event name */
|
|
name: z.nativeEnum(ARBITRUM_EVENT),
|
|
/** Hash of the transaction this event was emitted in */
|
|
tx: z.string(),
|
|
});
|
|
|
|
export type ArbitrumEvent = z.infer<typeof arbitrumEventSchema>;
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
// //
|
|
// Contribution Contracts //
|
|
// //
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
export const contributionContractContributorSchema = z.object({
|
|
/** Contract address contributed to */
|
|
address: ethereumAddressSchema,
|
|
/** Beneficiary address of the contributor. This address will receive the rewards */
|
|
beneficiary_address: ethereumAddressSchema.nullable(),
|
|
/** Amount contributed to the contract (atomic) */
|
|
amount: z.coerce.bigint(),
|
|
/** Amount reserved for the contributor (atomic) */
|
|
reserved: z.coerce.bigint(),
|
|
});
|
|
|
|
export type ContributionContractContributor = z.infer<typeof contributionContractContributorSchema>;
|
|
|
|
export const isContributionContractContributor = (
|
|
value: object
|
|
): value is ContributionContractContributor => {
|
|
// Because of the bigint coercion this needs to be explicitly checked before parsing
|
|
if (!value || !('reserved' in value) || value.reserved === undefined) return false;
|
|
return contributionContractContributorSchema.safeParse(value).success;
|
|
};
|
|
|
|
const contributionContractBaseSchema = z.object({
|
|
/** Contract address */
|
|
address: ethereumAddressSchema,
|
|
/** Contributors to the contract */
|
|
contributors: z.array(contributionContractContributorSchema),
|
|
/** Events emitted by the contract */
|
|
events: z.array(arbitrumEventSchema),
|
|
/** Address of the node operator */
|
|
operator_address: ethereumAddressSchema,
|
|
/** Public ed25519 key of the node. This is the node's "SN key" */
|
|
service_node_pubkey: ed25519PublicKeySchema,
|
|
/** Status of the contract @see {@link CONTRIBUTION_CONTRACT_STATUS} */
|
|
status: z
|
|
.nativeEnum(CONTRIBUTION_CONTRACT_STATUS)
|
|
.default(CONTRIBUTION_CONTRACT_STATUS.WaitForOperatorContrib),
|
|
});
|
|
|
|
export const contributionContractNotReadySchema = contributionContractBaseSchema.extend({
|
|
/**
|
|
* Basis points node fee (divide by 10,000 to get a decimal percentage)
|
|
* Nullable because it's set ~250ms after the contract is created
|
|
*/
|
|
fee: z.number().nullable(),
|
|
/** If true, the contract must be manually finalized by the operator before it will join the network */
|
|
manual_finalize: z.coerce.boolean(),
|
|
/** Nullable because it's set ~250ms after the contract is created */
|
|
pubkey_bls: z.string().nullable(),
|
|
});
|
|
|
|
export const contributionContractSchema = contributionContractBaseSchema.extend({
|
|
/**
|
|
* Basis points node fee (divide by 10,000 to get a decimal percentage)
|
|
* Nullable because it's set ~250ms after the contract is created
|
|
*/
|
|
fee: z.number(),
|
|
/** If true, the contract must be manually finalized by the operator before it will join the network */
|
|
manual_finalize: z.coerce.boolean(),
|
|
/** Nullable because it's set ~250ms after the contract is created */
|
|
pubkey_bls: z.string(),
|
|
});
|
|
|
|
export type ContributionContractNotReady = z.infer<typeof contributionContractNotReadySchema>;
|
|
export type ContributionContract = z.infer<typeof contributionContractSchema>;
|
|
|
|
export const contributionContractResponseSchema = z.object({
|
|
network: networkInfoSchema,
|
|
t: z.number(),
|
|
contracts: z.array(contributionContractNotReadySchema),
|
|
added_bls_keys: addedBlsKeysSchema,
|
|
});
|
|
|
|
export type ContributionContractResponse = z.infer<typeof contributionContractResponseSchema>;
|
|
|
|
export const contributionContractByKeyResponseSchema = z.object({
|
|
network: networkInfoSchema,
|
|
contract: contributionContractNotReadySchema.nullable(),
|
|
t: z.number(),
|
|
});
|
|
|
|
export type ContributionContractByKeyResponse = z.infer<
|
|
typeof contributionContractByKeyResponseSchema
|
|
>;
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
// //
|
|
// Vesting Contracts //
|
|
// //
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
export const vestingContractSchema = z.object({
|
|
/** Contract address */
|
|
address: ethereumAddressSchema,
|
|
/** Beneficiary address of the contract. This is the contract's owner/controller */
|
|
beneficiary: ethereumAddressSchema,
|
|
/** Initial amount of tokens in the contract */
|
|
initial_amount: z.coerce.bigint(),
|
|
/** Initial beneficiary address of the contract */
|
|
initial_beneficiary: ethereumAddressSchema,
|
|
/** Address of the contract revoker. This address can effectively take control of the contract */
|
|
revoker: ethereumAddressSchema,
|
|
/** Timestamp when the contract's lock period ends */
|
|
time_end: z.coerce.number(),
|
|
/** Timestamp when the contract's lock period starts */
|
|
time_start: z.coerce.number(),
|
|
/** Whether the beneficiary address is transferable */
|
|
transferable_beneficiary: z.coerce.boolean(),
|
|
});
|
|
|
|
export type VestingContract = z.infer<typeof vestingContractSchema>;
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
// //
|
|
// Node Registrations //
|
|
// //
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
export const nodeRegistrationSchema = z.object({
|
|
/** Ethereum address of the node operator */
|
|
operator: ethereumAddressSchema,
|
|
/** BLS public key of the node */
|
|
pubkey_bls: z.string(),
|
|
/** Ed25519 public key of the node. This is the node's "SN key" */
|
|
pubkey_ed25519: ed25519PublicKeySchema,
|
|
/** BLS signature of the node */
|
|
sig_bls: z.string(),
|
|
/** Ed25519 signature of the node */
|
|
sig_ed25519: z.string(),
|
|
/** Timestamp of when the registration command was executed */
|
|
timestamp: z.number(),
|
|
});
|
|
|
|
export type Registration = z.infer<typeof nodeRegistrationSchema>;
|
|
|
|
export const registrationsResponseSchema = z.object({
|
|
network: networkInfoSchema,
|
|
registrations: z.array(nodeRegistrationSchema),
|
|
});
|
|
|
|
export type RegistrationsResponse = z.infer<typeof registrationsResponseSchema>;
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
// //
|
|
// Rewards //
|
|
// //
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
export const rewardsInfoSchema = z.object({
|
|
/** Total amount of claimable tokens for the given address. This includes the earnt rewards as well as unlocked stakes that are available to be claimed. */
|
|
amount: z.coerce.bigint(),
|
|
/** Total amount of tokens in the lifetime of the network that have been liquidated from the stakes for this address. */
|
|
lifetime_liquidated_stakes: z.coerce.bigint(),
|
|
/** Total amount of tokens in the lifetime of the network that has been staked into nodes for this address. */
|
|
lifetime_locked_stakes: z.coerce.bigint(),
|
|
/** Total amount of tokens in the lifetime of the network that has been earnt from staking into nodes for this address. */
|
|
lifetime_rewards: z.coerce.bigint(),
|
|
/** Total amount of tokens in the lifetime of the network that has been unlocked from the nodes this address has staked into. */
|
|
lifetime_unlocked_stakes: z.coerce.bigint(),
|
|
/** Amount of tokens currently locked into nodes on the network. This is defined as `lifetime locked - lifetime unlocked`. */
|
|
locked_stakes: z.coerce.bigint(),
|
|
/** Amount of tokens that have been unstaked from nodes but cannot be claimed until the time lock on those individual stakes have been unlocked. */
|
|
timelocked_stakes: z.coerce.bigint(),
|
|
/** Amount of tokens that have been claimed from the nodes this address has staked into. */
|
|
claimed_stakes: z.coerce.bigint(),
|
|
/** Amount of tokens that have been claimed from the rewards that have been earned from the nodes this address has staked into. */
|
|
claimed_rewards: z.coerce.bigint(),
|
|
});
|
|
|
|
export type BlsRewardsInfo = z.infer<typeof rewardsInfoSchema>;
|
|
|
|
export const blsRewardsResponseSchema = z.object({
|
|
rewards: rewardsInfoSchema,
|
|
network: networkInfoSchema,
|
|
t: z.number(),
|
|
});
|
|
|
|
export type BlsRewardsResponse = z.infer<typeof blsRewardsResponseSchema>;
|
|
|
|
export const blsRewardsSignatureSchema = z.object({
|
|
aggregate_pubkey: z.string(),
|
|
/** The lifetime amount that the address can submit to the smart contract (the claimed amount
|
|
* allowed will be this minus any previously claimed amounts)*/
|
|
amount: z.coerce.bigint(),
|
|
/** The appchain blockchain height that the rewards have been calculated for */
|
|
height: z.number(),
|
|
/** The message that has been signed by the network */
|
|
msg_to_sign: z.string(),
|
|
/** Non signer contract ids (The nodes that didn't sign the rewards amount) */
|
|
non_signer_indices: nonSignerIndicesSchema,
|
|
/** Aggregate BLS signature for the rewards amount */
|
|
signature: z.string(),
|
|
});
|
|
|
|
export type BlsRewardsSignature = z.infer<typeof blsRewardsSignatureSchema>;
|
|
|
|
export const blsRewardsSignatureResponseSchema = z.object({
|
|
network: networkInfoSchema,
|
|
rewards: blsRewardsSignatureSchema,
|
|
t: z.number(),
|
|
});
|
|
|
|
export type BlsRewardsSignatureResponse = z.infer<typeof blsRewardsSignatureResponseSchema>;
|
|
|
|
export const dailyRewardInfo = z.object({
|
|
block: z.number(),
|
|
lifetime_rewards: z.number(),
|
|
timestamp: z.number(),
|
|
});
|
|
|
|
export type DailyRewardInfo = z.infer<typeof dailyRewardInfo>;
|
|
|
|
export const dailyRewardsResponseSchema = z.object({
|
|
network: networkInfoSchema,
|
|
rewards: z.array(dailyRewardInfo),
|
|
});
|
|
|
|
export type DailyRewardsResponse = z.infer<typeof dailyRewardsResponseSchema>;
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
// //
|
|
// Exits & Liquidations //
|
|
// //
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
export const blsExitSignatureSchema = z.object({
|
|
/** The bls pubkey of the node to exit/liquidate */
|
|
bls_pubkey: z.string(),
|
|
/** The message that has been signed by the network */
|
|
msg_to_sign: z.string(),
|
|
/** Non signer contract ids (The nodes that didn't sign the exit) */
|
|
non_signer_indices: nonSignerIndicesSchema,
|
|
/** Exit BLS signature */
|
|
signature: z.string(),
|
|
/** Timestamp of when the exit signature was created */
|
|
timestamp: z.number(),
|
|
});
|
|
|
|
export type BlsExitSignature = z.infer<typeof blsExitSignatureSchema>;
|
|
|
|
export const blsExitSignatureResponseSchema = z.object({
|
|
network: networkInfoSchema,
|
|
result: blsExitSignatureSchema,
|
|
t: z.number(),
|
|
});
|
|
|
|
export type BlsExitSignatureResponse = z.infer<typeof blsExitSignatureResponseSchema>;
|
|
|
|
export const exitLiquidationListItemSchema = z.object({
|
|
info: z.object({
|
|
bls_public_key: z.string(),
|
|
}),
|
|
height: z.number(),
|
|
liquidation_height: z.number(),
|
|
service_node_pubkey: z.string(),
|
|
type: z.string(),
|
|
version: z.string(),
|
|
});
|
|
|
|
export type ExitLiquidationListItem = z.infer<typeof exitLiquidationListItemSchema>;
|
|
|
|
export const exitLiquidationListSchema = z.object({
|
|
network: networkInfoSchema,
|
|
result: z.array(exitLiquidationListItemSchema),
|
|
t: z.number(),
|
|
});
|
|
|
|
export type ExitLiquidationListResponse = z.infer<typeof exitLiquidationListSchema>;
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
// //
|
|
// Stakes //
|
|
// //
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
export const stakeContributorSchema = z.object({
|
|
address: ethereumAddressSchema,
|
|
beneficiary: ethereumAddressSchema,
|
|
amount: z.coerce.bigint(),
|
|
});
|
|
|
|
export type StakeContributor = z.infer<typeof stakeContributorSchema>;
|
|
|
|
export const stakeSchema = z.object({
|
|
/** Whether the stake is running. If false, the stake is decommissioned */
|
|
active: z.coerce.boolean(),
|
|
/** Contract id of the stake. It's integer id in the sn rewards contract */
|
|
contract_id: z.number(),
|
|
/** Contributors to the stake */
|
|
contributors: z.array(stakeContributorSchema),
|
|
// decommission_count: z.number(),
|
|
deregistration_height: z.number().nullable(),
|
|
/** Number of blocks earned towards decommissioning (if currently active),
|
|
* or the number of blocks remaining until the service node is eligible
|
|
* for deregistration (if currently decommissioned). */
|
|
earned_downtime_blocks: z.number(),
|
|
/** Arbitrum events associated with the stake */
|
|
events: z.array(arbitrumEventSchema),
|
|
exit_type: z.nativeEnum(EXIT_TYPE).nullable(),
|
|
// fetched_block_height: z.number(),
|
|
// funded: z.coerce.boolean(),
|
|
// is_liquidatable: z.coerce.boolean(),
|
|
// is_removable: z.coerce.boolean(),
|
|
/** Last appchain block height the rewards awarded for */
|
|
last_reward_block_height: z.number(),
|
|
/** Last appchain block height the node sent an uptime proof */
|
|
last_uptime_proof: z.number(),
|
|
liquidation_height: z.number().nullable(),
|
|
// lokinet_version: z.array(z.number()).nullable(),
|
|
/** Ethereum address of the node operator */
|
|
operator_address: ethereumAddressSchema,
|
|
operator_fee: z.number(),
|
|
// payable: z.coerce.boolean(),
|
|
/** BLS public key of the node */
|
|
pubkey_bls: z.string(),
|
|
/** Ed25519 public key of the node */
|
|
pubkey_ed25519: ed25519PublicKeySchema,
|
|
// public_ip: z.string().nullable(),
|
|
// pulse_votes: z.object({
|
|
// missed: z.array(z.array(z.number())),
|
|
// }),
|
|
// quorumnet_port: z.number().nullable(),
|
|
/** Appchain blockchain height that the registration finalized */
|
|
registration_height: z.number(),
|
|
// registration_hf_version: z.string(),
|
|
/** Appchain blockchain height that the node requested to unlock */
|
|
requested_unlock_height: z.number(),
|
|
/** Ed25519 public key of the node */
|
|
service_node_pubkey: ed25519PublicKeySchema,
|
|
// service_node_version: z.array(z.number()),
|
|
/** Total stake amount required for the node at registration time */
|
|
staking_requirement: z.coerce.bigint(),
|
|
// state_height: z.number(),
|
|
// storage_lmq_port: z.number().nullable(),
|
|
// storage_port: z.number().nullable(),
|
|
// storage_server_version: z.array(z.number()),
|
|
// swarm: z.string().nullable(),
|
|
// swarm_id: z.string().nullable(),
|
|
/** Total amount contributed to the node */
|
|
total_contributed: z.coerce.bigint(),
|
|
});
|
|
|
|
export type Stake = z.infer<typeof stakeSchema>;
|
|
|
|
export const stakesResponseSchema = z.object({
|
|
network: networkInfoSchema,
|
|
contracts: z.array(contributionContractNotReadySchema),
|
|
stakes: z.array(stakeSchema),
|
|
vesting: z.array(vestingContractSchema),
|
|
added_bls_keys: addedBlsKeysSchema,
|
|
t: z.number(),
|
|
});
|
|
|
|
export type StakesResponse = z.infer<typeof stakesResponseSchema>;
|