UNPKG

@substrate/api-sidecar

Version:

REST service that makes it easy to interact with blockchain nodes built using Substrate's FRAME framework.

273 lines 13.4 kB
"use strict"; // Copyright 2017-2025 Parity Technologies (UK) Ltd. // This file is part of Substrate API Sidecar. // // Substrate API Sidecar is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see <http://www.gnu.org/licenses/>. Object.defineProperty(exports, "__esModule", { value: true }); exports.AccountsBalanceInfoService = void 0; const http_errors_1 = require("http-errors"); const AbstractService_1 = require("../AbstractService"); class AccountsBalanceInfoService extends AbstractService_1.AbstractService { /** * Fetch balance information for an account at a given block. * N.B. assumes all non native tokens are from ORML tokens pallet. * * @param hash `BlockHash` to make call at. * @param address Address of the account to get the balance info of. * @param token Token to get the balance info of. */ async fetchAccountBalanceInfo(hash, historicApi, address, token, denominate) { var _a; const { api } = this; if (denominate && historicApi.registry.chainDecimals.length === 0) { throw new http_errors_1.BadRequest("Invalid use of the query parameter `denominated`. This chain doesn't have a valid chain decimal to denominate a value."); } const capitalizeTokens = historicApi.registry.chainTokens.map((token) => token.toUpperCase()); const tokenIdx = capitalizeTokens.indexOf(token); const decimal = historicApi.registry.chainDecimals[tokenIdx]; /** * Check two different cases where a historicApi is needed in order * to have the correct runtime methods. * * a) Does the block use the oldest api where the free balance is found * using `historicApi.query.balances.freeBalance`. * * b) Does the block use an older api where the free balance is within the * AccountInfo type, but the storage does not yet have the `.at` method. * * NOTE: This PR also checks to see if `frozen` exists or `miscFrozen` and `feeFrozen`. * There was a breaking change in https://github.com/paritytech/substrate/pull/12951 which * changed the format of the Account Data returned by the chain. */ if (historicApi.query.balances.freeBalance) { const [header, free, locks, reserved, nonce] = await Promise.all([ api.rpc.chain.getHeader(hash), historicApi.query.balances.freeBalance(address), historicApi.query.balances.locks(address), historicApi.query.balances.reservedBalance(address), historicApi.query.system.accountNonce(address), ]).catch((err) => { throw this.createHttpErrorForAddr(address, err); }); const at = { hash, height: header.number.toNumber().toString(10), }; if (free) { return { at, nonce, tokenSymbol: token, free: this.inDenominationBal(denominate, free, decimal), reserved: this.inDenominationBal(denominate, reserved, decimal), miscFrozen: 'miscFrozen does not exist for this runtime', feeFrozen: 'feeFrozen does not exist for this runtime', frozen: 'frozen does not exist for this runtime', transferable: 'transferable formula not supported for this runtime', locks: this.inDenominationLocks(denominate, locks, decimal), }; } else { throw new http_errors_1.BadRequest('Account not found'); } } else if (!historicApi.query.system.account.at) { const [header, accountInfo, locks] = await Promise.all([ api.rpc.chain.getHeader(hash), historicApi.query.system.account(address), historicApi.query.balances.locks(address), ]).catch((err) => { throw this.createHttpErrorForAddr(address, err); }); const { data, nonce } = accountInfo; let free, reserved, feeFrozen, miscFrozen, frozen, transferable; if ((_a = accountInfo.data) === null || _a === void 0 ? void 0 : _a.frozen) { const deriveData = await this.api.derive.balances.all(address); free = data.free; reserved = data.reserved; frozen = data.frozen; transferable = deriveData.transferable ? deriveData.transferable : 'transferable formula not supported for this runtime'; miscFrozen = 'miscFrozen does not exist for this runtime'; feeFrozen = 'feeFrozen does not exist for this runtime'; } else { const tmpData = data; free = tmpData.free; reserved = tmpData.reserved; feeFrozen = tmpData.feeFrozen; miscFrozen = tmpData.miscFrozen; transferable = 'transferable formula not supported for this runtime'; frozen = 'frozen does not exist for this runtime'; } const at = { hash, height: header.number.toNumber().toString(10), }; if (accountInfo && locks) { return { at, nonce, tokenSymbol: token, free: this.inDenominationBal(denominate, free, decimal), reserved: this.inDenominationBal(denominate, reserved, decimal), miscFrozen: typeof miscFrozen === 'string' ? miscFrozen : this.inDenominationBal(denominate, miscFrozen, decimal), feeFrozen: typeof feeFrozen === 'string' ? feeFrozen : this.inDenominationBal(denominate, feeFrozen, decimal), frozen: typeof frozen === 'string' ? frozen : this.inDenominationBal(denominate, frozen, decimal), transferable: typeof transferable === 'string' ? transferable : this.inDenominationBal(denominate, transferable, decimal), locks: this.inDenominationLocks(denominate, locks, decimal), }; } else { throw new http_errors_1.BadRequest('Account not found'); } } let locks, header, accountInfo, accountData; // We assume the first token is the native token if (token.toUpperCase() === historicApi.registry.chainTokens[0].toUpperCase()) { [header, locks, accountInfo] = await Promise.all([ api.rpc.chain.getHeader(hash), historicApi.query.balances.locks(address), historicApi.query.system.account(address), ]).catch((err) => { throw this.createHttpErrorForAddr(address, err); }); accountData = accountInfo.data != null ? accountInfo.data : await historicApi.query.balances.account(address); } else { // Assume we are using ORML token pallet let locksAny, accountDataAny; try { [header, locksAny, accountDataAny, accountInfo] = await Promise.all([ api.rpc.chain.getHeader(hash), historicApi.query.tokens.locks(address, { Token: token }), historicApi.query.tokens.accounts(address, { Token: token, }), historicApi.query.system.account(address), ]); } catch { throw new http_errors_1.BadRequest('An error occured while attempting to query for a non-native token; ' + 'the token specified is likely invalid.'); } // Coerce the ORML query results from polkadot-js generic Codec to exact type locks = locksAny; accountData = accountDataAny; } const at = { hash, height: header.number.toNumber().toString(10), }; if (accountData && locks && accountInfo) { const { nonce } = accountInfo; let free, reserved, feeFrozen, miscFrozen, frozen, transferable; if (accountData.miscFrozen) { const tmpData = accountData; free = tmpData.free; reserved = tmpData.reserved; feeFrozen = tmpData.feeFrozen; miscFrozen = tmpData.miscFrozen; frozen = 'frozen does not exist for this runtime'; transferable = 'transferable formula not supported for this runtime'; } else { const deriveData = await this.api.derive.balances.all(address); const tmpData = accountData; free = tmpData.free; reserved = tmpData.reserved; frozen = tmpData.frozen; transferable = deriveData.transferable ? deriveData.transferable : 'transferable formula not supported for this runtime'; feeFrozen = 'feeFrozen does not exist for this runtime'; miscFrozen = 'miscFrozen does not exist for this runtime'; } return { at, nonce, tokenSymbol: token, free: this.inDenominationBal(denominate, free, decimal), reserved: this.inDenominationBal(denominate, reserved, decimal), miscFrozen: typeof miscFrozen === 'string' ? miscFrozen : this.inDenominationBal(denominate, miscFrozen, decimal), feeFrozen: typeof feeFrozen === 'string' ? feeFrozen : this.inDenominationBal(denominate, feeFrozen, decimal), frozen: typeof frozen === 'string' ? frozen : this.inDenominationBal(denominate, frozen, decimal), transferable: typeof transferable === 'string' ? transferable : this.inDenominationBal(denominate, transferable, decimal), locks: this.inDenominationLocks(denominate, locks, decimal), }; } else { throw new http_errors_1.BadRequest('Account not found'); } } /** * Apply a denomination to a balance depending on the chains decimal value. * * @param balance free balance available encoded as Balance. This will be * represented as an atomic value. * @param dec The chains given decimal token value. It must be > 0, and it * is applied to the given atomic value given by the `balance`. */ applyDenominationBalance(balance, dec) { const strBalance = balance.toString(); // We dont want to denominate a zero balance or zero decimal if (strBalance === '0' || dec === 0) { return strBalance; } // If the denominated value will be less then zero, pad it correctly if (strBalance.length <= dec) { return '.'.padEnd(dec - strBalance.length + 1, '0').concat(strBalance); } const lenDiff = strBalance.length - dec; return strBalance.substring(0, lenDiff) + '.' + strBalance.substring(lenDiff, strBalance.length); } /** * Parse and denominate the `amount` key in each BalanceLock * * @param locks A vector containing BalanceLock objects * @param dec The chains given decimal value */ applyDenominationLocks(locks, dec) { return locks.map((lock) => { return { id: lock.id, amount: this.applyDenominationBalance(lock.amount, dec), reasons: lock.reasons, }; }); } /** * Either denominate a value, or return the original Balance as an atomic value. * * @param denominate Boolean to determine whether or not we denominate a balance * @param bal Inputted Balance * @param dec Decimal value used to denominate a Balance */ inDenominationBal(denominate, bal, dec) { return denominate ? this.applyDenominationBalance(bal, dec) : bal; } /** * Either denominate the Balance's within Locks or return the original Locks. * * @param denominate Boolean to determine whether or not we denominate a balance * @param locks Inputted Vec<BalanceLock>, only the amount key will be denominated * @param dec Decimal value used to denominate a Balance */ inDenominationLocks(denominate, locks, dec) { return denominate ? this.applyDenominationLocks(locks, dec) : locks; } } exports.AccountsBalanceInfoService = AccountsBalanceInfoService; //# sourceMappingURL=AccountsBalanceInfoService.js.map