@subwallet/extension-base 1.3.73-0 → 1.3.74-0

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.
Files changed (108) hide show
  1. package/background/KoniTypes.d.ts +42 -5
  2. package/background/KoniTypes.js +14 -1
  3. package/cjs/background/KoniTypes.js +16 -2
  4. package/cjs/core/logic-validation/transfer.js +35 -57
  5. package/cjs/koni/background/handlers/Extension.js +599 -144
  6. package/cjs/koni/background/handlers/State.js +5 -2
  7. package/cjs/koni/background/handlers/Tabs.js +3 -2
  8. package/cjs/packageInfo.js +1 -1
  9. package/cjs/services/balance-service/helpers/subscribe/substrate/index.js +0 -2
  10. package/cjs/services/chain-service/handler/SubstrateApi.js +6 -1
  11. package/cjs/services/chain-service/index.js +1 -0
  12. package/cjs/services/chain-service/utils/index.js +4 -0
  13. package/cjs/services/event-service/index.js +1 -0
  14. package/cjs/services/fee-service/utils/index.js +4 -4
  15. package/cjs/services/inapp-notification-service/consts.js +4 -2
  16. package/cjs/services/inapp-notification-service/index.js +51 -6
  17. package/cjs/services/inapp-notification-service/interfaces.js +2 -0
  18. package/cjs/services/inapp-notification-service/utils/common.js +4 -0
  19. package/cjs/services/keyring-service/context/account-context.js +44 -0
  20. package/cjs/services/keyring-service/context/handlers/Multisig.js +186 -0
  21. package/cjs/services/keyring-service/context/state.js +12 -0
  22. package/cjs/services/multisig-service/index.js +627 -0
  23. package/cjs/services/multisig-service/utils.js +242 -0
  24. package/cjs/services/request-service/handler/SubstrateRequestHandler.js +25 -0
  25. package/cjs/services/request-service/index.js +5 -1
  26. package/cjs/services/storage-service/DatabaseService.js +5 -2
  27. package/cjs/services/storage-service/db-stores/InappNotification.js +20 -2
  28. package/cjs/services/substrate-proxy-service/index.js +22 -7
  29. package/cjs/services/transaction-service/helpers/index.js +8 -0
  30. package/cjs/services/transaction-service/index.js +348 -147
  31. package/cjs/services/transaction-service/types.js +18 -1
  32. package/cjs/types/account/info/keyring.js +5 -0
  33. package/cjs/types/account/info/proxy.js +1 -0
  34. package/cjs/types/multisig/index.js +14 -0
  35. package/cjs/types/transaction/error.js +9 -2
  36. package/cjs/utils/account/transform.js +28 -4
  37. package/cjs/utils/logger/Logger.js +294 -0
  38. package/cjs/utils/logger/index.js +42 -0
  39. package/cjs/utils/logger/types.js +1 -0
  40. package/core/logic-validation/transfer.d.ts +2 -2
  41. package/core/logic-validation/transfer.js +10 -32
  42. package/koni/background/handlers/Extension.d.ts +7 -0
  43. package/koni/background/handlers/Extension.js +498 -43
  44. package/koni/background/handlers/State.d.ts +2 -0
  45. package/koni/background/handlers/State.js +5 -2
  46. package/koni/background/handlers/Tabs.js +3 -2
  47. package/package.json +42 -6
  48. package/packageInfo.js +1 -1
  49. package/services/balance-service/helpers/subscribe/substrate/index.js +0 -2
  50. package/services/chain-service/handler/SubstrateApi.js +7 -2
  51. package/services/chain-service/index.js +1 -0
  52. package/services/chain-service/types.d.ts +1 -1
  53. package/services/chain-service/utils/index.js +4 -0
  54. package/services/event-service/index.d.ts +1 -0
  55. package/services/event-service/index.js +1 -0
  56. package/services/event-service/types.d.ts +1 -0
  57. package/services/fee-service/utils/index.js +4 -4
  58. package/services/inapp-notification-service/consts.d.ts +3 -1
  59. package/services/inapp-notification-service/consts.js +5 -3
  60. package/services/inapp-notification-service/index.d.ts +3 -2
  61. package/services/inapp-notification-service/index.js +51 -6
  62. package/services/inapp-notification-service/interfaces.d.ts +18 -2
  63. package/services/inapp-notification-service/interfaces.js +2 -0
  64. package/services/inapp-notification-service/utils/common.d.ts +1 -0
  65. package/services/inapp-notification-service/utils/common.js +3 -0
  66. package/services/keyring-service/context/account-context.d.ts +9 -1
  67. package/services/keyring-service/context/account-context.js +44 -0
  68. package/services/keyring-service/context/handlers/Multisig.d.ts +18 -0
  69. package/services/keyring-service/context/handlers/Multisig.js +180 -0
  70. package/services/keyring-service/context/state.d.ts +2 -0
  71. package/services/keyring-service/context/state.js +12 -0
  72. package/services/multisig-service/index.d.ts +245 -0
  73. package/services/multisig-service/index.js +620 -0
  74. package/services/multisig-service/utils.d.ts +95 -0
  75. package/services/multisig-service/utils.js +227 -0
  76. package/services/request-service/handler/SubstrateRequestHandler.d.ts +1 -0
  77. package/services/request-service/handler/SubstrateRequestHandler.js +25 -0
  78. package/services/request-service/index.d.ts +2 -1
  79. package/services/request-service/index.js +5 -1
  80. package/services/storage-service/DatabaseService.d.ts +3 -2
  81. package/services/storage-service/DatabaseService.js +5 -2
  82. package/services/storage-service/db-stores/InappNotification.d.ts +3 -2
  83. package/services/storage-service/db-stores/InappNotification.js +20 -2
  84. package/services/substrate-proxy-service/index.d.ts +4 -1
  85. package/services/substrate-proxy-service/index.js +22 -8
  86. package/services/transaction-service/helpers/index.js +8 -0
  87. package/services/transaction-service/index.d.ts +31 -0
  88. package/services/transaction-service/index.js +270 -69
  89. package/services/transaction-service/types.d.ts +28 -3
  90. package/services/transaction-service/types.js +12 -1
  91. package/types/account/info/keyring.d.ts +14 -1
  92. package/types/account/info/keyring.js +6 -0
  93. package/types/account/info/proxy.d.ts +1 -0
  94. package/types/account/info/proxy.js +1 -0
  95. package/types/multisig/index.d.ts +76 -0
  96. package/types/multisig/index.js +8 -0
  97. package/types/notification/index.d.ts +8 -0
  98. package/types/substrateProxyAccount/index.d.ts +26 -1
  99. package/types/transaction/error.d.ts +6 -1
  100. package/types/transaction/error.js +7 -1
  101. package/types/transaction/request.d.ts +0 -1
  102. package/utils/account/transform.js +28 -4
  103. package/utils/logger/Logger.d.ts +31 -0
  104. package/utils/logger/Logger.js +267 -0
  105. package/utils/logger/index.d.ts +15 -0
  106. package/utils/logger/index.js +29 -0
  107. package/utils/logger/types.d.ts +23 -0
  108. package/utils/logger/types.js +1 -0
