@talismn/balances 0.0.0-pr2043-20250618043535 → 0.0.0-pr2043-20250618082459
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/declarations/src/getMiniMetadata/getMetadataRpc.d.ts +3 -0
- package/dist/declarations/src/getMiniMetadata/getMiniMetadatas.d.ts +4 -0
- package/dist/declarations/src/getMiniMetadata/getSpecVersion.d.ts +6 -0
- package/dist/declarations/src/getMiniMetadata/getUpdatedMiniMetadatas.d.ts +4 -0
- package/dist/declarations/src/getMiniMetadata/index.d.ts +4 -0
- package/dist/declarations/src/modules/SubstrateAssetsModule.d.ts +1 -1
- package/dist/declarations/src/modules/index.d.ts +207 -1
- package/dist/declarations/src/modules/util/buildStorageCoders.d.ts +3 -3
- package/dist/declarations/src/types/balances.d.ts +217 -5
- package/dist/talismn-balances.cjs.dev.js +278 -10
- package/dist/talismn-balances.cjs.prod.js +278 -10
- package/dist/talismn-balances.esm.js +279 -11
- package/package.json +8 -8
@@ -1,4 +1,4 @@
|
|
1
|
-
import { fetchInitMiniMetadatas, evmErc20TokenId as evmErc20TokenId$1, EvmErc20TokenSchema, evmNativeTokenId, evmUniswapV2TokenId, githubTokenLogoUrl, subAssetTokenId, subForeignAssetTokenId, subNativeTokenId, subPsp22TokenId, subTokensTokenId } from '@talismn/chaindata-provider';
|
1
|
+
import { fetchInitMiniMetadatas, evmErc20TokenId as evmErc20TokenId$1, EvmErc20TokenSchema, evmNativeTokenId, evmUniswapV2TokenId, githubTokenLogoUrl, parseSubAssetTokenId, subAssetTokenId, subForeignAssetTokenId, subNativeTokenId, subPsp22TokenId, subTokensTokenId } from '@talismn/chaindata-provider';
|
2
2
|
import { Dexie, liveQuery } from 'dexie';
|
3
3
|
import { from, Observable, scan, share, map, switchAll, combineLatest, mergeMap, toArray, interval, startWith, exhaustMap, pipe, filter, shareReplay, combineLatestWith, distinctUntilChanged, firstValueFrom, BehaviorSubject, debounceTime, takeUntil, switchMap, withLatestFrom, concatMap } from 'rxjs';
|
4
4
|
import anylogger from 'anylogger';
|
@@ -12,7 +12,9 @@ import { parseAbi, isHex, hexToBigInt } from 'viem';
|
|
12
12
|
import isEqual from 'lodash/isEqual';
|
13
13
|
import { defineMethod } from '@substrate/txwrapper-core';
|
14
14
|
import { unifyMetadata, decAnyMetadata, getDynamicBuilder, getLookupFn, getMetadataVersion, compactMetadata, encodeMetadata, decodeScale, papiParse, encodeStateKey } from '@talismn/scale';
|
15
|
+
import { keys, toPairs } from 'lodash';
|
15
16
|
import camelCase from 'lodash/camelCase';
|
17
|
+
import { fetchBestMetadata, getScaleApi } from '@talismn/sapi';
|
16
18
|
import { Metadata, TypeRegistry } from '@polkadot/types';
|
17
19
|
import groupBy from 'lodash/groupBy';
|
18
20
|
import { mergeUint8, toHex } from '@polkadot-api/utils';
|
@@ -20,7 +22,6 @@ import { Binary, AccountId } from 'polkadot-api';
|
|
20
22
|
import PromisePool from '@supercharge/promise-pool';
|
21
23
|
import { ChainConnectionError } from '@talismn/chain-connector';
|
22
24
|
import { u32, u128, Struct } from 'scale-ts';
|
23
|
-
import { getScaleApi } from '@talismn/sapi';
|
24
25
|
import upperFirst from 'lodash/upperFirst';
|
25
26
|
import { Abi } from '@polkadot/api-contract';
|
26
27
|
|
@@ -108,7 +109,7 @@ class EvmTokenFetcher {
|
|
108
109
|
|
109
110
|
var packageJson = {
|
110
111
|
name: "@talismn/balances",
|
111
|
-
version: "0.0.0-pr2043-
|
112
|
+
version: "0.0.0-pr2043-20250618082459"};
|
112
113
|
|
113
114
|
const libVersion = packageJson.version;
|
114
115
|
|
@@ -2939,6 +2940,139 @@ async function getPoolBalance(publicClient, contractAddress, accountAddress) {
|
|
2939
2940
|
}
|
2940
2941
|
}
|
2941
2942
|
|
2943
|
+
// cache the promise so it can be shared across multiple calls
|
2944
|
+
const CACHE_GET_SPEC_VERSION = new Map();
|
2945
|
+
const fetchSpecVersion = async (chainConnector, networkId) => {
|
2946
|
+
const {
|
2947
|
+
specVersion
|
2948
|
+
} = await chainConnector.send(networkId, "state_getRuntimeVersion", [true]);
|
2949
|
+
return specVersion;
|
2950
|
+
};
|
2951
|
+
|
2952
|
+
/**
|
2953
|
+
* fetches the spec version of a network. current request is cached in case of multiple calls (all balance subs kick in at once)
|
2954
|
+
*/
|
2955
|
+
const getSpecVersion = async (chainConnector, networkId) => {
|
2956
|
+
if (CACHE_GET_SPEC_VERSION.has(networkId)) return CACHE_GET_SPEC_VERSION.get(networkId);
|
2957
|
+
const pResult = fetchSpecVersion(chainConnector, networkId);
|
2958
|
+
CACHE_GET_SPEC_VERSION.set(networkId, pResult);
|
2959
|
+
try {
|
2960
|
+
return await pResult;
|
2961
|
+
} catch (cause) {
|
2962
|
+
throw new Error(`Failed to fetch specVersion for network ${networkId}`, {
|
2963
|
+
cause
|
2964
|
+
});
|
2965
|
+
} finally {
|
2966
|
+
CACHE_GET_SPEC_VERSION.delete(networkId);
|
2967
|
+
}
|
2968
|
+
};
|
2969
|
+
|
2970
|
+
// share requests as all modules will call this at once
|
2971
|
+
const CACHE$1 = new Map();
|
2972
|
+
const getMetadataRpc = async (chainConnector, networkId) => {
|
2973
|
+
if (CACHE$1.has(networkId)) return CACHE$1.get(networkId);
|
2974
|
+
const pResult = fetchBestMetadata((...args) => chainConnector.send(networkId, ...args), true // allow fallback to 14 as modules dont use any v15 or v16 specifics yet
|
2975
|
+
);
|
2976
|
+
CACHE$1.set(networkId, pResult);
|
2977
|
+
try {
|
2978
|
+
return await pResult;
|
2979
|
+
} catch (cause) {
|
2980
|
+
throw new Error(`Failed to fetch metadataRpc for network ${networkId}`, {
|
2981
|
+
cause
|
2982
|
+
});
|
2983
|
+
} finally {
|
2984
|
+
CACHE$1.delete(networkId);
|
2985
|
+
}
|
2986
|
+
};
|
2987
|
+
|
2988
|
+
// share requests as all modules will call this at once
|
2989
|
+
const CACHE = new Map();
|
2990
|
+
const getMiniMetadatas = async (chainConnector, chaindataProvider, networkId, specVersion) => {
|
2991
|
+
if (CACHE.has(networkId)) return CACHE.get(networkId);
|
2992
|
+
const pResult = fetchMiniMetadatas(chainConnector, chaindataProvider, networkId, specVersion);
|
2993
|
+
CACHE.set(networkId, pResult);
|
2994
|
+
try {
|
2995
|
+
return await pResult;
|
2996
|
+
} catch (cause) {
|
2997
|
+
throw new Error(`Failed to fetch metadataRpc for network ${networkId}`, {
|
2998
|
+
cause
|
2999
|
+
});
|
3000
|
+
} finally {
|
3001
|
+
CACHE.delete(networkId);
|
3002
|
+
}
|
3003
|
+
};
|
3004
|
+
const fetchMiniMetadatas = async (chainConnector, chaindataProvider, chainId, specVersion) => {
|
3005
|
+
const metadataRpc = await getMetadataRpc(chainConnector, chainId);
|
3006
|
+
const chainConnectors = {
|
3007
|
+
substrate: chainConnector
|
3008
|
+
};
|
3009
|
+
const modules = defaultBalanceModules.map(mod => mod({
|
3010
|
+
chainConnectors,
|
3011
|
+
chaindataProvider
|
3012
|
+
})).filter(mod => mod.type.startsWith("substrate-"));
|
3013
|
+
return Promise.all(modules.map(async mod => {
|
3014
|
+
const source = mod.type;
|
3015
|
+
const chainMeta = await mod.fetchSubstrateChainMeta(chainId, {}, metadataRpc, {});
|
3016
|
+
return {
|
3017
|
+
id: deriveMiniMetadataId({
|
3018
|
+
source,
|
3019
|
+
chainId,
|
3020
|
+
specVersion,
|
3021
|
+
libVersion
|
3022
|
+
}),
|
3023
|
+
source,
|
3024
|
+
chainId,
|
3025
|
+
specVersion,
|
3026
|
+
libVersion,
|
3027
|
+
data: chainMeta?.miniMetadata ?? null
|
3028
|
+
};
|
3029
|
+
}));
|
3030
|
+
};
|
3031
|
+
|
3032
|
+
const getUpdatedMiniMetadatas = async (chainConnector, chaindataProvider, networkId, specVersion) => {
|
3033
|
+
const miniMetadatas = await getMiniMetadatas(chainConnector, chaindataProvider, networkId, specVersion);
|
3034
|
+
await db.transaction("readwrite", "miniMetadatas", async tx => {
|
3035
|
+
await tx.miniMetadatas.where({
|
3036
|
+
networkId
|
3037
|
+
}).delete();
|
3038
|
+
await tx.miniMetadatas.bulkPut(miniMetadatas);
|
3039
|
+
});
|
3040
|
+
return miniMetadatas;
|
3041
|
+
};
|
3042
|
+
|
3043
|
+
const getMiniMetadata = async (chaindataProvider, chainConnector, chainId, source) => {
|
3044
|
+
const specVersion = await getSpecVersion(chainConnector, chainId);
|
3045
|
+
|
3046
|
+
// TODO when working a chaindata branch, need a way to pass the libVersion used to derive the miniMetadataId got github
|
3047
|
+
const miniMetadataId = deriveMiniMetadataId({
|
3048
|
+
source,
|
3049
|
+
chainId,
|
3050
|
+
specVersion,
|
3051
|
+
libVersion
|
3052
|
+
});
|
3053
|
+
|
3054
|
+
// lookup local ones
|
3055
|
+
const [dbMiniMetadata, ghMiniMetadata] = await Promise.all([db.miniMetadatas.get(miniMetadataId), chaindataProvider.miniMetadataById(miniMetadataId)]);
|
3056
|
+
const miniMetadata = dbMiniMetadata ?? ghMiniMetadata;
|
3057
|
+
if (miniMetadata) return miniMetadata;
|
3058
|
+
|
3059
|
+
// update from live chain metadata and persist locally
|
3060
|
+
const miniMetadatas = await getUpdatedMiniMetadatas(chainConnector, chaindataProvider, chainId, specVersion);
|
3061
|
+
const found = miniMetadatas.find(m => m.id === miniMetadataId);
|
3062
|
+
if (!found) {
|
3063
|
+
log.warn("MiniMetadata not found in updated miniMetadatas", {
|
3064
|
+
source,
|
3065
|
+
chainId,
|
3066
|
+
specVersion,
|
3067
|
+
libVersion,
|
3068
|
+
miniMetadataId,
|
3069
|
+
miniMetadatas
|
3070
|
+
});
|
3071
|
+
throw new Error(`MiniMetadata not found for ${source} on ${chainId}`);
|
3072
|
+
}
|
3073
|
+
return found;
|
3074
|
+
};
|
3075
|
+
|
2942
3076
|
/**
|
2943
3077
|
* Wraps a BalanceModule's fetch/subscribe methods with a single `balances` method.
|
2944
3078
|
* This `balances` method will subscribe if a callback parameter is provided, or otherwise fetch.
|
@@ -3244,7 +3378,7 @@ const SubAssetsModule = hydrate => {
|
|
3244
3378
|
const tokens = {};
|
3245
3379
|
for (const tokenConfig of moduleConfig?.tokens ?? []) {
|
3246
3380
|
try {
|
3247
|
-
const assetId =
|
3381
|
+
const assetId = tokenConfig.assetId;
|
3248
3382
|
const assetStateKey = tryEncode(assetCoder, BigInt(assetId)) ?? tryEncode(assetCoder, assetId);
|
3249
3383
|
const metadataStateKey = tryEncode(metadataCoder, BigInt(assetId)) ?? tryEncode(metadataCoder, assetId);
|
3250
3384
|
if (assetStateKey === null || metadataStateKey === null) throw new Error(`Failed to encode stateKey for asset ${assetId} on chain ${chainId}`);
|
@@ -3286,13 +3420,47 @@ const SubAssetsModule = hydrate => {
|
|
3286
3420
|
async subscribeBalances({
|
3287
3421
|
addressesByToken
|
3288
3422
|
}, callback) {
|
3289
|
-
const
|
3290
|
-
|
3291
|
-
if (
|
3292
|
-
|
3293
|
-
|
3294
|
-
});
|
3295
|
-
|
3423
|
+
const byNetwork = keys(addressesByToken).reduce((acc, tokenId) => {
|
3424
|
+
const networkId = parseSubAssetTokenId(tokenId).networkId;
|
3425
|
+
if (!acc[networkId]) acc[networkId] = {};
|
3426
|
+
acc[networkId][tokenId] = addressesByToken[tokenId];
|
3427
|
+
return acc;
|
3428
|
+
}, {});
|
3429
|
+
const controller = new AbortController();
|
3430
|
+
await Promise.all(toPairs(byNetwork).map(async ([networkId, addressesByToken]) => {
|
3431
|
+
const queries = await buildNetworkQueries(networkId, chainConnector, chaindataProvider, addressesByToken);
|
3432
|
+
if (controller.signal.aborted) return;
|
3433
|
+
const stateHelper = new RpcStateQueryHelper(chainConnector, queries);
|
3434
|
+
const unsubscribe = await stateHelper.subscribe((error, result) => {
|
3435
|
+
// console.log("SubstrateAssetsModule.callback", { error, result })
|
3436
|
+
if (error) return callback(error);
|
3437
|
+
const balances = result?.filter(b => b !== null) ?? [];
|
3438
|
+
if (balances.length > 0) callback(null, new Balances(balances));
|
3439
|
+
});
|
3440
|
+
controller.signal.addEventListener("abort", () => {
|
3441
|
+
log.debug("TMP subscribeBalances aborted, unsubscribing from network", networkId);
|
3442
|
+
unsubscribe();
|
3443
|
+
});
|
3444
|
+
}));
|
3445
|
+
|
3446
|
+
// const networkIds = uniq(uniq(keys(addressesByToken)).map((tokenId) => parseSubAssetTokenId(tokenId).networkId))
|
3447
|
+
// const
|
3448
|
+
|
3449
|
+
//console.log("SubstrateAssetsModule.subscribeBalances 1", { addressesByToken })
|
3450
|
+
// const queries = await buildQueries(chaindataProvider, addressesByToken)
|
3451
|
+
// //console.log("SubstrateAssetsModule.subscribeBalances 2", { queries, addressesByToken })
|
3452
|
+
// const unsubscribe = await new RpcStateQueryHelper(chainConnector, queries).subscribe(
|
3453
|
+
// (error, result) => {
|
3454
|
+
// // console.log("SubstrateAssetsModule.callback", { error, result })
|
3455
|
+
// if (error) return callback(error)
|
3456
|
+
// const balances = result?.filter((b): b is SubAssetsBalance => b !== null) ?? []
|
3457
|
+
// if (balances.length > 0) callback(null, new Balances(balances))
|
3458
|
+
// },
|
3459
|
+
// )
|
3460
|
+
|
3461
|
+
return () => {
|
3462
|
+
controller.abort();
|
3463
|
+
};
|
3296
3464
|
},
|
3297
3465
|
async fetchBalances(addressesByToken) {
|
3298
3466
|
assert(chainConnectors.substrate, "This module requires a substrate chain connector");
|
@@ -3366,9 +3534,109 @@ const SubAssetsModule = hydrate => {
|
|
3366
3534
|
}
|
3367
3535
|
};
|
3368
3536
|
};
|
3537
|
+
async function buildNetworkQueries(networkId, chainConnector, chaindataProvider, addressesByToken) {
|
3538
|
+
const miniMetadata = await getMiniMetadata(chaindataProvider, chainConnector, networkId, moduleType$4);
|
3539
|
+
const network = await chaindataProvider.chainById(networkId);
|
3540
|
+
const tokensById = await chaindataProvider.tokensById();
|
3541
|
+
const chainIds = [networkId];
|
3542
|
+
const chains = network ? {
|
3543
|
+
[networkId]: network
|
3544
|
+
} : {};
|
3545
|
+
const miniMetadatas = new Map([[miniMetadata.id, miniMetadata]]);
|
3546
|
+
const chainStorageCoders = buildStorageCoders({
|
3547
|
+
chainIds,
|
3548
|
+
chains,
|
3549
|
+
miniMetadatas,
|
3550
|
+
moduleType: moduleType$4,
|
3551
|
+
coders: {
|
3552
|
+
storage: ["Assets", "Account"]
|
3553
|
+
}
|
3554
|
+
});
|
3555
|
+
return Object.entries(addressesByToken).flatMap(([tokenId, addresses]) => {
|
3556
|
+
const token = tokensById[tokenId];
|
3557
|
+
if (!token) {
|
3558
|
+
log.warn(`Token ${tokenId} not found`);
|
3559
|
+
return [];
|
3560
|
+
}
|
3561
|
+
if (token.type !== "substrate-assets") {
|
3562
|
+
log.debug(`This module doesn't handle tokens of type ${token.type}`);
|
3563
|
+
return [];
|
3564
|
+
}
|
3565
|
+
const networkId = token.networkId;
|
3566
|
+
if (!networkId) {
|
3567
|
+
log.warn(`Token ${tokenId} has no chain`);
|
3568
|
+
return [];
|
3569
|
+
}
|
3570
|
+
const chain = chains[networkId];
|
3571
|
+
if (!chain) {
|
3572
|
+
log.warn(`Chain ${networkId} for token ${tokenId} not found`);
|
3573
|
+
return [];
|
3574
|
+
}
|
3575
|
+
return addresses.flatMap(address => {
|
3576
|
+
const scaleCoder = chainStorageCoders.get(networkId)?.storage;
|
3577
|
+
const stateKey = tryEncode(scaleCoder, BigInt(token.assetId), address) ?? tryEncode(scaleCoder, token.assetId, address);
|
3578
|
+
if (!stateKey) {
|
3579
|
+
log.warn(`Invalid assetId / address in ${networkId} storage query ${token.assetId} / ${address}`);
|
3580
|
+
return [];
|
3581
|
+
}
|
3582
|
+
const decodeResult = change => {
|
3583
|
+
/** NOTE: This type is only a hint for typescript, the chain can actually return whatever it wants to */
|
3584
|
+
|
3585
|
+
const decoded = decodeScale(scaleCoder, change, `Failed to decode substrate-assets balance on chain ${networkId}`) ?? {
|
3586
|
+
balance: 0n,
|
3587
|
+
status: {
|
3588
|
+
type: "Liquid"
|
3589
|
+
}};
|
3590
|
+
const isFrozen = decoded?.status?.type === "Frozen";
|
3591
|
+
const amount = (decoded?.balance ?? 0n).toString();
|
3592
|
+
|
3593
|
+
// due to the following balance calculations, which are made in the `Balance` type:
|
3594
|
+
//
|
3595
|
+
// total balance = (free balance) + (reserved balance)
|
3596
|
+
// transferable balance = (free balance) - (frozen balance)
|
3597
|
+
//
|
3598
|
+
// when `isFrozen` is true we need to set **both** the `free` and `frozen` amounts
|
3599
|
+
// of this balance to the value we received from the RPC.
|
3600
|
+
//
|
3601
|
+
// if we only set the `frozen` amount, then the `total` calculation will be incorrect!
|
3602
|
+
const free = amount;
|
3603
|
+
const frozen = token.isFrozen || isFrozen ? amount : "0";
|
3604
|
+
|
3605
|
+
// include balance values even if zero, so that newly-zero values overwrite old values
|
3606
|
+
const balanceValues = [{
|
3607
|
+
type: "free",
|
3608
|
+
label: "free",
|
3609
|
+
amount: free.toString()
|
3610
|
+
}, {
|
3611
|
+
type: "locked",
|
3612
|
+
label: "frozen",
|
3613
|
+
amount: frozen.toString()
|
3614
|
+
}];
|
3615
|
+
return {
|
3616
|
+
source: "substrate-assets",
|
3617
|
+
status: "live",
|
3618
|
+
address,
|
3619
|
+
networkId,
|
3620
|
+
tokenId: token.id,
|
3621
|
+
values: balanceValues
|
3622
|
+
};
|
3623
|
+
};
|
3624
|
+
return {
|
3625
|
+
chainId: networkId,
|
3626
|
+
stateKey,
|
3627
|
+
decodeResult
|
3628
|
+
};
|
3629
|
+
});
|
3630
|
+
});
|
3631
|
+
}
|
3369
3632
|
async function buildQueries$3(chaindataProvider, addressesByToken) {
|
3370
3633
|
const allChains = await chaindataProvider.chainsById();
|
3371
3634
|
const tokens = await chaindataProvider.tokensById();
|
3635
|
+
|
3636
|
+
// const networkIds = Object.keys(addressesByToken)
|
3637
|
+
|
3638
|
+
// const
|
3639
|
+
// const miniMetadatas = await getMiniMetadatas(chainConnector, chaindataProvider, network)
|
3372
3640
|
const miniMetadatas = new Map((await db.miniMetadatas.toArray()).map(miniMetadata => [miniMetadata.id, miniMetadata]));
|
3373
3641
|
const uniqueChainIds = getUniqueChainIds(addressesByToken, tokens);
|
3374
3642
|
const chains = Object.fromEntries(uniqueChainIds.map(chainId => [chainId, allChains[chainId]]));
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@talismn/balances",
|
3
|
-
"version": "0.0.0-pr2043-
|
3
|
+
"version": "0.0.0-pr2043-20250618082459",
|
4
4
|
"author": "Talisman",
|
5
5
|
"homepage": "https://talisman.xyz",
|
6
6
|
"license": "GPL-3.0-or-later",
|
@@ -33,12 +33,12 @@
|
|
33
33
|
"rxjs": "^7.8.1",
|
34
34
|
"scale-ts": "^1.6.1",
|
35
35
|
"viem": "^2.27.3",
|
36
|
-
"@talismn/chain-connector": "0.0.0-pr2043-
|
37
|
-
"@talismn/
|
38
|
-
"@talismn/
|
39
|
-
"@talismn/
|
36
|
+
"@talismn/chain-connector": "0.0.0-pr2043-20250618082459",
|
37
|
+
"@talismn/chaindata-provider": "0.0.0-pr2043-20250618082459",
|
38
|
+
"@talismn/sapi": "0.0.0-pr2043-20250618082459",
|
39
|
+
"@talismn/token-rates": "0.0.0-pr2043-20250618082459",
|
40
|
+
"@talismn/chain-connector-evm": "0.0.0-pr2043-20250618082459",
|
40
41
|
"@talismn/scale": "0.1.2",
|
41
|
-
"@talismn/token-rates": "0.0.0-pr2043-20250618043535",
|
42
42
|
"@talismn/util": "0.4.2"
|
43
43
|
},
|
44
44
|
"devDependencies": {
|
@@ -54,8 +54,8 @@
|
|
54
54
|
"jest": "^29.7.0",
|
55
55
|
"ts-jest": "^29.2.5",
|
56
56
|
"typescript": "^5.6.3",
|
57
|
-
"@talismn/
|
58
|
-
"@talismn/
|
57
|
+
"@talismn/tsconfig": "0.0.2",
|
58
|
+
"@talismn/eslint-config": "0.0.3"
|
59
59
|
},
|
60
60
|
"peerDependencies": {
|
61
61
|
"@polkadot/api-contract": "*",
|