Skip to content

Commit 0123aeb

Browse files
authored
fix: rewrite the PJS balances derive for transferable balances (#1745)
1 parent 7753738 commit 0123aeb

File tree

1 file changed

+53
-9
lines changed

1 file changed

+53
-9
lines changed

src/services/accounts/AccountsBalanceInfoService.ts

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616

1717
import { ApiDecoration } from '@polkadot/api/types';
1818
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';
2020
import { PalletBalancesAccountData } from '@polkadot/types/lookup';
21+
import { BN, bnMax } from '@polkadot/util';
2122
import { BadRequest } from 'http-errors';
2223

2324
import { IAccountBalanceInfo, IBalanceLock } from '../../types/responses';
@@ -109,13 +110,11 @@ export class AccountsBalanceInfoService extends AbstractService {
109110

110111
let free, reserved, feeFrozen, miscFrozen, frozen, transferable;
111112
if (accountInfo.data?.frozen) {
112-
const deriveData = await this.api.derive.balances.all(address);
113+
const maybeTransferable = await this.calculateTransferable(data, hash, address);
113114
free = data.free;
114115
reserved = data.reserved;
115116
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';
119118
miscFrozen = 'miscFrozen does not exist for this runtime';
120119
feeFrozen = 'feeFrozen does not exist for this runtime';
121120
} else {
@@ -206,14 +205,12 @@ export class AccountsBalanceInfoService extends AbstractService {
206205
frozen = 'frozen does not exist for this runtime';
207206
transferable = 'transferable formula not supported for this runtime';
208207
} else {
209-
const deriveData = await this.api.derive.balances.all(address);
210208
const tmpData = accountData as PalletBalancesAccountData;
209+
const maybeTransferable = await this.calculateTransferable(tmpData, hash, address);
211210
free = tmpData.free;
212211
reserved = tmpData.reserved;
213212
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';
217214
feeFrozen = 'feeFrozen does not exist for this runtime';
218215
miscFrozen = 'miscFrozen does not exist for this runtime';
219216
}
@@ -237,6 +234,53 @@ export class AccountsBalanceInfoService extends AbstractService {
237234
}
238235
}
239236

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+
240284
/**
241285
* Apply a denomination to a balance depending on the chains decimal value.
242286
*

0 commit comments

Comments
 (0)