@@ -0,0 +1,620 @@
1
+ // Copyright 2019-2022 @subwallet/extension-base authors & contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { ExtrinsicType } from '@subwallet/extension-base/background/KoniTypes';
5
+ import { ALL_ACCOUNT_KEY } from '@subwallet/extension-base/constants';
6
+ import { ServiceStatus } from '@subwallet/extension-base/services/base/types';
7
+ import { NotificationDescriptionMap, NotificationTitleMap } from '@subwallet/extension-base/services/inapp-notification-service/consts';
8
+ import { NotificationActionType, NotificationTab } from '@subwallet/extension-base/services/inapp-notification-service/interfaces';
9
+ import { decodeCallData, DEFAULT_BLOCK_HASH, genPendingMultisigTxKey, getCallData, getMultisigTxType } from '@subwallet/extension-base/services/multisig-service/utils';
10
+ import { _reformatAddressWithChain, addLazy, createPromiseHandler, isSameAddress, reformatAddress } from '@subwallet/extension-base/utils';
11
+ import { createLogger } from '@subwallet/extension-base/utils/logger';
12
+ import { BehaviorSubject } from 'rxjs';
13
+ /**
14
+ * Query key for multisig multisigs subscription
15
+ */
16
+ const MULTISIG_QUERY_KEY = 'query_multisig_multisigs';
17
+
18
+ /**
19
+ * Interface representing multisig extrinsic data from the Substrate pallet
20
+ */
21
+
22
+ const multisigServiceLogger = createLogger('MultisigService');
23
+
24
+ /**
25
+ * Interface representing a pending multisig extrinsic with the current signer context
26
+ */
27
+
28
+ /**
29
+ * Interface representing raw pending multisig extrinsic data from the chain
30
+ */
31
+
32
+ /**
33
+ * Map of pending multisig extrinsics
34
+ * Key is created using genPendingMultisigTxKey function
35
+ */
36
+
37
+ /**
38
+ * Request interface for getting pending extrinsics
39
+ */
40
+
41
+ export let MultisigTxType;
42
+
43
+ /**
44
+ * Mapping of extrinsic categories to their corresponding pallet methods
45
+ * Used to categorize multisig extrinsics by their call method
46
+ */
47
+ (function (MultisigTxType) {
48
+ MultisigTxType["TRANSFER"] = "Transfer";
49
+ MultisigTxType["TRANSFER_NFT"] = "TransferNFT";
50
+ MultisigTxType["STAKING"] = "Staking";
51
+ MultisigTxType["REDEEM"] = "Redeem";
52
+ MultisigTxType["UNSTAKE"] = "Unstake";
53
+ MultisigTxType["WITHDRAW"] = "Withdraw";
54
+ MultisigTxType["CANCEL_UNSTAKE"] = "CancelUnstake";
55
+ MultisigTxType["CLAIM_REWARD"] = "ClaimReward";
56
+ MultisigTxType["NOMINATE"] = "Nominate";
57
+ MultisigTxType["LENDING"] = "Lending";
58
+ MultisigTxType["SWAP"] = "Swap";
59
+ MultisigTxType["SET_TOKEN_PAY_FEE"] = "SetTokenPayFee";
60
+ MultisigTxType["GOV_VOTE"] = "govVote";
61
+ MultisigTxType["GOV_REMOVE_VOTE"] = "govRemoveVote";
62
+ MultisigTxType["GOV_UNLOCK_VOTE"] = "govUnlockVote";
63
+ MultisigTxType["ADD_PROXY"] = "AddProxy";
64
+ MultisigTxType["REMOVE_PROXY"] = "RemoveProxy";
65
+ MultisigTxType["UNKNOWN"] = "Unknown";
66
+ })(MultisigTxType || (MultisigTxType = {}));
67
+ export const MULTISIG_TX_TYPE_MAP = {
68
+ transfer: ['balances.transferAll', 'balances.transferKeepAlive', 'balances.transfer', 'foreignAssets.transfer', 'foreignAssets.transferKeepAlive', 'currencies.transfer', 'tokens.transferAll', 'tokens.transfer', 'assets.transfer', 'assetManager.transfer', 'subtensorModule.transferStake'],
69
+ transfer_nft: ['nft.transfer', 'nfts.transfer', 'unique.transfer', 'uniques.transfer'],
70
+ staking: ['homa.mint', 'vtokenMinting.mint', 'liquidStaking.stake', 'parachainStaking.joinDelegators', 'parachainStaking.delegatorStakeMore', 'dappsStaking.bondAndStake', 'parachainStaking.nominate', 'parachainStaking.bondExtra', 'collatorStaking.lock', 'collatorStaking.stake', 'parachainStaking.delegate', 'parachainStaking.delegateWithAutoCompound', 'parachainStaking.delegatorBondMore', 'staking.bond', 'pooledStaking.requestDelegate', 'subtensorModule.addStakeLimit', 'nominationPools.bondExtra', 'nominationPools.join'],
71
+ redeem: ['aggregatedDex.swapWithExactSupply', 'stablePool.swap', 'ammRoute.swapExactTokensForTokens'],
72
+ unstake: ['homa.requestRedeem', 'vtokenMinting.redeem', 'liquidStaking.unstake', 'parachainStaking.delegatorStakeLess', 'parachainStaking.leaveDelegators', 'dappsStaking.unbondAndUnstake', 'parachainStaking.scheduleNominatorUnbond', 'parachainStaking.scheduleRevokeNomination', 'collatorStaking.unstakeFrom && collatorStaking.unlock', 'parachainStaking.scheduleDelegatorBondLess', 'parachainStaking.scheduleRevokeDelegation', 'staking.unbond', 'pooledStaking.requestUndelegate', 'subtensorModule.removeStakeLimit', 'nominationPools.unbond'],
73
+ withdraw: ['homa.claimRedemption', 'parachainStaking.unlockUnstaked', 'dappsStaking.withdrawUnbonded', 'parachainStaking.executeNominationRequest', 'collatorStaking.release', 'parachainStaking.executeDelegationRequest', 'staking.withdrawUnbonded', 'nominationPools.withdrawUnbonded'],
74
+ cancelUnstake: ['parachainStaking.cancelLeaveCandidates', 'parachainStaking.cancelNominationRequest', 'parachainStaking.cancelDelegationRequest', 'staking.rebond'],
75
+ claim: ['parachainStaking.incrementDelegatorRewards', 'parachainStaking.claimRewards', 'dappsStaking.claimStaker', 'collatorStaking.claimRewards', 'pooledStaking.claimManualRewards', 'nominationPools.claimPayout'],
76
+ nominate: ['staking.nominate', 'subtensorModule.moveStake'],
77
+ lending: ['loans.mint', 'loans.redeem', 'loans.redeemAll'],
78
+ // consider remove
79
+ swap: ['assetConversion.swapExactTokensForTokens'],
80
+ setTokenPayFee: ['multiTransactionPayment.setCurrency'],
81
+ govVote: ['convictionVoting.vote'],
82
+ govRemoveVote: ['convictionVoting.removeVote'],
83
+ govUnlockVote: ['convictionVoting.unlock'],
84
+ addProxy: ['proxy.addProxy'],
85
+ removeProxy: ['proxy.removeProxy', 'proxy.removeProxies']
86
+ };
87
+
88
+ /**
89
+ * Service for managing multisig extrinsics
90
+ * Handles subscription to pending multisig extrinsics across supported chains
91
+ * and provides methods to query and monitor multisig extrinsic status
92
+ */
93
+ export class MultisigService {
94
+ status = ServiceStatus.NOT_INITIALIZED;
95
+ startPromiseHandler = createPromiseHandler();
96
+ stopPromiseHandler = createPromiseHandler();
97
+
98
+ /** BehaviorSubject that holds the current map of pending multisig extrinsics */
99
+ pendingMultisigTxSubject = new BehaviorSubject({});
100
+ /** Function to unsubscribe from all active subscriptions */
101
+
102
+ /** Promise to check if the subscription logic is currently running */
103
+
104
+ /** Set to track notified transaction keys to avoid duplicate notifications */
105
+ notifiedTxKeys = new Set();
106
+
107
+ /**
108
+ * Creates an instance of MultisigService
109
+ * @param eventService - Service for handling application events
110
+ * @param chainService - Service for managing chain connections
111
+ * @param keyringService - Service for managing accounts and keyring
112
+ * @param inappNotificationService - Service for creating in-app notifications (optional)
113
+ */
114
+ constructor(eventService, chainService, keyringService, inappNotificationService) {
115
+ this.eventService = eventService;
116
+ this.chainService = chainService;
117
+ this.keyringService = keyringService;
118
+ this.inappNotificationService = inappNotificationService;
119
+ this.status = ServiceStatus.NOT_INITIALIZED;
120
+ }
121
+
122
+ /**
123
+ * Starts the multisig service
124
+ * Subscribes to pending multisig extrinsics for all multisig accounts
125
+ * @returns Promise that resolves when the service has started, or rejects on error
126
+ */
127
+ async start() {
128
+ try {
129
+ if (this.status === ServiceStatus.STOPPING) {
130
+ await this.waitForStopped();
131
+ }
132
+ if (this.status === ServiceStatus.STARTED || this.status === ServiceStatus.STARTING) {
133
+ return await this.waitForStarted();
134
+ }
135
+ this.status = ServiceStatus.STARTING;
136
+ await this.runSubscribePendingMultisigTxs();
137
+ this.stopPromiseHandler = createPromiseHandler();
138
+ this.status = ServiceStatus.STARTED;
139
+ this.eventService.emit('multisig-service.ready', true);
140
+ this.startPromiseHandler.resolve();
141
+ } catch (error) {
142
+ this.status = ServiceStatus.NOT_INITIALIZED;
143
+ this.startPromiseHandler.reject(error);
144
+ throw error;
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Stops the multisig service
150
+ * Unsubscribes from all active subscriptions
151
+ * @returns Promise that resolves when the service has stopped, or rejects on error
152
+ */
153
+ async stop() {
154
+ try {
155
+ if (this.status === ServiceStatus.STARTING) {
156
+ await this.waitForStarted();
157
+ }
158
+ if (this.status === ServiceStatus.STOPPED || this.status === ServiceStatus.STOPPING) {
159
+ return await this.waitForStopped();
160
+ }
161
+ this.status = ServiceStatus.STOPPING;
162
+ this.runUnsubscribePendingMultisigTxs();
163
+ this.startPromiseHandler = createPromiseHandler();
164
+ this.status = ServiceStatus.STOPPED;
165
+ this.stopPromiseHandler.resolve();
166
+ } catch (error) {
167
+ this.stopPromiseHandler.reject(error);
168
+ throw error;
169
+ }
170
+ }
171
+
172
+ /**
173
+ * Initializes the multisig service
174
+ * Waits for chain and account services to be ready, then sets up event listeners
175
+ * @returns Promise that resolves when initialization is complete
176
+ */
177
+ async init() {
178
+ this.status = ServiceStatus.INITIALIZING;
179
+ await this.eventService.waitChainReady;
180
+ await this.eventService.waitAccountReady;
181
+ this.status = ServiceStatus.INITIALIZED;
182
+ this.eventService.onLazy(this.handleEvents.bind(this));
183
+ }
184
+
185
+ /**
186
+ * Waits for the service to start
187
+ * @returns Promise that resolves when the service has started
188
+ */
189
+ waitForStarted() {
190
+ return this.startPromiseHandler.promise;
191
+ }
192
+
193
+ /**
194
+ * Waits for the service to stop
195
+ * @returns Promise that resolves when the service has stopped
196
+ */
197
+ waitForStopped() {
198
+ return this.stopPromiseHandler.promise;
199
+ }
200
+
201
+ /**
202
+ * Handles application events and reloads multisig extrinsics when needed
203
+ * Reloads when accounts are added/removed or when supported chain state is updated
204
+ * @param events - Array of event items
205
+ * @param eventTypes - Array of event types that occurred
206
+ */
207
+ handleEvents(events, eventTypes) {
208
+ let needReload = false;
209
+ if (eventTypes.includes('account.add') || eventTypes.includes('account.remove')) {
210
+ needReload = true;
211
+ }
212
+ if (eventTypes.includes('chain.updateState') || eventTypes.includes('transaction.done')) {
213
+ for (const event of events) {
214
+ if (event.type === 'chain.updateState') {
215
+ var _chainInfo$substrateI;
216
+ const chainSlug = event.data[0];
217
+ const chainInfo = this.chainService.getChainInfoByKey(chainSlug);
218
+
219
+ // Only reload if the updated chain is in the supported chains list
220
+ if ((_chainInfo$substrateI = chainInfo.substrateInfo) !== null && _chainInfo$substrateI !== void 0 && _chainInfo$substrateI.supportMultisig) {
221
+ needReload = true;
222
+ break;
223
+ }
224
+ } else if (event.type === 'transaction.done') {
225
+ const txResult = event.data[0];
226
+ if (txResult.wrappingStatus === 'WRAP_RESULT') {
227
+ needReload = true;
228
+ break;
229
+ }
230
+ }
231
+ }
232
+ }
233
+ if (needReload) {
234
+ addLazy('reloadPendingMultisigTxsByEvents', () => {
235
+ if (this.status === ServiceStatus.STARTED) {
236
+ this.runSubscribePendingMultisigTxs().catch(e => multisigServiceLogger.error('Error in handleEvents reload', e));
237
+ }
238
+ }, 2000, undefined, true);
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Subscribes to multisig changes for all multisig addresses across supported chains
244
+ * Clears old subscriptions before creating new ones to avoid duplicates
245
+ * @returns Promise that resolves when subscription setup is complete
246
+ */
247
+ async runSubscribePendingMultisigTxs() {
248
+ if (this.status === ServiceStatus.STOPPING || this.status === ServiceStatus.STOPPED) {
249
+ return;
250
+ }
251
+ if (this.subscribePromise) {
252
+ return this.subscribePromise;
253
+ }
254
+ this.subscribePromise = (async () => {
255
+ await Promise.all([this.eventService.waitKeyringReady, this.eventService.waitChainReady]);
256
+ this.runUnsubscribePendingMultisigTxs();
257
+ const multisigAccounts = this.keyringService.context.getMultisigAccounts();
258
+ if (!multisigAccounts.length) {
259
+ return;
260
+ }
261
+ let cancel = false;
262
+ const unsubList = [];
263
+ const activeChainInfoMap = this.chainService.getActiveChainInfoMap();
264
+ const supportedActiveChains = Object.values(activeChainInfoMap).reduce((rs, chainInfo) => {
265
+ var _chainInfo$substrateI2;
266
+ if ((_chainInfo$substrateI2 = chainInfo.substrateInfo) !== null && _chainInfo$substrateI2 !== void 0 && _chainInfo$substrateI2.supportMultisig) {
267
+ rs.push(chainInfo.slug);
268
+ }
269
+ return rs;
270
+ }, []);
271
+ for (const chain of supportedActiveChains) {
272
+ const chainInfo = this.chainService.getChainInfoByKey(chain);
273
+ for (const account of multisigAccounts) {
274
+ const multisigAddress = account.id;
275
+ const signers = account.accounts[0].signers;
276
+ const reformatMultisigAddress = _reformatAddressWithChain(multisigAddress, chainInfo);
277
+ const reformatSigners = signers.map(s => _reformatAddressWithChain(s, chainInfo));
278
+ const threshold = account.accounts[0].threshold;
279
+ const unsub = this.subscribePendingMultisigTxs(chain, reformatMultisigAddress, reformatSigners, threshold, rs => {
280
+ multisigServiceLogger.debug(`pending multisig txs of address ${reformatMultisigAddress}`, rs);
281
+ !cancel && this.updatePendingMultisigTxSubjectByChain(reformatMultisigAddress, chain, rs);
282
+ });
283
+ unsubList.push(unsub);
284
+ }
285
+ }
286
+ this.unsubscribes = () => {
287
+ cancel = true;
288
+ unsubList.forEach(unsub => {
289
+ unsub === null || unsub === void 0 ? void 0 : unsub();
290
+ });
291
+ };
292
+ })();
293
+ try {
294
+ await this.subscribePromise;
295
+ } finally {
296
+ this.subscribePromise = undefined;
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Subscribes to pending multisig extrinsics for a specific multisig address on a chain
302
+ * Fetches initial data and sets up a subscription for updates
303
+ * @param chain - Chain identifier
304
+ * @param multisigAddress - Multisig address to monitor
305
+ * @param signers - List of signer addresses for the multisig
306
+ * @param threshold - Number of approval required
307
+ * @param callback - Callback function called with updated pending extrinsics
308
+ * @returns Function to unsubscribe from the subscription
309
+ */
310
+ async subscribePendingMultisigTxsPromise(chain, multisigAddress, signers, threshold, callback) {
311
+ const substrateApi = await this.chainService.getSubstrateApi(chain).isReady;
312
+ if (!substrateApi.api.query.multisig.multisigs) {
313
+ multisigServiceLogger.warn('This chain is not has multisig pallet', chain);
314
+ return () => undefined;
315
+ }
316
+ const keyQuery = MULTISIG_QUERY_KEY;
317
+ const rawKeys = await substrateApi.api.query.multisig.multisigs.keys(multisigAddress);
318
+ const rawKeysArgs = rawKeys.map(rawKey => rawKey.args);
319
+ const params = [{
320
+ section: 'query',
321
+ module: keyQuery.split('_')[1],
322
+ method: keyQuery.split('_')[2],
323
+ args: rawKeysArgs
324
+ }];
325
+ const subscription = substrateApi.subscribeDataWithMulti(params, async rs => {
326
+ try {
327
+ const items = [];
328
+ const pendingMultisigEntries = rs[keyQuery];
329
+ const blockCache = {};
330
+ await Promise.all(pendingMultisigEntries.map(async (_pendingMultisigInfo, index) => {
331
+ const pendingMultisigInfo = _pendingMultisigInfo;
332
+ if (!pendingMultisigInfo) {
333
+ return;
334
+ }
335
+ const blockHeight = pendingMultisigInfo.when.height;
336
+ const extrinsicIndex = pendingMultisigInfo.when.index;
337
+ const callHash = rawKeysArgs[index][1].toHex();
338
+
339
+ // Cache block-level data to avoid many RPC calls
340
+ let blockInfo = blockCache[blockHeight];
341
+ if (!blockInfo) {
342
+ const blockHash = await substrateApi.api.rpc.chain.getBlockHash(blockHeight);
343
+ const signedBlock = await substrateApi.api.rpc.chain.getBlock(blockHash);
344
+ const apiAt = await substrateApi.api.at(blockHash);
345
+ const timestamp = (await apiAt.query.timestamp.now()).toNumber();
346
+ blockInfo = {
347
+ blockHash,
348
+ signedBlock,
349
+ timestamp
350
+ };
351
+ blockCache[blockHeight] = blockInfo;
352
+ }
353
+ const extrinsicHash = blockInfo.signedBlock.block.extrinsics[extrinsicIndex].hash.toHex();
354
+ const callData = blockInfo.blockHash.toHex() === DEFAULT_BLOCK_HASH ? undefined : getCallData({
355
+ callHash,
356
+ extrinsicIndex,
357
+ block: blockInfo.signedBlock.block
358
+ });
359
+ const decodedCallData = decodeCallData({
360
+ api: substrateApi.api,
361
+ callData
362
+ });
363
+ items.push({
364
+ chain,
365
+ multisigAddress,
366
+ callHash,
367
+ callData,
368
+ decodedCallData,
369
+ blockHeight,
370
+ extrinsicIndex,
371
+ extrinsicHash,
372
+ threshold,
373
+ signerAddresses: signers,
374
+ depositAmount: pendingMultisigInfo.deposit,
375
+ depositor: pendingMultisigInfo.depositor,
376
+ approvals: pendingMultisigInfo.approvals,
377
+ timestamp: blockInfo.timestamp,
378
+ multisigTxType: getMultisigTxType(decodedCallData)
379
+ });
380
+ }));
381
+ callback(items);
382
+ } catch (error) {
383
+ multisigServiceLogger.error(`Multisig Service subscription error ${chain}/${multisigAddress}`, error);
384
+ addLazy(`resubscribeMultisig_${chain}_${multisigAddress}`, () => {
385
+ if (this.status === ServiceStatus.STARTED) {
386
+ this.runSubscribePendingMultisigTxs().catch(e => multisigServiceLogger.error('Error during resubscribeMultisig', e));
387
+ }
388
+ }, 1000, 4000, true);
389
+ }
390
+ });
391
+ return () => subscription.unsubscribe();
392
+ }
393
+
394
+ /**
395
+ * Wrapper function to subscribe to pending multisig extrinsics
396
+ * Returns an unsubscribe function that handles promise resolution
397
+ * @param chain - Chain identifier
398
+ * @param multisigAddress - Multisig address to monitor
399
+ * @param signers - List of signer addresses for the multisig
400
+ * @param threshold - Number of approval required
401
+ * @param callback - Callback function called with updated pending extrinsics
402
+ * @returns Function to unsubscribe from the subscription
403
+ */
404
+ subscribePendingMultisigTxs(chain, multisigAddress, signers, threshold, callback) {
405
+ const unsubPromise = this.subscribePendingMultisigTxsPromise(chain, multisigAddress, signers, threshold, callback);
406
+ return () => {
407
+ unsubPromise.then(unsub => {
408
+ unsub === null || unsub === void 0 ? void 0 : unsub();
409
+ }).catch(e => multisigServiceLogger.error('Error during unsubscribe in subscribePendingMultisigTxs', e));
410
+ };
411
+ }
412
+
413
+ /**
414
+ * Unsubscribes from all active multisig extrinsic subscriptions
415
+ */
416
+ runUnsubscribePendingMultisigTxs() {
417
+ this.unsubscribes && this.unsubscribes();
418
+ this.unsubscribes = undefined;
419
+ }
420
+
421
+ /**
422
+ * Updates the multisig extrinsic map for a specific chain and multisig address
423
+ * Removes old extrinsics and adds new ones, then notifies all subscribers
424
+ * Creates notifications for new pending transactions that require approval
425
+ * @param multisigAddress - Multisig address
426
+ * @param chain - Chain identifier
427
+ * @param rawPendingTxs - Array of raw pending multisig extrinsics to update
428
+ */
429
+ updatePendingMultisigTxSubjectByChain(multisigAddress, chain, rawPendingTxs) {
430
+ const allAddresses = this.keyringService.context.getAllAddresses();
431
+ const currentMap = this.getPendingMultisigTxMap();
432
+ const excludedPrefix = `${chain}___${multisigAddress}___`;
433
+ const filteredMap = {};
434
+
435
+ // 1. Clean old extrinsics of multisigAddress and chain
436
+ for (const [key, value] of Object.entries(currentMap)) {
437
+ if (key.startsWith(excludedPrefix)) {
438
+ this.notifiedTxKeys.delete(key);
439
+ } else {
440
+ filteredMap[key] = value;
441
+ }
442
+ }
443
+ const newTxMap = {};
444
+ const newNotifiedTxs = [];
445
+
446
+ // 2. Create new extrinsics of multisigAddress and chain
447
+ for (const rawTx of rawPendingTxs) {
448
+ const extrinsicHash = rawTx.extrinsicHash;
449
+ const signerAddresses = rawTx.signerAddresses;
450
+ if (!extrinsicHash || !signerAddresses || signerAddresses.length === 0) {
451
+ multisigServiceLogger.warn('Skipping multisig extrinsic due to missing required fields: extrinsicHash or signerAddresses');
452
+ continue;
453
+ }
454
+ for (const signerAddress of signerAddresses) {
455
+ // Do not create pending tx for account not in wallet
456
+ if (!allAddresses.includes(reformatAddress(signerAddress))) {
457
+ continue;
458
+ }
459
+ const reformatSignerAddress = _reformatAddressWithChain(signerAddress, this.chainService.getChainInfoByKey(chain));
460
+ const key = genPendingMultisigTxKey(chain, multisigAddress, reformatSignerAddress, extrinsicHash);
461
+ const pendingTx = {
462
+ ...rawTx,
463
+ currentSigner: reformatSignerAddress,
464
+ id: key
465
+ };
466
+ newTxMap[key] = pendingTx;
467
+
468
+ // Track new transactions that need notification
469
+ // Only notify if this is a new transaction (not already notified)
470
+ if (!this.notifiedTxKeys.has(key) && !currentMap[key]) {
471
+ // skip notified for account has already approved
472
+ if (pendingTx.approvals.includes(signerAddress)) {
473
+ continue;
474
+ }
475
+ newNotifiedTxs.push(pendingTx);
476
+ this.notifiedTxKeys.add(key);
477
+ }
478
+ }
479
+ }
480
+
481
+ // 3. Replace the extrinsics of multisigAddress and chain
482
+ // Find approved transactions to clear notifications
483
+ this.clearMultisigApprovalNotifications(newTxMap, multisigAddress, chain).catch(e => multisigServiceLogger.error('Failed to clear multisig approval notifications:', e));
484
+ this.pendingMultisigTxSubject.next({
485
+ ...filteredMap,
486
+ ...newTxMap
487
+ });
488
+
489
+ // 4. Create notifications for new pending transactions
490
+ if (newNotifiedTxs.length > 0) {
491
+ this.createMultisigApprovalNotifications(newNotifiedTxs).catch(error => {
492
+ multisigServiceLogger.error('Failed to create multisig approval notifications:', error);
493
+ });
494
+ }
495
+ }
496
+
497
+ /**
498
+ * Clears notifications for approved pending multisig transactions
499
+ * @private
500
+ * @param newTxMap - Map of current pending multisig transactions
501
+ * @param multisigAddress - Multisig address
502
+ * @param chain - ChainSlug of the multisig transactions
503
+ */
504
+ async clearMultisigApprovalNotifications(newTxMap, multisigAddress, chain) {
505
+ const currentNotifications = await this.inappNotificationService.fetchNotificationsByParams({
506
+ notificationTab: NotificationTab.MULTISIG,
507
+ proxyId: ALL_ACCOUNT_KEY,
508
+ metadata: {
509
+ multisigAddress,
510
+ chain
511
+ }
512
+ });
513
+ const unapprovedPendingTxs = Object.values(newTxMap).reduce((set, tx) => {
514
+ if (!tx.approvals.find(address => isSameAddress(tx.currentSigner, address))) {
515
+ set.add(tx.id);
516
+ }
517
+ return set;
518
+ }, new Set());
519
+ const notificationsIdsToDelete = currentNotifications.reduce((ids, notification) => {
520
+ const metadata = notification.metadata.multisigKey;
521
+ if (!unapprovedPendingTxs.has(metadata)) {
522
+ ids.push(notification.id);
523
+ }
524
+ return ids;
525
+ }, []);
526
+ if (notificationsIdsToDelete.length > 0) {
527
+ await this.inappNotificationService.cleanUpNotificationByIds(notificationsIdsToDelete);
528
+ }
529
+ }
530
+
531
+ /**
532
+ * Creates notifications for pending multisig transactions that require approval
533
+ * @param pendingTxs - Array of pending multisig transactions that need approval
534
+ */
535
+ async createMultisigApprovalNotifications(pendingTxs) {
536
+ const notifications = pendingTxs.map(tx => {
537
+ const actionType = NotificationActionType.MULTISIG_APPROVAL;
538
+ const timestamp = Date.now();
539
+ const multisigKey = genPendingMultisigTxKey(tx.chain, tx.multisigAddress, tx.currentSigner, tx.extrinsicHash);
540
+ const notificationId = `${actionType}___${multisigKey}___${timestamp}`;
541
+ return {
542
+ id: notificationId,
543
+ address: reformatAddress(tx.currentSigner),
544
+ // save notification to default address because it's managed by account
545
+ title: NotificationTitleMap[actionType],
546
+ description: NotificationDescriptionMap[actionType](),
547
+ time: timestamp,
548
+ extrinsicType: ExtrinsicType.MULTISIG_APPROVE_TX,
549
+ isRead: false,
550
+ actionType,
551
+ metadata: {
552
+ multisigKey,
553
+ chain: tx.chain,
554
+ multisigAddress: tx.multisigAddress,
555
+ extrinsicHash: tx.extrinsicHash,
556
+ callHash: tx.callHash,
557
+ blockHeight: tx.blockHeight,
558
+ extrinsicIndex: tx.extrinsicIndex,
559
+ currentSigner: tx.currentSigner,
560
+ approvals: tx.approvals,
561
+ multisigTxType: tx.multisigTxType
562
+ }
563
+ };
564
+ });
565
+ multisigServiceLogger.debug('notifications', notifications);
566
+
567
+ // Group notifications by address to batch write
568
+ const notificationsByAddress = {};
569
+ for (const notification of notifications) {
570
+ const address = notification.address;
571
+ if (!notificationsByAddress[address]) {
572
+ notificationsByAddress[address] = [];
573
+ }
574
+ notificationsByAddress[address] = [...notificationsByAddress[address], notification];
575
+ }
576
+
577
+ // Write notifications for each address
578
+ for (const [address, addressNotifications] of Object.entries(notificationsByAddress)) {
579
+ if (!address || !addressNotifications || !addressNotifications.length) {
580
+ continue;
581
+ }
582
+ await this.inappNotificationService.validateAndWriteNotificationsToDB(addressNotifications, address);
583
+ }
584
+ }
585
+
586
+ /**
587
+ * Subscribes to changes in the pending multisig extrinsic map
588
+ * @returns BehaviorSubject that emits updates when the extrinsic map changes
589
+ */
590
+ subscribePendingMultisigTxMap() {
591
+ return this.pendingMultisigTxSubject;
592
+ }
593
+
594
+ /**
595
+ * Gets a snapshot of the current pending multisig extrinsic map
596
+ * @returns Copy of the current pending multisig extrinsic map
597
+ */
598
+ getPendingMultisigTxMap() {
599
+ return {
600
+ ...this.pendingMultisigTxSubject.getValue()
601
+ };
602
+ }
603
+
604
+ /**
605
+ * Gets pending extrinsics for a specific multisig address
606
+ * @param request - Request object containing the multisig address
607
+ * @returns Array of pending multisig extrinsics matching the criteria
608
+ */
609
+ getPendingTxsForMultisigAddress(request) {
610
+ const {
611
+ chain,
612
+ multisigAddress
613
+ } = request;
614
+ const currentMap = this.getPendingMultisigTxMap();
615
+ if (chain) {
616
+ return Object.values(currentMap).filter(tx => isSameAddress(tx.multisigAddress, multisigAddress) && tx.chain === chain);
617
+ }
618
+ return Object.values(currentMap).filter(tx => isSameAddress(tx.multisigAddress, multisigAddress));
619
+ }
620
+ }