From 9259b68a3d539e7eff2fe62948196a0de9dfd12f Mon Sep 17 00:00:00 2001 From: Kurdistan Tech Ministry Date: Sat, 21 Feb 2026 14:23:58 +0300 Subject: [PATCH] Refactor: remove dead relay staking code, add AH reward handlers - Delete NewEra.ts (all relay staking code was dead) - Refactor Rewards.ts: remove relay/parachain handlers, add handleAHRewarded/handleAHSlashed for Asset Hub staking rewards - Add staking::Rewarded and staking::Slashed handlers to pezkuwi-assethub.yaml - Remove dead cachedStakingRewardEraIndex from Cache.ts - Clean unused PEZKUWI_RELAY_GENESIS import from PoolRewards.ts --- pezkuwi-assethub.yaml | 12 + src/index.ts | 1 - src/mappings/Cache.ts | 25 -- src/mappings/NewEra.ts | 452 ------------------------------------ src/mappings/PoolRewards.ts | 1 - src/mappings/Rewards.ts | 131 +++-------- 6 files changed, 40 insertions(+), 582 deletions(-) delete mode 100644 src/mappings/NewEra.ts diff --git a/pezkuwi-assethub.yaml b/pezkuwi-assethub.yaml index 0959584..28c941c 100644 --- a/pezkuwi-assethub.yaml +++ b/pezkuwi-assethub.yaml @@ -61,6 +61,18 @@ dataSources: filter: module: nominationPools method: UnbondingPoolSlashed + # Staking rewards on AH (validators + direct nominators) + - handler: handleAHRewarded + kind: substrate/EventHandler + filter: + module: staking + method: Rewarded + # Staking slashes on AH + - handler: handleAHSlashed + kind: substrate/EventHandler + filter: + module: staking + method: Slashed # Era changes on AH (staking pallet lives here) - old format - handler: handleAHNewEra kind: substrate/EventHandler diff --git a/src/index.ts b/src/index.ts index c66bb3a..670abed 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,7 +3,6 @@ export * from "./mappings/HistoryElements"; export * from "./mappings/Rewards"; export * from "./mappings/PoolRewards"; export * from "./mappings/Transfers"; -export * from "./mappings/NewEra"; export * from "./mappings/PoolStakers"; export * from "./mappings/Governance"; import "@pezkuwi/api-augment"; diff --git a/src/mappings/Cache.ts b/src/mappings/Cache.ts index 00cba96..b57e1ab 100644 --- a/src/mappings/Cache.ts +++ b/src/mappings/Cache.ts @@ -10,8 +10,6 @@ let rewardDestinationByAddress: { let controllersByStash: { [blockId: string]: { [address: string]: string } } = {}; -let parachainStakingRewardEra: { [blockId: string]: number } = {}; - let poolMembers: { [blockId: number]: [string, any][]; } = {}; @@ -183,29 +181,6 @@ export async function cachedController( } } -export async function cachedStakingRewardEraIndex( - event: SubstrateEvent, -): Promise { - const blockId = blockNumber(event); - let cachedEra = parachainStakingRewardEra[blockId]; - - if (cachedEra !== undefined) { - return cachedEra; - } else { - const era = await api.query.parachainStaking.round(); - - const paymentDelay = - api.consts.parachainStaking.rewardPaymentDelay.toHuman(); - // HACK: used to get data from object - const eraIndex = - (era.toJSON() as { current: number }).current - Number(paymentDelay); - - parachainStakingRewardEra = {}; - parachainStakingRewardEra[blockId] = eraIndex; - return eraIndex; - } -} - export async function getPoolMembers( blockId: number, ): Promise<[string, any][]> { diff --git a/src/mappings/NewEra.ts b/src/mappings/NewEra.ts deleted file mode 100644 index db26882..0000000 --- a/src/mappings/NewEra.ts +++ /dev/null @@ -1,452 +0,0 @@ -import { SubstrateEvent, SubstrateBlock } from "@subql/types"; -import { eventId } from "./common"; -import { EraValidatorInfo, StakingApy, ActiveStaker } from "../types"; -import { IndividualExposure } from "../types"; -import { Option } from "@pezkuwi/types"; -import { Exposure } from "@pezkuwi/types/interfaces/staking"; -import { - PEZKUWI_RELAY_GENESIS, - PEZKUWI_ASSET_HUB_GENESIS, - STAKING_TYPE_RELAYCHAIN, - STAKING_TYPE_NOMINATION_POOL, - INFLATION_FALLOFF, - INFLATION_MAX, - INFLATION_MIN, - INFLATION_STAKE_TARGET, - PERBILL_DIVISOR, -} from "./constants"; - -let relayStakersInitialized = false; - -/** - * Block handler: on the FIRST block processed, query the live chain state - * for all current era's elected nominators and validators, then save them - * as ActiveStakers. This ensures existing stakers are captured even if - * StakersElected events were missed or had parsing issues. - */ -export async function handleRelayBlock(block: SubstrateBlock): Promise { - if (relayStakersInitialized) return; - relayStakersInitialized = true; - - logger.info("Initializing active relay stakers from live chain state..."); - - // Safety: staking pallet was removed from relay chain in spec 1_020_006 - if (!api.query.staking || !api.query.staking.activeEra) { - logger.info( - "Staking pallet not available on relay chain - skipping relay staker init", - ); - return; - } - - let activeEraOpt: Option; - try { - activeEraOpt = (await api.query.staking.activeEra()) as Option; - } catch (e) { - logger.warn(`Failed to query staking.activeEra on relay: ${e}`); - return; - } - if (activeEraOpt.isNone) { - logger.info("No active era found on relay chain"); - return; - } - const currentEra = activeEraOpt.unwrap().index.toNumber(); - logger.info(`Current active era: ${currentEra}`); - - const activeNominators = new Set(); - const activeValidators = new Set(); - - // Read all validators from overview (includes validators with only self-stake) - const overviews = - await api.query.staking.erasStakersOverview.entries(currentEra); - for (const [key, ov] of overviews) { - const [, validatorId] = key.args; - activeValidators.add(validatorId.toString()); - } - - // Read all paged exposure entries for current era (contains nominators) - const pages = await api.query.staking.erasStakersPaged.entries(currentEra); - for (const [key, exp] of pages) { - const [, validatorId] = key.args; - activeValidators.add(validatorId.toString()); - - let exposure: any; - try { - const asOpt = exp as Option; - if (asOpt.isNone) continue; - exposure = asOpt.unwrap(); - } catch { - exposure = exp as any; - } - - if (exposure.others) { - for (const other of exposure.others) { - activeNominators.add(other.who.toString()); - } - } - } - - // Fallback: if overview had no results, try legacy erasStakersClipped - if (activeValidators.size === 0) { - const clipped = - await api.query.staking.erasStakersClipped.entries(currentEra); - for (const [key, exposure] of clipped) { - const [, validatorId] = key.args; - activeValidators.add(validatorId.toString()); - const exp = exposure as unknown as Exposure; - for (const other of exp.others) { - activeNominators.add(other.who.toString()); - } - } - } - - // Save validators as active stakers - for (const address of activeValidators) { - const stakerId = `${PEZKUWI_RELAY_GENESIS}-${STAKING_TYPE_RELAYCHAIN}-${address}`; - const staker = ActiveStaker.create({ - id: stakerId, - networkId: PEZKUWI_RELAY_GENESIS, - stakingType: STAKING_TYPE_RELAYCHAIN, - address, - }); - await staker.save(); - } - - // Save nominators as active stakers - for (const address of activeNominators) { - const stakerId = `${PEZKUWI_RELAY_GENESIS}-${STAKING_TYPE_RELAYCHAIN}-${address}`; - const staker = ActiveStaker.create({ - id: stakerId, - networkId: PEZKUWI_RELAY_GENESIS, - stakingType: STAKING_TYPE_RELAYCHAIN, - address, - }); - await staker.save(); - } - - logger.info( - `Initialized ${activeValidators.size} validators + ${activeNominators.size} nominators as active relay stakers`, - ); -} - -export async function handleStakersElected( - event: SubstrateEvent, -): Promise { - await handleNewEra(event); -} - -export async function handleNewEra(event: SubstrateEvent): Promise { - // Safety: staking pallet was removed from relay chain in spec 1_020_006 - if (!api.query.staking || !api.query.staking.currentEra) { - logger.warn("Staking pallet not available - skipping handleNewEra"); - return; - } - - let currentEra: number; - try { - currentEra = ((await api.query.staking.currentEra()) as Option) - .unwrap() - .toNumber(); - } catch (e) { - logger.warn(`Failed to query staking.currentEra: ${e}`); - return; - } - - let validatorExposures: Array<{ - address: string; - total: bigint; - own: bigint; - others: IndividualExposure[]; - }>; - - if (api.query.staking.erasStakersOverview) { - validatorExposures = await processEraStakersPaged(event, currentEra); - } else { - validatorExposures = await processEraStakersClipped(event, currentEra); - } - - // Compute and save APY + active stakers - await updateStakingApyAndActiveStakers(currentEra, validatorExposures); -} - -interface ValidatorExposureData { - address: string; - total: bigint; - own: bigint; - others: IndividualExposure[]; -} - -async function processEraStakersClipped( - event: SubstrateEvent, - currentEra: number, -): Promise { - const exposures = - await api.query.staking.erasStakersClipped.entries(currentEra); - - const result: ValidatorExposureData[] = []; - - for (const [key, exposure] of exposures) { - const [, validatorId] = key.args; - let validatorIdString = validatorId.toString(); - const exp = exposure as unknown as Exposure; - const others = exp.others.map((other) => { - return { - who: other.who.toString(), - value: other.value.toString(), - } as IndividualExposure; - }); - - const eraValidatorInfo = new EraValidatorInfo( - eventId(event) + validatorIdString, - validatorIdString, - currentEra, - exp.total.toBigInt(), - exp.own.toBigInt(), - others, - ); - await eraValidatorInfo.save(); - - result.push({ - address: validatorIdString, - total: exp.total.toBigInt(), - own: exp.own.toBigInt(), - others, - }); - } - - return result; -} - -async function processEraStakersPaged( - event: SubstrateEvent, - currentEra: number, -): Promise { - const overview = - await api.query.staking.erasStakersOverview.entries(currentEra); - const pages = await api.query.staking.erasStakersPaged.entries(currentEra); - - interface AccumulatorType { - [key: string]: { [page: number]: IndividualExposure[] }; - } - - const othersCounted = pages.reduce( - (accumulator: AccumulatorType, [key, exp]) => { - const exposure = (exp as Option).unwrap(); - const [, validatorId, pageId] = key.args; - const pageNumber = (pageId as any).toNumber(); - const validatorIdString = validatorId.toString(); - - const others: IndividualExposure[] = exposure.others.map( - ({ who, value }: any) => { - return { - who: who.toString(), - value: value.toString(), - } as IndividualExposure; - }, - ); - - (accumulator[validatorIdString] = accumulator[validatorIdString] || {})[ - pageNumber - ] = others; - return accumulator; - }, - {}, - ); - - const result: ValidatorExposureData[] = []; - - for (const [key, exp] of overview) { - const exposure = (exp as Option).unwrap(); - const [, validatorId] = key.args; - let validatorIdString = validatorId.toString(); - - let others: IndividualExposure[] = []; - for (let i = 0; i < exposure.pageCount.toNumber(); ++i) { - others.push(...othersCounted[validatorIdString][i]); - } - - const eraValidatorInfo = new EraValidatorInfo( - eventId(event) + validatorIdString, - validatorIdString, - currentEra, - exposure.total.toBigInt(), - exposure.own.toBigInt(), - others, - ); - await eraValidatorInfo.save(); - - result.push({ - address: validatorIdString, - total: exposure.total.toBigInt(), - own: exposure.own.toBigInt(), - others, - }); - } - - return result; -} - -// ===== APY Calculation (Substrate inflation curve) ===== - -function calculateYearlyInflation(stakedPortion: number): number { - const idealStake = INFLATION_STAKE_TARGET; // No parachains on Pezkuwi - const idealInterest = INFLATION_MAX / idealStake; - - if (stakedPortion >= 0 && stakedPortion <= idealStake) { - return ( - INFLATION_MIN + - stakedPortion * (idealInterest - INFLATION_MIN / idealStake) - ); - } else { - return ( - INFLATION_MIN + - (idealInterest * idealStake - INFLATION_MIN) * - Math.pow(2, (idealStake - stakedPortion) / INFLATION_FALLOFF) - ); - } -} - -interface ValidatorAPYData { - totalStake: bigint; - commission: number; // 0.0 to 1.0 -} - -function calculateMaxAPY( - totalIssuance: bigint, - validators: ValidatorAPYData[], -): number { - if (validators.length === 0 || totalIssuance === BigInt(0)) return 0; - - const totalStaked = validators.reduce( - (sum, v) => sum + v.totalStake, - BigInt(0), - ); - if (totalStaked === BigInt(0)) return 0; - - // Use scaled division for precision with large BigInts - const SCALE = BigInt(1_000_000_000); - const stakedPortion = - Number((totalStaked * SCALE) / totalIssuance) / Number(SCALE); - - const yearlyInflation = calculateYearlyInflation(stakedPortion); - const averageValidatorRewardPercentage = yearlyInflation / stakedPortion; - const averageValidatorStake = totalStaked / BigInt(validators.length); - - let maxAPY = 0; - for (const v of validators) { - if (v.totalStake === BigInt(0)) continue; - const stakeRatio = - Number((averageValidatorStake * SCALE) / v.totalStake) / Number(SCALE); - const yearlyRewardPercentage = - averageValidatorRewardPercentage * stakeRatio; - const apy = yearlyRewardPercentage * (1 - v.commission); - if (apy > maxAPY) maxAPY = apy; - } - - return maxAPY; -} - -async function updateStakingApyAndActiveStakers( - currentEra: number, - validatorExposures: ValidatorExposureData[], -): Promise { - if (validatorExposures.length === 0) return; - - // 1. Get total issuance from the relay chain - const totalIssuance = ( - (await api.query.balances.totalIssuance()) as any - ).toBigInt(); - - // 2. Get validator commissions - const validatorAddresses = validatorExposures.map((v) => v.address); - const validatorPrefs = - await api.query.staking.validators.multi(validatorAddresses); - - const validatorsWithCommission: ValidatorAPYData[] = validatorExposures.map( - (v, i) => { - const prefs = validatorPrefs[i] as any; - const commissionPerbill = prefs.commission - ? Number(prefs.commission.toString()) - : 0; - return { - totalStake: v.total, - commission: commissionPerbill / PERBILL_DIVISOR, - }; - }, - ); - - // 3. Calculate maxAPY - const maxAPY = calculateMaxAPY(totalIssuance, validatorsWithCommission); - - // 4. Save StakingApy for relay chain (relaychain staking) - const relayApyId = `${PEZKUWI_RELAY_GENESIS}-${STAKING_TYPE_RELAYCHAIN}`; - const relayApy = StakingApy.create({ - id: relayApyId, - networkId: PEZKUWI_RELAY_GENESIS, - stakingType: STAKING_TYPE_RELAYCHAIN, - maxAPY, - }); - await relayApy.save(); - - // 5. Save StakingApy for Asset Hub (relaychain staking option) - const ahRelayApyId = `${PEZKUWI_ASSET_HUB_GENESIS}-${STAKING_TYPE_RELAYCHAIN}`; - const ahRelayApy = StakingApy.create({ - id: ahRelayApyId, - networkId: PEZKUWI_ASSET_HUB_GENESIS, - stakingType: STAKING_TYPE_RELAYCHAIN, - maxAPY, - }); - await ahRelayApy.save(); - - // 6. Save StakingApy for Asset Hub (nomination-pool staking option) - const ahPoolApyId = `${PEZKUWI_ASSET_HUB_GENESIS}-${STAKING_TYPE_NOMINATION_POOL}`; - const ahPoolApy = StakingApy.create({ - id: ahPoolApyId, - networkId: PEZKUWI_ASSET_HUB_GENESIS, - stakingType: STAKING_TYPE_NOMINATION_POOL, - maxAPY, - }); - await ahPoolApy.save(); - - logger.info( - `Era ${currentEra}: maxAPY=${(maxAPY * 100).toFixed(2)}% validators=${ - validatorExposures.length - } totalIssuance=${totalIssuance}`, - ); - - // 7. Collect all unique nominator addresses from exposures (active stakers) - const activeNominators = new Set(); - for (const v of validatorExposures) { - for (const nominator of v.others) { - activeNominators.add(nominator.who); - } - } - - // 8. Clear previous active stakers and save new ones - // For relay chain direct staking - for (const address of activeNominators) { - const relayStakerId = `${PEZKUWI_RELAY_GENESIS}-${STAKING_TYPE_RELAYCHAIN}-${address}`; - const staker = ActiveStaker.create({ - id: relayStakerId, - networkId: PEZKUWI_RELAY_GENESIS, - stakingType: STAKING_TYPE_RELAYCHAIN, - address, - }); - await staker.save(); - } - - // Also save validators themselves as active stakers - for (const v of validatorExposures) { - const validatorStakerId = `${PEZKUWI_RELAY_GENESIS}-${STAKING_TYPE_RELAYCHAIN}-${v.address}`; - const staker = ActiveStaker.create({ - id: validatorStakerId, - networkId: PEZKUWI_RELAY_GENESIS, - stakingType: STAKING_TYPE_RELAYCHAIN, - address: v.address, - }); - await staker.save(); - } - - logger.info( - `Era ${currentEra}: saved ${activeNominators.size} active stakers (relay)`, - ); -} diff --git a/src/mappings/PoolRewards.ts b/src/mappings/PoolRewards.ts index f5985af..614a096 100644 --- a/src/mappings/PoolRewards.ts +++ b/src/mappings/PoolRewards.ts @@ -21,7 +21,6 @@ import { import { getPoolMembers } from "./Cache"; import { Option } from "@pezkuwi/types"; import { - PEZKUWI_RELAY_GENESIS, PEZKUWI_ASSET_HUB_GENESIS, STAKING_TYPE_NOMINATION_POOL, } from "./constants"; diff --git a/src/mappings/Rewards.ts b/src/mappings/Rewards.ts index 8d86bde..950965a 100644 --- a/src/mappings/Rewards.ts +++ b/src/mappings/Rewards.ts @@ -22,12 +22,11 @@ import { callFromProxy, blockNumber, } from "./common"; +import { cachedRewardDestination, cachedController } from "./Cache"; import { - cachedRewardDestination, - cachedController, - cachedStakingRewardEraIndex, -} from "./Cache"; -import { PEZKUWI_RELAY_GENESIS, STAKING_TYPE_RELAYCHAIN } from "./constants"; + PEZKUWI_ASSET_HUB_GENESIS, + STAKING_TYPE_RELAYCHAIN, +} from "./constants"; function isPayoutStakers(call: any): boolean { return call.method == "payoutStakers"; @@ -43,13 +42,11 @@ function isPayoutValidator(call: any): boolean { function extractArgsFromPayoutStakers(call: any): [string, number] { const [validatorAddressRaw, eraRaw] = call.args; - return [validatorAddressRaw.toString(), (eraRaw as any).toNumber()]; } function extractArgsFromPayoutStakersByPage(call: any): [string, number] { const [validatorAddressRaw, eraRaw, _] = call.args; - return [validatorAddressRaw.toString(), (eraRaw as any).toNumber()]; } @@ -58,17 +55,15 @@ function extractArgsFromPayoutValidator( sender: string, ): [string, number] { const [eraRaw] = call.args; - return [sender, (eraRaw as any).toNumber()]; } -export async function handleRewarded( +/** + * Handle staking::Rewarded on Asset Hub + */ +export async function handleAHRewarded( rewardEvent: SubstrateEvent, ): Promise { - await handleReward(rewardEvent); -} - -export async function handleReward(rewardEvent: SubstrateEvent): Promise { await handleRewardForTxHistory(rewardEvent); let accumulatedReward = await updateAccumulatedReward(rewardEvent, true); await updateAccountRewards( @@ -76,11 +71,23 @@ export async function handleReward(rewardEvent: SubstrateEvent): Promise { RewardType.reward, accumulatedReward.amount, ); - await saveMultiStakingReward( - rewardEvent, - RewardType.reward, - STAKING_TYPE_RELAYCHAIN, + await saveMultiStakingReward(rewardEvent, RewardType.reward); +} + +/** + * Handle staking::Slashed on Asset Hub + */ +export async function handleAHSlashed( + slashEvent: SubstrateEvent, +): Promise { + await handleSlashForTxHistory(slashEvent); + let accumulatedReward = await updateAccumulatedReward(slashEvent, false); + await updateAccountRewards( + slashEvent, + RewardType.slash, + accumulatedReward.amount, ); + await saveMultiStakingReward(slashEvent, RewardType.slash); } async function handleRewardForTxHistory( @@ -89,7 +96,6 @@ async function handleRewardForTxHistory( let element = await HistoryElement.get(eventId(rewardEvent)); if (element !== undefined) { - // already processed reward previously return; } @@ -215,32 +221,12 @@ function determinePayoutCallsArgs( } } -export async function handleSlashed(slashEvent: SubstrateEvent): Promise { - await handleSlash(slashEvent); -} - -export async function handleSlash(slashEvent: SubstrateEvent): Promise { - await handleSlashForTxHistory(slashEvent); - let accumulatedReward = await updateAccumulatedReward(slashEvent, false); - await updateAccountRewards( - slashEvent, - RewardType.slash, - accumulatedReward.amount, - ); - await saveMultiStakingReward( - slashEvent, - RewardType.slash, - STAKING_TYPE_RELAYCHAIN, - ); -} - async function getValidators(era: number): Promise> { const eraStakersInSlashEra = await (api.query.staking.erasStakersClipped ? api.query.staking.erasStakersClipped.keys(era) : api.query.staking.erasStakersOverview.keys(era)); const validatorsInSlashEra = eraStakersInSlashEra.map((key: any) => { let [, validatorId] = key.args; - return validatorId.toString(); }); return new Set(validatorsInSlashEra); @@ -252,7 +238,6 @@ async function handleSlashForTxHistory( let element = await HistoryElement.get(eventId(slashEvent)); if (element !== undefined) { - // already processed reward previously return; } const eraWrapped = await api.query.staking.currentEra(); @@ -396,48 +381,6 @@ async function updateAccountRewards( await accountReward.save(); } -async function handleParachainRewardForTxHistory( - rewardEvent: SubstrateEvent, -): Promise { - let [account, amount] = decodeDataFromReward(rewardEvent); - handleGenericForTxHistory( - rewardEvent, - account.toString(), - async (element: HistoryElement) => { - const eraIndex = await cachedStakingRewardEraIndex(rewardEvent); - - const validatorEvent = rewardEvent.block.events.find( - (event) => - event.event.section == rewardEvent.event.section && - event.event.method == rewardEvent.event.method, - ); - const validatorId = validatorEvent?.event.data[0].toString(); - element.reward = { - eventIdx: rewardEvent.idx, - amount: amount.toString(), - isReward: true, - stash: account.toString(), - validator: validatorId, - era: eraIndex, - }; - - return element; - }, - ); -} - -export async function handleParachainRewarded( - rewardEvent: SubstrateEvent, -): Promise { - await handleParachainRewardForTxHistory(rewardEvent); - let accumulatedReward = await updateAccumulatedReward(rewardEvent, true); - await updateAccountRewards( - rewardEvent, - RewardType.reward, - accumulatedReward.amount, - ); -} - // ============= GENERICS ================ interface AccumulatedInterface { @@ -500,23 +443,6 @@ export async function handleGenericForTxHistory( (await fieldCallback(element)).save(); } -interface AccountRewardsInterface { - id: string; - - address: string; - - blockNumber: number; - - timestamp: bigint; - - amount: bigint; - - accumulatedAmount: bigint; - - type: RewardType; - save(): Promise; -} - export function eventRecordToSubstrateEvent(eventRecord: any): SubstrateEvent { return eventRecord as unknown as SubstrateEvent; } @@ -538,19 +464,18 @@ function decodeDataFromReward(event: SubstrateEvent): [any, any] { * Save a reward/slash to the multi-staking Reward entity * (used by PezWallet dashboard for rewards aggregation) */ -export async function saveMultiStakingReward( +async function saveMultiStakingReward( event: SubstrateEvent, rewardType: RewardType, - stakingType: string, ): Promise { const [accountId, amount] = decodeDataFromReward(event); const accountAddress = accountId.toString(); - const id = `${eventId(event)}-${accountAddress}-${stakingType}`; + const id = `${eventId(event)}-${accountAddress}-${STAKING_TYPE_RELAYCHAIN}`; const reward = Reward.create({ id, - networkId: PEZKUWI_RELAY_GENESIS, - stakingType, + networkId: PEZKUWI_ASSET_HUB_GENESIS, + stakingType: STAKING_TYPE_RELAYCHAIN, address: accountAddress, type: rewardType, amount: (amount as any).toBigInt(),