diff --git a/src/renderer/shared/api/balances/service/balanceService.ts b/src/renderer/shared/api/balances/service/balanceService.ts index bc6d7c261..9575aae02 100644 --- a/src/renderer/shared/api/balances/service/balanceService.ts +++ b/src/renderer/shared/api/balances/service/balanceService.ts @@ -5,6 +5,7 @@ import { type AccountData, type Balance as ChainBalance } from '@polkadot/types/ import { type PalletBalancesBalanceLock } from '@polkadot/types/lookup'; import { type Codec } from '@polkadot/types/types'; import { type BN, BN_ZERO, hexToU8a } from '@polkadot/util'; +import { camelCase } from 'lodash'; import noop from 'lodash/noop'; import uniq from 'lodash/uniq'; @@ -18,7 +19,7 @@ import { type LockTypes, type OrmlExtras, } from '@/shared/core'; -import { getAssetId, getRepeatedIndex, toAddress } from '@/shared/lib/utils'; +import { getAssetId, getRepeatedIndex, groupBy, isHex, nullable, toAddress } from '@/shared/lib/utils'; type NoIdBalance = Omit; @@ -45,25 +46,25 @@ function subscribeBalances( ): UnsubscribePromise[] { const uniqueAccountIds = uniq(accountIds); - const { nativeAsset, statemineAssets, ormlAssets } = chain.assets.reduce<{ - nativeAsset?: Asset; - statemineAssets: Asset[]; - ormlAssets: Asset[]; - }>( - (acc, asset) => { - if (asset.type === AssetType.NATIVE) acc.nativeAsset = asset; - if (asset.type === AssetType.STATEMINE) acc.statemineAssets.push(asset); - if (asset.type === AssetType.ORML) acc.ormlAssets.push(asset); + const nativeAsset = chain.assets.find((asset) => asset.type === AssetType.NATIVE); + const statemineAssets = chain.assets.filter((asset) => asset.type === AssetType.STATEMINE); + const ormlAssets = chain.assets.filter((asset) => asset.type === AssetType.ORML); - return acc; - }, - { nativeAsset: undefined, statemineAssets: [], ormlAssets: [] }, - ); + const stateminePalletGroups = groupBy(statemineAssets, (asset) => { + if (asset.typeExtras && 'palletName' in asset.typeExtras) { + return camelCase(asset.typeExtras.palletName); + } + + return 'assets'; + }); return [ subscribeNativeAssetsChange(api, chain, nativeAsset?.assetId, uniqueAccountIds, callback), - subscribeStatemineAssetsChange(api, chain, statemineAssets, uniqueAccountIds, callback), subscribeOrmlAssetsChange(api, chain, ormlAssets, uniqueAccountIds, callback), + + ...Object.entries(stateminePalletGroups).map(([pallet, assets]) => { + return subscribeStatemineAssetsChange(api, pallet, chain, assets, uniqueAccountIds, callback); + }), ]; } @@ -143,25 +144,40 @@ function subscribeNativeAssetsChange( function subscribeStatemineAssetsChange( api: ApiPromise, + pallet: string, chain: Chain, assets: Asset[], accountIds: AccountId[], callback: (newBalances: NoIdBalance[]) => void, ): UnsubscribePromise { - if (!api || !assets.length || !accountIds.length || !api.query.assets) return Promise.resolve(noop); + if (!api || !assets.length || !accountIds.length) return Promise.resolve(noop); + + if (!api.query[pallet]) { + throw new Error(`Pallet ${pallet} not found.`); + } + + const type = api.tx.foreignAssets.transfer.meta.args[0].type; + if (nullable(type)) { + return Promise.resolve(noop); + } + + const assetsTuples = assets.reduce<[string | Codec, Address][]>((acc, asset) => { + const assetId = getAssetId(asset); + // @ts-expect-error type argument in createType has incorrect types + const location = isHex(assetId) ? api.createType(type, assetId) : assetId; - const assetsTuples = assets.reduce<[string, Address][]>((acc, asset) => { for (const accountId of accountIds) { - acc.push([getAssetId(asset), toAddress(accountId, { prefix: chain.addressPrefix })]); + acc.push([location, toAddress(accountId, { prefix: chain.addressPrefix })]); } return acc; }, []); - return api.query.assets.account.multi(assetsTuples, (data) => { + return api.query[pallet].account.multi(assetsTuples, (data) => { const newBalances: NoIdBalance[] = []; for (const [index, accountInfo] of data.entries()) { + // @ts-expect-error it's hard to type such cases const free = accountInfo.isNone ? BN_ZERO : accountInfo.unwrap().balance.toBn(); const accountIndex = index % accountIds.length; const assetIndex = getRepeatedIndex(index, accountIds.length); diff --git a/src/renderer/shared/core/types/asset.ts b/src/renderer/shared/core/types/asset.ts index 647a9de57..24e2e1986 100644 --- a/src/renderer/shared/core/types/asset.ts +++ b/src/renderer/shared/core/types/asset.ts @@ -29,6 +29,7 @@ export const enum AssetType { export type StatemineExtras = { assetId: string; + palletName?: string; }; export type OrmlExtras = { diff --git a/src/renderer/shared/lib/utils/__tests__/arrays.test.ts b/src/renderer/shared/lib/utils/__tests__/arrays.test.ts index 58a82a4ca..a568836bc 100644 --- a/src/renderer/shared/lib/utils/__tests__/arrays.test.ts +++ b/src/renderer/shared/lib/utils/__tests__/arrays.test.ts @@ -1,4 +1,4 @@ -import { addUnique, merge, splice } from '../arrays'; +import { addUnique, groupBy, merge, splice } from '../arrays'; describe('shared/lib/onChainUtils/arrays', () => { test('should insert element in the beginning', () => { @@ -145,4 +145,24 @@ describe('shared/lib/onChainUtils/arrays', () => { ]); }); }); + + describe('groupBy', () => { + it('should group', () => { + const list = [ + { type: 'a', v: 1 }, + { type: 'b', v: 1 }, + { type: 'a', v: 2 }, + ] as const; + + const groups = groupBy(list, (v) => v.type); + + expect(groups).toEqual({ + a: [ + { type: 'a', v: 1 }, + { type: 'a', v: 2 }, + ], + b: [{ type: 'b', v: 1 }], + }); + }); + }); }); diff --git a/src/renderer/shared/lib/utils/arrays.ts b/src/renderer/shared/lib/utils/arrays.ts index b76e16a5c..40212d614 100644 --- a/src/renderer/shared/lib/utils/arrays.ts +++ b/src/renderer/shared/lib/utils/arrays.ts @@ -134,3 +134,24 @@ export const merge = ({ a, b, mergeBy, sort }: MergeParams) => { return isFunction(sort) ? res.sort(sort) : res; }; + +export const groupBy = ( + iterable: Iterable, + map: (value: T) => K, +): Record => { + const groups: Partial> = {}; + + for (const item of iterable) { + const itemKey = map(item); + + let list = groups[itemKey]; + if (list === undefined) { + list = []; + groups[itemKey] = list; + } + + list.push(item); + } + + return groups as Record; +}; diff --git a/src/renderer/shared/lib/utils/strings.ts b/src/renderer/shared/lib/utils/strings.ts index 3ce492aec..2ba74932e 100644 --- a/src/renderer/shared/lib/utils/strings.ts +++ b/src/renderer/shared/lib/utils/strings.ts @@ -1,3 +1,4 @@ +import { type HexString } from '@/shared/core'; import { type Identity } from '@/shared/core/types/identity'; /** @@ -159,3 +160,7 @@ export const splitCamelCaseString = (value: string): string => { export const addLeadingZero = (value: number): string => { return value < 10 ? `0${value}` : `${value}`; }; + +export const isHex = (v: string): v is HexString => { + return v.startsWith('0x'); +};