@metamask-previews/assets-controller 0.0.0-preview-40468f94 → 0.0.0-preview-fb7aa07ff
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 +1 -0
- package/dist/data-sources/SnapDataSource.cjs +193 -464
- package/dist/data-sources/SnapDataSource.cjs.map +1 -1
- package/dist/data-sources/SnapDataSource.d.cts +24 -86
- package/dist/data-sources/SnapDataSource.d.cts.map +1 -1
- package/dist/data-sources/SnapDataSource.d.mts +24 -86
- package/dist/data-sources/SnapDataSource.d.mts.map +1 -1
- package/dist/data-sources/SnapDataSource.mjs +191 -458
- package/dist/data-sources/SnapDataSource.mjs.map +1 -1
- package/dist/data-sources/index.cjs +7 -36
- package/dist/data-sources/index.cjs.map +1 -1
- package/dist/data-sources/index.d.cts +1 -1
- package/dist/data-sources/index.d.cts.map +1 -1
- package/dist/data-sources/index.d.mts +1 -1
- package/dist/data-sources/index.d.mts.map +1 -1
- package/dist/data-sources/index.mjs +5 -13
- package/dist/data-sources/index.mjs.map +1 -1
- package/dist/data-sources/initDataSources.cjs +9 -3
- package/dist/data-sources/initDataSources.cjs.map +1 -1
- package/dist/data-sources/initDataSources.d.cts +2 -4
- package/dist/data-sources/initDataSources.d.cts.map +1 -1
- package/dist/data-sources/initDataSources.d.mts +2 -4
- package/dist/data-sources/initDataSources.d.mts.map +1 -1
- package/dist/data-sources/initDataSources.mjs +9 -3
- package/dist/data-sources/initDataSources.mjs.map +1 -1
- package/dist/index.cjs +7 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +5 -13
- package/dist/index.mjs.map +1 -1
- package/package.json +7 -2
|
@@ -9,7 +9,9 @@ var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (
|
|
|
9
9
|
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
10
|
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
11
|
};
|
|
12
|
-
var _SnapDataSource_instances, _SnapDataSource_messenger,
|
|
12
|
+
var _SnapDataSource_instances, _SnapDataSource_messenger, _SnapDataSource_handleSnapBalancesUpdatedBound, _SnapDataSource_handlePermissionStateChangeBound, _SnapDataSource_keyringClientCache, _SnapDataSource_subscribeToEvents, _SnapDataSource_handleSnapBalancesUpdated, _SnapDataSource_isChainSupportedBySnap, _SnapDataSource_registerActionHandlers, _SnapDataSource_getRunnableSnaps, _SnapDataSource_getSnapPermissions, _SnapDataSource_discoverKeyringSnaps, _SnapDataSource_getKeyringClient;
|
|
13
|
+
import { KeyringClient } from "@metamask/keyring-snap-client";
|
|
14
|
+
import { HandlerType, SnapCaveatType } from "@metamask/snaps-utils";
|
|
13
15
|
import { AbstractDataSource } from "./AbstractDataSource.mjs";
|
|
14
16
|
import { projectLogger, createModuleLogger } from "../logger.mjs";
|
|
15
17
|
const log = createModuleLogger(projectLogger, 'SnapDataSource');
|
|
@@ -17,89 +19,29 @@ const log = createModuleLogger(projectLogger, 'SnapDataSource');
|
|
|
17
19
|
// CONSTANTS
|
|
18
20
|
// ============================================================================
|
|
19
21
|
export const SNAP_DATA_SOURCE_NAME = 'SnapDataSource';
|
|
20
|
-
|
|
21
|
-
export const
|
|
22
|
-
|
|
23
|
-
export const
|
|
24
|
-
//
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
export const TRON_CHAIN_PREFIX = 'tron:';
|
|
28
|
-
// Default networks
|
|
29
|
-
export const SOLANA_MAINNET = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';
|
|
30
|
-
export const SOLANA_DEVNET = 'solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1';
|
|
31
|
-
export const SOLANA_TESTNET = 'solana:4uhcVJyU9pJkvQyS88uRDiswHXSCkY3z';
|
|
32
|
-
export const BITCOIN_MAINNET = 'bip122:000000000019d6689c085ae165831e93';
|
|
33
|
-
export const BITCOIN_TESTNET = 'bip122:000000000933ea01ad0ee984209779ba';
|
|
34
|
-
export const TRON_MAINNET = 'tron:728126428';
|
|
35
|
-
export const TRON_SHASTA = 'tron:2494104990';
|
|
36
|
-
export const TRON_NILE = 'tron:3448148188';
|
|
37
|
-
// Hex format alternatives for Tron
|
|
38
|
-
export const TRON_MAINNET_HEX = 'tron:0x2b6653dc';
|
|
39
|
-
export const TRON_SHASTA_HEX = 'tron:0x94a9059e';
|
|
40
|
-
export const TRON_NILE_HEX = 'tron:0xcd8690dc';
|
|
41
|
-
// Default poll intervals
|
|
42
|
-
export const DEFAULT_SOLANA_POLL_INTERVAL = 30000; // 30 seconds
|
|
43
|
-
export const DEFAULT_BITCOIN_POLL_INTERVAL = 60000; // 1 minute
|
|
44
|
-
export const DEFAULT_TRON_POLL_INTERVAL = 30000; // 30 seconds
|
|
45
|
-
export const DEFAULT_SNAP_POLL_INTERVAL = 30000; // Default for unknown snaps
|
|
46
|
-
// All default networks
|
|
47
|
-
export const ALL_DEFAULT_NETWORKS = [
|
|
48
|
-
SOLANA_MAINNET,
|
|
49
|
-
SOLANA_DEVNET,
|
|
50
|
-
SOLANA_TESTNET,
|
|
51
|
-
BITCOIN_MAINNET,
|
|
52
|
-
BITCOIN_TESTNET,
|
|
53
|
-
TRON_MAINNET,
|
|
54
|
-
TRON_SHASTA,
|
|
55
|
-
TRON_NILE,
|
|
56
|
-
TRON_MAINNET_HEX,
|
|
57
|
-
TRON_SHASTA_HEX,
|
|
58
|
-
TRON_NILE_HEX,
|
|
59
|
-
];
|
|
60
|
-
export const SNAP_REGISTRY = {
|
|
61
|
-
solana: {
|
|
62
|
-
snapId: SOLANA_SNAP_ID,
|
|
63
|
-
chainPrefix: SOLANA_CHAIN_PREFIX,
|
|
64
|
-
pollInterval: DEFAULT_SOLANA_POLL_INTERVAL,
|
|
65
|
-
},
|
|
66
|
-
bitcoin: {
|
|
67
|
-
snapId: BITCOIN_SNAP_ID,
|
|
68
|
-
chainPrefix: BITCOIN_CHAIN_PREFIX,
|
|
69
|
-
pollInterval: DEFAULT_BITCOIN_POLL_INTERVAL,
|
|
70
|
-
},
|
|
71
|
-
tron: {
|
|
72
|
-
snapId: TRON_SNAP_ID,
|
|
73
|
-
chainPrefix: TRON_CHAIN_PREFIX,
|
|
74
|
-
pollInterval: DEFAULT_TRON_POLL_INTERVAL,
|
|
75
|
-
},
|
|
76
|
-
};
|
|
22
|
+
/** The permission name for snap keyring endowment */
|
|
23
|
+
export const KEYRING_PERMISSION = 'endowment:keyring';
|
|
24
|
+
/** The permission name for snap assets endowment (contains chainIds) */
|
|
25
|
+
export const ASSETS_PERMISSION = 'endowment:assets';
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// PERMISSION UTILITIES
|
|
28
|
+
// ============================================================================
|
|
77
29
|
/**
|
|
78
|
-
*
|
|
30
|
+
* Getter function to get the chainIds caveat from a permission.
|
|
79
31
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
|
|
83
|
-
export function getSnapTypeForChain(chainId) {
|
|
84
|
-
if (chainId.startsWith(SOLANA_CHAIN_PREFIX)) {
|
|
85
|
-
return 'solana';
|
|
86
|
-
}
|
|
87
|
-
if (chainId.startsWith(BITCOIN_CHAIN_PREFIX)) {
|
|
88
|
-
return 'bitcoin';
|
|
89
|
-
}
|
|
90
|
-
if (chainId.startsWith(TRON_CHAIN_PREFIX)) {
|
|
91
|
-
return 'tron';
|
|
92
|
-
}
|
|
93
|
-
return null;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Check if a chain ID is supported by a snap.
|
|
32
|
+
* This does basic validation of the caveat, but does not validate the type or
|
|
33
|
+
* value of the namespaces object itself, as this is handled by the
|
|
34
|
+
* `PermissionsController` when the permission is requested.
|
|
97
35
|
*
|
|
98
|
-
* @param
|
|
99
|
-
* @returns
|
|
36
|
+
* @param permission - The permission to get the `chainIds` caveat from.
|
|
37
|
+
* @returns An array of `chainIds` that the snap supports, or null if none.
|
|
100
38
|
*/
|
|
101
|
-
export function
|
|
102
|
-
|
|
39
|
+
export function getChainIdsCaveat(permission) {
|
|
40
|
+
if (!permission?.caveats) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
const caveat = permission.caveats.find((permCaveat) => permCaveat.type === SnapCaveatType.ChainIds);
|
|
44
|
+
return caveat ? caveat.value : null;
|
|
103
45
|
}
|
|
104
46
|
/**
|
|
105
47
|
* Extract chain ID from a CAIP-19 asset ID.
|
|
@@ -112,23 +54,9 @@ export function extractChainFromAssetId(assetId) {
|
|
|
112
54
|
const parts = assetId.split('/');
|
|
113
55
|
return parts[0];
|
|
114
56
|
}
|
|
115
|
-
// Helper functions for specific chain types
|
|
116
|
-
export function isSolanaChain(chainId) {
|
|
117
|
-
return chainId.startsWith(SOLANA_CHAIN_PREFIX);
|
|
118
|
-
}
|
|
119
|
-
export function isBitcoinChain(chainId) {
|
|
120
|
-
return chainId.startsWith(BITCOIN_CHAIN_PREFIX);
|
|
121
|
-
}
|
|
122
|
-
export function isTronChain(chainId) {
|
|
123
|
-
return chainId.startsWith(TRON_CHAIN_PREFIX);
|
|
124
|
-
}
|
|
125
57
|
const defaultSnapState = {
|
|
126
|
-
activeChains:
|
|
127
|
-
|
|
128
|
-
solana: { version: null, available: false },
|
|
129
|
-
bitcoin: { version: null, available: false },
|
|
130
|
-
tron: { version: null, available: false },
|
|
131
|
-
},
|
|
58
|
+
activeChains: [],
|
|
59
|
+
chainToSnap: {},
|
|
132
60
|
};
|
|
133
61
|
// ============================================================================
|
|
134
62
|
// SNAP DATA SOURCE
|
|
@@ -137,16 +65,10 @@ const defaultSnapState = {
|
|
|
137
65
|
* Unified Snap data source that routes requests to the appropriate wallet snap
|
|
138
66
|
* based on the chain ID prefix.
|
|
139
67
|
*
|
|
140
|
-
* Supports:
|
|
141
|
-
* - Solana chains (solana:*) → @metamask/solana-wallet-snap
|
|
142
|
-
* - Bitcoin chains (bip122:*) → @metamask/bitcoin-wallet-snap
|
|
143
|
-
* - Tron chains (tron:*) → @metamask/tron-wallet-snap
|
|
144
|
-
*
|
|
145
68
|
* @example
|
|
146
69
|
* ```typescript
|
|
147
70
|
* const snapDataSource = new SnapDataSource({
|
|
148
71
|
* messenger,
|
|
149
|
-
* snapProvider: metamaskProvider,
|
|
150
72
|
* });
|
|
151
73
|
*
|
|
152
74
|
* // Fetch will automatically route to the correct snap
|
|
@@ -158,122 +80,81 @@ const defaultSnapState = {
|
|
|
158
80
|
*/
|
|
159
81
|
export class SnapDataSource extends AbstractDataSource {
|
|
160
82
|
constructor(options) {
|
|
161
|
-
const configuredNetworks = options.configuredNetworks ?? ALL_DEFAULT_NETWORKS;
|
|
162
83
|
super(SNAP_DATA_SOURCE_NAME, {
|
|
163
84
|
...defaultSnapState,
|
|
164
85
|
...options.state,
|
|
165
|
-
activeChains: configuredNetworks,
|
|
166
86
|
});
|
|
167
87
|
_SnapDataSource_instances.add(this);
|
|
168
88
|
_SnapDataSource_messenger.set(this, void 0);
|
|
169
|
-
|
|
89
|
+
/** Bound handler for snap keyring balance updates, stored for cleanup */
|
|
90
|
+
_SnapDataSource_handleSnapBalancesUpdatedBound.set(this, void 0);
|
|
91
|
+
_SnapDataSource_handlePermissionStateChangeBound.set(this, void 0);
|
|
92
|
+
/** Cache of KeyringClient instances per snap ID to avoid re-instantiation */
|
|
93
|
+
_SnapDataSource_keyringClientCache.set(this, new Map());
|
|
170
94
|
__classPrivateFieldSet(this, _SnapDataSource_messenger, options.messenger, "f");
|
|
171
|
-
|
|
95
|
+
// Bind handlers for cleanup in destroy()
|
|
96
|
+
__classPrivateFieldSet(this, _SnapDataSource_handleSnapBalancesUpdatedBound, __classPrivateFieldGet(this, _SnapDataSource_instances, "m", _SnapDataSource_handleSnapBalancesUpdated).bind(this), "f");
|
|
97
|
+
__classPrivateFieldSet(this, _SnapDataSource_handlePermissionStateChangeBound, __classPrivateFieldGet(this, _SnapDataSource_instances, "m", _SnapDataSource_discoverKeyringSnaps).bind(this), "f");
|
|
172
98
|
__classPrivateFieldGet(this, _SnapDataSource_instances, "m", _SnapDataSource_registerActionHandlers).call(this);
|
|
173
|
-
__classPrivateFieldGet(this, _SnapDataSource_instances, "m",
|
|
174
|
-
//
|
|
175
|
-
__classPrivateFieldGet(this, _SnapDataSource_instances, "m",
|
|
176
|
-
// Silently ignore availability check failures on init
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
/**
|
|
180
|
-
* Get info about all snaps.
|
|
181
|
-
*
|
|
182
|
-
* @returns Record of snap info keyed by snap type.
|
|
183
|
-
*/
|
|
184
|
-
getSnapsInfo() {
|
|
185
|
-
const result = {};
|
|
186
|
-
for (const [snapType, config] of Object.entries(SNAP_REGISTRY)) {
|
|
187
|
-
const state = this.state.snaps[snapType];
|
|
188
|
-
result[snapType] = {
|
|
189
|
-
...config,
|
|
190
|
-
version: state.version,
|
|
191
|
-
available: state.available,
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
return result;
|
|
195
|
-
}
|
|
196
|
-
/**
|
|
197
|
-
* Check if a specific snap is available.
|
|
198
|
-
*
|
|
199
|
-
* @param snapType - The snap type to check (solana, bitcoin, tron).
|
|
200
|
-
* @returns True if the snap is available.
|
|
201
|
-
*/
|
|
202
|
-
isSnapAvailable(snapType) {
|
|
203
|
-
return this.state.snaps[snapType]?.available ?? false;
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Force refresh snap availability check.
|
|
207
|
-
*/
|
|
208
|
-
async refreshSnapsStatus() {
|
|
209
|
-
await __classPrivateFieldGet(this, _SnapDataSource_instances, "m", _SnapDataSource_checkAllSnapsAvailability).call(this);
|
|
99
|
+
__classPrivateFieldGet(this, _SnapDataSource_instances, "m", _SnapDataSource_subscribeToEvents).call(this);
|
|
100
|
+
// Discover keyring-capable snaps and populate activeChains dynamically
|
|
101
|
+
__classPrivateFieldGet(this, _SnapDataSource_instances, "m", _SnapDataSource_discoverKeyringSnaps).call(this);
|
|
210
102
|
}
|
|
211
103
|
// ============================================================================
|
|
212
|
-
//
|
|
213
|
-
// ============================================================================
|
|
214
|
-
addNetworks(chainIds) {
|
|
215
|
-
const snapChains = chainIds.filter(isSnapSupportedChain);
|
|
216
|
-
const newChains = snapChains.filter((chain) => !this.state.activeChains.includes(chain));
|
|
217
|
-
if (newChains.length > 0) {
|
|
218
|
-
const updated = [...this.state.activeChains, ...newChains];
|
|
219
|
-
this.updateActiveChains(updated, (updatedChains) => __classPrivateFieldGet(this, _SnapDataSource_messenger, "f").call('AssetsController:activeChainsUpdate', SNAP_DATA_SOURCE_NAME, updatedChains));
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
removeNetworks(chainIds) {
|
|
223
|
-
const chainSet = new Set(chainIds);
|
|
224
|
-
const updated = this.state.activeChains.filter((chain) => !chainSet.has(chain));
|
|
225
|
-
if (updated.length !== this.state.activeChains.length) {
|
|
226
|
-
this.updateActiveChains(updated, (updatedChains) => __classPrivateFieldGet(this, _SnapDataSource_messenger, "f").call('AssetsController:activeChainsUpdate', SNAP_DATA_SOURCE_NAME, updatedChains));
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
// ============================================================================
|
|
230
|
-
// FETCH - Routes to appropriate snap(s)
|
|
104
|
+
// FETCH
|
|
231
105
|
// ============================================================================
|
|
232
106
|
async fetch(request) {
|
|
233
|
-
|
|
234
|
-
|
|
107
|
+
var _a;
|
|
108
|
+
// Guard against undefined request
|
|
109
|
+
// Note: chainIds filtering is done by middleware/subscribe before calling fetch
|
|
110
|
+
if (!request?.accounts || !request?.chainIds?.length) {
|
|
235
111
|
return {};
|
|
236
112
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
...request,
|
|
248
|
-
chainIds: chains,
|
|
249
|
-
});
|
|
250
|
-
}));
|
|
251
|
-
// Merge all results
|
|
252
|
-
const mergedResponse = {};
|
|
253
|
-
for (const result of results) {
|
|
254
|
-
if (result.assetsBalance) {
|
|
255
|
-
mergedResponse.assetsBalance = {
|
|
256
|
-
...mergedResponse.assetsBalance,
|
|
257
|
-
...result.assetsBalance,
|
|
258
|
-
};
|
|
113
|
+
const results = {
|
|
114
|
+
assetsBalance: {},
|
|
115
|
+
assetsMetadata: {},
|
|
116
|
+
};
|
|
117
|
+
// Fetch balances for each account using its snap ID from metadata
|
|
118
|
+
for (const account of request.accounts) {
|
|
119
|
+
// Skip accounts without snap metadata (non-snap accounts)
|
|
120
|
+
const snapId = account.metadata.snap?.id;
|
|
121
|
+
if (!snapId) {
|
|
122
|
+
continue;
|
|
259
123
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
};
|
|
124
|
+
// Skip accounts whose snap doesn't support any of the requested chains
|
|
125
|
+
const snapSupportsRequestedChains = request.chainIds.some((chainId) => this.state.chainToSnap[chainId] === snapId);
|
|
126
|
+
if (!snapSupportsRequestedChains) {
|
|
127
|
+
continue;
|
|
265
128
|
}
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
129
|
+
const accountId = account.id;
|
|
130
|
+
try {
|
|
131
|
+
const client = __classPrivateFieldGet(this, _SnapDataSource_instances, "m", _SnapDataSource_getKeyringClient).call(this, snapId);
|
|
132
|
+
// Step 1: Get the list of assets for this account
|
|
133
|
+
const accountAssets = await client.listAccountAssets(accountId);
|
|
134
|
+
// If no assets, skip to next account
|
|
135
|
+
if (!accountAssets || accountAssets.length === 0) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// Step 2: Get balances for those specific assets
|
|
139
|
+
const balances = await client.getAccountBalances(accountId, accountAssets);
|
|
140
|
+
// Transform keyring response to DataResponse format
|
|
141
|
+
if (balances && typeof balances === 'object' && results.assetsBalance) {
|
|
142
|
+
for (const [assetId, balance] of Object.entries(balances)) {
|
|
143
|
+
(_a = results.assetsBalance)[accountId] ?? (_a[accountId] = {});
|
|
144
|
+
const accountBalances = results.assetsBalance[accountId];
|
|
145
|
+
if (accountBalances) {
|
|
146
|
+
accountBalances[assetId] = {
|
|
147
|
+
amount: balance.amount,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
271
152
|
}
|
|
272
|
-
|
|
273
|
-
|
|
153
|
+
catch {
|
|
154
|
+
// Expected when account doesn't belong to this snap
|
|
274
155
|
}
|
|
275
156
|
}
|
|
276
|
-
return
|
|
157
|
+
return results;
|
|
277
158
|
}
|
|
278
159
|
// ============================================================================
|
|
279
160
|
// MIDDLEWARE
|
|
@@ -361,19 +242,19 @@ export class SnapDataSource extends AbstractDataSource {
|
|
|
361
242
|
if (!request?.chainIds) {
|
|
362
243
|
return;
|
|
363
244
|
}
|
|
364
|
-
// Filter to
|
|
365
|
-
const
|
|
366
|
-
if (
|
|
245
|
+
// Filter to chains we have a snap for
|
|
246
|
+
const supportedChains = request.chainIds.filter((chainId) => __classPrivateFieldGet(this, _SnapDataSource_instances, "m", _SnapDataSource_isChainSupportedBySnap).call(this, chainId));
|
|
247
|
+
if (supportedChains.length === 0) {
|
|
367
248
|
return;
|
|
368
249
|
}
|
|
369
250
|
if (isUpdate) {
|
|
370
251
|
const existing = this.activeSubscriptions.get(subscriptionId);
|
|
371
252
|
if (existing) {
|
|
372
|
-
existing.chains =
|
|
253
|
+
existing.chains = supportedChains;
|
|
373
254
|
// Do a fetch to get latest data on subscription update
|
|
374
255
|
this.fetch({
|
|
375
256
|
...request,
|
|
376
|
-
chainIds:
|
|
257
|
+
chainIds: supportedChains,
|
|
377
258
|
})
|
|
378
259
|
.then(async (fetchResponse) => {
|
|
379
260
|
if (Object.keys(fetchResponse.assetsBalance ?? {}).length > 0) {
|
|
@@ -395,13 +276,13 @@ export class SnapDataSource extends AbstractDataSource {
|
|
|
395
276
|
cleanup: () => {
|
|
396
277
|
// No timer to clear - we use event-based updates
|
|
397
278
|
},
|
|
398
|
-
chains:
|
|
279
|
+
chains: supportedChains,
|
|
399
280
|
});
|
|
400
281
|
// Initial fetch to get current balances
|
|
401
282
|
try {
|
|
402
283
|
const fetchResponse = await this.fetch({
|
|
403
284
|
...request,
|
|
404
|
-
chainIds:
|
|
285
|
+
chainIds: supportedChains,
|
|
405
286
|
});
|
|
406
287
|
if (Object.keys(fetchResponse.assetsBalance ?? {}).length > 0) {
|
|
407
288
|
await __classPrivateFieldGet(this, _SnapDataSource_messenger, "f").call('AssetsController:assetsUpdate', fetchResponse, SNAP_DATA_SOURCE_NAME);
|
|
@@ -415,52 +296,68 @@ export class SnapDataSource extends AbstractDataSource {
|
|
|
415
296
|
// CLEANUP
|
|
416
297
|
// ============================================================================
|
|
417
298
|
destroy() {
|
|
299
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
300
|
+
const messenger = __classPrivateFieldGet(this, _SnapDataSource_messenger, "f");
|
|
301
|
+
// Unsubscribe from snap keyring events
|
|
302
|
+
try {
|
|
303
|
+
messenger.unsubscribe('AccountsController:accountBalancesUpdated', __classPrivateFieldGet(this, _SnapDataSource_handleSnapBalancesUpdatedBound, "f"));
|
|
304
|
+
}
|
|
305
|
+
catch (error) {
|
|
306
|
+
log('Failed to unsubscribe from snap keyring events', { error });
|
|
307
|
+
}
|
|
308
|
+
// Unsubscribe from permission changes
|
|
309
|
+
try {
|
|
310
|
+
messenger.unsubscribe('PermissionController:stateChange', __classPrivateFieldGet(this, _SnapDataSource_handlePermissionStateChangeBound, "f"));
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
log('Failed to unsubscribe from permission changes', { error });
|
|
314
|
+
}
|
|
315
|
+
// Clean up active subscriptions
|
|
418
316
|
for (const [subscriptionId] of this.activeSubscriptions) {
|
|
419
317
|
this.unsubscribe(subscriptionId).catch(() => {
|
|
420
318
|
// Ignore cleanup errors
|
|
421
319
|
});
|
|
422
320
|
}
|
|
321
|
+
// Clear keyring client cache
|
|
322
|
+
__classPrivateFieldGet(this, _SnapDataSource_keyringClientCache, "f").clear();
|
|
423
323
|
}
|
|
424
324
|
}
|
|
425
|
-
_SnapDataSource_messenger = new WeakMap(),
|
|
426
|
-
//
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
catch (error) {
|
|
434
|
-
log('Failed to subscribe to snap keyring events', { error });
|
|
435
|
-
}
|
|
325
|
+
_SnapDataSource_messenger = new WeakMap(), _SnapDataSource_handleSnapBalancesUpdatedBound = new WeakMap(), _SnapDataSource_handlePermissionStateChangeBound = new WeakMap(), _SnapDataSource_keyringClientCache = new WeakMap(), _SnapDataSource_instances = new WeakSet(), _SnapDataSource_subscribeToEvents = function _SnapDataSource_subscribeToEvents() {
|
|
326
|
+
// Subscribe to snap keyring events for real-time balance updates.
|
|
327
|
+
// The snaps emit AccountBalancesUpdated events when balances change,
|
|
328
|
+
// which are re-published by AccountsController.
|
|
329
|
+
__classPrivateFieldGet(this, _SnapDataSource_messenger, "f").subscribe('AccountsController:accountBalancesUpdated', __classPrivateFieldGet(this, _SnapDataSource_handleSnapBalancesUpdatedBound, "f"));
|
|
330
|
+
// Subscribe to permission changes to detect new keyring snaps at runtime.
|
|
331
|
+
// Re-runs snap discovery when permissions change.
|
|
332
|
+
__classPrivateFieldGet(this, _SnapDataSource_messenger, "f").subscribe('PermissionController:stateChange', __classPrivateFieldGet(this, _SnapDataSource_handlePermissionStateChangeBound, "f"));
|
|
436
333
|
}, _SnapDataSource_handleSnapBalancesUpdated = function _SnapDataSource_handleSnapBalancesUpdated(payload) {
|
|
437
334
|
// Transform the snap keyring payload to DataResponse format
|
|
438
|
-
|
|
439
|
-
assetsBalance: {},
|
|
440
|
-
};
|
|
335
|
+
let assetsBalance;
|
|
441
336
|
for (const [accountId, assets] of Object.entries(payload.balances)) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
// Remove account if no snap assets
|
|
453
|
-
if (Object.keys(response.assetsBalance[accountId]).length === 0) {
|
|
454
|
-
delete response.assetsBalance[accountId];
|
|
337
|
+
let accountAssets;
|
|
338
|
+
for (const [assetId, balance] of Object.entries(assets)) {
|
|
339
|
+
const chainId = extractChainFromAssetId(assetId);
|
|
340
|
+
if (__classPrivateFieldGet(this, _SnapDataSource_instances, "m", _SnapDataSource_isChainSupportedBySnap).call(this, chainId)) {
|
|
341
|
+
accountAssets ?? (accountAssets = {});
|
|
342
|
+
accountAssets[assetId] = {
|
|
343
|
+
amount: balance.amount,
|
|
344
|
+
};
|
|
455
345
|
}
|
|
456
346
|
}
|
|
347
|
+
if (accountAssets) {
|
|
348
|
+
assetsBalance ?? (assetsBalance = {});
|
|
349
|
+
assetsBalance[accountId] = accountAssets;
|
|
350
|
+
}
|
|
457
351
|
}
|
|
458
352
|
// Only report if we have snap-related updates
|
|
459
|
-
if (
|
|
353
|
+
if (assetsBalance) {
|
|
354
|
+
const response = { assetsBalance };
|
|
460
355
|
__classPrivateFieldGet(this, _SnapDataSource_messenger, "f")
|
|
461
356
|
.call('AssetsController:assetsUpdate', response, SNAP_DATA_SOURCE_NAME)
|
|
462
357
|
.catch(console.error);
|
|
463
358
|
}
|
|
359
|
+
}, _SnapDataSource_isChainSupportedBySnap = function _SnapDataSource_isChainSupportedBySnap(chainId) {
|
|
360
|
+
return this.state.activeChains.includes(chainId);
|
|
464
361
|
}, _SnapDataSource_registerActionHandlers = function _SnapDataSource_registerActionHandlers() {
|
|
465
362
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
466
363
|
const messenger = __classPrivateFieldGet(this, _SnapDataSource_messenger, "f");
|
|
@@ -469,250 +366,86 @@ _SnapDataSource_messenger = new WeakMap(), _SnapDataSource_snapProvider = new We
|
|
|
469
366
|
messenger.registerActionHandler('SnapDataSource:fetch', async (request) => this.fetch(request));
|
|
470
367
|
messenger.registerActionHandler('SnapDataSource:subscribe', async (request) => this.subscribe(request));
|
|
471
368
|
messenger.registerActionHandler('SnapDataSource:unsubscribe', async (subscriptionId) => this.unsubscribe(subscriptionId));
|
|
472
|
-
},
|
|
473
|
-
// ============================================================================
|
|
474
|
-
// SNAP AVAILABILITY
|
|
475
|
-
// ============================================================================
|
|
476
|
-
/**
|
|
477
|
-
* Get all installed snaps from the snap provider.
|
|
478
|
-
*
|
|
479
|
-
* @returns A map of snap IDs to their versions.
|
|
480
|
-
*/
|
|
481
|
-
async function _SnapDataSource_getInstalledSnaps() {
|
|
369
|
+
}, _SnapDataSource_getRunnableSnaps = function _SnapDataSource_getRunnableSnaps() {
|
|
482
370
|
try {
|
|
483
|
-
|
|
484
|
-
method: 'wallet_getSnaps',
|
|
485
|
-
params: {},
|
|
486
|
-
});
|
|
487
|
-
return snaps;
|
|
371
|
+
return __classPrivateFieldGet(this, _SnapDataSource_messenger, "f").call('SnapController:getRunnableSnaps');
|
|
488
372
|
}
|
|
489
373
|
catch (error) {
|
|
490
|
-
log('Failed to get
|
|
491
|
-
return
|
|
374
|
+
log('Failed to get runnable snaps', error);
|
|
375
|
+
return [];
|
|
492
376
|
}
|
|
493
|
-
},
|
|
494
|
-
/**
|
|
495
|
-
* Check availability for a single snap type on-demand.
|
|
496
|
-
* This is called before each fetch to ensure we have the latest availability status.
|
|
497
|
-
*
|
|
498
|
-
* @param snapType - The snap type to check (solana, bitcoin, tron)
|
|
499
|
-
* @returns True if the snap is available, false otherwise
|
|
500
|
-
*/
|
|
501
|
-
async function _SnapDataSource_checkSnapAvailabilityOnDemand(snapType) {
|
|
502
|
-
const config = SNAP_REGISTRY[snapType];
|
|
503
|
-
const currentState = this.state.snaps[snapType];
|
|
504
|
-
// If already marked as available, return true (snap was found previously)
|
|
505
|
-
if (currentState.available) {
|
|
506
|
-
return true;
|
|
507
|
-
}
|
|
508
|
-
// Check if snap is now available (handles timing issues where snap wasn't ready at init)
|
|
377
|
+
}, _SnapDataSource_getSnapPermissions = function _SnapDataSource_getSnapPermissions(snapId) {
|
|
509
378
|
try {
|
|
510
|
-
|
|
511
|
-
const snap = snaps[config.snapId];
|
|
512
|
-
if (snap) {
|
|
513
|
-
// Snap is now available - update state
|
|
514
|
-
this.state.snaps[snapType] = {
|
|
515
|
-
version: snap.version,
|
|
516
|
-
available: true,
|
|
517
|
-
};
|
|
518
|
-
return true;
|
|
519
|
-
}
|
|
520
|
-
return false;
|
|
379
|
+
return __classPrivateFieldGet(this, _SnapDataSource_messenger, "f").call('PermissionController:getPermissions', snapId);
|
|
521
380
|
}
|
|
522
|
-
catch {
|
|
523
|
-
|
|
381
|
+
catch (error) {
|
|
382
|
+
log('Failed to get permissions for snap', { snapId, error });
|
|
383
|
+
return undefined;
|
|
524
384
|
}
|
|
525
|
-
},
|
|
385
|
+
}, _SnapDataSource_discoverKeyringSnaps = function _SnapDataSource_discoverKeyringSnaps() {
|
|
526
386
|
try {
|
|
527
|
-
const
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
387
|
+
const runnableSnaps = __classPrivateFieldGet(this, _SnapDataSource_instances, "m", _SnapDataSource_getRunnableSnaps).call(this);
|
|
388
|
+
const chainToSnap = {};
|
|
389
|
+
const supportedChains = [];
|
|
390
|
+
for (const snap of runnableSnaps) {
|
|
391
|
+
const permissions = __classPrivateFieldGet(this, _SnapDataSource_instances, "m", _SnapDataSource_getSnapPermissions).call(this, snap.id);
|
|
392
|
+
// Must have endowment:keyring permission to be a keyring snap
|
|
393
|
+
if (!permissions?.[KEYRING_PERMISSION]) {
|
|
394
|
+
continue;
|
|
535
395
|
}
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
396
|
+
// Get chainIds caveat from the assets permission (not keyring permission)
|
|
397
|
+
// The chainIds are stored in endowment:assets
|
|
398
|
+
const assetsPermission = permissions[ASSETS_PERMISSION];
|
|
399
|
+
const chainIds = getChainIdsCaveat(assetsPermission);
|
|
400
|
+
// Map each chain to this snap (first snap wins if multiple support same chain)
|
|
401
|
+
if (chainIds) {
|
|
402
|
+
for (const chainId of chainIds) {
|
|
403
|
+
if (!(chainId in chainToSnap)) {
|
|
404
|
+
chainToSnap[chainId] = snap.id;
|
|
405
|
+
supportedChains.push(chainId);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
541
408
|
}
|
|
542
409
|
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
//
|
|
546
|
-
|
|
547
|
-
this.
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
};
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
}, _SnapDataSource_accountSupportsChain = function _SnapDataSource_accountSupportsChain(account, chainId) {
|
|
554
|
-
const scopes = account.scopes ?? [];
|
|
555
|
-
// If no scopes defined, assume it supports the chain (backward compatibility)
|
|
556
|
-
if (scopes.length === 0) {
|
|
557
|
-
return true;
|
|
558
|
-
}
|
|
559
|
-
// Extract namespace and reference from chainId
|
|
560
|
-
const [chainNamespace, chainReference] = chainId.split(':');
|
|
561
|
-
for (const scope of scopes) {
|
|
562
|
-
const [scopeNamespace, scopeReference] = scope.split(':');
|
|
563
|
-
// Check if namespaces match
|
|
564
|
-
if (scopeNamespace !== chainNamespace) {
|
|
565
|
-
continue;
|
|
566
|
-
}
|
|
567
|
-
// Wildcard scope (e.g., "solana:0" means all chains in that namespace)
|
|
568
|
-
if (scopeReference === '0') {
|
|
569
|
-
return true;
|
|
570
|
-
}
|
|
571
|
-
// Exact match check
|
|
572
|
-
if (scopeReference === chainReference) {
|
|
573
|
-
return true;
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
return false;
|
|
577
|
-
}, _SnapDataSource_groupChainsBySnap = function _SnapDataSource_groupChainsBySnap(chainIds) {
|
|
578
|
-
const groups = {};
|
|
579
|
-
for (const chainId of chainIds) {
|
|
580
|
-
const snapType = getSnapTypeForChain(chainId);
|
|
581
|
-
if (snapType) {
|
|
582
|
-
groups[snapType] ?? (groups[snapType] = []);
|
|
583
|
-
const snapChains = groups[snapType];
|
|
584
|
-
if (snapChains) {
|
|
585
|
-
snapChains.push(chainId);
|
|
586
|
-
}
|
|
410
|
+
// Update chainToSnap mapping
|
|
411
|
+
this.state.chainToSnap = chainToSnap;
|
|
412
|
+
// Notify if chains changed
|
|
413
|
+
try {
|
|
414
|
+
this.updateActiveChains(supportedChains, (updatedChains) => {
|
|
415
|
+
__classPrivateFieldGet(this, _SnapDataSource_messenger, "f").call('AssetsController:activeChainsUpdate', SNAP_DATA_SOURCE_NAME, updatedChains);
|
|
416
|
+
});
|
|
587
417
|
}
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
}, _SnapDataSource_fetchFromSnap = async function _SnapDataSource_fetchFromSnap(snapType, request) {
|
|
591
|
-
var _a;
|
|
592
|
-
const config = SNAP_REGISTRY[snapType];
|
|
593
|
-
// Check snap availability on-demand - handles timing issues where snap
|
|
594
|
-
// wasn't ready during initialization but is now available
|
|
595
|
-
const isAvailable = await __classPrivateFieldGet(this, _SnapDataSource_instances, "m", _SnapDataSource_checkSnapAvailabilityOnDemand).call(this, snapType);
|
|
596
|
-
if (!isAvailable) {
|
|
597
|
-
log(`${snapType} snap not available, skipping fetch`);
|
|
598
|
-
// Return errors for these chains so they can fallback to other data sources
|
|
599
|
-
const errors = {};
|
|
600
|
-
for (const chainId of request.chainIds) {
|
|
601
|
-
errors[chainId] = `${snapType} snap not available`;
|
|
418
|
+
catch {
|
|
419
|
+
// AssetsController not ready yet - expected during initialization
|
|
602
420
|
}
|
|
603
|
-
return { errors };
|
|
604
421
|
}
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
};
|
|
609
|
-
// Fetch balances for each account using Keyring API
|
|
610
|
-
// Important: Must first get account assets, then request balances for those specific assets
|
|
611
|
-
for (const account of request.accounts) {
|
|
612
|
-
// Filter to only process accounts that support the chains being fetched
|
|
613
|
-
const accountSupportedChains = request.chainIds.filter((chainId) => __classPrivateFieldGet(this, _SnapDataSource_instances, "m", _SnapDataSource_accountSupportsChain).call(this, account, chainId));
|
|
614
|
-
// Skip accounts that don't support any of the requested chains
|
|
615
|
-
if (accountSupportedChains.length === 0) {
|
|
616
|
-
continue;
|
|
617
|
-
}
|
|
618
|
-
const accountId = account.id;
|
|
422
|
+
catch (error) {
|
|
423
|
+
log('Keyring snap discovery failed', { error });
|
|
424
|
+
this.state.chainToSnap = {};
|
|
619
425
|
try {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
snapId: config.snapId,
|
|
623
|
-
accountId,
|
|
624
|
-
});
|
|
625
|
-
const accountAssets = await __classPrivateFieldGet(this, _SnapDataSource_snapProvider, "f").request({
|
|
626
|
-
method: 'wallet_invokeSnap',
|
|
627
|
-
params: {
|
|
628
|
-
snapId: config.snapId,
|
|
629
|
-
request: {
|
|
630
|
-
method: 'keyring_listAccountAssets',
|
|
631
|
-
params: {
|
|
632
|
-
id: accountId, // Account UUID
|
|
633
|
-
},
|
|
634
|
-
},
|
|
635
|
-
},
|
|
636
|
-
});
|
|
637
|
-
log(`${snapType} snap keyring_listAccountAssets response`, {
|
|
638
|
-
accountId,
|
|
639
|
-
assetCount: accountAssets?.length ?? 0,
|
|
640
|
-
assets: accountAssets,
|
|
641
|
-
});
|
|
642
|
-
// If no assets, skip to next account
|
|
643
|
-
if (!accountAssets || accountAssets.length === 0) {
|
|
644
|
-
log(`${snapType} snap: account has no assets, skipping balance fetch`, {
|
|
645
|
-
accountId,
|
|
646
|
-
});
|
|
647
|
-
continue;
|
|
648
|
-
}
|
|
649
|
-
// Step 2: Get balances for those specific assets
|
|
650
|
-
log(`${snapType} snap calling keyring_getAccountBalances`, {
|
|
651
|
-
snapId: config.snapId,
|
|
652
|
-
accountId,
|
|
653
|
-
requestedAssets: accountAssets.length,
|
|
654
|
-
});
|
|
655
|
-
const balances = await __classPrivateFieldGet(this, _SnapDataSource_snapProvider, "f").request({
|
|
656
|
-
method: 'wallet_invokeSnap',
|
|
657
|
-
params: {
|
|
658
|
-
snapId: config.snapId,
|
|
659
|
-
request: {
|
|
660
|
-
method: 'keyring_getAccountBalances',
|
|
661
|
-
params: {
|
|
662
|
-
id: accountId, // Account UUID (the keyring API uses 'id' not 'accountId')
|
|
663
|
-
assets: accountAssets, // Must pass specific asset types from listAccountAssets
|
|
664
|
-
},
|
|
665
|
-
},
|
|
666
|
-
},
|
|
667
|
-
});
|
|
668
|
-
log(`${snapType} snap keyring_getAccountBalances response`, {
|
|
669
|
-
accountId,
|
|
670
|
-
balances,
|
|
671
|
-
balancesType: typeof balances,
|
|
672
|
-
isNull: balances === null,
|
|
673
|
-
isUndefined: balances === undefined,
|
|
674
|
-
assetCount: balances ? Object.keys(balances).length : 0,
|
|
426
|
+
this.updateActiveChains([], (updatedChains) => {
|
|
427
|
+
__classPrivateFieldGet(this, _SnapDataSource_messenger, "f").call('AssetsController:activeChainsUpdate', SNAP_DATA_SOURCE_NAME, updatedChains);
|
|
675
428
|
});
|
|
676
|
-
// Transform keyring response to DataResponse format
|
|
677
|
-
// Note: snap may return null/undefined if account doesn't belong to this snap
|
|
678
|
-
if (balances && typeof balances === 'object' && results.assetsBalance) {
|
|
679
|
-
const balanceEntries = Object.entries(balances);
|
|
680
|
-
log(`${snapType} snap processing ${balanceEntries.length} balances for account ${accountId}`);
|
|
681
|
-
for (const [assetId, balance] of balanceEntries) {
|
|
682
|
-
// Initialize account balances if not exists
|
|
683
|
-
(_a = results.assetsBalance)[accountId] ?? (_a[accountId] = {});
|
|
684
|
-
// Store raw balance for this asset
|
|
685
|
-
// Use rawAmount if available (preferred - smallest unit), fall back to amount
|
|
686
|
-
// Note: Snaps should return rawAmount in smallest unit (satoshis, lamports, etc.)
|
|
687
|
-
const accountBalances = results.assetsBalance[accountId];
|
|
688
|
-
if (accountBalances) {
|
|
689
|
-
accountBalances[assetId] = {
|
|
690
|
-
amount: balance.rawAmount ?? balance.amount,
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
else if (!balances) {
|
|
696
|
-
log(`${snapType} snap returned empty/null for account (account may not belong to this snap)`, {
|
|
697
|
-
accountId,
|
|
698
|
-
balances,
|
|
699
|
-
});
|
|
700
|
-
}
|
|
701
429
|
}
|
|
702
|
-
catch
|
|
703
|
-
//
|
|
704
|
-
log(`${snapType} snap fetch FAILED for account`, {
|
|
705
|
-
accountId,
|
|
706
|
-
error: error instanceof Error ? error.message : String(error),
|
|
707
|
-
errorStack: error instanceof Error ? error.stack : undefined,
|
|
708
|
-
});
|
|
430
|
+
catch {
|
|
431
|
+
// AssetsController not ready yet - expected during initialization
|
|
709
432
|
}
|
|
710
433
|
}
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
434
|
+
}, _SnapDataSource_getKeyringClient = function _SnapDataSource_getKeyringClient(snapId) {
|
|
435
|
+
const cachedClient = __classPrivateFieldGet(this, _SnapDataSource_keyringClientCache, "f").get(snapId);
|
|
436
|
+
if (cachedClient) {
|
|
437
|
+
return cachedClient;
|
|
438
|
+
}
|
|
439
|
+
const client = new KeyringClient({
|
|
440
|
+
send: async (request) => (await __classPrivateFieldGet(this, _SnapDataSource_messenger, "f").call('SnapController:handleRequest', {
|
|
441
|
+
snapId: snapId,
|
|
442
|
+
origin: 'metamask',
|
|
443
|
+
handler: HandlerType.OnKeyringRequest,
|
|
444
|
+
request,
|
|
445
|
+
})),
|
|
714
446
|
});
|
|
715
|
-
|
|
447
|
+
__classPrivateFieldGet(this, _SnapDataSource_keyringClientCache, "f").set(snapId, client);
|
|
448
|
+
return client;
|
|
716
449
|
};
|
|
717
450
|
// ============================================================================
|
|
718
451
|
// FACTORY
|