|
| 1 | +// Copyright (c) 2026 IOTA Stiftung |
| 2 | +// SPDX-License-Identifier: Apache-2.0 |
| 3 | + |
| 4 | +import type { IotaClient } from '@iota/iota-sdk/client'; |
| 5 | +import { normalizeIotaAddress } from '@iota/iota-sdk/utils'; |
| 6 | +import { ValidatorSchema } from '../../types'; |
| 7 | +import type { IotaValidatorSummaryExtended } from '../../types'; |
| 8 | +import { bytesToBase64, getMoveFields, MoveStructFields } from './helpers'; |
| 9 | + |
| 10 | +/** |
| 11 | + * Fetch the full validator metadata from a validator wrapper object. |
| 12 | + * Handles the Versioned unwrapping common to inactive, pending, and candidate validators. |
| 13 | + */ |
| 14 | +export async function getValidatorsMetadata( |
| 15 | + client: IotaClient, |
| 16 | + validatorObjectId: string, |
| 17 | +): Promise<IotaValidatorSummaryExtended | null> { |
| 18 | + const validatorObject = await client.getObject({ |
| 19 | + id: normalizeIotaAddress(validatorObjectId), |
| 20 | + options: { |
| 21 | + showContent: true, |
| 22 | + }, |
| 23 | + }); |
| 24 | + const validator = ValidatorSchema.safeParse(validatorObject.data?.content); |
| 25 | + const validatorFieldId = validator.data?.fields.value.fields.inner.fields.id.id; |
| 26 | + if (!validatorFieldId) { |
| 27 | + return null; |
| 28 | + } |
| 29 | + const dynamicFields = await client.getDynamicFields({ |
| 30 | + parentId: normalizeIotaAddress(validatorFieldId), |
| 31 | + limit: 1, |
| 32 | + }); |
| 33 | + const dfObjectId = dynamicFields.data?.[0]?.objectId; |
| 34 | + if (!dfObjectId) { |
| 35 | + return null; |
| 36 | + } |
| 37 | + const dfObject = await client.getObject({ |
| 38 | + id: normalizeIotaAddress(dfObjectId), |
| 39 | + options: { |
| 40 | + showContent: true, |
| 41 | + }, |
| 42 | + }); |
| 43 | + |
| 44 | + const content = dfObject.data?.content; |
| 45 | + if (!content || content.dataType !== 'moveObject') { |
| 46 | + return null; |
| 47 | + } |
| 48 | + |
| 49 | + const fields = getMoveFields(content); |
| 50 | + const value = fields.value as MoveStructFields; |
| 51 | + if (!value?.fields) { |
| 52 | + return null; |
| 53 | + } |
| 54 | + |
| 55 | + const metadata = (value.fields.metadata as MoveStructFields)?.fields || {}; |
| 56 | + const stakingPool = (value.fields.staking_pool as MoveStructFields)?.fields || {}; |
| 57 | + const exchangeRates = (stakingPool.exchange_rates as MoveStructFields)?.fields || {}; |
| 58 | + |
| 59 | + return { |
| 60 | + authorityPubkeyBytes: bytesToBase64(metadata.authority_pubkey_bytes), |
| 61 | + commissionRate: String(value.fields.commission_rate), |
| 62 | + description: String(metadata.description), |
| 63 | + exchangeRatesId: (exchangeRates.id as { id: string })?.id, |
| 64 | + exchangeRatesSize: String(exchangeRates.size), |
| 65 | + gasPrice: String(value.fields.gas_price), |
| 66 | + imageUrl: String(metadata.image_url), |
| 67 | + iotaAddress: String(metadata.iota_address), |
| 68 | + name: String(metadata.name), |
| 69 | + netAddress: String(metadata.net_address), |
| 70 | + networkPubkeyBytes: bytesToBase64(metadata.network_pubkey_bytes), |
| 71 | + nextEpochCommissionRate: String(value.fields.next_epoch_commission_rate), |
| 72 | + nextEpochGasPrice: String(value.fields.next_epoch_gas_price), |
| 73 | + nextEpochStake: String(value.fields.next_epoch_stake), |
| 74 | + operationCapId: String(value.fields.operation_cap_id), |
| 75 | + p2pAddress: String(metadata.p2p_address), |
| 76 | + pendingPoolTokenWithdraw: String(stakingPool.pending_pool_token_withdraw), |
| 77 | + pendingStake: String(stakingPool.pending_stake), |
| 78 | + pendingTotalIotaWithdraw: String(stakingPool.pending_total_iota_withdraw), |
| 79 | + poolTokenBalance: String(stakingPool.pool_token_balance), |
| 80 | + primaryAddress: String(metadata.primary_address), |
| 81 | + projectUrl: String(metadata.project_url), |
| 82 | + proofOfPossessionBytes: bytesToBase64(metadata.proof_of_possession), |
| 83 | + protocolPubkeyBytes: bytesToBase64(metadata.protocol_pubkey_bytes), |
| 84 | + rewardsPool: String(stakingPool.rewards_pool), |
| 85 | + stakingPoolId: (stakingPool.id as { id: string })?.id, |
| 86 | + stakingPoolIotaBalance: String(stakingPool.iota_balance), |
| 87 | + votingPower: String(value.fields.voting_power), |
| 88 | + }; |
| 89 | +} |
0 commit comments