@talismn/balances 0.0.0-pr675-20230413170259 → 0.0.0-pr677-20230413171850

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/CHANGELOG.md CHANGED
@@ -1,16 +1,19 @@
1
1
  # @talismn/balances
2
2
 
3
- ## 0.0.0-pr675-20230413170259
3
+ ## 0.0.0-pr677-20230413171850
4
4
 
5
5
  ### Patch Changes
6
6
 
7
7
  - fb8ee962: feat: proxy dapp websocket requests to talisman wallet backend when available
8
+ - 306e7160: feat: crowdloan and nom pool balances
8
9
  - Updated dependencies [fb8ee962]
9
10
  - Updated dependencies [c898da98]
10
- - @talismn/chain-connector@0.0.0-pr675-20230413170259
11
- - @talismn/chain-connector-evm@0.0.0-pr675-20230413170259
12
- - @talismn/chaindata-provider@0.0.0-pr675-20230413170259
13
- - @talismn/token-rates@0.0.0-pr675-20230413170259
11
+ - Updated dependencies [306e7160]
12
+ - @talismn/chain-connector@0.0.0-pr677-20230413171850
13
+ - @talismn/chain-connector-evm@0.0.0-pr677-20230413171850
14
+ - @talismn/util@0.0.0-pr677-20230413171850
15
+ - @talismn/chaindata-provider@0.0.0-pr677-20230413171850
16
+ - @talismn/token-rates@0.0.0-pr677-20230413171850
14
17
 
15
18
  ## 0.4.0
16
19
 
@@ -1,4 +1,5 @@
1
1
  import type { Registry } from "@polkadot/types-codec/types";
2
+ import { ChainConnector } from "@talismn/chain-connector";
2
3
  import { ChainId } from "@talismn/chaindata-provider";
3
4
  import { BalanceModule, DefaultChainMeta, DefaultModuleConfig, DefaultTransferParams, ExtendableChainMeta, ExtendableModuleConfig, ExtendableTokenType, ExtendableTransferParams } from "./BalanceModule";
4
5
  import { AddressesByToken, Balance, BalanceJson, Balances, SubscriptionCallback, UnsubscribeFn } from "./types";
