|
| 1 | +// Copyright 2017-2025 Parity Technologies (UK) Ltd. |
| 2 | +// This file is part of Substrate API Sidecar. |
| 3 | +// |
| 4 | +// Substrate API Sidecar is free software: you can redistribute it and/or modify |
| 5 | +// it under the terms of the GNU General Public License as published by |
| 6 | +// the Free Software Foundation, either version 3 of the License, or |
| 7 | +// (at your option) any later version. |
| 8 | +// |
| 9 | +// This program is distributed in the hope that it will be useful, |
| 10 | +// but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | +// GNU General Public License for more details. |
| 13 | +// |
| 14 | +// You should have received a copy of the GNU General Public License |
| 15 | +// along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 16 | + |
| 17 | +import { RequestHandler } from 'express'; |
| 18 | + |
| 19 | +import { validateAddress, validateUseRcBlock } from '../../middleware'; |
| 20 | +import { AccountsForeignAssetsService } from '../../services/accounts'; |
| 21 | +import AbstractController from '../AbstractController'; |
| 22 | + |
| 23 | +/** |
| 24 | + * Get foreign asset balance information for an address. |
| 25 | + * |
| 26 | + * Paths: |
| 27 | + * - `address`: The address to query |
| 28 | + * |
| 29 | + * Query: |
| 30 | + * - (Optional)`at`: Block at which to retrieve balance information. Block |
| 31 | + * identifier, as the block height or block hash. Defaults to most recent block. |
| 32 | + * - (Optional) `useRcBlock`: When true, treats the `at` parameter as a relay chain block |
| 33 | + * to find corresponding Asset Hub blocks. Only supported for Asset Hub endpoints. |
| 34 | + * - (Optional)`foreignAssets`: Comma-separated list of multilocation JSON strings to filter by. |
| 35 | + * If not provided, all foreign asset balances for the account will be returned. |
| 36 | + * |
| 37 | + * `/accounts/:address/foreign-asset-balances` |
| 38 | + * Returns: |
| 39 | + * - When using `useRcBlock=true`: An array of response objects, one for each Asset Hub block found |
| 40 | + * in the specified relay chain block. Returns empty array `[]` if no Asset Hub blocks found. |
| 41 | + * - When using `useRcBlock=false` or omitted: A single response object. |
| 42 | + * |
| 43 | + * Response object structure: |
| 44 | + * - `at`: Block number and hash at which the call was made. |
| 45 | + * - `foreignAssets`: An array of `ForeignAssetBalance` objects which have a multilocation attached to them |
| 46 | + * - `multiLocation`: The multilocation identifier of the foreign asset. |
| 47 | + * - `balance`: The balance of the foreign asset. |
| 48 | + * - `isFrozen`: Whether the asset is frozen for non-admin transfers. |
| 49 | + * - `isSufficient`: Whether a non-zero balance of this asset is a deposit of sufficient |
| 50 | + * value to account for the state bloat associated with its balance storage. If set to |
| 51 | + * `true`, then non-zero balances may be stored without a `consumer` reference (and thus |
| 52 | + * an ED in the Balances pallet or whatever else is used to control user-account state |
| 53 | + * growth). |
| 54 | + * - `rcBlockHash`: The relay chain block hash used for the query. Only present when `useRcBlock=true`. |
| 55 | + * - `rcBlockNumber`: The relay chain block number used for the query. Only present when `useRcBlock=true`. |
| 56 | + * - `ahTimestamp`: The Asset Hub block timestamp. Only present when `useRcBlock=true`. |
| 57 | + * |
| 58 | + * Substrate Reference: |
| 59 | + * - ForeignAssets Pallet: https://crates.parity.io/pallet_assets/index.html |
| 60 | + * - `AssetBalance`: https://crates.parity.io/pallet_assets/struct.AssetBalance.html |
| 61 | + * - XCM Multilocations: https://wiki.polkadot.network/docs/learn-xcm |
| 62 | + * |
| 63 | + */ |
| 64 | +export default class AccountsForeignAssetsController extends AbstractController<AccountsForeignAssetsService> { |
| 65 | + static controllerName = 'AccountsForeignAssets'; |
| 66 | + static requiredPallets = [['ForeignAssets']]; |
| 67 | + |
| 68 | + constructor(api: string) { |
| 69 | + super(api, '/accounts/:address', new AccountsForeignAssetsService(api)); |
| 70 | + this.initRoutes(); |
| 71 | + } |
| 72 | + |
| 73 | + protected initRoutes(): void { |
| 74 | + this.router.use(this.path, validateAddress, validateUseRcBlock); |
| 75 | + |
| 76 | + this.safeMountAsyncGetHandlers([['/foreign-asset-balances', this.getForeignAssetBalances]]); |
| 77 | + } |
| 78 | + |
| 79 | + private getForeignAssetBalances: RequestHandler = async ( |
| 80 | + { params: { address }, query: { at, useRcBlock, foreignAssets } }, |
| 81 | + res, |
| 82 | + ): Promise<void> => { |
| 83 | + if (useRcBlock === 'true') { |
| 84 | + const rcAtResults = await this.getHashFromRcAt(at); |
| 85 | + |
| 86 | + // Return empty array if no Asset Hub blocks found |
| 87 | + if (rcAtResults.length === 0) { |
| 88 | + AccountsForeignAssetsController.sanitizedSend(res, []); |
| 89 | + return; |
| 90 | + } |
| 91 | + |
| 92 | + const foreignAssetsArray = Array.isArray(foreignAssets) ? (foreignAssets as string[]) : []; |
| 93 | + |
| 94 | + // Process each Asset Hub block found |
| 95 | + const results = []; |
| 96 | + for (const { ahHash, rcBlockHash, rcBlockNumber } of rcAtResults) { |
| 97 | + const result = await this.service.fetchForeignAssetBalances(ahHash, address, foreignAssetsArray); |
| 98 | + |
| 99 | + const apiAt = await this.api.at(ahHash); |
| 100 | + const ahTimestamp = await apiAt.query.timestamp.now(); |
| 101 | + |
| 102 | + const enhancedResult = { |
| 103 | + ...result, |
| 104 | + rcBlockHash: rcBlockHash.toString(), |
| 105 | + rcBlockNumber, |
| 106 | + ahTimestamp: ahTimestamp.toString(), |
| 107 | + }; |
| 108 | + |
| 109 | + results.push(enhancedResult); |
| 110 | + } |
| 111 | + |
| 112 | + AccountsForeignAssetsController.sanitizedSend(res, results); |
| 113 | + } else { |
| 114 | + const hash = await this.getHashFromAt(at); |
| 115 | + const foreignAssetsArray = Array.isArray(foreignAssets) ? (foreignAssets as string[]) : []; |
| 116 | + const result = await this.service.fetchForeignAssetBalances(hash, address, foreignAssetsArray); |
| 117 | + AccountsForeignAssetsController.sanitizedSend(res, result); |
| 118 | + } |
| 119 | + }; |
| 120 | +} |
0 commit comments