Skip to content

Commit 1772e85

Browse files
feat: Add accounts/{}/foreign-asset-balances (#1834)
* feat: Add accounts/{}/foreign-asset-balances * feat: Query optimization for accounts/{}/foreign-asset-balances Inspired from Filippo's suggestion * fix: Have isFrozen return false if pallet is not available * docs: Add docs for foreign-asset-balances * feat: Address Tarik's comments on typing Using XcmVersionedLocation now. * style: Remove annoying inline linting rules from AccountsFerignAssetsService.ts
1 parent 22ee44c commit 1772e85

File tree

14 files changed

+624
-2
lines changed

14 files changed

+624
-2
lines changed

docs-v2/dist/bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs-v2/openapi-v1.yaml

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,70 @@ paths:
434434
application/json:
435435
schema:
436436
$ref: '#/components/schemas/Error'
437+
/accounts/{accountId}/foreign-asset-balances:
438+
get:
439+
tags:
440+
- accounts
441+
summary: Get an array of foreign-asset-balances for an account.
442+
description: Returns information about an account's foreign-asset-balances. This is
443+
specific to the foreignAssets pallet for parachains. If no `foreignAssets` query parameter
444+
is provided, all foreign-asset-balances for the given account will be returned.
445+
operationId: getForeignAssetBalances
446+
parameters:
447+
- name: accountId
448+
in: path
449+
description: SS58 address of the account.
450+
required: true
451+
schema:
452+
type: string
453+
format: SS58
454+
- name: at
455+
in: query
456+
description: Block at which to query foreign-asset-balance info for the
457+
specified account.
458+
required: false
459+
schema:
460+
type: string
461+
description: Block height (as a positive integer) or hash
462+
(as a hex string).
463+
format: unsignedInteger or $hex
464+
- name: useRcBlock
465+
in: query
466+
description: When set to 'true', uses the relay chain block specified in the 'at' parameter to determine corresponding Asset Hub block(s) for retrieving foreign-asset-balance info. Only supported for Asset Hub endpoints. Requires 'at' parameter to specify the relay chain block. When used, returns an array of response objects (one for each Asset Hub block found) or empty array if none found.
467+
required: false
468+
schema:
469+
type: boolean
470+
description: When set to 'true', uses relay chain block mode with the 'at' parameter.
471+
- name: foreignAssets
472+
in: query
473+
description: An array of multilocation JSON strings to be queried. If not supplied, all
474+
foreign asset balances associated with the `accountId` will be returned. The array query param
475+
format follows Express 4.x API. ex:`?foreignAssets[]={"parents":"1","interior":{"X1":{"Parachain":"2125"}}}&foreignAssets[]={"parents":"2","interior":{"X1":{"GlobalConsensus":"Polkadot"}}}`.
476+
required: false
477+
schema:
478+
type: array
479+
items:
480+
type: string
481+
description: An array of multilocation objects represented as JSON strings.
482+
format: Array of JSON strings
483+
responses:
484+
"200":
485+
description: successful operation
486+
content:
487+
application/json:
488+
schema:
489+
oneOf:
490+
- $ref: '#/components/schemas/AccountForeignAssetsBalances'
491+
- type: array
492+
items:
493+
$ref: '#/components/schemas/AccountForeignAssetsBalances'
494+
description: Returns a single object when using standard parameters. Returns an array when using 'useRcBlock' parameter (array contains one object per Asset Hub block found, or empty array if none found).
495+
"400":
496+
description: Invalid Address, invalid blockId supplied for at query param, or invalid parameter combination
497+
content:
498+
application/json:
499+
schema:
500+
$ref: '#/components/schemas/Error'
437501
/accounts/{accountId}/proxy-info:
438502
get:
439503
tags:
@@ -5065,6 +5129,49 @@ components:
50655129
type: string
50665130
description: The Asset Hub block timestamp. Only present when `useRcBlock` parameter is used.
50675131
format: unsignedInteger
5132+
AccountForeignAssetsBalances:
5133+
type: object
5134+
properties:
5135+
at:
5136+
$ref: '#/components/schemas/BlockIdentifiers'
5137+
foreignAssets:
5138+
type: array
5139+
description: An array of queried foreign assets.
5140+
items:
5141+
$ref: '#/components/schemas/ForeignAssetBalance'
5142+
rcBlockHash:
5143+
type: string
5144+
description: The relay chain block hash used for the query. Only present when `useRcBlock` parameter is used.
5145+
rcBlockNumber:
5146+
type: string
5147+
description: The relay chain block number used for the query. Only present when `useRcBlock` parameter is used.
5148+
format: unsignedInteger
5149+
ahTimestamp:
5150+
type: string
5151+
description: The Asset Hub block timestamp. Only present when `useRcBlock` parameter is used.
5152+
format: unsignedInteger
5153+
ForeignAssetBalance:
5154+
type: object
5155+
properties:
5156+
multiLocation:
5157+
type: object
5158+
description: The multilocation identifier of the foreign asset. This is an XCM multilocation
5159+
object that uniquely identifies an asset across different parachains.
5160+
balance:
5161+
type: string
5162+
description: The balance of the foreign asset.
5163+
format: unsignedInteger
5164+
isFrozen:
5165+
type: boolean
5166+
description: Whether the asset is frozen for non-admin transfers. Returns false if the
5167+
runtime does not support isFrozen.
5168+
isSufficient:
5169+
type: boolean
5170+
description: Whether a non-zero balance of this asset is a deposit of sufficient
5171+
value to account for the state bloat associated with its balance storage. If set to
5172+
`true`, then non-zero balances may be stored without a `consumer` reference (and thus
5173+
an ED in the Balances pallet or whatever else is used to control user-account state
5174+
growth).
50685175
AccountProxyInfo:
50695176
type: object
50705177
properties:

docs/dist/app.bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/src/openapi-v1.yaml

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -434,6 +434,70 @@ paths:
434434
application/json:
435435
schema:
436436
$ref: '#/components/schemas/Error'
437+
/accounts/{accountId}/foreign-asset-balances:
438+
get:
439+
tags:
440+
- accounts
441+
summary: Get an array of foreign-asset-balances for an account.
442+
description: Returns information about an account's foreign-asset-balances. This is
443+
specific to the foreignAssets pallet for parachains. If no `foreignAssets` query parameter
444+
is provided, all foreign-asset-balances for the given account will be returned.
445+
operationId: getForeignAssetBalances
446+
parameters:
447+
- name: accountId
448+
in: path
449+
description: SS58 address of the account.
450+
required: true
451+
schema:
452+
type: string
453+
format: SS58
454+
- name: at
455+
in: query
456+
description: Block at which to query foreign-asset-balance info for the
457+
specified account.
458+
required: false
459+
schema:
460+
type: string
461+
description: Block height (as a positive integer) or hash
462+
(as a hex string).
463+
format: unsignedInteger or $hex
464+
- name: useRcBlock
465+
in: query
466+
description: When set to 'true', uses the relay chain block specified in the 'at' parameter to determine corresponding Asset Hub block(s) for retrieving foreign-asset-balance info. Only supported for Asset Hub endpoints. Requires 'at' parameter to specify the relay chain block. When used, returns an array of response objects (one for each Asset Hub block found) or empty array if none found.
467+
required: false
468+
schema:
469+
type: boolean
470+
description: When set to 'true', uses relay chain block mode with the 'at' parameter.
471+
- name: foreignAssets
472+
in: query
473+
description: An array of multilocation JSON strings to be queried. If not supplied, all
474+
foreign asset balances associated with the `accountId` will be returned. The array query param
475+
format follows Express 4.x API. ex:`?foreignAssets[]={"parents":"1","interior":{"X1":{"Parachain":"2125"}}}&foreignAssets[]={"parents":"2","interior":{"X1":{"GlobalConsensus":"Polkadot"}}}`.
476+
required: false
477+
schema:
478+
type: array
479+
items:
480+
type: string
481+
description: An array of multilocation objects represented as JSON strings.
482+
format: Array of JSON strings
483+
responses:
484+
"200":
485+
description: successful operation
486+
content:
487+
application/json:
488+
schema:
489+
oneOf:
490+
- $ref: '#/components/schemas/AccountForeignAssetsBalances'
491+
- type: array
492+
items:
493+
$ref: '#/components/schemas/AccountForeignAssetsBalances'
494+
description: Returns a single object when using standard parameters. Returns an array when using 'useRcBlock' parameter (array contains one object per Asset Hub block found, or empty array if none found).
495+
"400":
496+
description: Invalid Address, invalid blockId supplied for at query param, or invalid parameter combination
497+
content:
498+
application/json:
499+
schema:
500+
$ref: '#/components/schemas/Error'
437501
/accounts/{accountId}/proxy-info:
438502
get:
439503
tags:
@@ -5065,6 +5129,49 @@ components:
50655129
type: string
50665130
description: The Asset Hub block timestamp. Only present when `useRcBlock` parameter is used.
50675131
format: unsignedInteger
5132+
AccountForeignAssetsBalances:
5133+
type: object
5134+
properties:
5135+
at:
5136+
$ref: '#/components/schemas/BlockIdentifiers'
5137+
foreignAssets:
5138+
type: array
5139+
description: An array of queried foreign assets.
5140+
items:
5141+
$ref: '#/components/schemas/ForeignAssetBalance'
5142+
rcBlockHash:
5143+
type: string
5144+
description: The relay chain block hash used for the query. Only present when `useRcBlock` parameter is used.
5145+
rcBlockNumber:
5146+
type: string
5147+
description: The relay chain block number used for the query. Only present when `useRcBlock` parameter is used.
5148+
format: unsignedInteger
5149+
ahTimestamp:
5150+
type: string
5151+
description: The Asset Hub block timestamp. Only present when `useRcBlock` parameter is used.
5152+
format: unsignedInteger
5153+
ForeignAssetBalance:
5154+
type: object
5155+
properties:
5156+
multiLocation:
5157+
type: object
5158+
description: The multilocation identifier of the foreign asset. This is an XCM multilocation
5159+
object that uniquely identifies an asset across different parachains.
5160+
balance:
5161+
type: string
5162+
description: The balance of the foreign asset.
5163+
format: unsignedInteger
5164+
isFrozen:
5165+
type: boolean
5166+
description: Whether the asset is frozen for non-admin transfers. Returns false if the
5167+
runtime does not support isFrozen.
5168+
isSufficient:
5169+
type: boolean
5170+
description: Whether a non-zero balance of this asset is a deposit of sufficient
5171+
value to account for the state bloat associated with its balance storage. If set to
5172+
`true`, then non-zero balances may be stored without a `consumer` reference (and thus
5173+
an ED in the Balances pallet or whatever else is used to control user-account state
5174+
growth).
50685175
AccountProxyInfo:
50695176
type: object
50705177
properties:

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+
}

0 commit comments

Comments
 (0)