mirror of
https://github.com/lux-tel/websites.git
synced 2026-10-30 20:31:36 +00:00
215 lines
7.5 KiB
TypeScript
215 lines
7.5 KiB
TypeScript
import { ARBITRUM_EVENT, CONTRIBUTION_CONTRACT_STATUS } from '@session/staking-api-js/enums';
|
|
import {
|
|
type ContributionContract,
|
|
contributionContractSchema,
|
|
} from '@session/staking-api-js/schema';
|
|
import { areHexesEqual } from '@session/util-crypto/string';
|
|
import type { Address } from 'viem';
|
|
import { getTotalStakedAmountForAddress } from '../components/getTotalStakedAmountForAddress';
|
|
import logger from '../lib/logger';
|
|
import { sortEvents } from './parseEvents';
|
|
import { sortingReservedContractsDesc, sortingTotalStakedDesc } from './parseStakes';
|
|
|
|
const contractStateSortOrderIfOperator = {
|
|
[CONTRIBUTION_CONTRACT_STATUS.WaitForFinalized]: 1,
|
|
[CONTRIBUTION_CONTRACT_STATUS.WaitForOperatorContrib]: 2,
|
|
[CONTRIBUTION_CONTRACT_STATUS.OpenForPublicContrib]: 3,
|
|
[CONTRIBUTION_CONTRACT_STATUS.Finalized]: 4,
|
|
};
|
|
|
|
/**
|
|
* If the connected wallet is the contract operator, then the contracts are sorted by {@link contractStateSortOrderIfOperator}
|
|
*
|
|
* If the state is the same OR the connected wallet isn't the contract operator, then the contract are sorted by the total staked amount descending
|
|
* then by the operator fee ascending
|
|
*/
|
|
export function sortContracts(a: ContributionContract, b: ContributionContract, address?: Address) {
|
|
const operatorA = areHexesEqual(a.operator_address, address);
|
|
const operatorB = areHexesEqual(b.operator_address, address);
|
|
|
|
const priorityA = operatorA
|
|
? (contractStateSortOrderIfOperator[a.status] ?? Number.POSITIVE_INFINITY)
|
|
: Number.POSITIVE_INFINITY;
|
|
const priorityB = operatorB
|
|
? (contractStateSortOrderIfOperator[b.status] ?? Number.POSITIVE_INFINITY)
|
|
: Number.POSITIVE_INFINITY;
|
|
|
|
if (priorityA !== priorityB) {
|
|
// Priority ascending
|
|
return priorityA - priorityB;
|
|
}
|
|
|
|
const reservedSort = sortingReservedContractsDesc(a, b, address);
|
|
|
|
if (reservedSort !== 0) {
|
|
return -reservedSort;
|
|
}
|
|
|
|
const stakeSort = sortingTotalStakedDesc(a, b, address);
|
|
if (stakeSort !== 0) {
|
|
return -stakeSort;
|
|
}
|
|
|
|
if (a.fee !== b.fee) {
|
|
return a.fee - b.fee;
|
|
}
|
|
|
|
const openForContributionBlockA =
|
|
a.events.find(({ name }) => name === ARBITRUM_EVENT.OpenForPublicContribution)?.block ??
|
|
Number.POSITIVE_INFINITY;
|
|
const openForContributionBlockB =
|
|
b.events.find(({ name }) => name === ARBITRUM_EVENT.OpenForPublicContribution)?.block ??
|
|
Number.POSITIVE_INFINITY;
|
|
|
|
return openForContributionBlockA - openForContributionBlockB;
|
|
}
|
|
|
|
export type ParseStakesParams = {
|
|
contracts: Array<ContributionContract>;
|
|
address?: Address;
|
|
blockHeight: number;
|
|
addedBlsKeys: Record<string, number>;
|
|
nodeMinLifespanArbBlocks: number;
|
|
runningStakesBlsKeysSet: Set<string>;
|
|
};
|
|
|
|
/**
|
|
* Sorts contracts by deploy block descending.
|
|
* @param contracts - The contracts to sort.
|
|
* @returns The sorted contracts.
|
|
*/
|
|
export function sortContractByDeployBlockDesc(contracts: Array<ContributionContract>) {
|
|
const deployBlockMap = new Map<Address, number>();
|
|
const _contracts: Array<ContributionContract> = [];
|
|
|
|
for (const contract of contracts) {
|
|
contract.events.sort(sortEvents);
|
|
const deployEventBlock = contract.events.find(
|
|
(event) => event.name === ARBITRUM_EVENT.NewServiceNodeContributionContract
|
|
)?.block;
|
|
if (deployEventBlock) {
|
|
deployBlockMap.set(contract.address, deployEventBlock);
|
|
}
|
|
_contracts.push(contract);
|
|
}
|
|
|
|
/**
|
|
* Sort contracts by the block number they were deployed at
|
|
* The nullish coalescing operator will never get called, but this is required to satisfy the transpiler
|
|
*/
|
|
_contracts.sort(
|
|
(a, b) => (deployBlockMap.get(b.address) ?? 0) - (deployBlockMap.get(a.address) ?? 0)
|
|
);
|
|
|
|
return _contracts;
|
|
}
|
|
|
|
/**
|
|
* Parses the stakes and contracts.
|
|
* @param contracts - The contracts to parse.
|
|
* @param address - The address to filter by.
|
|
* @param addedBlsKeys - The added BLS keys.
|
|
* @param runningStakesBlsKeysSet - The running stakes BLS keys set.
|
|
* @param nodeMinLifespanArbBlocks - The node min lifespan in Arbitrum blocks.
|
|
* @returns The parsed stakes and contracts.
|
|
*/
|
|
export function parseContracts({
|
|
contracts,
|
|
address,
|
|
addedBlsKeys,
|
|
runningStakesBlsKeysSet,
|
|
nodeMinLifespanArbBlocks,
|
|
}: ParseStakesParams) {
|
|
const networkBlsKeys = new Set(Object.keys(addedBlsKeys));
|
|
const networkContractIds = new Set(Object.values(addedBlsKeys));
|
|
|
|
const _contracts = sortContractByDeployBlockDesc(contracts);
|
|
|
|
const added = new Set();
|
|
const hiddenContractsWithStakes: Array<ContributionContract> = [];
|
|
const visibleContracts: Array<ContributionContract> = [];
|
|
const joiningContracts: Array<ContributionContract> = [];
|
|
const awaitingOperatorContracts: Array<ContributionContract> = [];
|
|
|
|
/**
|
|
* The contract array is pre-sorted in descending order by the block number it was deployed at.
|
|
* The "latest" of each contract is added to the visible contracts array, any future duplicate
|
|
* contracts are hidden unless the wallet has a stake for that contract.
|
|
*/
|
|
for (const contract of _contracts) {
|
|
const { pubkey_bls, status, events, contributors } = contract;
|
|
|
|
if (added.has(pubkey_bls) || runningStakesBlsKeysSet.has(pubkey_bls)) {
|
|
if (
|
|
status !== CONTRIBUTION_CONTRACT_STATUS.Finalized &&
|
|
address &&
|
|
getTotalStakedAmountForAddress(contributors, address) > 0n
|
|
) {
|
|
logger.debug(
|
|
`Contract has duplicate pubkey, but has stakes, showing with warning: ${pubkey_bls}`
|
|
);
|
|
hiddenContractsWithStakes.push(contract);
|
|
} else {
|
|
logger.debug(`Contract has duplicate pubkey, hiding: ${pubkey_bls}`);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (status === CONTRIBUTION_CONTRACT_STATUS.Finalized) {
|
|
const lastFinalized = events.filter((event) => event.name === 'Finalized')[0];
|
|
if (!lastFinalized) {
|
|
logger.warn(`Contract is finalized, but no Finalized event, showing: ${pubkey_bls}`);
|
|
} else if (lastFinalized.block > nodeMinLifespanArbBlocks) {
|
|
logger.debug(
|
|
`Contract was finalized at block ${lastFinalized.block}, this is within the minimum lifespan (${nodeMinLifespanArbBlocks}), showing: ${pubkey_bls}`
|
|
);
|
|
joiningContracts.push(contract);
|
|
added.add(pubkey_bls);
|
|
continue;
|
|
} else {
|
|
logger.debug(
|
|
`Contract was finalized at block ${lastFinalized.block}, this is greater than the lifespan limit (${nodeMinLifespanArbBlocks}), hiding: ${pubkey_bls}`
|
|
);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (contract.status === CONTRIBUTION_CONTRACT_STATUS.WaitForOperatorContrib) {
|
|
awaitingOperatorContracts.push(contract);
|
|
added.add(pubkey_bls);
|
|
continue;
|
|
}
|
|
|
|
visibleContracts.push(contract);
|
|
added.add(pubkey_bls);
|
|
}
|
|
hiddenContractsWithStakes.sort((a, b) => sortContracts(a, b, address));
|
|
visibleContracts.sort((a, b) => sortContracts(a, b, address));
|
|
joiningContracts.sort((a, b) => sortContracts(a, b, address));
|
|
|
|
return {
|
|
visibleContracts,
|
|
joiningContracts,
|
|
hiddenContractsWithStakes,
|
|
awaitingOperatorContracts,
|
|
networkBlsKeys,
|
|
networkContractIds,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Filters out the contracts that are not ready to be used.
|
|
* @param contracts - The contracts to filter.
|
|
* @returns The filtered contracts.
|
|
*/
|
|
export function getReadyContracts(contracts: Array<object>) {
|
|
const readyContracts: Array<ContributionContract> = [];
|
|
for (const contract of contracts) {
|
|
if (contributionContractSchema.safeParse(contract).success) {
|
|
// the safeParse assets the type, so we can safely cast
|
|
readyContracts.push(contract as ContributionContract);
|
|
}
|
|
}
|
|
return readyContracts;
|
|
}
|