33
44import type { ApiPromise } from '@polkadot/api' ;
55import type { DeriveBalancesAccountData , DeriveBalancesAll , DeriveDemocracyLock , DeriveStakingAccount } from '@polkadot/api-derive/types' ;
6+ import type { VestingInfo } from '@polkadot/react-hooks' ;
67import type { Raw } from '@polkadot/types' ;
78import type { BlockNumber , ValidatorPrefsTo145 , Voting } from '@polkadot/types/interfaces' ;
89import type { PalletBalancesReserveData } from '@polkadot/types/lookup' ;
@@ -15,6 +16,7 @@ import { useBestNumberRelay, useStakingAsyncApis } from '@polkadot/react-hooks';
1516import { BlockToTime , FormatBalance } from '@polkadot/react-query' ;
1617import { BN_MAX_INTEGER , BN_ZERO , bnMax , formatBalance , formatNumber , isObject } from '@polkadot/util' ;
1718
19+ import { recalculateVesting } from './util/calculateVesting.js' ;
1820import CryptoType from './CryptoType.js' ;
1921import DemocracyLocks from './DemocracyLocks.js' ;
2022import Expander from './Expander.js' ;
@@ -60,6 +62,8 @@ interface Props {
6062 democracyLocks ?: DeriveDemocracyLock [ ] ;
6163 extraInfo ?: [ string , string ] [ ] ;
6264 stakingInfo ?: DeriveStakingAccount ;
65+ vestingBestNumber ?: BlockNumber ;
66+ vestingInfo ?: VestingInfo ;
6367 votingOf ?: Voting ;
6468 withBalance ?: boolean | BalanceActiveType ;
6569 withBalanceToggle ?: false ;
@@ -239,7 +243,7 @@ function renderValidatorPrefs ({ stakingInfo, withValidatorPrefs = false }: Prop
239243 ) ;
240244}
241245
242- function createBalanceItems ( formatIndex : number , lookup : Record < string , string > , t : TFunction , { address, apiOverride, balanceDisplay, balancesAll, bestNumber, convictionLocks, democracyLocks, isAllLocked, otherBonded, ownBonded, stakingInfo, votingOf, withBalanceToggle, withLabel } : { address : string ; apiOverride : ApiPromise | undefined , balanceDisplay : BalanceActiveType ; balancesAll ?: DeriveBalancesAll | DeriveBalancesAccountData ; bestNumber ?: BlockNumber ; convictionLocks ?: RefLock [ ] ; democracyLocks ?: DeriveDemocracyLock [ ] ; isAllLocked : boolean ; otherBonded : BN [ ] ; ownBonded : BN ; stakingInfo ?: DeriveStakingAccount ; votingOf ?: Voting ; withBalanceToggle : boolean , withLabel : boolean } ) : React . ReactNode {
246+ function createBalanceItems ( formatIndex : number , lookup : Record < string , string > , t : TFunction , { address, apiOverride, balanceDisplay, balancesAll, bestNumber, convictionLocks, democracyLocks, isAllLocked, otherBonded, ownBonded, stakingInfo, vestingBestNumber , vestingInfo , votingOf, withBalanceToggle, withLabel } : { address : string ; apiOverride : ApiPromise | undefined , balanceDisplay : BalanceActiveType ; balancesAll ?: DeriveBalancesAll | DeriveBalancesAccountData ; bestNumber ?: BlockNumber ; convictionLocks ?: RefLock [ ] ; democracyLocks ?: DeriveDemocracyLock [ ] ; isAllLocked : boolean ; otherBonded : BN [ ] ; ownBonded : BN ; stakingInfo ?: DeriveStakingAccount ; vestingBestNumber ?: BlockNumber ; vestingInfo ?: VestingInfo ; votingOf ?: Voting ; withBalanceToggle : boolean , withLabel : boolean } ) : React . ReactNode {
243247 const allItems : React . ReactNode [ ] = [ ] ;
244248 const deriveBalances = balancesAll as DeriveBalancesAll ;
245249
@@ -266,8 +270,37 @@ function createBalanceItems (formatIndex: number, lookup: Record<string, string>
266270 </ React . Fragment >
267271 ) ;
268272
269- if ( bestNumber && balanceDisplay . vested && deriveBalances ?. isVesting ) {
270- const allVesting = deriveBalances . vesting . filter ( ( { endBlock } ) => bestNumber . lt ( endBlock ) ) ;
273+ // Use separate vestingInfo if provided (cross-chain vesting support),
274+ // otherwise fall back to vesting data from balancesAll
275+ const vestingData : DeriveBalancesAll | undefined = ( vestingInfo || deriveBalances ) as DeriveBalancesAll | undefined ;
276+
277+ // Use relay chain block number for vesting calculations when provided
278+ // (vesting schedules use relay chain blocks even after Asset Hub migration)
279+ const vestingBlockNumber = vestingBestNumber || bestNumber ;
280+
281+ // When we have a separate vestingBestNumber, it means vesting schedules use
282+ // relay chain blocks but derive calculated with wrong block number.
283+ // We need to recalculate the vested amounts manually.
284+ const vesting : DeriveBalancesAll | undefined = ( vestingBestNumber && vestingData ?. isVesting && vestingData . vesting . length > 0 )
285+ ? ( ( ) => {
286+ const recalculated = recalculateVesting ( vestingData . vesting , vestingBestNumber ) ;
287+
288+ // The original claimable (calculated with wrong blocks) represents the offset
289+ // between what Asset Hub thinks and reality. Add it to get actual claimable.
290+ const actualClaimable = recalculated . vestedBalance . add ( vestingData . vestedClaimable ) ;
291+
292+ // Override with recalculated values
293+ return {
294+ ...vestingData ,
295+ vestedBalance : recalculated . vestedBalance ,
296+ vestedClaimable : actualClaimable ,
297+ vestingLocked : recalculated . vestingLocked
298+ } as DeriveBalancesAll ;
299+ } ) ( )
300+ : vestingData ;
301+
302+ if ( vestingBlockNumber && balanceDisplay . vested && vesting ?. isVesting ) {
303+ const allVesting = vesting . vesting . filter ( ( { endBlock } ) => vestingBlockNumber . lt ( endBlock ) ) ;
271304
272305 allItems . push (
273306 < React . Fragment key = { 2 } >
@@ -281,34 +314,55 @@ function createBalanceItems (formatIndex: number, lookup: Record<string, string>
281314 tooltip = { `${ address } -vested-trigger` }
282315 />
283316 }
284- value = { deriveBalances . vestedBalance }
317+ value = { vesting . vestedBalance }
285318 >
286319 < StyledTooltip trigger = { `${ address } -vested-trigger` } >
287320 < div className = 'tooltip-header' >
288- { formatBalance ( deriveBalances . vestedClaimable . abs ( ) , { forceUnit : '-' } ) }
321+ { formatBalance ( vesting . vestedClaimable . abs ( ) , { forceUnit : '-' } ) }
289322 < div className = 'faded' > { t ( 'available to be unlocked' ) } </ div >
290323 </ div >
291- { allVesting . map ( ( { endBlock, locked, perBlock, vested } , index ) => (
292- < div
293- className = 'inner'
294- key = { `item:${ index } ` }
295- >
296- < div >
297- < p > { formatBalance ( locked , { forceUnit : '-' } ) } { t ( 'fully vested in' ) } </ p >
298- < BlockToTime
299- api = { apiOverride }
300- value = { endBlock . sub ( bestNumber ) }
301- />
302- </ div >
303- < div className = 'middle' >
304- (Block { formatNumber ( endBlock ) } @ { formatBalance ( perBlock ) } /block)
305- </ div >
306- < div >
307- { formatBalance ( vested , { forceUnit : '-' } ) }
308- < div > { t ( 'already vested' ) } </ div >
324+ { allVesting . map ( ( { endBlock, locked, perBlock, startingBlock, vested } , index ) => {
325+ // Recalculate vested amount for this schedule using correct block number
326+ let vestedAmount = vested ;
327+
328+ if ( vestingBestNumber ) {
329+ if ( vestingBlockNumber . lt ( startingBlock ) ) {
330+ vestedAmount = BN_ZERO ;
331+ } else if ( vestingBlockNumber . gte ( endBlock ) ) {
332+ vestedAmount = locked ;
333+ } else {
334+ const blocksPassed = vestingBlockNumber . sub ( startingBlock ) ;
335+
336+ vestedAmount = blocksPassed . mul ( perBlock ) ;
337+
338+ if ( vestedAmount . gt ( locked ) ) {
339+ vestedAmount = locked ;
340+ }
341+ }
342+ }
343+
344+ return (
345+ < div
346+ className = 'inner'
347+ key = { `item:${ index } ` }
348+ >
349+ < div >
350+ < p > { formatBalance ( locked , { forceUnit : '-' } ) } { t ( 'fully vested in' ) } </ p >
351+ < BlockToTime
352+ api = { apiOverride }
353+ value = { endBlock . sub ( vestingBlockNumber ) }
354+ />
355+ </ div >
356+ < div className = 'middle' >
357+ (Block { formatNumber ( endBlock ) } @ { formatBalance ( perBlock ) } /block)
358+ </ div >
359+ < div >
360+ { formatBalance ( vestedAmount , { forceUnit : '-' } ) }
361+ < div > { t ( 'already vested' ) } </ div >
362+ </ div >
309363 </ div >
310- </ div >
311- ) ) }
364+ ) ;
365+ } ) }
312366 </ StyledTooltip >
313367 </ FormatBalance >
314368 </ React . Fragment >
@@ -532,7 +586,7 @@ function createBalanceItems (formatIndex: number, lookup: Record<string, string>
532586}
533587
534588function renderBalances ( props : Props , lookup : Record < string , string > , bestNumber : BlockNumber | undefined , apiOverride : ApiPromise | undefined , t : TFunction ) : React . ReactNode [ ] {
535- const { address, balancesAll, convictionLocks, democracyLocks, stakingInfo, votingOf, withBalance = true , withBalanceToggle = false , withLabel = false } = props ;
589+ const { address, balancesAll, convictionLocks, democracyLocks, stakingInfo, vestingBestNumber , vestingInfo , votingOf, withBalance = true , withBalanceToggle = false , withLabel = false } : Props = props ;
536590 const balanceDisplay = withBalance === true
537591 ? DEFAULT_BALANCES
538592 : withBalance || false ;
@@ -543,7 +597,7 @@ function renderBalances (props: Props, lookup: Record<string, string>, bestNumbe
543597
544598 const [ ownBonded , otherBonded ] = calcBonded ( stakingInfo , balanceDisplay . bonded ) ;
545599 const isAllLocked = ! ! balancesAll && balancesAll . lockedBreakdown . some ( ( { amount } ) : boolean => amount ?. isMax ( ) ) ;
546- const baseOpts = { address, apiOverride, balanceDisplay, bestNumber, convictionLocks, democracyLocks, isAllLocked, otherBonded, ownBonded, votingOf, withBalanceToggle, withLabel } ;
600+ const baseOpts = { address, apiOverride, balanceDisplay, bestNumber, convictionLocks, democracyLocks, isAllLocked, otherBonded, ownBonded, vestingBestNumber , vestingInfo , votingOf, withBalanceToggle, withLabel } ;
547601 const items = [ createBalanceItems ( 0 , lookup , t , { ...baseOpts , balancesAll, stakingInfo } ) ] ;
548602
549603 withBalanceToggle && balancesAll ?. additional . length && balancesAll . additional . forEach ( ( balancesAll , index ) : void => {
0 commit comments