@@ -23,7 +23,13 @@ import { BlockNumberSource, IAccountVestingInfo, IVestingSchedule } from 'src/ty
2323import { ApiPromiseRegistry } from '../../apiRegistry' ;
2424import { assetHubSpecNames } from '../../chains-config' ;
2525import { getInclusionBlockNumber } from '../../util/relay/getRelayParentNumber' ;
26- import { calculateTotalUnlockable , calculateUnlockable , IVestingSchedule as IVestingCalcSchedule } from '../../util/vesting/vestingCalculations' ;
26+ import {
27+ calculateTotalVested ,
28+ calculateVested ,
29+ calculateVestedClaimable ,
30+ calculateVestingTotal ,
31+ IVestingSchedule as IVestingCalcSchedule ,
32+ } from '../../util/vesting/vestingCalculations' ;
2733import { AbstractService } from '../AbstractService' ;
2834import { MIGRATION_BOUNDARIES , relayToSpecMapping } from '../consts' ;
2935
@@ -37,14 +43,25 @@ type MigrationState = 'pre-migration' | 'during-migration' | 'post-migration';
3743 */
3844const ASSET_HUB_PARA_ID = 1000 ;
3945
46+ /**
47+ * Vesting lock ID used in balances.locks
48+ * This is "vesting " padded to 8 bytes (0x76657374696e6720)
49+ */
50+ const VESTING_ID = '0x76657374696e6720' ;
51+
4052export class AccountsVestingInfoService extends AbstractService {
4153 /**
4254 * Fetch vesting information for an account at a given block.
4355 *
4456 * @param hash `BlockHash` to make call at
4557 * @param address address of the account to get the vesting info of
58+ * @param includeVested whether to calculate and include vested amounts (default: false)
4659 */
47- async fetchAccountVestingInfo ( hash : BlockHash , address : string ) : Promise < IAccountVestingInfo > {
60+ async fetchAccountVestingInfo (
61+ hash : BlockHash ,
62+ address : string ,
63+ includeVested = false ,
64+ ) : Promise < IAccountVestingInfo > {
4865 const { api } = this ;
4966
5067 const historicApi = await api . at ( hash ) ;
@@ -77,12 +94,23 @@ export class AccountsVestingInfoService extends AbstractService {
7794 const unwrapVesting = vesting . unwrap ( ) ;
7895 const vestingArray : VestingInfo [ ] = Array . isArray ( unwrapVesting ) ? unwrapVesting : [ unwrapVesting ] ;
7996
80- // Calculate unlockable amounts based on chain type and migration state
81- const unlockableResult = await this . calculateVestingUnlockable ( hash , blockNumber , vestingArray ) ;
97+ // If includeVested is not requested, return raw vesting data
98+ if ( ! includeVested ) {
99+ return {
100+ at,
101+ vesting : vestingArray ,
102+ } ;
103+ }
104+
105+ // Get the on-chain vesting lock amount from balances.locks
106+ const vestingLocked = await this . getVestingLocked ( historicApi , address ) ;
82107
83- if ( unlockableResult === null ) {
84- // Unable to calculate unlockable (e.g., during migration or missing relay connection)
85- // Return raw vesting data without unlockable calculations
108+ // Calculate vested amounts based on chain type and migration state
109+ const vestingResult = await this . calculateVestingAmounts ( hash , blockNumber , vestingArray , vestingLocked ) ;
110+
111+ if ( vestingResult === null ) {
112+ // Unable to calculate (e.g., during migration or missing relay connection)
113+ // Return raw vesting data without calculations
86114 return {
87115 at,
88116 vesting : vestingArray ,
@@ -91,48 +119,79 @@ export class AccountsVestingInfoService extends AbstractService {
91119
92120 return {
93121 at,
94- vesting : unlockableResult . schedules ,
95- totalUnlockable : unlockableResult . totalUnlockable ,
96- blockNumberForCalculation : unlockableResult . blockNumberForCalculation ,
97- blockNumberSource : unlockableResult . blockNumberSource ,
122+ vesting : vestingResult . schedules ,
123+ vestedBalance : vestingResult . vestedBalance ,
124+ vestingTotal : vestingResult . vestingTotal ,
125+ vestedClaimable : vestingResult . vestedClaimable ,
126+ blockNumberForCalculation : vestingResult . blockNumberForCalculation ,
127+ blockNumberSource : vestingResult . blockNumberSource ,
98128 } ;
99129 }
100130
101131 /**
102- * Calculate unlockable amounts for vesting schedules.
132+ * Get the vesting lock amount from balances.locks.
133+ * Returns 0 if no vesting lock exists.
134+ */
135+ private async getVestingLocked (
136+ historicApi : Awaited < ReturnType < typeof this . api . at > > ,
137+ address : string ,
138+ ) : Promise < BN > {
139+ if ( ! historicApi . query . balances ?. locks ) {
140+ return new BN ( 0 ) ;
141+ }
142+
143+ const locks = await historicApi . query . balances . locks ( address ) ;
144+
145+ for ( const lock of locks ) {
146+ if ( lock . id . toHex ( ) === VESTING_ID ) {
147+ return new BN ( lock . amount . toString ( ) ) ;
148+ }
149+ }
150+
151+ return new BN ( 0 ) ;
152+ }
153+
154+ /**
155+ * Calculate vested amounts for vesting schedules.
103156 * Returns null if calculation cannot be performed (e.g., during migration window).
104157 */
105- private async calculateVestingUnlockable (
158+ private async calculateVestingAmounts (
106159 hash : BlockHash ,
107160 blockNumber : number ,
108161 vestingArray : VestingInfo [ ] ,
162+ vestingLocked : BN ,
109163 ) : Promise < {
110164 schedules : IVestingSchedule [ ] ;
111- totalUnlockable : string ;
165+ vestedBalance : string ;
166+ vestingTotal : string ;
167+ vestedClaimable : string ;
112168 blockNumberForCalculation : string ;
113169 blockNumberSource : BlockNumberSource ;
114170 } | null > {
115171 const specName = this . specName ;
116172 const isAssetHub = assetHubSpecNames . has ( specName ) ;
117173
118174 if ( isAssetHub ) {
119- return this . calculateForAssetHub ( hash , blockNumber , vestingArray ) ;
175+ return this . calculateForAssetHub ( hash , blockNumber , vestingArray , vestingLocked ) ;
120176 } else {
121- return this . calculateForRelayChain ( blockNumber , vestingArray ) ;
177+ return this . calculateForRelayChain ( blockNumber , vestingArray , vestingLocked ) ;
122178 }
123179 }
124180
125181 /**
126- * Calculate unlockable for Asset Hub chains.
182+ * Calculate vested amounts for Asset Hub chains.
127183 * Post-migration: uses relay chain inclusion block number for calculations.
128184 */
129185 private async calculateForAssetHub (
130186 hash : BlockHash ,
131187 blockNumber : number ,
132188 vestingArray : VestingInfo [ ] ,
189+ vestingLocked : BN ,
133190 ) : Promise < {
134191 schedules : IVestingSchedule [ ] ;
135- totalUnlockable : string ;
192+ vestedBalance : string ;
193+ vestingTotal : string ;
194+ vestedClaimable : string ;
136195 blockNumberForCalculation : string ;
137196 blockNumberSource : BlockNumberSource ;
138197 } | null > {
@@ -147,8 +206,8 @@ export class AccountsVestingInfoService extends AbstractService {
147206 const migrationState = this . getAssetHubMigrationState ( blockNumber , boundaries ) ;
148207
149208 if ( migrationState === 'during-migration' ) {
150- // During migration window, return 0 for unlockable
151- return this . createZeroUnlockableResult ( vestingArray , blockNumber . toString ( ) , 'self' ) ;
209+ // During migration window, return 0 for claimable
210+ return this . createZeroClaimableResult ( vestingArray , vestingLocked , blockNumber . toString ( ) , 'self' ) ;
152211 }
153212
154213 if ( migrationState === 'pre-migration' ) {
@@ -161,39 +220,37 @@ export class AccountsVestingInfoService extends AbstractService {
161220 if ( relayApis . length === 0 ) {
162221 throw new InternalServerError (
163222 'Relay chain connection required for vesting calculations on Asset Hub post-migration. ' +
164- 'Please configure MULTI_CHAIN_URL with a relay chain endpoint.' ,
223+ 'Please configure MULTI_CHAIN_URL with a relay chain endpoint.' ,
165224 ) ;
166225 }
167226
168227 const rcApi = relayApis [ 0 ] . api ;
169228
170229 // Get the relay chain inclusion block number for this Asset Hub block
171- const inclusionResult = await getInclusionBlockNumber (
172- this . api ,
173- rcApi ,
174- hash ,
175- ASSET_HUB_PARA_ID ,
176- ) ;
230+ const inclusionResult = await getInclusionBlockNumber ( this . api , rcApi , hash , ASSET_HUB_PARA_ID ) ;
177231
178232 if ( ! inclusionResult . found || inclusionResult . inclusionBlockNumber === null ) {
179233 // Inclusion not found within search depth
180234 // Fall back to relay parent number for calculation
181- return this . performCalculation ( vestingArray , new BN ( inclusionResult . relayParentNumber ) , 'relay' ) ;
235+ return this . performCalculation ( vestingArray , vestingLocked , new BN ( inclusionResult . relayParentNumber ) , 'relay' ) ;
182236 }
183237
184- return this . performCalculation ( vestingArray , new BN ( inclusionResult . inclusionBlockNumber ) , 'relay' ) ;
238+ return this . performCalculation ( vestingArray , vestingLocked , new BN ( inclusionResult . inclusionBlockNumber ) , 'relay' ) ;
185239 }
186240
187241 /**
188- * Calculate unlockable for relay chains.
242+ * Calculate vested amounts for relay chains.
189243 * Pre-migration: uses the chain's own block number for calculations.
190244 */
191245 private calculateForRelayChain (
192246 blockNumber : number ,
193247 vestingArray : VestingInfo [ ] ,
248+ vestingLocked : BN ,
194249 ) : {
195250 schedules : IVestingSchedule [ ] ;
196- totalUnlockable : string ;
251+ vestedBalance : string ;
252+ vestingTotal : string ;
253+ vestedClaimable : string ;
197254 blockNumberForCalculation : string ;
198255 blockNumberSource : BlockNumberSource ;
199256 } | null {
@@ -203,43 +260,46 @@ export class AccountsVestingInfoService extends AbstractService {
203260 if ( ! assetHubSpec ) {
204261 // Not a known relay chain with migration boundaries
205262 // Use single-chain calculation
206- return this . performCalculation ( vestingArray , new BN ( blockNumber ) , 'self' ) ;
263+ return this . performCalculation ( vestingArray , vestingLocked , new BN ( blockNumber ) , 'self' ) ;
207264 }
208265
209266 const boundaries = MIGRATION_BOUNDARIES [ assetHubSpec ] ;
210267
211268 if ( ! boundaries ) {
212269 // No boundaries defined, use single-chain calculation
213- return this . performCalculation ( vestingArray , new BN ( blockNumber ) , 'self' ) ;
270+ return this . performCalculation ( vestingArray , vestingLocked , new BN ( blockNumber ) , 'self' ) ;
214271 }
215272
216273 const migrationState = this . getRelayChainMigrationState ( blockNumber , boundaries ) ;
217274
218275 if ( migrationState === 'during-migration' ) {
219- // During migration window, return 0 for unlockable
220- return this . createZeroUnlockableResult ( vestingArray , blockNumber . toString ( ) , 'self' ) ;
276+ // During migration window, return 0 for claimable
277+ return this . createZeroClaimableResult ( vestingArray , vestingLocked , blockNumber . toString ( ) , 'self' ) ;
221278 }
222279
223280 if ( migrationState === 'post-migration' ) {
224281 // Post-migration: vesting no longer exists on relay chain
225- // Return 0 for unlockable since vesting has migrated
226- return this . createZeroUnlockableResult ( vestingArray , blockNumber . toString ( ) , 'self' ) ;
282+ // Return 0 for claimable since vesting has migrated
283+ return this . createZeroClaimableResult ( vestingArray , vestingLocked , blockNumber . toString ( ) , 'self' ) ;
227284 }
228285
229286 // Pre-migration: use relay chain's own block number
230- return this . performCalculation ( vestingArray , new BN ( blockNumber ) , 'self' ) ;
287+ return this . performCalculation ( vestingArray , vestingLocked , new BN ( blockNumber ) , 'self' ) ;
231288 }
232289
233290 /**
234- * Perform the actual unlockable calculation for vesting schedules.
291+ * Perform the actual vesting calculation for vesting schedules.
235292 */
236293 private performCalculation (
237294 vestingArray : VestingInfo [ ] ,
295+ vestingLocked : BN ,
238296 currentBlock : BN ,
239297 source : BlockNumberSource ,
240298 ) : {
241299 schedules : IVestingSchedule [ ] ;
242- totalUnlockable : string ;
300+ vestedBalance : string ;
301+ vestingTotal : string ;
302+ vestedClaimable : string ;
243303 blockNumberForCalculation : string ;
244304 blockNumberSource : BlockNumberSource ;
245305 } {
@@ -250,48 +310,67 @@ export class AccountsVestingInfoService extends AbstractService {
250310 startingBlock : new BN ( v . startingBlock . toString ( ) ) ,
251311 } ) ) ;
252312
253- // Calculate unlockable for each schedule
313+ // Calculate vested for each schedule
254314 const schedules : IVestingSchedule [ ] = vestingArray . map ( ( v , idx ) => ( {
255315 locked : v . locked . toString ( ) ,
256316 perBlock : v . perBlock . toString ( ) ,
257317 startingBlock : v . startingBlock . toString ( ) ,
258- unlockable : calculateUnlockable ( currentBlock , calcSchedules [ idx ] ) . toString ( ) ,
318+ vested : calculateVested ( currentBlock , calcSchedules [ idx ] ) . toString ( ) ,
259319 } ) ) ;
260320
261- const totalUnlockable = calculateTotalUnlockable ( currentBlock , calcSchedules ) ;
321+ // Calculate aggregate values
322+ const vestedBalance = calculateTotalVested ( currentBlock , calcSchedules ) ;
323+ const vestingTotal = calculateVestingTotal ( calcSchedules ) ;
324+ const vestedClaimable = calculateVestedClaimable ( vestingLocked , vestingTotal , vestedBalance ) ;
262325
263326 return {
264327 schedules,
265- totalUnlockable : totalUnlockable . toString ( ) ,
328+ vestedBalance : vestedBalance . toString ( ) ,
329+ vestingTotal : vestingTotal . toString ( ) ,
330+ vestedClaimable : vestedClaimable . toString ( ) ,
266331 blockNumberForCalculation : currentBlock . toString ( ) ,
267332 blockNumberSource : source ,
268333 } ;
269334 }
270335
271336 /**
272- * Create a result with zero unlockable for all schedules.
337+ * Create a result with zero claimable for all schedules.
273338 * Used during migration windows or post-migration on relay chain.
274339 */
275- private createZeroUnlockableResult (
340+ private createZeroClaimableResult (
276341 vestingArray : VestingInfo [ ] ,
342+ _vestingLocked : BN ,
277343 blockNumber : string ,
278344 source : BlockNumberSource ,
279345 ) : {
280346 schedules : IVestingSchedule [ ] ;
281- totalUnlockable : string ;
347+ vestedBalance : string ;
348+ vestingTotal : string ;
349+ vestedClaimable : string ;
282350 blockNumberForCalculation : string ;
283351 blockNumberSource : BlockNumberSource ;
284352 } {
353+ // Convert VestingInfo to calculation interface
354+ const calcSchedules : IVestingCalcSchedule [ ] = vestingArray . map ( ( v ) => ( {
355+ locked : new BN ( v . locked . toString ( ) ) ,
356+ perBlock : new BN ( v . perBlock . toString ( ) ) ,
357+ startingBlock : new BN ( v . startingBlock . toString ( ) ) ,
358+ } ) ) ;
359+
360+ const vestingTotal = calculateVestingTotal ( calcSchedules ) ;
361+
285362 const schedules : IVestingSchedule [ ] = vestingArray . map ( ( v ) => ( {
286363 locked : v . locked . toString ( ) ,
287364 perBlock : v . perBlock . toString ( ) ,
288365 startingBlock : v . startingBlock . toString ( ) ,
289- unlockable : '0' ,
366+ vested : '0' ,
290367 } ) ) ;
291368
292369 return {
293370 schedules,
294- totalUnlockable : '0' ,
371+ vestedBalance : '0' ,
372+ vestingTotal : vestingTotal . toString ( ) ,
373+ vestedClaimable : '0' ,
295374 blockNumberForCalculation : blockNumber ,
296375 blockNumberSource : source ,
297376 } ;
0 commit comments