@@ -8,8 +9,9 @@ import { AddressesByToken, Balance, BalanceJson, Balances, SubscriptionCallback,
8
9
  */
9
10
  export declare function balances<TModuleType extends string, TTokenType extends ExtendableTokenType, TChainMeta extends ExtendableChainMeta = DefaultChainMeta, TModuleConfig extends ExtendableModuleConfig = DefaultModuleConfig, TTransferParams extends ExtendableTransferParams = DefaultTransferParams>(balanceModule: BalanceModule<TModuleType, TTokenType, TChainMeta, TModuleConfig, TTransferParams>, addressesByToken: AddressesByToken<TTokenType>): Promise<Balances>;
10
11
  export declare function balances<TModuleType extends string, TTokenType extends ExtendableTokenType, TChainMeta extends ExtendableChainMeta = DefaultChainMeta, TModuleConfig extends ExtendableModuleConfig = DefaultModuleConfig, TTransferParams extends ExtendableTransferParams = DefaultTransferParams>(balanceModule: BalanceModule<TModuleType, TTokenType, TChainMeta, TModuleConfig, TTransferParams>, addressesByToken: AddressesByToken<TTokenType>, callback: SubscriptionCallback<Balances>): Promise<UnsubscribeFn>;
12
+ export type GetOrCreateTypeRegistry = (chainId: ChainId, metadataRpc?: `0x${string}`) => Registry;
11
13
  export declare const createTypeRegistryCache: () => {
12
- getOrCreateTypeRegistry: (chainId: ChainId, metadataRpc?: `0x${string}`) => Registry;
14
+ getOrCreateTypeRegistry: GetOrCreateTypeRegistry;
13
15
  };
14
16
  export declare const filterMirrorTokens: (balance: Balance, i: number, balances: Balance[]) => boolean;
15
17
  export declare const getValidSubscriptionIds: () => Set<string>;
@@ -33,3 +35,20 @@ export declare class StorageHelper {
33
35
  tag(tags: any): this;
34
36
  decode(input?: string | null): import("@polkadot/types-codec/types").Codec | undefined;
35
37
  }
38
+ /**
39
+ * Pass some these into an `RpcStateQueryHelper` in order to easily batch multiple state queries into the one rpc call.
40
+ */
41
+ export type RpcStateQuery<T> = {
42
+ chainId: string;
43
+ stateKey: string;
44
+ decodeResult: (change: string | null) => T;
45
+ };
46
+ /**
47
+ * Used by a variety of balance modules to help batch multiple state queries into the one rpc call.
48
+ */
49
+ export declare class RpcStateQueryHelper<T> {
50
+ #private;
51
+ constructor(chainConnector: ChainConnector, queries: Array<RpcStateQuery<T>>);
52
+ subscribe(callback: SubscriptionCallback<T[]>, timeout?: number | false, subscribeMethod?: string, responseMethod?: string, unsubscribeMethod?: string): Promise<UnsubscribeFn>;
53
+ fetch(method?: string): Promise<T[]>;
54
+ }
@@ -134,8 +134,20 @@ export declare class Balance {
134
134
  get free(): BalanceFormatter;
135
135
  /** The reserved balance of this token. Is included in the total. */
136
136
  get reserved(): BalanceFormatter;
137
+ get reserves(): {
138
+ amount: BalanceFormatter;
139
+ label: string;
140
+ meta?: unknown;
141
+ }[];
137
142
  /** The frozen balance of this token. Is included in the free amount. */
138
143
  get locked(): BalanceFormatter;
144
+ get locks(): {
145
+ amount: BalanceFormatter;
146
+ label: string;
147
+ meta?: unknown;
148
+ includeInTransferable?: boolean | undefined;
149
+ excludeFromFeePayable?: boolean | undefined;
150
+ }[];
139
151
  /** @depreacted - use balance.locked */
140
152
  get frozen(): BalanceFormatter;
141
153
  /** The transferable balance of this token. Is generally the free amount - the miscFrozen amount. */
@@ -30,6 +30,11 @@ export type BalanceStatus = BalanceStatusLive | "live" | "cache" | "stale";
30
30
  export type IBalance = {
31
31
  /** The module that this balance was retrieved by */
32
32
  source: string;
33
+ /**
34
+ * For modules which fetch balances via module sources, this is the sub-source
35
+ * e.g. `staking` or `crowdloans`
36
+ **/
37
+ subSource?: string;
33
38
  /** Has this balance never been fetched, or is it from a cache, or is it up to date? */
34
39
  status: BalanceStatus;
35
40
  /** The address of the account which owns this balance */
@@ -59,6 +64,7 @@ export type Amount = string;
59
64
  export type AmountWithLabel<TLabel extends string> = {
60
65
  label: TLabel;
61
66
  amount: Amount;
67
+ meta?: unknown;
62
68
  };
63
69
  /** A labelled locked amount of a balance */
64
70
  export type LockedAmount<TLabel extends string> = AmountWithLabel<TLabel> & {
@@ -4,11 +4,13 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var dexie = require('dexie');
6
6
  var types = require('@polkadot/types');
7
- var anylogger = require('anylogger');
8
7
  var util = require('@talismn/util');
8
+ var groupBy = require('lodash/groupBy');
9
+ var anylogger = require('anylogger');
9
10
 
10
11
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
11
12
 
13
+ var groupBy__default = /*#__PURE__*/_interopDefault(groupBy);
12
14
  var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
13
15
 
14
16
  // TODO: Document default balances module purpose/usage
@@ -67,7 +69,7 @@ const db = new TalismanBalancesDatabase();
67
69
 
68
70
  var packageJson = {
69
71
  name: "@talismn/balances",
70
- version: "0.0.0-pr675-20230413170259",
72
+ version: "0.0.0-pr677-20230413171850",
71
73
  author: "Talisman",
72
74
  homepage: "https://talisman.xyz",
73
75
  license: "UNLICENSED",
@@ -100,20 +102,22 @@ var packageJson = {
100
102
  "@talismn/token-rates": "workspace:^",
101
103
  "@talismn/util": "workspace:^",
102
104
  anylogger: "^1.0.11",
103
- dexie: "^3.2.3"
105
+ dexie: "^3.2.3",
106
+ lodash: "4.17.21"
104
107
  },
105
108
  devDependencies: {
106
- "@polkadot/types": "^9.10.5",
109
+ "@polkadot/types": "^10.1.4",
107
110
  "@talismn/eslint-config": "workspace:^",
108
111
  "@talismn/tsconfig": "workspace:^",
109
112
  "@types/jest": "^27.5.1",
113
+ "@types/lodash": "^4.14.180",
110
114
  eslint: "^8.4.0",
111
115
  jest: "^28.1.0",
112
116
  "ts-jest": "^28.0.2",
113
117
  typescript: "^4.6.4"
114
118
  },
115
119
  peerDependencies: {
116
- "@polkadot/types": "9.x"
120
+ "@polkadot/types": "10.x"
117
121
  },
118
122
  preconstruct: {
119
123
  entrypoints: [
@@ -146,8 +150,14 @@ const createTypeRegistryCache = () => {
146
150
  if (cached) return cached;
147
151
  const typeRegistry = new types.TypeRegistry();
148
152
  if (typeof metadataRpc === "string") {
149
- const metadata = new types.Metadata(typeRegistry, metadataRpc);
150
- metadata.registry.setMetadata(metadata);
153
+ try {
154
+ const metadata = new types.Metadata(typeRegistry, metadataRpc);
155
+ metadata.registry.setMetadata(metadata);
156
+ } catch (cause) {
157
+ log.warn(new Error(`Failed to set metadata for chain ${chainId}`, {
158
+ cause
159
+ }));
160
+ }
151
161
  }
152
162
  typeRegistryCache.set(chainId, typeRegistry);
153
163
  return typeRegistry;
@@ -286,6 +296,75 @@ class StorageHelper {
286
296
  }
287
297
  }
288
298
 
299
+ /**
300
+ * Pass some these into an `RpcStateQueryHelper` in order to easily batch multiple state queries into the one rpc call.
301
+ */
302
+
303
+ /**
304
+ * Used by a variety of balance modules to help batch multiple state queries into the one rpc call.
305
+ */
306
+ class RpcStateQueryHelper {
307
+ #chainConnector;
308
+ #queries;
309
+ constructor(chainConnector, queries) {
310
+ this.#chainConnector = chainConnector;
311
+ this.#queries = queries;
312
+ }
313
+ async subscribe(callback, timeout = false, subscribeMethod = "state_subscribeStorage", responseMethod = "state_storage", unsubscribeMethod = "state_unsubscribeStorage") {
314
+ const queriesByChain = groupBy__default["default"](this.#queries, "chainId");
315
+ const subscriptions = Object.entries(queriesByChain).map(([chainId, queries]) => {
316
+ const params = [queries.map(({
317
+ stateKey
318
+ }) => stateKey)];
319
+ const unsubscribe = this.#chainConnector.subscribe(chainId, subscribeMethod, responseMethod, params, (error, result) => {
320
+ error ? callback(error) : callback(null, this.#distributeChangesToQueryDecoders.call(this, chainId, result));
321
+ }, timeout);
322
+ return () => unsubscribe(unsubscribeMethod);
323
+ }).map(subscription => subscription.catch(error => {
324
+ log.warn(`Failed to create subscription: ${error.message}`);
325
+ return () => {};
326
+ }));
327
+ return () => subscriptions.forEach(subscription => subscription.then(unsubscribe => unsubscribe()));
328
+ }
329
+ async fetch(method = "state_queryStorageAt") {
330
+ const queriesByChain = groupBy__default["default"](this.#queries, "chainId");
331
+ const resultsByChain = await Promise.all(Object.entries(queriesByChain).map(async ([chainId, queries]) => {
332
+ const params = [queries.map(({
333
+ stateKey
334
+ }) => stateKey)];
335
+ const result = (await this.#chainConnector.send(chainId, method, params))[0];
336
+ return this.#distributeChangesToQueryDecoders.call(this, chainId, result);
337
+ }));
338
+ return resultsByChain.flatMap(result => result);
339
+ }
340
+ #distributeChangesToQueryDecoders(chainId, result) {
341
+ if (typeof result !== "object" || result === null) return [];
342
+ if (!util.hasOwnProperty(result, "changes") || typeof result.changes !== "object") return [];
343
+ if (!Array.isArray(result.changes)) return [];
344
+ return result.changes.flatMap(([reference, change]) => {
345
+ if (typeof reference !== "string") {
346
+ log.warn(`Received non-string reference in RPC result: ${reference}`);
347
+ return [];
348
+ }
349
+ if (typeof change !== "string" && change !== null) {
350
+ log.warn(`Received non-string and non-null change in RPC result: ${reference} | ${change}`);
351
+ return [];
352
+ }
353
+ const query = this.#queries.find(({
354
+ chainId: cId,
355
+ stateKey
356
+ }) => cId === chainId && stateKey === reference);
357
+ if (!query) {
358
+ log.warn(`Failed to find query:\n${reference} in\n${this.#queries.map(({
359
+ stateKey
360
+ }) => stateKey)}`);
361
+ return [];
362
+ }
363
+ return query.decodeResult(change);
364
+ });
365
+ }
366
+ }
367
+
289
368
  const BalanceStatusLive = subscriptionId => `live-${subscriptionId}`;
290
369
  function excludeFromTransferableAmount(locks) {
291
370
  if (typeof locks === "string") return BigInt(locks);
@@ -536,13 +615,14 @@ class Balance {
536
615
  get id() {
537
616
  const {
538
617
  source,
618
+ subSource,
539
619
  address,
540
620
  chainId,
541
621
  evmNetworkId,
542
622
  tokenId
543
623
  } = this.#storage;
544
624
  const locationId = chainId !== undefined ? chainId : evmNetworkId;
545
- return `${source}-${address}-${locationId}-${tokenId}`;
625
+ return [source, address, locationId, tokenId, subSource].filter(Boolean).join("-");
546
626
  }
547
627
  get source() {
548
628
  return this.#storage.source;
@@ -597,10 +677,36 @@ class Balance {
597
677
  get reserved() {
598
678
  return this.#format(typeof this.#storage.reserves === "string" ? BigInt(this.#storage.reserves) : Array.isArray(this.#storage.reserves) ? this.#storage.reserves.map(reserve => BigInt(reserve.amount)).reduce((a, b) => a + b, BigInt("0")) : BigInt(this.#storage.reserves?.amount || "0"));
599
679
  }
680
+ get reserves() {
681
+ return (Array.isArray(this.#storage.reserves) ? this.#storage.reserves : [this.#storage.reserves]).flatMap(reserve => {
682
+ if (reserve === undefined) return [];
683
+ if (typeof reserve === "string") return {
684
+ label: "other",
685
+ amount: this.#format(reserve)
686
+ };
687
+ return {
688
+ ...reserve,
689
+ amount: this.#format(reserve.amount)
690
+ };
691
+ });
692
+ }
600
693
  /** The frozen balance of this token. Is included in the free amount. */
601
694
  get locked() {
602
695
  return this.#format(typeof this.#storage.locks === "string" ? BigInt(this.#storage.locks) : Array.isArray(this.#storage.locks) ? this.#storage.locks.map(lock => BigInt(lock.amount)).reduce((a, b) => util.BigMath.max(a, b), BigInt("0")) : BigInt(this.#storage.locks?.amount || "0"));
603
696
  }
697
+ get locks() {
698
+ return (Array.isArray(this.#storage.locks) ? this.#storage.locks : [this.#storage.locks]).flatMap(lock => {
699
+ if (lock === undefined) return [];
700
+ if (typeof lock === "string") return {
701
+ label: "other",
702
+ amount: this.#format(lock)
703
+ };
704
+ return {
705
+ ...lock,
706
+ amount: this.#format(lock.amount)
707
+ };
708
+ });
709
+ }
604
710
  /** @depreacted - use balance.locked */
605
711
  get frozen() {
606
712
  return this.locked;
@@ -720,6 +826,7 @@ exports.BalanceStatusLive = BalanceStatusLive;
720
826
  exports.Balances = Balances;
721
827
  exports.DefaultBalanceModule = DefaultBalanceModule;
722
828
  exports.FiatSumBalancesFormatter = FiatSumBalancesFormatter;
829
+ exports.RpcStateQueryHelper = RpcStateQueryHelper;
723
830
  exports.StorageHelper = StorageHelper;
724
831
  exports.SumBalancesFormatter = SumBalancesFormatter;
725
832
  exports.TalismanBalancesDatabase = TalismanBalancesDatabase;
@@ -4,11 +4,13 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var dexie = require('dexie');
6
6
  var types = require('@polkadot/types');
7
- var anylogger = require('anylogger');
8
7
  var util = require('@talismn/util');
8
+ var groupBy = require('lodash/groupBy');
9
+ var anylogger = require('anylogger');
9
10
 
10
11
  function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }
11
12
 
13
+ var groupBy__default = /*#__PURE__*/_interopDefault(groupBy);
12
14
  var anylogger__default = /*#__PURE__*/_interopDefault(anylogger);
13
15
 
14
16
  // TODO: Document default balances module purpose/usage
@@ -67,7 +69,7 @@ const db = new TalismanBalancesDatabase();
67
69
 
68
70
  var packageJson = {
69
71
  name: "@talismn/balances",
70
- version: "0.0.0-pr675-20230413170259",
72
+ version: "0.0.0-pr677-20230413171850",
71
73
  author: "Talisman",
72
74
  homepage: "https://talisman.xyz",
73
75
  license: "UNLICENSED",
@@ -100,20 +102,22 @@ var packageJson = {
100
102
  "@talismn/token-rates": "workspace:^",
101
103
  "@talismn/util": "workspace:^",
102
104
  anylogger: "^1.0.11",
103
- dexie: "^3.2.3"
105
+ dexie: "^3.2.3",
106
+ lodash: "4.17.21"
104
107
  },
105
108
  devDependencies: {
106
- "@polkadot/types": "^9.10.5",
109
+ "@polkadot/types": "^10.1.4",
107
110
  "@talismn/eslint-config": "workspace:^",
108
111
  "@talismn/tsconfig": "workspace:^",
109
112
  "@types/jest": "^27.5.1",
113
+ "@types/lodash": "^4.14.180",
110
114
  eslint: "^8.4.0",
111
115
  jest: "^28.1.0",
112
116
  "ts-jest": "^28.0.2",
113
117
  typescript: "^4.6.4"
114
118
  },
115
119
  peerDependencies: {
116
- "@polkadot/types": "9.x"
120
+ "@polkadot/types": "10.x"
117
121
  },
118
122
  preconstruct: {
119
123
  entrypoints: [
@@ -146,8 +150,14 @@ const createTypeRegistryCache = () => {
146
150
  if (cached) return cached;
147
151
  const typeRegistry = new types.TypeRegistry();
148
152
  if (typeof metadataRpc === "string") {
149
- const metadata = new types.Metadata(typeRegistry, metadataRpc);
150
- metadata.registry.setMetadata(metadata);
153
+ try {
154
+ const metadata = new types.Metadata(typeRegistry, metadataRpc);
155
+ metadata.registry.setMetadata(metadata);
156
+ } catch (cause) {
157
+ log.warn(new Error(`Failed to set metadata for chain ${chainId}`, {
158
+ cause
159
+ }));
160
+ }
151
161
  }
152
162
  typeRegistryCache.set(chainId, typeRegistry);
153
163
  return typeRegistry;
@@ -286,6 +296,75 @@ class StorageHelper {
286
296
  }
287
297
  }
288
298
 
299
+ /**
300
+ * Pass some these into an `RpcStateQueryHelper` in order to easily batch multiple state queries into the one rpc call.
301
+ */
302
+
303
+ /**
304
+ * Used by a variety of balance modules to help batch multiple state queries into the one rpc call.
305
+ */
306
+ class RpcStateQueryHelper {
307
+ #chainConnector;
308
+ #queries;
309
+ constructor(chainConnector, queries) {
310
+ this.#chainConnector = chainConnector;
311
+ this.#queries = queries;
312
+ }
313
+ async subscribe(callback, timeout = false, subscribeMethod = "state_subscribeStorage", responseMethod = "state_storage", unsubscribeMethod = "state_unsubscribeStorage") {
314
+ const queriesByChain = groupBy__default["default"](this.#queries, "chainId");
315
+ const subscriptions = Object.entries(queriesByChain).map(([chainId, queries]) => {
316
+ const params = [queries.map(({
317
+ stateKey
318
+ }) => stateKey)];
319
+ const unsubscribe = this.#chainConnector.subscribe(chainId, subscribeMethod, responseMethod, params, (error, result) => {
320
+ error ? callback(error) : callback(null, this.#distributeChangesToQueryDecoders.call(this, chainId, result));
321
+ }, timeout);
322
+ return () => unsubscribe(unsubscribeMethod);
323
+ }).map(subscription => subscription.catch(error => {
324
+ log.warn(`Failed to create subscription: ${error.message}`);
325
+ return () => {};
326
+ }));
327
+ return () => subscriptions.forEach(subscription => subscription.then(unsubscribe => unsubscribe()));
328
+ }
329
+ async fetch(method = "state_queryStorageAt") {
330
+ const queriesByChain = groupBy__default["default"](this.#queries, "chainId");
331
+ const resultsByChain = await Promise.all(Object.entries(queriesByChain).map(async ([chainId, queries]) => {
332
+ const params = [queries.map(({
333
+ stateKey
334
+ }) => stateKey)];
335
+ const result = (await this.#chainConnector.send(chainId, method, params))[0];
336
+ return this.#distributeChangesToQueryDecoders.call(this, chainId, result);
337
+ }));
338
+ return resultsByChain.flatMap(result => result);
339
+ }
340
+ #distributeChangesToQueryDecoders(chainId, result) {
341
+ if (typeof result !== "object" || result === null) return [];
342
+ if (!util.hasOwnProperty(result, "changes") || typeof result.changes !== "object") return [];
343
+ if (!Array.isArray(result.changes)) return [];
344
+ return result.changes.flatMap(([reference, change]) => {
345
+ if (typeof reference !== "string") {
346
+ log.warn(`Received non-string reference in RPC result: ${reference}`);
347
+ return [];
348
+ }
349
+ if (typeof change !== "string" && change !== null) {
350
+ log.warn(`Received non-string and non-null change in RPC result: ${reference} | ${change}`);
351
+ return [];
352
+ }
353
+ const query = this.#queries.find(({
354
+ chainId: cId,
355
+ stateKey
356
+ }) => cId === chainId && stateKey === reference);
357
+ if (!query) {
358
+ log.warn(`Failed to find query:\n${reference} in\n${this.#queries.map(({
359
+ stateKey
360
+ }) => stateKey)}`);
361
+ return [];
362
+ }
363
+ return query.decodeResult(change);
364
+ });
365
+ }
366
+ }
367
+
289
368
  const BalanceStatusLive = subscriptionId => `live-${subscriptionId}`;
290
369
  function excludeFromTransferableAmount(locks) {
291
370
  if (typeof locks === "string") return BigInt(locks);
@@ -536,13 +615,14 @@ class Balance {
536
615
  get id() {
537
616
  const {
538
617
  source,
618
+ subSource,
539
619
  address,
540
620
  chainId,
541
621
  evmNetworkId,
542
622
  tokenId
543
623
  } = this.#storage;
544
624
  const locationId = chainId !== undefined ? chainId : evmNetworkId;
545
- return `${source}-${address}-${locationId}-${tokenId}`;
625
+ return [source, address, locationId, tokenId, subSource].filter(Boolean).join("-");
546
626
  }
547
627
  get source() {
548
628
  return this.#storage.source;
@@ -597,10 +677,36 @@ class Balance {
597
677
  get reserved() {
598
678
  return this.#format(typeof this.#storage.reserves === "string" ? BigInt(this.#storage.reserves) : Array.isArray(this.#storage.reserves) ? this.#storage.reserves.map(reserve => BigInt(reserve.amount)).reduce((a, b) => a + b, BigInt("0")) : BigInt(this.#storage.reserves?.amount || "0"));
599
679
  }
680
+ get reserves() {
681
+ return (Array.isArray(this.#storage.reserves) ? this.#storage.reserves : [this.#storage.reserves]).flatMap(reserve => {
682
+ if (reserve === undefined) return [];
683
+ if (typeof reserve === "string") return {
684
+ label: "other",
685
+ amount: this.#format(reserve)
686
+ };
687
+ return {
688
+ ...reserve,
689
+ amount: this.#format(reserve.amount)
690
+ };
691
+ });
692
+ }
600
693
  /** The frozen balance of this token. Is included in the free amount. */
601
694
  get locked() {
602
695
  return this.#format(typeof this.#storage.locks === "string" ? BigInt(this.#storage.locks) : Array.isArray(this.#storage.locks) ? this.#storage.locks.map(lock => BigInt(lock.amount)).reduce((a, b) => util.BigMath.max(a, b), BigInt("0")) : BigInt(this.#storage.locks?.amount || "0"));
603
696
  }
697
+ get locks() {
698
+ return (Array.isArray(this.#storage.locks) ? this.#storage.locks : [this.#storage.locks]).flatMap(lock => {
699
+ if (lock === undefined) return [];
700
+ if (typeof lock === "string") return {
701
+ label: "other",
702
+ amount: this.#format(lock)
703
+ };
704
+ return {
705
+ ...lock,
706
+ amount: this.#format(lock.amount)
707
+ };
708
+ });
709
+ }
604
710
  /** @depreacted - use balance.locked */
605
711
  get frozen() {
606
712
  return this.locked;
@@ -720,6 +826,7 @@ exports.BalanceStatusLive = BalanceStatusLive;
720
826
  exports.Balances = Balances;
721
827
  exports.DefaultBalanceModule = DefaultBalanceModule;
722
828
  exports.FiatSumBalancesFormatter = FiatSumBalancesFormatter;
829
+ exports.RpcStateQueryHelper = RpcStateQueryHelper;
723
830
  exports.StorageHelper = StorageHelper;
724
831
  exports.SumBalancesFormatter = SumBalancesFormatter;
725
832
  exports.TalismanBalancesDatabase = TalismanBalancesDatabase;
@@ -1,7 +1,8 @@
1
1
  import { Dexie } from 'dexie';
2
2
  import { decorateStorage, StorageKey, TypeRegistry, Metadata } from '@polkadot/types';
3
+ import { hasOwnProperty, BigMath, isArrayOf, planckToTokens } from '@talismn/util';
4
+ import groupBy from 'lodash/groupBy';
3
5
  import anylogger from 'anylogger';
4
- import { BigMath, isArrayOf, planckToTokens } from '@talismn/util';
5
6
 
6
7
  // TODO: Document default balances module purpose/usage
7
8
  const DefaultBalanceModule = type => ({
@@ -59,7 +60,7 @@ const db = new TalismanBalancesDatabase();
59
60
 
60
61
  var packageJson = {
61
62
  name: "@talismn/balances",
62
- version: "0.0.0-pr675-20230413170259",
63
+ version: "0.0.0-pr677-20230413171850",
63
64
  author: "Talisman",
64
65
  homepage: "https://talisman.xyz",
65
66
  license: "UNLICENSED",
@@ -92,20 +93,22 @@ var packageJson = {
92
93
  "@talismn/token-rates": "workspace:^",
93
94
  "@talismn/util": "workspace:^",
94
95
  anylogger: "^1.0.11",
95
- dexie: "^3.2.3"
96
+ dexie: "^3.2.3",
97
+ lodash: "4.17.21"
96
98
  },
97
99
  devDependencies: {
98
- "@polkadot/types": "^9.10.5",
100
+ "@polkadot/types": "^10.1.4",
99
101
  "@talismn/eslint-config": "workspace:^",
100
102
  "@talismn/tsconfig": "workspace:^",
101
103
  "@types/jest": "^27.5.1",
104
+ "@types/lodash": "^4.14.180",
102
105
  eslint: "^8.4.0",
103
106
  jest: "^28.1.0",
104
107
  "ts-jest": "^28.0.2",
105
108
  typescript: "^4.6.4"
106
109
  },
107
110
  peerDependencies: {
108
- "@polkadot/types": "9.x"
111
+ "@polkadot/types": "10.x"
109
112
  },
110
113
  preconstruct: {
111
114
  entrypoints: [
@@ -138,8 +141,14 @@ const createTypeRegistryCache = () => {
138
141
  if (cached) return cached;
139
142
  const typeRegistry = new TypeRegistry();
140
143
  if (typeof metadataRpc === "string") {
141
- const metadata = new Metadata(typeRegistry, metadataRpc);
142
- metadata.registry.setMetadata(metadata);
144
+ try {
145
+ const metadata = new Metadata(typeRegistry, metadataRpc);
146
+ metadata.registry.setMetadata(metadata);
147
+ } catch (cause) {
148
+ log.warn(new Error(`Failed to set metadata for chain ${chainId}`, {
149
+ cause
150
+ }));
151
+ }
143
152
  }
144
153
  typeRegistryCache.set(chainId, typeRegistry);
145
154
  return typeRegistry;
@@ -278,6 +287,75 @@ class StorageHelper {
278
287
  }
279
288
  }
280
289
 
290
+ /**
291
+ * Pass some these into an `RpcStateQueryHelper` in order to easily batch multiple state queries into the one rpc call.
292
+ */
293
+
294
+ /**
295
+ * Used by a variety of balance modules to help batch multiple state queries into the one rpc call.
296
+ */
297
+ class RpcStateQueryHelper {
298
+ #chainConnector;
299
+ #queries;
300
+ constructor(chainConnector, queries) {
301
+ this.#chainConnector = chainConnector;
302
+ this.#queries = queries;
303
+ }
304
+ async subscribe(callback, timeout = false, subscribeMethod = "state_subscribeStorage", responseMethod = "state_storage", unsubscribeMethod = "state_unsubscribeStorage") {
305
+ const queriesByChain = groupBy(this.#queries, "chainId");
306
+ const subscriptions = Object.entries(queriesByChain).map(([chainId, queries]) => {
307
+ const params = [queries.map(({
308
+ stateKey
309
+ }) => stateKey)];
310
+ const unsubscribe = this.#chainConnector.subscribe(chainId, subscribeMethod, responseMethod, params, (error, result) => {
311
+ error ? callback(error) : callback(null, this.#distributeChangesToQueryDecoders.call(this, chainId, result));
312
+ }, timeout);
313
+ return () => unsubscribe(unsubscribeMethod);
314
+ }).map(subscription => subscription.catch(error => {
315
+ log.warn(`Failed to create subscription: ${error.message}`);
316
+ return () => {};
317
+ }));
318
+ return () => subscriptions.forEach(subscription => subscription.then(unsubscribe => unsubscribe()));
319
+ }
320
+ async fetch(method = "state_queryStorageAt") {
321
+ const queriesByChain = groupBy(this.#queries, "chainId");
322
+ const resultsByChain = await Promise.all(Object.entries(queriesByChain).map(async ([chainId, queries]) => {
323
+ const params = [queries.map(({
324
+ stateKey
325
+ }) => stateKey)];
326
+ const result = (await this.#chainConnector.send(chainId, method, params))[0];
327
+ return this.#distributeChangesToQueryDecoders.call(this, chainId, result);
328
+ }));
329
+ return resultsByChain.flatMap(result => result);
330
+ }
331
+ #distributeChangesToQueryDecoders(chainId, result) {
332
+ if (typeof result !== "object" || result === null) return [];
333
+ if (!hasOwnProperty(result, "changes") || typeof result.changes !== "object") return [];
334
+ if (!Array.isArray(result.changes)) return [];
335
+ return result.changes.flatMap(([reference, change]) => {
336
+ if (typeof reference !== "string") {
337
+ log.warn(`Received non-string reference in RPC result: ${reference}`);
338
+ return [];
339
+ }
340
+ if (typeof change !== "string" && change !== null) {
341
+ log.warn(`Received non-string and non-null change in RPC result: ${reference} | ${change}`);
342
+ return [];
343
+ }
344
+ const query = this.#queries.find(({
345
+ chainId: cId,
346
+ stateKey
347
+ }) => cId === chainId && stateKey === reference);
348
+ if (!query) {
349
+ log.warn(`Failed to find query:\n${reference} in\n${this.#queries.map(({
350
+ stateKey
351
+ }) => stateKey)}`);
352
+ return [];
353
+ }
354
+ return query.decodeResult(change);
355
+ });
356
+ }
357
+ }
358
+
281
359
  const BalanceStatusLive = subscriptionId => `live-${subscriptionId}`;
282
360
  function excludeFromTransferableAmount(locks) {
283
361
  if (typeof locks === "string") return BigInt(locks);
@@ -528,13 +606,14 @@ class Balance {
528
606
  get id() {
529
607
  const {
530
608
  source,
609
+ subSource,
531
610
  address,
532
611
  chainId,
533
612
  evmNetworkId,
534
613
  tokenId
535
614
  } = this.#storage;
536
615
  const locationId = chainId !== undefined ? chainId : evmNetworkId;
537
- return `${source}-${address}-${locationId}-${tokenId}`;
616
+ return [source, address, locationId, tokenId, subSource].filter(Boolean).join("-");
538
617
  }
539
618
  get source() {
540
619
  return this.#storage.source;
@@ -589,10 +668,36 @@ class Balance {
589
668
  get reserved() {
590
669
  return this.#format(typeof this.#storage.reserves === "string" ? BigInt(this.#storage.reserves) : Array.isArray(this.#storage.reserves) ? this.#storage.reserves.map(reserve => BigInt(reserve.amount)).reduce((a, b) => a + b, BigInt("0")) : BigInt(this.#storage.reserves?.amount || "0"));
591
670
  }
671
+ get reserves() {
672
+ return (Array.isArray(this.#storage.reserves) ? this.#storage.reserves : [this.#storage.reserves]).flatMap(reserve => {
673
+ if (reserve === undefined) return [];
674
+ if (typeof reserve === "string") return {
675
+ label: "other",
676
+ amount: this.#format(reserve)
677
+ };
678
+ return {
679
+ ...reserve,
680
+ amount: this.#format(reserve.amount)
681
+ };
682
+ });
683
+ }
592
684
  /** The frozen balance of this token. Is included in the free amount. */
593
685
  get locked() {
594
686
  return this.#format(typeof this.#storage.locks === "string" ? BigInt(this.#storage.locks) : Array.isArray(this.#storage.locks) ? this.#storage.locks.map(lock => BigInt(lock.amount)).reduce((a, b) => BigMath.max(a, b), BigInt("0")) : BigInt(this.#storage.locks?.amount || "0"));
595
687
  }
688
+ get locks() {
689
+ return (Array.isArray(this.#storage.locks) ? this.#storage.locks : [this.#storage.locks]).flatMap(lock => {
690
+ if (lock === undefined) return [];
691
+ if (typeof lock === "string") return {
692
+ label: "other",
693
+ amount: this.#format(lock)
694
+ };
695
+ return {
696
+ ...lock,
697
+ amount: this.#format(lock.amount)
698
+ };
699
+ });
700
+ }
596
701
  /** @depreacted - use balance.locked */
597
702
  get frozen() {
598
703
  return this.locked;
@@ -706,4 +811,4 @@ class SumBalancesFormatter {
706
811
  }
707
812
  }
708
813
 
709
- export { Balance, BalanceFormatter, BalanceStatusLive, Balances, DefaultBalanceModule, FiatSumBalancesFormatter, StorageHelper, SumBalancesFormatter, TalismanBalancesDatabase, balances, createSubscriptionId, createTypeRegistryCache, db, deleteSubscriptionId, deriveStatuses, excludeFromFeePayableLocks, excludeFromTransferableAmount, filterMirrorTokens, getValidSubscriptionIds, includeInTotalExtraAmount };
814
+ export { Balance, BalanceFormatter, BalanceStatusLive, Balances, DefaultBalanceModule, FiatSumBalancesFormatter, RpcStateQueryHelper, StorageHelper, SumBalancesFormatter, TalismanBalancesDatabase, balances, createSubscriptionId, createTypeRegistryCache, db, deleteSubscriptionId, deriveStatuses, excludeFromFeePayableLocks, excludeFromTransferableAmount, filterMirrorTokens, getValidSubscriptionIds, includeInTotalExtraAmount };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talismn/balances",
3
- "version": "0.0.0-pr675-20230413170259",
3
+ "version": "0.0.0-pr677-20230413171850",
4
4
  "author": "Talisman",
5
5
  "homepage": "https://talisman.xyz",
6
6
  "license": "UNLICENSED",
@@ -27,26 +27,28 @@
27
27
  "clean": "rm -rf dist && rm -rf .turbo rm -rf node_modules"
28
28
  },
29
29
  "dependencies": {
30
- "@talismn/chain-connector": "^0.0.0-pr675-20230413170259",
31
- "@talismn/chain-connector-evm": "^0.0.0-pr675-20230413170259",
32
- "@talismn/chaindata-provider": "^0.0.0-pr675-20230413170259",
33
- "@talismn/token-rates": "^0.0.0-pr675-20230413170259",
34
- "@talismn/util": "^0.1.8",
30
+ "@talismn/chain-connector": "^0.0.0-pr677-20230413171850",
31
+ "@talismn/chain-connector-evm": "^0.0.0-pr677-20230413171850",
32
+ "@talismn/chaindata-provider": "^0.0.0-pr677-20230413171850",
33
+ "@talismn/token-rates": "^0.0.0-pr677-20230413171850",
34
+ "@talismn/util": "^0.0.0-pr677-20230413171850",
35
35
  "anylogger": "^1.0.11",
36
- "dexie": "^3.2.3"
36
+ "dexie": "^3.2.3",
37
+ "lodash": "4.17.21"
37
38
  },
38
39
  "devDependencies": {
39
- "@polkadot/types": "^9.10.5",
40
+ "@polkadot/types": "^10.1.4",
40
41
  "@talismn/eslint-config": "^0.0.1",
41
42
  "@talismn/tsconfig": "^0.0.2",
42
43
  "@types/jest": "^27.5.1",
44
+ "@types/lodash": "^4.14.180",
43
45
  "eslint": "^8.4.0",
44
46
  "jest": "^28.1.0",
45
47
  "ts-jest": "^28.0.2",
46
48
  "typescript": "^4.6.4"
47
49
  },
48
50
  "peerDependencies": {
49
- "@polkadot/types": "9.x"
51
+ "@polkadot/types": "10.x"
50
52
  },
51
53
  "preconstruct": {
52
54
  "entrypoints": [