Skip to content

Commit aa307b2

Browse files
committed
feat: Add accounts/{}/foreign-asset-balances
1 parent 22ee44c commit aa307b2

File tree

10 files changed

+415
-0
lines changed

10 files changed

+415
-0
lines changed

src/chains-config/assetHubKusamaControllers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const assetHubKusamaControllers: ControllerConfig = {
2525
'AccountsAssets',
2626
'AccountsBalanceInfo',
2727
'AccountsCompare',
28+
'AccountsForeignAssets',
2829
'AccountsPoolAssets',
2930
'AccountsProxyInfo',
3031
'AccountsStakingInfo',

src/chains-config/assetHubNextWestendControllers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const assetHubNextWestendControllers: ControllerConfig = {
2424
controllers: [
2525
'AccountsAssets',
2626
'AccountsBalanceInfo',
27+
'AccountsForeignAssets',
2728
'AccountsPoolAssets',
2829
'AccountsProxyInfo',
2930
'AccountsStakingInfo',

src/chains-config/assetHubPolkadotControllers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export const assetHubPolkadotControllers: ControllerConfig = {
2525
'AccountsAssets',
2626
'AccountsBalanceInfo',
2727
'AccountsCompare',
28+
'AccountsForeignAssets',
2829
'AccountsPoolAssets',
2930
'AccountsProxyInfo',
3031
'AccountsStakingInfo',

src/chains-config/assetHubWestendControllers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const assetHubWestendControllers: ControllerConfig = {
2424
controllers: [
2525
'AccountsAssets',
2626
'AccountsBalanceInfo',
27+
'AccountsForeignAssets',
2728
'AccountsPoolAssets',
2829
'AccountsProxyInfo',
2930
'AccountsStakingInfo',
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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+
}

src/controllers/accounts/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export { default as AccountsAssets } from './AccountsAssetsController';
1818
export { default as AccountsBalanceInfo } from './AccountsBalanceInfoController';
1919
export { default as AccountsCompare } from './AccountsCompareController';
2020
export { default as AccountsConvert } from './AccountsConvertController';
21+
export { default as AccountsForeignAssets } from './AccountsForeignAssetsController';
2122
export { default as AccountsPoolAssets } from './AccountsPoolAssetsController';
2223
export { default as AccountsProxyInfo } from './AccountsProxyInfoController';
2324
export { default as AccountsStakingInfo } from './AccountsStakingInfoController';

src/controllers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
AccountsBalanceInfo,
2020
AccountsCompare,
2121
AccountsConvert,
22+
AccountsForeignAssets,
2223
AccountsPoolAssets,
2324
AccountsProxyInfo,
2425
AccountsStakingInfo,
@@ -95,6 +96,7 @@ export const controllers = {
9596
AccountsBalanceInfo,
9697
AccountsCompare,
9798
AccountsConvert,
99+
AccountsForeignAssets,
98100
AccountsPoolAssets,
99101
AccountsProxyInfo,
100102
AccountsStakingInfo,

0 commit comments

Comments
 (0)