16
16
17
17
import { ApiDecoration } from '@polkadot/api/types' ;
18
18
import { Vec } from '@polkadot/types' ;
19
- import { AccountData , Balance , BalanceLock , BlockHash , Index } from '@polkadot/types/interfaces' ;
19
+ import { AccountData , Balance , BalanceLock , BalanceLockTo212 , BlockHash , Index } from '@polkadot/types/interfaces' ;
20
20
import { PalletBalancesAccountData } from '@polkadot/types/lookup' ;
21
+ import { BN , bnMax } from '@polkadot/util' ;
21
22
import { BadRequest } from 'http-errors' ;
22
23
23
24
import { IAccountBalanceInfo , IBalanceLock } from '../../types/responses' ;
@@ -109,13 +110,11 @@ export class AccountsBalanceInfoService extends AbstractService {
109
110
110
111
let free , reserved , feeFrozen , miscFrozen , frozen , transferable ;
111
112
if ( accountInfo . data ?. frozen ) {
112
- const deriveData = await this . api . derive . balances . all ( address ) ;
113
+ const maybeTransferable = await this . calculateTransferable ( data , hash , address ) ;
113
114
free = data . free ;
114
115
reserved = data . reserved ;
115
116
frozen = data . frozen ;
116
- transferable = deriveData . transferable
117
- ? deriveData . transferable
118
- : 'transferable formula not supported for this runtime' ;
117
+ transferable = maybeTransferable ? maybeTransferable : 'transferable formula not supported for this runtime' ;
119
118
miscFrozen = 'miscFrozen does not exist for this runtime' ;
120
119
feeFrozen = 'feeFrozen does not exist for this runtime' ;
121
120
} else {
@@ -206,14 +205,12 @@ export class AccountsBalanceInfoService extends AbstractService {
206
205
frozen = 'frozen does not exist for this runtime' ;
207
206
transferable = 'transferable formula not supported for this runtime' ;
208
207
} else {
209
- const deriveData = await this . api . derive . balances . all ( address ) ;
210
208
const tmpData = accountData as PalletBalancesAccountData ;
209
+ const maybeTransferable = await this . calculateTransferable ( tmpData , hash , address ) ;
211
210
free = tmpData . free ;
212
211
reserved = tmpData . reserved ;
213
212
frozen = tmpData . frozen ;
214
- transferable = deriveData . transferable
215
- ? deriveData . transferable
216
- : 'transferable formula not supported for this runtime' ;
213
+ transferable = maybeTransferable ? maybeTransferable : 'transferable formula not supported for this runtime' ;
217
214
feeFrozen = 'feeFrozen does not exist for this runtime' ;
218
215
miscFrozen = 'miscFrozen does not exist for this runtime' ;
219
216
}
@@ -237,6 +234,53 @@ export class AccountsBalanceInfoService extends AbstractService {
237
234
}
238
235
}
239
236
237
+ private async calculateTransferable ( data : PalletBalancesAccountData , hash : BlockHash , addr : string ) {
238
+ const apiAt = await this . api . at ( hash ) ;
239
+ const allLocked = await this . isAllLocked ( addr , hash ) ;
240
+
241
+ let transferable ;
242
+ if ( data ?. frozen ) {
243
+ const { frozen, free, reserved } = data ;
244
+ const noFrozenReserved = frozen . isZero ( ) && reserved . isZero ( ) ;
245
+ const ED = apiAt . consts . balances . existentialDeposit ;
246
+ const maybeED = noFrozenReserved ? new BN ( 0 ) : ED ;
247
+ const frozenReserveDif = frozen . sub ( reserved ) ;
248
+
249
+ transferable = apiAt . registry . createType (
250
+ 'Balance' ,
251
+ allLocked ? 0 : bnMax ( new BN ( 0 ) , free . sub ( bnMax ( maybeED , frozenReserveDif ) ) ) ,
252
+ ) ;
253
+ }
254
+
255
+ return transferable ;
256
+ }
257
+
258
+ private async isAllLocked ( addr : string , hash : BlockHash ) : Promise < boolean > {
259
+ const apiAt = await this . api . at ( hash ) ;
260
+ try {
261
+ // Get current block header (contains block number) and locks in parallel
262
+ const [ header , locks ] = await Promise . all ( [
263
+ this . api . rpc . chain . getHeader ( ) , // Current best block
264
+ apiAt . query . balances . locks ( addr ) ,
265
+ ] ) ;
266
+
267
+ const bestNumber = header . number . unwrap ( ) ;
268
+
269
+ if ( ! Array . isArray ( locks ) || locks . length === 0 ) {
270
+ return false ;
271
+ }
272
+
273
+ // Filter locks that haven't expired yet
274
+ const validLocks = ( locks as unknown as BalanceLockTo212 [ ] ) . filter ( ( { until } ) => ! until || until . gt ( bestNumber ) ) ;
275
+
276
+ // Check if any valid lock has maximum amount (all funds locked)
277
+ return validLocks . some ( ( lock ) => lock . amount . isMax ( ) ) ;
278
+ } catch ( error ) {
279
+ console . error ( 'Error checking if all funds are locked:' , error ) ;
280
+ return false ;
281
+ }
282
+ }
283
+
240
284
/**
241
285
* Apply a denomination to a balance depending on the chains decimal value.
242
286
*
0 commit comments