@metamask-previews/bridge-controller 80.1.0-preview-4f4c98e → 80.1.0-preview-3866c0ff1

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.
@@ -1,15 +1,3 @@
1
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
- 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");
4
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
- };
6
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
- if (kind === "m") throw new TypeError("Private method is not writable");
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
- };
12
- var _BridgeController_instances, _BridgeController_abortController, _BridgeController_batchSellTradesAbortController, _BridgeController_quotesFirstFetched, _BridgeController_location, _BridgeController_clientId, _BridgeController_clientVersion, _BridgeController_getLayer1GasFee, _BridgeController_fetchFn, _BridgeController_trackMetaMetricsFn, _BridgeController_trace, _BridgeController_config, _BridgeController_getUseAssetsControllerForRates, _BridgeController_trackQuoteValidationFailures, _BridgeController_fetchAssetExchangeRates, _BridgeController_hasInsufficientBalance, _BridgeController_appendInsufficientBalAndResetApproval, _BridgeController_shouldResetApproval, _BridgeController_fetchBridgeQuotes, _BridgeController_handleQuoteStreaming, _BridgeController_setMinimumBalanceForRentExemptionInLamports, _BridgeController_getMultichainSelectedAccount, _BridgeController_getNetworkClientByChainId, _BridgeController_getJwt, _BridgeController_getRequestMetadata, _BridgeController_getQuoteFetchData, _BridgeController_getEventProperties, _BridgeController_trackInputChangedEvents, _BridgeController_getUSDTMainnetAllowance;
13
1
  /* eslint-disable @typescript-eslint/explicit-function-return-type */
14
2
  import { BigNumber } from '@ethersproject/bignumber';
15
3
  import { Contract } from '@ethersproject/contracts';
@@ -152,6 +140,29 @@ const MESSENGER_EXPOSED_METHODS = [
152
140
  'trackUnifiedSwapBridgeEvent',
153
141
  ];
154
142
  export class BridgeController extends StaticIntervalPollingController() {
143
+ #abortController;
144
+ #batchSellTradesAbortController;
145
+ #quotesFirstFetched;
146
+ /**
147
+ * Stores the location/entry point from which the user initiated the swap or bridge flow.
148
+ * Set via setLocation() before navigating to the swap/bridge flow.
149
+ * Used as default for all subsequent internal events.
150
+ */
151
+ #location = MetaMetricsSwapsEventSource.Unknown;
152
+ #clientId;
153
+ #clientVersion;
154
+ #getLayer1GasFee;
155
+ #fetchFn;
156
+ #trackMetaMetricsFn;
157
+ #trace;
158
+ #config;
159
+ /**
160
+ * Returns whether to use AssetsController for exchange rates.
161
+ * Set via constructor option getUseAssetsControllerForRates; defaults to false.
162
+ *
163
+ * @returns True when exchange rates should be read from AssetsController:getExchangeRatesForBridge.
164
+ */
165
+ #getUseAssetsControllerForRates;
155
166
  constructor({ messenger, state, clientId, clientVersion, getLayer1GasFee, fetchFn, config, trackMetaMetricsFn, traceFn, getUseAssetsControllerForRates, }) {
156
167
  super({
157
168
  name: BRIDGE_CONTROLLER_NAME,
@@ -162,879 +173,857 @@ export class BridgeController extends StaticIntervalPollingController() {
162
173
  ...state,
163
174
  },
164
175
  });
165
- _BridgeController_instances.add(this);
166
- _BridgeController_abortController.set(this, void 0);
167
- _BridgeController_batchSellTradesAbortController.set(this, void 0);
168
- _BridgeController_quotesFirstFetched.set(this, void 0);
169
- /**
170
- * Stores the location/entry point from which the user initiated the swap or bridge flow.
171
- * Set via setLocation() before navigating to the swap/bridge flow.
172
- * Used as default for all subsequent internal events.
173
- */
174
- _BridgeController_location.set(this, MetaMetricsSwapsEventSource.Unknown);
175
- _BridgeController_clientId.set(this, void 0);
176
- _BridgeController_clientVersion.set(this, void 0);
177
- _BridgeController_getLayer1GasFee.set(this, void 0);
178
- _BridgeController_fetchFn.set(this, void 0);
179
- _BridgeController_trackMetaMetricsFn.set(this, void 0);
180
- _BridgeController_trace.set(this, void 0);
181
- _BridgeController_config.set(this, void 0);
182
- /**
183
- * Returns whether to use AssetsController for exchange rates.
184
- * Set via constructor option getUseAssetsControllerForRates; defaults to false.
185
- *
186
- * @returns True when exchange rates should be read from AssetsController:getExchangeRatesForBridge.
187
- */
188
- _BridgeController_getUseAssetsControllerForRates.set(this, void 0);
189
- this._executePoll = async (pollingInput) => {
190
- await __classPrivateFieldGet(this, _BridgeController_fetchBridgeQuotes, "f").call(this, pollingInput);
191
- };
192
- /**
193
- * Updates the quote request at the specified index with the given parameters, then starts
194
- * polling for quotes.
195
- *
196
- * @param paramsToUpdate - The parameters to update in the quote request at the specified index
197
- * @param context - metrics context
198
- * @param quoteRequestIndex - The index of the quote request to update
199
- * @param quoteRequestCount - The number of quote requests in the UI
200
- */
201
- this.updateBridgeQuoteRequestParams = async (paramsToUpdate, context, quoteRequestIndex = 0, quoteRequestCount = 1) => {
202
- // Guard against updating a quote request that doesn't exist
203
- if (quoteRequestIndex >= quoteRequestCount) {
204
- return;
205
- }
206
- __classPrivateFieldGet(this, _BridgeController_trackInputChangedEvents, "f").call(this, paramsToUpdate, context.feature_id, quoteRequestIndex);
207
- this.resetState(AbortReason.QuoteRequestUpdated, quoteRequestIndex);
176
+ this.setIntervalLength(REFRESH_INTERVAL_MS);
177
+ this.#abortController = new AbortController();
178
+ this.#getLayer1GasFee = getLayer1GasFee;
179
+ this.#clientId = clientId;
180
+ this.#clientVersion = clientVersion;
181
+ this.#fetchFn = fetchFn;
182
+ this.#trackMetaMetricsFn = trackMetaMetricsFn;
183
+ this.#config = config ?? {};
184
+ this.#trace = traceFn ?? ((_request, fn) => fn?.());
185
+ this.#getUseAssetsControllerForRates =
186
+ getUseAssetsControllerForRates ?? (() => false);
187
+ this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
188
+ }
189
+ _executePoll = async (pollingInput) => {
190
+ await this.#fetchBridgeQuotes(pollingInput);
191
+ };
192
+ /**
193
+ * Updates the quote request at the specified index with the given parameters, then starts
194
+ * polling for quotes.
195
+ *
196
+ * @param paramsToUpdate - The parameters to update in the quote request at the specified index
197
+ * @param context - metrics context
198
+ * @param quoteRequestIndex - The index of the quote request to update
199
+ * @param quoteRequestCount - The number of quote requests in the UI
200
+ */
201
+ updateBridgeQuoteRequestParams = async (paramsToUpdate, context, quoteRequestIndex = 0, quoteRequestCount = 1) => {
202
+ // Guard against updating a quote request that doesn't exist
203
+ if (quoteRequestIndex >= quoteRequestCount) {
204
+ return;
205
+ }
206
+ this.#trackInputChangedEvents(paramsToUpdate, context.feature_id, quoteRequestIndex);
207
+ this.resetState(AbortReason.QuoteRequestUpdated, quoteRequestIndex);
208
+ this.update((state) => {
209
+ // Update only the specified quote request and keep the rest of the quote requests unchanged
210
+ state.quoteRequest = state.quoteRequest
211
+ .slice(0, quoteRequestIndex)
212
+ .concat({
213
+ ...DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest[0],
214
+ ...paramsToUpdate,
215
+ })
216
+ .concat(state.quoteRequest.slice(quoteRequestIndex + 1, quoteRequestCount));
217
+ state.tokenSecurityTypeDestination =
218
+ context.token_security_type_destination ?? null;
219
+ });
220
+ // BatchSell and Unified swaps both use the same polling logic so both validations should pass
221
+ if (isValidQuoteRequest(paramsToUpdate) &&
222
+ isValidBatchSellQuoteRequest(this.state.quoteRequest)) {
223
+ this.#quotesFirstFetched = Date.now();
224
+ // Update the insufficientBal and resetApproval params for the quote request
225
+ const quoteWithInsufficientBalAndResetApproval = await this.#appendInsufficientBalAndResetApproval(paramsToUpdate);
208
226
  this.update((state) => {
209
- // Update only the specified quote request and keep the rest of the quote requests unchanged
210
- state.quoteRequest = state.quoteRequest
211
- .slice(0, quoteRequestIndex)
212
- .concat({
213
- ...DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest[0],
214
- ...paramsToUpdate,
215
- })
216
- .concat(state.quoteRequest.slice(quoteRequestIndex + 1, quoteRequestCount));
217
- state.tokenSecurityTypeDestination =
218
- context.token_security_type_destination ?? null;
227
+ state.quoteRequest[quoteRequestIndex] =
228
+ quoteWithInsufficientBalAndResetApproval;
219
229
  });
220
- // BatchSell and Unified swaps both use the same polling logic so both validations should pass
221
- if (isValidQuoteRequest(paramsToUpdate) &&
222
- isValidBatchSellQuoteRequest(this.state.quoteRequest)) {
223
- __classPrivateFieldSet(this, _BridgeController_quotesFirstFetched, Date.now(), "f");
224
- // Update the insufficientBal and resetApproval params for the quote request
225
- const quoteWithInsufficientBalAndResetApproval = await __classPrivateFieldGet(this, _BridgeController_appendInsufficientBalAndResetApproval, "f").call(this, paramsToUpdate);
226
- this.update((state) => {
227
- state.quoteRequest[quoteRequestIndex] =
228
- quoteWithInsufficientBalAndResetApproval;
229
- });
230
- // Set refresh rate based on the source chain before starting polling
231
- this.setChainIntervalLength();
232
- this.startPolling({
233
- quoteRequests: this.state.quoteRequest,
234
- context,
235
- });
236
- }
237
- };
238
- /**
239
- * Fetches quotes for specified request without updating the controller state
240
- * This method does not start polling for quotes and does not emit UnifiedSwapBridge events
241
- *
242
- * @param quoteRequest - The parameters for quote requests to fetch
243
- * @param featureId - The feature ID that maps to quoteParam overrides from LD
244
- * @param abortSignal - The abort signal to cancel all the requests
245
- * @returns A list of validated quotes
246
- */
247
- this.fetchQuotes = async (quoteRequest, featureId, abortSignal = null) => {
248
- const bridgeFeatureFlags = getBridgeFeatureFlags(this.messenger);
249
- const jwt = await __classPrivateFieldGet(this, _BridgeController_getJwt, "f").call(this);
250
- // If featureId is specified, retrieve the quoteRequestOverrides for that featureId
251
- const quoteRequestOverrides = featureId
252
- ? bridgeFeatureFlags.quoteRequestOverrides?.[featureId]
253
- : undefined;
254
- const resetApproval = await __classPrivateFieldGet(this, _BridgeController_shouldResetApproval, "f").call(this, quoteRequest);
255
- // If quoteRequestOverrides is specified, merge it with the quoteRequest
256
- const { quotes: baseQuotes, validationFailures } = await fetchBridgeQuotes(quoteRequestOverrides
257
- ? { ...quoteRequest, ...quoteRequestOverrides, resetApproval }
258
- : { ...quoteRequest, resetApproval }, abortSignal, __classPrivateFieldGet(this, _BridgeController_clientId, "f"), jwt, __classPrivateFieldGet(this, _BridgeController_fetchFn, "f"), __classPrivateFieldGet(this, _BridgeController_config, "f").customBridgeApiBaseUrl ?? BRIDGE_PROD_API_BASE_URL, featureId, __classPrivateFieldGet(this, _BridgeController_clientVersion, "f"));
259
- __classPrivateFieldGet(this, _BridgeController_trackQuoteValidationFailures, "f").call(this, validationFailures, featureId);
260
- const srcChainIds = Array.from(new Set(baseQuotes.map((quote) => quote.quote.srcChainId))).filter(Boolean);
261
- const quotesWithFees = srcChainIds.length > 1 || srcChainIds.length === 0
262
- ? // Don't append fees if there are multiple srcChainIds
263
- baseQuotes
264
- : await appendFeesToQuotes(formatChainIdToCaip(srcChainIds[0]), baseQuotes, this.messenger, __classPrivateFieldGet(this, _BridgeController_getLayer1GasFee, "f"), __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getMultichainSelectedAccount).call(this, quoteRequest.walletAddress));
265
- return sortQuotes(quotesWithFees, featureId);
266
- };
267
- /**
268
- * Fetches gasless transaction data and fees for BatchSell quotes.
269
- * To use this in the clients, add a listener for the recommendedQuotes and call
270
- * this handler whenever they change.
271
- *
272
- * @param quotes - The quotes to fetch the gasless transaction data and fees for
273
- * @param stxEnabled - Flag to estimate gas cost more precisely for the batch sell feature.
274
- */
275
- this.updateBatchSellTrades = async (quotes, stxEnabled) => {
276
- __classPrivateFieldGet(this, _BridgeController_batchSellTradesAbortController, "f")?.abort(AbortReason.GaslessTxBatchFetched);
277
- __classPrivateFieldSet(this, _BridgeController_batchSellTradesAbortController, new AbortController(), "f");
230
+ // Set refresh rate based on the source chain before starting polling
231
+ this.setChainIntervalLength();
232
+ this.startPolling({
233
+ quoteRequests: this.state.quoteRequest,
234
+ context,
235
+ });
236
+ }
237
+ };
238
+ /**
239
+ * Fetches quotes for specified request without updating the controller state
240
+ * This method does not start polling for quotes and does not emit UnifiedSwapBridge events
241
+ *
242
+ * @param quoteRequest - The parameters for quote requests to fetch
243
+ * @param featureId - The feature ID that maps to quoteParam overrides from LD
244
+ * @param abortSignal - The abort signal to cancel all the requests
245
+ * @returns A list of validated quotes
246
+ */
247
+ fetchQuotes = async (quoteRequest, featureId, abortSignal = null) => {
248
+ const bridgeFeatureFlags = getBridgeFeatureFlags(this.messenger);
249
+ const jwt = await this.#getJwt();
250
+ // If featureId is specified, retrieve the quoteRequestOverrides for that featureId
251
+ const quoteRequestOverrides = featureId
252
+ ? bridgeFeatureFlags.quoteRequestOverrides?.[featureId]
253
+ : undefined;
254
+ const resetApproval = await this.#shouldResetApproval(quoteRequest);
255
+ // If quoteRequestOverrides is specified, merge it with the quoteRequest
256
+ const { quotes: baseQuotes, validationFailures } = await fetchBridgeQuotes(quoteRequestOverrides
257
+ ? { ...quoteRequest, ...quoteRequestOverrides, resetApproval }
258
+ : { ...quoteRequest, resetApproval }, abortSignal, this.#clientId, jwt, this.#fetchFn, this.#config.customBridgeApiBaseUrl ?? BRIDGE_PROD_API_BASE_URL, featureId, this.#clientVersion);
259
+ this.#trackQuoteValidationFailures(validationFailures, featureId);
260
+ const srcChainIds = Array.from(new Set(baseQuotes.map((quote) => quote.quote.srcChainId))).filter(Boolean);
261
+ const quotesWithFees = srcChainIds.length > 1 || srcChainIds.length === 0
262
+ ? // Don't append fees if there are multiple srcChainIds
263
+ baseQuotes
264
+ : await appendFeesToQuotes(formatChainIdToCaip(srcChainIds[0]), baseQuotes, this.messenger, this.#getLayer1GasFee, this.#getMultichainSelectedAccount(quoteRequest.walletAddress));
265
+ return sortQuotes(quotesWithFees, featureId);
266
+ };
267
+ /**
268
+ * Fetches gasless transaction data and fees for BatchSell quotes.
269
+ * To use this in the clients, add a listener for the recommendedQuotes and call
270
+ * this handler whenever they change.
271
+ *
272
+ * @param quotes - The quotes to fetch the gasless transaction data and fees for
273
+ * @param stxEnabled - Flag to estimate gas cost more precisely for the batch sell feature.
274
+ */
275
+ updateBatchSellTrades = async (quotes, stxEnabled) => {
276
+ this.#batchSellTradesAbortController?.abort(AbortReason.GaslessTxBatchFetched);
277
+ this.#batchSellTradesAbortController = new AbortController();
278
+ this.update((state) => {
279
+ // Set loading status again if recommended quotes are re-ordered
280
+ state.batchSellTradesLoadingStatus = RequestStatus.LOADING;
281
+ });
282
+ try {
283
+ const batchSellTradesResponse = await fetchBatchSellTrades(quotes, stxEnabled, this.#batchSellTradesAbortController.signal, this.#clientId, await this.#getJwt(), this.#fetchFn, this.#config.customBridgeApiBaseUrl ?? BRIDGE_PROD_API_BASE_URL, this.#clientVersion);
278
284
  this.update((state) => {
279
- // Set loading status again if recommended quotes are re-ordered
280
- state.batchSellTradesLoadingStatus = RequestStatus.LOADING;
285
+ state.batchSellTrades = batchSellTradesResponse;
286
+ state.batchSellTradesLoadingStatus = RequestStatus.FETCHED;
281
287
  });
282
- try {
283
- const batchSellTradesResponse = await fetchBatchSellTrades(quotes, stxEnabled, __classPrivateFieldGet(this, _BridgeController_batchSellTradesAbortController, "f").signal, __classPrivateFieldGet(this, _BridgeController_clientId, "f"), await __classPrivateFieldGet(this, _BridgeController_getJwt, "f").call(this), __classPrivateFieldGet(this, _BridgeController_fetchFn, "f"), __classPrivateFieldGet(this, _BridgeController_config, "f").customBridgeApiBaseUrl ?? BRIDGE_PROD_API_BASE_URL, __classPrivateFieldGet(this, _BridgeController_clientVersion, "f"));
284
- this.update((state) => {
285
- state.batchSellTrades = batchSellTradesResponse;
286
- state.batchSellTradesLoadingStatus = RequestStatus.FETCHED;
287
- });
288
- // TODO if fee.asset.assetId is not in exchange rates, fetch the exchange rate and update the state
289
- }
290
- catch (error) {
291
- // Ignore abort errors
292
- if (error.toString().includes('AbortError') ||
293
- error.toString().includes('FetchRequestCanceledException') ||
294
- [
295
- AbortReason.ResetState,
296
- AbortReason.NewQuoteRequest,
297
- AbortReason.QuoteRequestUpdated,
298
- AbortReason.TransactionSubmitted,
299
- AbortReason.GaslessTxBatchFetched,
300
- ].includes(error)) {
301
- // Exit the function early to prevent other state updates
302
- return;
303
- }
304
- this.update((state) => {
305
- // Reset the batch sell trades if the fetch fails to avoid showing stale data
306
- state.batchSellTrades = DEFAULT_BRIDGE_CONTROLLER_STATE.batchSellTrades;
307
- // Update loading status
308
- state.batchSellTradesLoadingStatus = RequestStatus.ERROR;
309
- });
310
- console.log(`Failed to fetch batch sell trades`, error);
311
- }
312
- };
313
- _BridgeController_trackQuoteValidationFailures.set(this, (validationFailures, featureId) => {
314
- if (validationFailures.length === 0) {
288
+ // TODO if fee.asset.assetId is not in exchange rates, fetch the exchange rate and update the state
289
+ }
290
+ catch (error) {
291
+ // Ignore abort errors
292
+ if (error.toString().includes('AbortError') ||
293
+ error.toString().includes('FetchRequestCanceledException') ||
294
+ [
295
+ AbortReason.ResetState,
296
+ AbortReason.NewQuoteRequest,
297
+ AbortReason.QuoteRequestUpdated,
298
+ AbortReason.TransactionSubmitted,
299
+ AbortReason.GaslessTxBatchFetched,
300
+ ].includes(error)) {
301
+ // Exit the function early to prevent other state updates
315
302
  return;
316
303
  }
317
- this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.QuotesValidationFailed, {
318
- feature_id: featureId,
319
- failures: validationFailures,
320
- location: __classPrivateFieldGet(this, _BridgeController_location, "f"),
304
+ this.update((state) => {
305
+ // Reset the batch sell trades if the fetch fails to avoid showing stale data
306
+ state.batchSellTrades = DEFAULT_BRIDGE_CONTROLLER_STATE.batchSellTrades;
307
+ // Update loading status
308
+ state.batchSellTradesLoadingStatus = RequestStatus.ERROR;
321
309
  });
310
+ console.log(`Failed to fetch batch sell trades`, error);
311
+ }
312
+ };
313
+ #trackQuoteValidationFailures = (validationFailures, featureId) => {
314
+ if (validationFailures.length === 0) {
315
+ return;
316
+ }
317
+ this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.QuotesValidationFailed, {
318
+ feature_id: featureId,
319
+ failures: validationFailures,
320
+ location: this.#location,
322
321
  });
323
- /**
324
- * Fetches the exchange rates for the assets in the quote request if they are not already in the state
325
- * In addition to the selected tokens, this also fetches the native asset for the source and destination chains
326
- *
327
- * @param quoteRequests - The quote requests to fetch the exchange rates for
328
- */
329
- _BridgeController_fetchAssetExchangeRates.set(this, async (quoteRequests) => {
330
- // Get unique assetIds for all quote requests
331
- const assetIds = new Set(quoteRequests.flatMap((quoteRequest) => [
332
- getAssetIdsForToken(quoteRequest.srcTokenAddress, quoteRequest.srcChainId),
333
- getAssetIdsForToken(quoteRequest.destTokenAddress, quoteRequest.destChainId),
334
- ].flat()));
335
- const currency = __classPrivateFieldGet(this, _BridgeController_getUseAssetsControllerForRates, "f").call(this)
336
- ? this.messenger.call('AssetsController:getExchangeRatesForBridge')
337
- .currentCurrency
338
- : this.messenger.call('CurrencyRateController:getState').currentCurrency;
339
- if (assetIds.size === 0) {
340
- return;
341
- }
342
- const pricesByAssetId = await fetchAssetPrices({
343
- assetIds,
344
- currencies: new Set([currency, 'usd']),
345
- clientId: __classPrivateFieldGet(this, _BridgeController_clientId, "f"),
346
- clientVersion: __classPrivateFieldGet(this, _BridgeController_clientVersion, "f"),
347
- fetchFn: __classPrivateFieldGet(this, _BridgeController_fetchFn, "f"),
348
- signal: __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.signal,
349
- });
350
- const exchangeRates = toExchangeRates(currency, pricesByAssetId);
322
+ };
323
+ /**
324
+ * Fetches the exchange rates for the assets in the quote request if they are not already in the state
325
+ * In addition to the selected tokens, this also fetches the native asset for the source and destination chains
326
+ *
327
+ * @param quoteRequests - The quote requests to fetch the exchange rates for
328
+ */
329
+ #fetchAssetExchangeRates = async (quoteRequests) => {
330
+ // Get unique assetIds for all quote requests
331
+ const assetIds = new Set(quoteRequests.flatMap((quoteRequest) => [
332
+ getAssetIdsForToken(quoteRequest.srcTokenAddress, quoteRequest.srcChainId),
333
+ getAssetIdsForToken(quoteRequest.destTokenAddress, quoteRequest.destChainId),
334
+ ].flat()));
335
+ const currency = this.#getUseAssetsControllerForRates()
336
+ ? this.messenger.call('AssetsController:getExchangeRatesForBridge')
337
+ .currentCurrency
338
+ : this.messenger.call('CurrencyRateController:getState').currentCurrency;
339
+ if (assetIds.size === 0) {
340
+ return;
341
+ }
342
+ const pricesByAssetId = await fetchAssetPrices({
343
+ assetIds,
344
+ currencies: new Set([currency, 'usd']),
345
+ clientId: this.#clientId,
346
+ clientVersion: this.#clientVersion,
347
+ fetchFn: this.#fetchFn,
348
+ signal: this.#abortController?.signal,
349
+ });
350
+ const exchangeRates = toExchangeRates(currency, pricesByAssetId);
351
+ this.update((state) => {
352
+ state.assetExchangeRates = {
353
+ ...state.assetExchangeRates,
354
+ ...exchangeRates,
355
+ };
356
+ });
357
+ };
358
+ #hasInsufficientBalance = async (quoteRequest) => {
359
+ try {
360
+ const srcChainIdInHex = formatChainIdToHex(quoteRequest.srcChainId);
361
+ const provider = this.#getNetworkClientByChainId(srcChainIdInHex)?.provider;
362
+ const normalizedSrcTokenAddress = formatAddressToCaipReference(quoteRequest.srcTokenAddress);
363
+ return !(provider &&
364
+ normalizedSrcTokenAddress &&
365
+ quoteRequest.srcTokenAmount &&
366
+ srcChainIdInHex &&
367
+ (await hasSufficientBalance(provider, quoteRequest.walletAddress, normalizedSrcTokenAddress, quoteRequest.srcTokenAmount, srcChainIdInHex)));
368
+ }
369
+ catch (error) {
370
+ console.warn('Failed to set insufficientBal', error);
371
+ // Fall back to true so the backend returns quotes
372
+ return true;
373
+ }
374
+ };
375
+ #appendInsufficientBalAndResetApproval = async (quoteRequest) => {
376
+ const isSrcChainNonEVM = isNonEvmChainId(quoteRequest.srcChainId);
377
+ const providerConfig = isSrcChainNonEVM
378
+ ? undefined
379
+ : this.#getNetworkClientByChainId(formatChainIdToHex(quoteRequest.srcChainId))?.configuration;
380
+ let insufficientBal;
381
+ let resetApproval = Boolean(quoteRequest.resetApproval);
382
+ if (isSrcChainNonEVM) {
383
+ // If the source chain is not an EVM network, use value from params
384
+ insufficientBal = quoteRequest.insufficientBal;
385
+ }
386
+ else if (providerConfig?.rpcUrl?.includes('tenderly')) {
387
+ // If the rpcUrl is a tenderly fork (e2e tests), set insufficientBal=true
388
+ // The bridge-api filters out quotes if the balance on mainnet is insufficient so this override allows quotes to always be returned
389
+ insufficientBal = true;
390
+ }
391
+ else {
392
+ // Set loading status if RPC calls are made before the quotes are fetched
351
393
  this.update((state) => {
352
- state.assetExchangeRates = {
353
- ...state.assetExchangeRates,
354
- ...exchangeRates,
355
- };
394
+ state.quotesLoadingStatus = RequestStatus.LOADING;
356
395
  });
357
- });
358
- _BridgeController_hasInsufficientBalance.set(this, async (quoteRequest) => {
359
- try {
360
- const srcChainIdInHex = formatChainIdToHex(quoteRequest.srcChainId);
361
- const provider = __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getNetworkClientByChainId).call(this, srcChainIdInHex)?.provider;
362
- const normalizedSrcTokenAddress = formatAddressToCaipReference(quoteRequest.srcTokenAddress);
363
- return !(provider &&
364
- normalizedSrcTokenAddress &&
365
- quoteRequest.srcTokenAmount &&
366
- srcChainIdInHex &&
367
- (await hasSufficientBalance(provider, quoteRequest.walletAddress, normalizedSrcTokenAddress, quoteRequest.srcTokenAmount, srcChainIdInHex)));
368
- }
369
- catch (error) {
370
- console.warn('Failed to set insufficientBal', error);
371
- // Fall back to true so the backend returns quotes
372
- return true;
396
+ resetApproval = await this.#shouldResetApproval(quoteRequest);
397
+ // Otherwise query the src token balance from the RPC provider
398
+ insufficientBal =
399
+ quoteRequest.insufficientBal ??
400
+ (await this.#hasInsufficientBalance(quoteRequest));
401
+ }
402
+ return {
403
+ ...quoteRequest,
404
+ insufficientBal,
405
+ resetApproval,
406
+ };
407
+ };
408
+ #shouldResetApproval = async (quoteRequest) => {
409
+ if (isNonEvmChainId(quoteRequest.srcChainId)) {
410
+ return false;
411
+ }
412
+ try {
413
+ const normalizedSrcTokenAddress = formatAddressToCaipReference(quoteRequest.srcTokenAddress);
414
+ if (isEthUsdt(quoteRequest.srcChainId, normalizedSrcTokenAddress)) {
415
+ const allowance = BigNumber.from(await this.#getUSDTMainnetAllowance(quoteRequest.walletAddress, normalizedSrcTokenAddress, quoteRequest.destChainId));
416
+ return allowance.lt(quoteRequest.srcTokenAmount) && allowance.gt(0);
373
417
  }
418
+ return false;
419
+ }
420
+ catch (error) {
421
+ console.warn('Failed to set resetApproval', error);
422
+ // Fall back to true so the backend returns quotes
423
+ return true;
424
+ }
425
+ };
426
+ stopPollingForQuotes = (reason, context) => {
427
+ this.stopAllPolling();
428
+ // If polling is stopped before quotes finish loading, track QuotesReceived
429
+ if (this.state.quotesLoadingStatus === RequestStatus.LOADING && context) {
430
+ this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.QuotesReceived, context);
431
+ }
432
+ // Clears quotes list in state
433
+ this.#abortController?.abort(reason);
434
+ this.#batchSellTradesAbortController?.abort(reason);
435
+ };
436
+ /**
437
+ * Sets the location/entry point for the current swap or bridge flow.
438
+ * Call this when the user enters the flow so that all internally-fired
439
+ * events (InputChanged, QuotesRequested, etc.) carry the correct location.
440
+ *
441
+ * @param location - The entry point from which the user initiated the flow
442
+ */
443
+ setLocation = (location) => {
444
+ this.#location = location;
445
+ };
446
+ /**
447
+ * Returns the location/entry point for the current swap or bridge flow.
448
+ *
449
+ * @returns The entry point from which the user initiated the flow
450
+ */
451
+ getLocation = () => {
452
+ return this.#location;
453
+ };
454
+ setInputPrimaryDenomination = (inputPrimaryDenomination) => {
455
+ this.update((state) => {
456
+ state.inputPrimaryDenomination = inputPrimaryDenomination;
374
457
  });
375
- _BridgeController_appendInsufficientBalAndResetApproval.set(this, async (quoteRequest) => {
376
- const isSrcChainNonEVM = isNonEvmChainId(quoteRequest.srcChainId);
377
- const providerConfig = isSrcChainNonEVM
378
- ? undefined
379
- : __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getNetworkClientByChainId).call(this, formatChainIdToHex(quoteRequest.srcChainId))?.configuration;
380
- let insufficientBal;
381
- let resetApproval = Boolean(quoteRequest.resetApproval);
382
- if (isSrcChainNonEVM) {
383
- // If the source chain is not an EVM network, use value from params
384
- insufficientBal = quoteRequest.insufficientBal;
385
- }
386
- else if (providerConfig?.rpcUrl?.includes('tenderly')) {
387
- // If the rpcUrl is a tenderly fork (e2e tests), set insufficientBal=true
388
- // The bridge-api filters out quotes if the balance on mainnet is insufficient so this override allows quotes to always be returned
389
- insufficientBal = true;
458
+ };
459
+ resetState = (reason = AbortReason.ResetState, quoteRequestIndex = null, context) => {
460
+ this.stopPollingForQuotes(reason, context);
461
+ this.update((state) => {
462
+ // Cannot do direct assignment to state, i.e. state = {... }, need to manually assign each field
463
+ if (quoteRequestIndex === null) {
464
+ // Clear all requests if index is null
465
+ state.quoteRequest = DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest;
390
466
  }
391
467
  else {
392
- // Set loading status if RPC calls are made before the quotes are fetched
393
- this.update((state) => {
394
- state.quotesLoadingStatus = RequestStatus.LOADING;
395
- });
396
- resetApproval = await __classPrivateFieldGet(this, _BridgeController_shouldResetApproval, "f").call(this, quoteRequest);
397
- // Otherwise query the src token balance from the RPC provider
398
- insufficientBal =
399
- quoteRequest.insufficientBal ??
400
- (await __classPrivateFieldGet(this, _BridgeController_hasInsufficientBalance, "f").call(this, quoteRequest));
468
+ // Otherwise only clear the specified request
469
+ state.quoteRequest = state.quoteRequest
470
+ .slice(0, quoteRequestIndex)
471
+ .concat(DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest[0])
472
+ .concat(state.quoteRequest.slice(quoteRequestIndex + 1));
401
473
  }
402
- return {
403
- ...quoteRequest,
404
- insufficientBal,
405
- resetApproval,
406
- };
474
+ state.quotesInitialLoadTime =
475
+ DEFAULT_BRIDGE_CONTROLLER_STATE.quotesInitialLoadTime;
476
+ state.quotes = DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
477
+ state.quotesLastFetched =
478
+ DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLastFetched;
479
+ state.quotesLoadingStatus =
480
+ DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLoadingStatus;
481
+ state.quoteFetchError = DEFAULT_BRIDGE_CONTROLLER_STATE.quoteFetchError;
482
+ state.quotesRefreshCount =
483
+ DEFAULT_BRIDGE_CONTROLLER_STATE.quotesRefreshCount;
484
+ state.assetExchangeRates =
485
+ DEFAULT_BRIDGE_CONTROLLER_STATE.assetExchangeRates;
486
+ state.minimumBalanceForRentExemptionInLamports =
487
+ DEFAULT_BRIDGE_CONTROLLER_STATE.minimumBalanceForRentExemptionInLamports;
488
+ state.tokenWarnings = DEFAULT_BRIDGE_CONTROLLER_STATE.tokenWarnings;
489
+ state.tokenSecurityTypeDestination =
490
+ DEFAULT_BRIDGE_CONTROLLER_STATE.tokenSecurityTypeDestination;
491
+ state.quoteStreamComplete =
492
+ DEFAULT_BRIDGE_CONTROLLER_STATE.quoteStreamComplete;
493
+ state.batchSellTrades = DEFAULT_BRIDGE_CONTROLLER_STATE.batchSellTrades;
494
+ state.batchSellTradesLoadingStatus =
495
+ DEFAULT_BRIDGE_CONTROLLER_STATE.batchSellTradesLoadingStatus;
407
496
  });
408
- _BridgeController_shouldResetApproval.set(this, async (quoteRequest) => {
409
- if (isNonEvmChainId(quoteRequest.srcChainId)) {
410
- return false;
497
+ };
498
+ /**
499
+ * Sets the interval length based on the source chain
500
+ */
501
+ setChainIntervalLength = () => {
502
+ const { state } = this;
503
+ // Assume that BatchSell quote requests all have the same source chain
504
+ // Use the first one to determine refresh rate
505
+ const { srcChainId } = state.quoteRequest[0];
506
+ const bridgeFeatureFlags = getBridgeFeatureFlags(this.messenger);
507
+ const refreshRateOverride = srcChainId
508
+ ? bridgeFeatureFlags.chains[formatChainIdToCaip(srcChainId)]?.refreshRate
509
+ : undefined;
510
+ const defaultRefreshRate = bridgeFeatureFlags.refreshRate;
511
+ this.setIntervalLength(refreshRateOverride ?? defaultRefreshRate);
512
+ };
513
+ #fetchBridgeQuotes = async ({ quoteRequests, context, }) => {
514
+ this.#abortController?.abort(AbortReason.NewQuoteRequest);
515
+ this.#batchSellTradesAbortController?.abort(AbortReason.NewQuoteRequest);
516
+ this.#abortController = new AbortController();
517
+ const quoteTraceStartTime = Date.now();
518
+ const quoteTraceRequestId = uuid();
519
+ const quoteAbortSignal = this.#abortController.signal;
520
+ this.#fetchAssetExchangeRates(quoteRequests).catch((error) => console.warn('Failed to fetch asset exchange rates', error));
521
+ this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.QuotesRequested, context);
522
+ const { sse, maxRefreshCount } = getBridgeFeatureFlags(this.messenger);
523
+ const shouldStream = sse?.enabled &&
524
+ hasMinimumRequiredVersion(this.#clientVersion, sse.minimumVersion);
525
+ const isBatchSellRequest = quoteRequests.length > 1;
526
+ this.update((state) => {
527
+ state.quoteFetchError = DEFAULT_BRIDGE_CONTROLLER_STATE.quoteFetchError;
528
+ state.tokenWarnings = DEFAULT_BRIDGE_CONTROLLER_STATE.tokenWarnings;
529
+ state.quoteStreamComplete =
530
+ DEFAULT_BRIDGE_CONTROLLER_STATE.quoteStreamComplete;
531
+ state.quotesLastFetched = Date.now();
532
+ state.quotesLoadingStatus = RequestStatus.LOADING;
533
+ // Prevent clients from displaying stale batch sell fees
534
+ if (quoteRequests.length > 1) {
535
+ state.batchSellTradesLoadingStatus = RequestStatus.LOADING;
536
+ state.batchSellTrades = DEFAULT_BRIDGE_CONTROLLER_STATE.batchSellTrades;
411
537
  }
538
+ });
539
+ const jwt = await this.#getJwt();
540
+ const [firstQuoteRequest] = quoteRequests;
541
+ let traceResult = 'error';
542
+ let traceName = TraceName.BatchSellQuotesFetched;
543
+ if (!isBatchSellRequest) {
544
+ traceName = isCrossChain(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId)
545
+ ? TraceName.BridgeQuotesFetched
546
+ : TraceName.SwapQuotesFetched;
547
+ }
548
+ const tracedProviders = new Set();
549
+ const traceWithoutImpact = async (request) => {
412
550
  try {
413
- const normalizedSrcTokenAddress = formatAddressToCaipReference(quoteRequest.srcTokenAddress);
414
- if (isEthUsdt(quoteRequest.srcChainId, normalizedSrcTokenAddress)) {
415
- const allowance = BigNumber.from(await __classPrivateFieldGet(this, _BridgeController_getUSDTMainnetAllowance, "f").call(this, quoteRequest.walletAddress, normalizedSrcTokenAddress, quoteRequest.destChainId));
416
- return allowance.lt(quoteRequest.srcTokenAmount) && allowance.gt(0);
417
- }
418
- return false;
419
- }
420
- catch (error) {
421
- console.warn('Failed to set resetApproval', error);
422
- // Fall back to true so the backend returns quotes
423
- return true;
551
+ await this.#trace(request, () => undefined);
424
552
  }
425
- });
426
- this.stopPollingForQuotes = (reason, context) => {
427
- this.stopAllPolling();
428
- // If polling is stopped before quotes finish loading, track QuotesReceived
429
- if (this.state.quotesLoadingStatus === RequestStatus.LOADING && context) {
430
- this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.QuotesReceived, context);
553
+ catch {
554
+ // Telemetry failures must not affect quote fetching or state updates.
431
555
  }
432
- // Clears quotes list in state
433
- __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.abort(reason);
434
- __classPrivateFieldGet(this, _BridgeController_batchSellTradesAbortController, "f")?.abort(reason);
435
- };
436
- /**
437
- * Sets the location/entry point for the current swap or bridge flow.
438
- * Call this when the user enters the flow so that all internally-fired
439
- * events (InputChanged, QuotesRequested, etc.) carry the correct location.
440
- *
441
- * @param location - The entry point from which the user initiated the flow
442
- */
443
- this.setLocation = (location) => {
444
- __classPrivateFieldSet(this, _BridgeController_location, location, "f");
445
- };
446
- /**
447
- * Returns the location/entry point for the current swap or bridge flow.
448
- *
449
- * @returns The entry point from which the user initiated the flow
450
- */
451
- this.getLocation = () => {
452
- return __classPrivateFieldGet(this, _BridgeController_location, "f");
453
556
  };
454
- this.setInputPrimaryDenomination = (inputPrimaryDenomination) => {
455
- this.update((state) => {
456
- state.inputPrimaryDenomination = inputPrimaryDenomination;
557
+ const traceProviderFirstResult = (providerData) => {
558
+ const provider = formatProviderLabel(providerData);
559
+ if (isBatchSellRequest || tracedProviders.has(provider)) {
560
+ return;
561
+ }
562
+ tracedProviders.add(provider);
563
+ // Provider telemetry must not delay quote processing.
564
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
565
+ traceWithoutImpact({
566
+ name: TraceName.QuoteProviderFirstResult,
567
+ startTime: quoteTraceStartTime,
568
+ data: {
569
+ provider,
570
+ feature_id: context.feature_id,
571
+ request_id: quoteTraceRequestId,
572
+ swap_type: getSwapType(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId),
573
+ srcChainId: formatChainIdToCaip(firstQuoteRequest.srcChainId),
574
+ destChainId: formatChainIdToCaip(firstQuoteRequest.destChainId),
575
+ result: 'success',
576
+ },
457
577
  });
458
578
  };
459
- this.resetState = (reason = AbortReason.ResetState, quoteRequestIndex = null, context) => {
460
- this.stopPollingForQuotes(reason, context);
461
- this.update((state) => {
462
- // Cannot do direct assignment to state, i.e. state = {... }, need to manually assign each field
463
- if (quoteRequestIndex === null) {
464
- // Clear all requests if index is null
465
- state.quoteRequest = DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest;
579
+ try {
580
+ const selectedAccount = this.#getMultichainSelectedAccount(firstQuoteRequest.walletAddress);
581
+ // This call is not awaited to prevent blocking quote fetching if the snap takes too long to respond
582
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
583
+ this.#setMinimumBalanceForRentExemptionInLamports(firstQuoteRequest.srcChainId, selectedAccount?.metadata?.snap?.id);
584
+ // Use SSE if enabled and return early
585
+ if (shouldStream || isBatchSellRequest) {
586
+ const quoteCount = await this.#handleQuoteStreaming({
587
+ quoteRequests,
588
+ featureId: context.feature_id,
589
+ jwt,
590
+ selectedAccount,
591
+ signal: quoteAbortSignal,
592
+ traceProviderFirstResult,
593
+ });
594
+ if (quoteAbortSignal.aborted) {
595
+ traceResult = 'cancelled';
596
+ return;
466
597
  }
467
- else {
468
- // Otherwise only clear the specified request
469
- state.quoteRequest = state.quoteRequest
470
- .slice(0, quoteRequestIndex)
471
- .concat(DEFAULT_BRIDGE_CONTROLLER_STATE.quoteRequest[0])
472
- .concat(state.quoteRequest.slice(quoteRequestIndex + 1));
598
+ traceResult = quoteCount > 0 ? 'success' : 'no_quotes';
599
+ }
600
+ else {
601
+ // Otherwise use regular fetch
602
+ const quotes = await this.fetchQuotes(firstQuoteRequest, context.feature_id, quoteAbortSignal);
603
+ for (const quote of quotes) {
604
+ traceProviderFirstResult(quote.quote);
473
605
  }
474
- state.quotesInitialLoadTime =
475
- DEFAULT_BRIDGE_CONTROLLER_STATE.quotesInitialLoadTime;
476
- state.quotes = DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
477
- state.quotesLastFetched =
478
- DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLastFetched;
479
- state.quotesLoadingStatus =
480
- DEFAULT_BRIDGE_CONTROLLER_STATE.quotesLoadingStatus;
481
- state.quoteFetchError = DEFAULT_BRIDGE_CONTROLLER_STATE.quoteFetchError;
482
- state.quotesRefreshCount =
483
- DEFAULT_BRIDGE_CONTROLLER_STATE.quotesRefreshCount;
484
- state.assetExchangeRates =
485
- DEFAULT_BRIDGE_CONTROLLER_STATE.assetExchangeRates;
486
- state.minimumBalanceForRentExemptionInLamports =
487
- DEFAULT_BRIDGE_CONTROLLER_STATE.minimumBalanceForRentExemptionInLamports;
488
- state.tokenWarnings = DEFAULT_BRIDGE_CONTROLLER_STATE.tokenWarnings;
489
- state.tokenSecurityTypeDestination =
490
- DEFAULT_BRIDGE_CONTROLLER_STATE.tokenSecurityTypeDestination;
491
- state.quoteStreamComplete =
492
- DEFAULT_BRIDGE_CONTROLLER_STATE.quoteStreamComplete;
493
- state.batchSellTrades = DEFAULT_BRIDGE_CONTROLLER_STATE.batchSellTrades;
494
- state.batchSellTradesLoadingStatus =
495
- DEFAULT_BRIDGE_CONTROLLER_STATE.batchSellTradesLoadingStatus;
496
- });
497
- };
498
- /**
499
- * Sets the interval length based on the source chain
500
- */
501
- this.setChainIntervalLength = () => {
502
- const { state } = this;
503
- // Assume that BatchSell quote requests all have the same source chain
504
- // Use the first one to determine refresh rate
505
- const { srcChainId } = state.quoteRequest[0];
506
- const bridgeFeatureFlags = getBridgeFeatureFlags(this.messenger);
507
- const refreshRateOverride = srcChainId
508
- ? bridgeFeatureFlags.chains[formatChainIdToCaip(srcChainId)]?.refreshRate
509
- : undefined;
510
- const defaultRefreshRate = bridgeFeatureFlags.refreshRate;
511
- this.setIntervalLength(refreshRateOverride ?? defaultRefreshRate);
512
- };
513
- _BridgeController_fetchBridgeQuotes.set(this, async ({ quoteRequests, context, }) => {
514
- __classPrivateFieldGet(this, _BridgeController_abortController, "f")?.abort(AbortReason.NewQuoteRequest);
515
- __classPrivateFieldGet(this, _BridgeController_batchSellTradesAbortController, "f")?.abort(AbortReason.NewQuoteRequest);
516
- __classPrivateFieldSet(this, _BridgeController_abortController, new AbortController(), "f");
517
- const quoteTraceStartTime = Date.now();
518
- const quoteTraceRequestId = uuid();
519
- const quoteAbortSignal = __classPrivateFieldGet(this, _BridgeController_abortController, "f").signal;
520
- __classPrivateFieldGet(this, _BridgeController_fetchAssetExchangeRates, "f").call(this, quoteRequests).catch((error) => console.warn('Failed to fetch asset exchange rates', error));
521
- this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.QuotesRequested, context);
522
- const { sse, maxRefreshCount } = getBridgeFeatureFlags(this.messenger);
523
- const shouldStream = sse?.enabled &&
524
- hasMinimumRequiredVersion(__classPrivateFieldGet(this, _BridgeController_clientVersion, "f"), sse.minimumVersion);
525
- const isBatchSellRequest = quoteRequests.length > 1;
606
+ this.update((state) => {
607
+ // Set the initial load time if this is the first fetch
608
+ if (state.quotesRefreshCount ===
609
+ DEFAULT_BRIDGE_CONTROLLER_STATE.quotesRefreshCount &&
610
+ this.#quotesFirstFetched) {
611
+ state.quotesInitialLoadTime = Date.now() - this.#quotesFirstFetched;
612
+ }
613
+ state.quotes = quotes.map(toQuoteResponseV2);
614
+ state.quotesLoadingStatus = RequestStatus.FETCHED;
615
+ });
616
+ traceResult = quotes.length > 0 ? 'success' : 'no_quotes';
617
+ }
618
+ }
619
+ catch (error) {
620
+ // Reset the quotes list if the fetch fails to avoid showing stale quotes
526
621
  this.update((state) => {
527
- state.quoteFetchError = DEFAULT_BRIDGE_CONTROLLER_STATE.quoteFetchError;
528
- state.tokenWarnings = DEFAULT_BRIDGE_CONTROLLER_STATE.tokenWarnings;
529
- state.quoteStreamComplete =
530
- DEFAULT_BRIDGE_CONTROLLER_STATE.quoteStreamComplete;
531
- state.quotesLastFetched = Date.now();
532
- state.quotesLoadingStatus = RequestStatus.LOADING;
533
- // Prevent clients from displaying stale batch sell fees
534
- if (quoteRequests.length > 1) {
535
- state.batchSellTradesLoadingStatus = RequestStatus.LOADING;
536
- state.batchSellTrades = DEFAULT_BRIDGE_CONTROLLER_STATE.batchSellTrades;
537
- }
622
+ state.quotes = DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
538
623
  });
539
- const jwt = await __classPrivateFieldGet(this, _BridgeController_getJwt, "f").call(this);
540
- const [firstQuoteRequest] = quoteRequests;
541
- let traceResult = 'error';
542
- let traceName = TraceName.BatchSellQuotesFetched;
543
- if (!isBatchSellRequest) {
544
- traceName = isCrossChain(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId)
545
- ? TraceName.BridgeQuotesFetched
546
- : TraceName.SwapQuotesFetched;
624
+ // Ignore abort errors
625
+ if (isExpectedQuoteAbort(error, quoteAbortSignal)) {
626
+ traceResult = 'cancelled';
627
+ // Exit the function early to prevent other state updates
628
+ return;
547
629
  }
548
- const tracedProviders = new Set();
549
- const traceWithoutImpact = async (request) => {
630
+ // Update loading status and error message
631
+ this.update((state) => {
632
+ // The error object reference is not guaranteed to exist on mobile so reading
633
+ // the message directly could cause an error.
634
+ let errorMessage;
550
635
  try {
551
- await __classPrivateFieldGet(this, _BridgeController_trace, "f").call(this, request, () => undefined);
636
+ errorMessage =
637
+ error?.message ?? error.toString();
552
638
  }
553
639
  catch {
554
- // Telemetry failures must not affect quote fetching or state updates.
640
+ // Intentionally empty
555
641
  }
556
- };
557
- const traceProviderFirstResult = (providerData) => {
558
- const provider = formatProviderLabel(providerData);
559
- if (isBatchSellRequest || tracedProviders.has(provider)) {
560
- return;
642
+ finally {
643
+ state.quoteFetchError = errorMessage ?? 'Unknown error';
561
644
  }
562
- tracedProviders.add(provider);
563
- // Provider telemetry must not delay quote processing.
564
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
565
- traceWithoutImpact({
566
- name: TraceName.QuoteProviderFirstResult,
567
- startTime: quoteTraceStartTime,
568
- data: {
569
- provider,
570
- feature_id: context.feature_id,
645
+ state.quotesLoadingStatus = RequestStatus.ERROR;
646
+ });
647
+ // Track event and log error
648
+ this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.QuotesError, context);
649
+ console.log(`Failed to ${shouldStream ? 'stream' : 'fetch'} bridge quotes`, error);
650
+ }
651
+ finally {
652
+ await traceWithoutImpact({
653
+ name: traceName,
654
+ startTime: quoteTraceStartTime,
655
+ data: {
656
+ srcChainId: formatChainIdToCaip(firstQuoteRequest.srcChainId),
657
+ destChainId: formatChainIdToCaip(firstQuoteRequest.destChainId),
658
+ ...(!isBatchSellRequest && {
571
659
  request_id: quoteTraceRequestId,
572
- swap_type: getSwapType(firstQuoteRequest.srcChainId, firstQuoteRequest.destChainId),
573
- srcChainId: formatChainIdToCaip(firstQuoteRequest.srcChainId),
574
- destChainId: formatChainIdToCaip(firstQuoteRequest.destChainId),
575
- result: 'success',
576
- },
577
- });
578
- };
579
- try {
580
- const selectedAccount = __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getMultichainSelectedAccount).call(this, firstQuoteRequest.walletAddress);
581
- // This call is not awaited to prevent blocking quote fetching if the snap takes too long to respond
582
- // eslint-disable-next-line @typescript-eslint/no-floating-promises
583
- __classPrivateFieldGet(this, _BridgeController_setMinimumBalanceForRentExemptionInLamports, "f").call(this, firstQuoteRequest.srcChainId, selectedAccount?.metadata?.snap?.id);
584
- // Use SSE if enabled and return early
585
- if (shouldStream || isBatchSellRequest) {
586
- const quoteCount = await __classPrivateFieldGet(this, _BridgeController_handleQuoteStreaming, "f").call(this, {
587
- quoteRequests,
588
- featureId: context.feature_id,
589
- jwt,
590
- selectedAccount,
591
- signal: quoteAbortSignal,
592
- traceProviderFirstResult,
593
- });
594
- if (quoteAbortSignal.aborted) {
595
- traceResult = 'cancelled';
596
- return;
597
- }
598
- traceResult = quoteCount > 0 ? 'success' : 'no_quotes';
599
- }
600
- else {
601
- // Otherwise use regular fetch
602
- const quotes = await this.fetchQuotes(firstQuoteRequest, context.feature_id, quoteAbortSignal);
603
- for (const quote of quotes) {
660
+ feature_id: context.feature_id,
661
+ result: traceResult,
662
+ }),
663
+ },
664
+ });
665
+ }
666
+ // Update refresh count after fetching, validation and fee calculation have completed
667
+ this.update((state) => {
668
+ state.quotesRefreshCount += 1;
669
+ });
670
+ const hasNoFundedQuoteRequests = quoteRequests.every(({ insufficientBal }) => Boolean(insufficientBal));
671
+ if (hasNoFundedQuoteRequests
672
+ ? // If all quote requests are insufficiently funded, stop polling
673
+ // So if a BatchSell has at least 1 sufficiently funded quote request, polling continues
674
+ true
675
+ : // Otherwise continue polling until the maximum number of refreshes has been reached
676
+ this.state.quotesRefreshCount >= maxRefreshCount) {
677
+ this.stopAllPolling();
678
+ }
679
+ };
680
+ #handleQuoteStreaming = async ({ quoteRequests, featureId, jwt, selectedAccount, signal, traceProviderFirstResult, }) => {
681
+ /**
682
+ * Tracks the number of valid quotes received from the current stream, which is used
683
+ * to determine when to clear the quotes list and set the initial load time
684
+ */
685
+ let validQuotesCounter = 0;
686
+ /**
687
+ * Tracks all pending promises from appendFeesToQuotes calls to ensure they complete
688
+ * before setting quotesLoadingStatus to FETCHED
689
+ */
690
+ const pendingFeeAppendPromises = new Set();
691
+ await fetchBridgeQuoteStream(this.#fetchFn, quoteRequests, signal, featureId, this.#clientId, jwt, this.#config.customBridgeApiBaseUrl ?? BRIDGE_PROD_API_BASE_URL, {
692
+ onQuoteValidationFailure: (validationFailures) => this.#trackQuoteValidationFailures(validationFailures, featureId),
693
+ onValidQuoteReceived: async (quote) => {
694
+ const feeAppendPromise = (async () => {
695
+ const quotesWithFees = await appendFeesToQuotes(quote.chainId, [quote], this.messenger, this.#getLayer1GasFee, selectedAccount);
696
+ if (quotesWithFees.length > 0) {
697
+ validQuotesCounter += 1;
604
698
  traceProviderFirstResult(quote.quote);
605
699
  }
606
700
  this.update((state) => {
607
- // Set the initial load time if this is the first fetch
608
- if (state.quotesRefreshCount ===
609
- DEFAULT_BRIDGE_CONTROLLER_STATE.quotesRefreshCount &&
610
- __classPrivateFieldGet(this, _BridgeController_quotesFirstFetched, "f")) {
611
- state.quotesInitialLoadTime = Date.now() - __classPrivateFieldGet(this, _BridgeController_quotesFirstFetched, "f");
701
+ // Clear previous quotes and quotes load time when first quote in the current
702
+ // polling loop is received
703
+ // This enables clients to continue showing the previous quotes while new
704
+ // quotes are loading
705
+ // Note: If there are no valid quotes until the 2nd fetch, quotesInitialLoadTime will be > refreshRate
706
+ if (validQuotesCounter === 1) {
707
+ state.quotes = DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
708
+ if (!state.quotesInitialLoadTime && this.#quotesFirstFetched) {
709
+ // Set the initial load time after the first quote is received
710
+ state.quotesInitialLoadTime =
711
+ Date.now() - this.#quotesFirstFetched;
712
+ }
612
713
  }
613
- state.quotes = quotes.map(toQuoteResponseV2);
614
- state.quotesLoadingStatus = RequestStatus.FETCHED;
714
+ state.quotes = [...state.quotes, ...quotesWithFees];
615
715
  });
616
- traceResult = quotes.length > 0 ? 'success' : 'no_quotes';
617
- }
618
- }
619
- catch (error) {
620
- // Reset the quotes list if the fetch fails to avoid showing stale quotes
621
- this.update((state) => {
622
- state.quotes = DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
716
+ })();
717
+ pendingFeeAppendPromises.add(feeAppendPromise);
718
+ feeAppendPromise
719
+ .catch((error) => {
720
+ // Catch errors to prevent them from breaking stream processing
721
+ // If appendFeesToQuotes throws, the state update never happens, so no invalid entry is added
722
+ console.error('Error appending fees to quote', error);
723
+ })
724
+ .finally(() => {
725
+ pendingFeeAppendPromises.delete(feeAppendPromise);
623
726
  });
624
- // Ignore abort errors
625
- if (isExpectedQuoteAbort(error, quoteAbortSignal)) {
626
- traceResult = 'cancelled';
627
- // Exit the function early to prevent other state updates
628
- return;
629
- }
630
- // Update loading status and error message
727
+ // Await the promise to ensure errors are caught and handled before continuing
728
+ // The promise is also tracked in pendingFeeAppendPromises for onClose to wait for
729
+ await feeAppendPromise;
730
+ },
731
+ onTokenWarning: (warning) => {
631
732
  this.update((state) => {
632
- // The error object reference is not guaranteed to exist on mobile so reading
633
- // the message directly could cause an error.
634
- let errorMessage;
635
- try {
636
- errorMessage =
637
- error?.message ?? error.toString();
733
+ const isDuplicate = state.tokenWarnings.some((existing) => existing.feature_id === warning.feature_id);
734
+ if (!isDuplicate) {
735
+ state.tokenWarnings = [...state.tokenWarnings, warning];
638
736
  }
639
- catch {
640
- // Intentionally empty
641
- }
642
- finally {
643
- state.quoteFetchError = errorMessage ?? 'Unknown error';
644
- }
645
- state.quotesLoadingStatus = RequestStatus.ERROR;
646
737
  });
647
- // Track event and log error
648
- this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.QuotesError, context);
649
- console.log(`Failed to ${shouldStream ? 'stream' : 'fetch'} bridge quotes`, error);
650
- }
651
- finally {
652
- await traceWithoutImpact({
653
- name: traceName,
654
- startTime: quoteTraceStartTime,
655
- data: {
656
- srcChainId: formatChainIdToCaip(firstQuoteRequest.srcChainId),
657
- destChainId: formatChainIdToCaip(firstQuoteRequest.destChainId),
658
- ...(!isBatchSellRequest && {
659
- request_id: quoteTraceRequestId,
660
- feature_id: context.feature_id,
661
- result: traceResult,
662
- }),
663
- },
738
+ },
739
+ onComplete: (data) => {
740
+ this.update((state) => {
741
+ state.quoteStreamComplete = data;
664
742
  });
665
- }
666
- // Update refresh count after fetching, validation and fee calculation have completed
667
- this.update((state) => {
668
- state.quotesRefreshCount += 1;
669
- });
670
- const hasNoFundedQuoteRequests = quoteRequests.every(({ insufficientBal }) => Boolean(insufficientBal));
671
- if (hasNoFundedQuoteRequests
672
- ? // If all quote requests are insufficiently funded, stop polling
673
- // So if a BatchSell has at least 1 sufficiently funded quote request, polling continues
674
- true
675
- : // Otherwise continue polling until the maximum number of refreshes has been reached
676
- this.state.quotesRefreshCount >= maxRefreshCount) {
677
- this.stopAllPolling();
678
- }
679
- });
680
- _BridgeController_handleQuoteStreaming.set(this, async ({ quoteRequests, featureId, jwt, selectedAccount, signal, traceProviderFirstResult, }) => {
681
- /**
682
- * Tracks the number of valid quotes received from the current stream, which is used
683
- * to determine when to clear the quotes list and set the initial load time
684
- */
685
- let validQuotesCounter = 0;
686
- /**
687
- * Tracks all pending promises from appendFeesToQuotes calls to ensure they complete
688
- * before setting quotesLoadingStatus to FETCHED
689
- */
690
- const pendingFeeAppendPromises = new Set();
691
- await fetchBridgeQuoteStream(__classPrivateFieldGet(this, _BridgeController_fetchFn, "f"), quoteRequests, signal, featureId, __classPrivateFieldGet(this, _BridgeController_clientId, "f"), jwt, __classPrivateFieldGet(this, _BridgeController_config, "f").customBridgeApiBaseUrl ?? BRIDGE_PROD_API_BASE_URL, {
692
- onQuoteValidationFailure: (validationFailures) => __classPrivateFieldGet(this, _BridgeController_trackQuoteValidationFailures, "f").call(this, validationFailures, featureId),
693
- onValidQuoteReceived: async (quote) => {
694
- const feeAppendPromise = (async () => {
695
- const quotesWithFees = await appendFeesToQuotes(quote.chainId, [quote], this.messenger, __classPrivateFieldGet(this, _BridgeController_getLayer1GasFee, "f"), selectedAccount);
696
- if (quotesWithFees.length > 0) {
697
- validQuotesCounter += 1;
698
- traceProviderFirstResult(quote.quote);
699
- }
700
- this.update((state) => {
701
- // Clear previous quotes and quotes load time when first quote in the current
702
- // polling loop is received
703
- // This enables clients to continue showing the previous quotes while new
704
- // quotes are loading
705
- // Note: If there are no valid quotes until the 2nd fetch, quotesInitialLoadTime will be > refreshRate
706
- if (validQuotesCounter === 1) {
707
- state.quotes = DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
708
- if (!state.quotesInitialLoadTime && __classPrivateFieldGet(this, _BridgeController_quotesFirstFetched, "f")) {
709
- // Set the initial load time after the first quote is received
710
- state.quotesInitialLoadTime =
711
- Date.now() - __classPrivateFieldGet(this, _BridgeController_quotesFirstFetched, "f");
712
- }
713
- }
714
- state.quotes = [...state.quotes, ...quotesWithFees];
715
- });
716
- })();
717
- pendingFeeAppendPromises.add(feeAppendPromise);
718
- feeAppendPromise
719
- .catch((error) => {
720
- // Catch errors to prevent them from breaking stream processing
721
- // If appendFeesToQuotes throws, the state update never happens, so no invalid entry is added
722
- console.error('Error appending fees to quote', error);
723
- })
724
- .finally(() => {
725
- pendingFeeAppendPromises.delete(feeAppendPromise);
726
- });
727
- // Await the promise to ensure errors are caught and handled before continuing
728
- // The promise is also tracked in pendingFeeAppendPromises for onClose to wait for
729
- await feeAppendPromise;
730
- },
731
- onTokenWarning: (warning) => {
732
- this.update((state) => {
733
- const isDuplicate = state.tokenWarnings.some((existing) => existing.feature_id === warning.feature_id);
734
- if (!isDuplicate) {
735
- state.tokenWarnings = [...state.tokenWarnings, warning];
736
- }
737
- });
738
- },
739
- onComplete: (data) => {
740
- this.update((state) => {
741
- state.quoteStreamComplete = data;
742
- });
743
- },
744
- onClose: async () => {
745
- // Wait for all pending appendFeesToQuotes operations to complete
746
- // before setting quotesLoadingStatus to FETCHED
747
- await Promise.allSettled(Array.from(pendingFeeAppendPromises));
748
- this.update((state) => {
749
- // If there are no valid quotes in the current stream, clear the quotes list
750
- // to remove quotes from the previous stream
751
- if (validQuotesCounter === 0) {
752
- state.quotes = DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
753
- }
754
- state.quotesLoadingStatus = RequestStatus.FETCHED;
755
- });
756
- },
757
- }, __classPrivateFieldGet(this, _BridgeController_clientVersion, "f"));
758
- return validQuotesCounter;
759
- });
760
- _BridgeController_setMinimumBalanceForRentExemptionInLamports.set(this, async (srcChainId, snapId) => {
761
- if (!isSolanaChainId(srcChainId) || !snapId) {
762
- return;
763
- }
764
- const minimumBalanceForRentExemptionInLamports = await getMinimumBalanceForRentExemptionInLamports(snapId, this.messenger);
765
- this.update((state) => {
766
- state.minimumBalanceForRentExemptionInLamports =
767
- minimumBalanceForRentExemptionInLamports;
768
- });
743
+ },
744
+ onClose: async () => {
745
+ // Wait for all pending appendFeesToQuotes operations to complete
746
+ // before setting quotesLoadingStatus to FETCHED
747
+ await Promise.allSettled(Array.from(pendingFeeAppendPromises));
748
+ this.update((state) => {
749
+ // If there are no valid quotes in the current stream, clear the quotes list
750
+ // to remove quotes from the previous stream
751
+ if (validQuotesCounter === 0) {
752
+ state.quotes = DEFAULT_BRIDGE_CONTROLLER_STATE.quotes;
753
+ }
754
+ state.quotesLoadingStatus = RequestStatus.FETCHED;
755
+ });
756
+ },
757
+ }, this.#clientVersion);
758
+ return validQuotesCounter;
759
+ };
760
+ #setMinimumBalanceForRentExemptionInLamports = async (srcChainId, snapId) => {
761
+ if (!isSolanaChainId(srcChainId) || !snapId) {
762
+ return;
763
+ }
764
+ const minimumBalanceForRentExemptionInLamports = await getMinimumBalanceForRentExemptionInLamports(snapId, this.messenger);
765
+ this.update((state) => {
766
+ state.minimumBalanceForRentExemptionInLamports =
767
+ minimumBalanceForRentExemptionInLamports;
769
768
  });
770
- _BridgeController_getJwt.set(this, async () => {
771
- try {
772
- const token = await this.messenger.call('AuthenticationController:getBearerToken');
773
- return token;
774
- }
775
- catch (error) {
776
- console.error('Error getting JWT token for bridge-api request', error);
777
- return undefined;
769
+ };
770
+ #getMultichainSelectedAccount(walletAddress) {
771
+ // Assume that all quotes in a batch are for the same account
772
+ const addressToUse = walletAddress ?? this.state.quoteRequest[0].walletAddress;
773
+ if (!addressToUse) {
774
+ throw new Error('Account address is required');
775
+ }
776
+ const selectedAccount = this.messenger.call('AccountsController:getAccountByAddress', addressToUse);
777
+ return selectedAccount;
778
+ }
779
+ #getNetworkClientByChainId(chainId) {
780
+ const networkClientId = this.messenger.call('NetworkController:findNetworkClientIdByChainId', chainId);
781
+ if (!networkClientId) {
782
+ throw new Error(`No network client found for chainId: ${chainId}`);
783
+ }
784
+ const networkClient = this.messenger.call('NetworkController:getNetworkClientById', networkClientId);
785
+ return networkClient;
786
+ }
787
+ #getJwt = async () => {
788
+ try {
789
+ const token = await this.messenger.call('AuthenticationController:getBearerToken');
790
+ return token;
791
+ }
792
+ catch (error) {
793
+ console.error('Error getting JWT token for bridge-api request', error);
794
+ return undefined;
795
+ }
796
+ };
797
+ #getRequestMetadata = (quoteRequestIndex = 0) => {
798
+ const quoteRequest = this.state.quoteRequest[quoteRequestIndex];
799
+ const { walletAddress } = quoteRequest;
800
+ const accountHardwareType = getAccountHardwareType(walletAddress
801
+ ? this.#getMultichainSelectedAccount(walletAddress)
802
+ : undefined);
803
+ return {
804
+ slippage_limit: quoteRequest.slippage ?? 0,
805
+ swap_type: getSwapTypeFromQuote(quoteRequest),
806
+ custom_slippage: isCustomSlippage(quoteRequest.slippage),
807
+ account_hardware_type: accountHardwareType,
808
+ is_hardware_wallet: accountHardwareType !== null,
809
+ };
810
+ };
811
+ #getQuoteFetchData = () => {
812
+ return {
813
+ quotes_count: this.state.quotes.length,
814
+ quotes_list: this.state.quotes.map(({ quote }) => formatProviderLabel(quote)),
815
+ initial_load_time_all_quotes: this.state.quotesInitialLoadTime ?? 0,
816
+ has_gas_included_quote: this.state.quotes.some(({ quote }) => quote.gasIncluded),
817
+ };
818
+ };
819
+ #getEventProperties = (eventName, propertiesFromClient, quoteRequestIndex = 0) => {
820
+ const clientProps = propertiesFromClient;
821
+ const baseProperties = {
822
+ ...propertiesFromClient,
823
+ location: clientProps?.location ?? this.#location,
824
+ action_type: MetricsActionType.SWAPBRIDGE_V1,
825
+ };
826
+ const inputPrimaryDenominationProperties = {
827
+ input_primary_denomination: this.state.inputPrimaryDenomination,
828
+ };
829
+ const batchSellClientChainProperties = propertiesFromClient;
830
+ const batchSellBaseProperties = {
831
+ chain_id_source: batchSellClientChainProperties.chain_id_source,
832
+ chain_id_destination: batchSellClientChainProperties.chain_id_destination,
833
+ location: clientProps?.location ?? this.#location,
834
+ };
835
+ const quoteRequest = this.state.quoteRequest[quoteRequestIndex];
836
+ switch (eventName) {
837
+ case UnifiedSwapBridgeEventName.ButtonClicked:
838
+ return {
839
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
840
+ ...baseProperties,
841
+ };
842
+ case UnifiedSwapBridgeEventName.PageViewed:
843
+ return {
844
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
845
+ ...this.#getRequestMetadata(),
846
+ ...inputPrimaryDenominationProperties,
847
+ ...baseProperties,
848
+ };
849
+ case BatchSellMetricsEventName.BatchSellTokenPageViewed:
850
+ return batchSellBaseProperties;
851
+ case BatchSellMetricsEventName.BatchSellTokenPageContinueClicked: {
852
+ const propsFromClient = propertiesFromClient;
853
+ return {
854
+ ...batchSellBaseProperties,
855
+ source_token_count: propsFromClient.source_token_addresses.length,
856
+ source_token_symbols: propsFromClient.source_token_symbols,
857
+ source_token_addresses: propsFromClient.source_token_addresses,
858
+ };
778
859
  }
779
- });
780
- _BridgeController_getRequestMetadata.set(this, (quoteRequestIndex = 0) => {
781
- const quoteRequest = this.state.quoteRequest[quoteRequestIndex];
782
- const { walletAddress } = quoteRequest;
783
- const accountHardwareType = getAccountHardwareType(walletAddress
784
- ? __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getMultichainSelectedAccount).call(this, walletAddress)
785
- : undefined);
786
- return {
787
- slippage_limit: quoteRequest.slippage ?? 0,
788
- swap_type: getSwapTypeFromQuote(quoteRequest),
789
- custom_slippage: isCustomSlippage(quoteRequest.slippage),
790
- account_hardware_type: accountHardwareType,
791
- is_hardware_wallet: accountHardwareType !== null,
792
- };
793
- });
794
- _BridgeController_getQuoteFetchData.set(this, () => {
795
- return {
796
- quotes_count: this.state.quotes.length,
797
- quotes_list: this.state.quotes.map(({ quote }) => formatProviderLabel(quote)),
798
- initial_load_time_all_quotes: this.state.quotesInitialLoadTime ?? 0,
799
- has_gas_included_quote: this.state.quotes.some(({ quote }) => quote.gasIncluded),
800
- };
801
- });
802
- _BridgeController_getEventProperties.set(this, (eventName, propertiesFromClient, quoteRequestIndex = 0) => {
803
- const clientProps = propertiesFromClient;
804
- const baseProperties = {
805
- ...propertiesFromClient,
806
- location: clientProps?.location ?? __classPrivateFieldGet(this, _BridgeController_location, "f"),
807
- action_type: MetricsActionType.SWAPBRIDGE_V1,
808
- };
809
- const inputPrimaryDenominationProperties = {
810
- input_primary_denomination: this.state.inputPrimaryDenomination,
811
- };
812
- const batchSellClientChainProperties = propertiesFromClient;
813
- const batchSellBaseProperties = {
814
- chain_id_source: batchSellClientChainProperties.chain_id_source,
815
- chain_id_destination: batchSellClientChainProperties.chain_id_destination,
816
- location: clientProps?.location ?? __classPrivateFieldGet(this, _BridgeController_location, "f"),
817
- };
818
- const quoteRequest = this.state.quoteRequest[quoteRequestIndex];
819
- switch (eventName) {
820
- case UnifiedSwapBridgeEventName.ButtonClicked:
821
- return {
822
- ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
823
- ...baseProperties,
824
- };
825
- case UnifiedSwapBridgeEventName.PageViewed:
826
- return {
827
- ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
828
- ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
829
- ...inputPrimaryDenominationProperties,
830
- ...baseProperties,
831
- };
832
- case BatchSellMetricsEventName.BatchSellTokenPageViewed:
833
- return batchSellBaseProperties;
834
- case BatchSellMetricsEventName.BatchSellTokenPageContinueClicked: {
835
- const propsFromClient = propertiesFromClient;
836
- return {
837
- ...batchSellBaseProperties,
838
- source_token_count: propsFromClient.source_token_addresses.length,
839
- source_token_symbols: propsFromClient.source_token_symbols,
840
- source_token_addresses: propsFromClient.source_token_addresses,
841
- };
842
- }
843
- case BatchSellMetricsEventName.BatchSellQuotePageViewed:
844
- case BatchSellMetricsEventName.BatchSellQuotePageReviewClicked: {
845
- const propsFromClient = propertiesFromClient;
846
- return {
847
- ...batchSellBaseProperties,
848
- source_token_count: propsFromClient.source_token_addresses.length,
849
- source_token_symbols: propsFromClient.source_token_symbols,
850
- source_token_addresses: propsFromClient.source_token_addresses,
851
- destination_token_symbol: propsFromClient.destination_token_symbol,
852
- destination_token_address: propsFromClient.destination_token_address,
853
- usd_amount_source_tokens: propsFromClient.usd_amount_source_tokens,
854
- usd_amount_source_total: propsFromClient.usd_amount_source_total,
855
- source_token_slippages: propsFromClient.source_token_slippages,
856
- };
857
- }
858
- case BatchSellMetricsEventName.BatchSellReviewModalSubmitted: {
859
- const reviewModalProperties = propertiesFromClient;
860
- return {
861
- ...batchSellBaseProperties,
862
- source_token_count: reviewModalProperties.source_token_addresses.length,
863
- source_token_symbols: reviewModalProperties.source_token_symbols,
864
- source_token_addresses: reviewModalProperties.source_token_addresses,
865
- destination_token_symbol: reviewModalProperties.destination_token_symbol,
866
- destination_token_address: reviewModalProperties.destination_token_address,
867
- usd_amount_source_tokens: reviewModalProperties.usd_amount_source_tokens,
868
- usd_amount_source_total: reviewModalProperties.usd_amount_source_total,
869
- source_token_slippages: reviewModalProperties.source_token_slippages,
870
- usd_quoted_gas: reviewModalProperties.usd_quoted_gas,
871
- usd_quoted_return: reviewModalProperties.usd_quoted_return,
872
- };
873
- }
874
- case UnifiedSwapBridgeEventName.FiatCryptoToggleClicked:
875
- return {
876
- ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
877
- swap_type: getSwapTypeFromQuote(quoteRequest),
878
- ...baseProperties,
879
- };
880
- case UnifiedSwapBridgeEventName.QuotesValidationFailed:
881
- return {
882
- ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
883
- refresh_count: this.state.quotesRefreshCount,
884
- ...baseProperties,
885
- };
886
- case UnifiedSwapBridgeEventName.QuotesReceived:
887
- return {
888
- ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
889
- ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
890
- ...__classPrivateFieldGet(this, _BridgeController_getQuoteFetchData, "f").call(this),
891
- refresh_count: this.state.quotesRefreshCount,
892
- has_sufficient_funds: !quoteRequest.insufficientBal,
893
- ...inputPrimaryDenominationProperties,
894
- ...baseProperties,
895
- };
896
- case UnifiedSwapBridgeEventName.QuotesRequested:
897
- return {
898
- ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
899
- ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
900
- has_sufficient_funds: !quoteRequest.insufficientBal,
901
- ...inputPrimaryDenominationProperties,
902
- ...baseProperties,
903
- };
904
- case UnifiedSwapBridgeEventName.QuotesError:
905
- return {
906
- ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
907
- ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
908
- error_message: this.state.quoteFetchError,
909
- has_sufficient_funds: !quoteRequest.insufficientBal,
910
- ...baseProperties,
911
- };
912
- case UnifiedSwapBridgeEventName.AllQuotesOpened:
913
- case UnifiedSwapBridgeEventName.AllQuotesSorted:
914
- case UnifiedSwapBridgeEventName.QuoteSelected:
915
- return {
916
- ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
917
- ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
918
- ...__classPrivateFieldGet(this, _BridgeController_getQuoteFetchData, "f").call(this),
919
- ...baseProperties,
920
- };
921
- case UnifiedSwapBridgeEventName.Failed: {
922
- // Populate the properties that the error occurred before the tx was submitted
923
- return {
924
- ...baseProperties,
925
- ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
926
- ...__classPrivateFieldGet(this, _BridgeController_getRequestMetadata, "f").call(this),
927
- ...__classPrivateFieldGet(this, _BridgeController_getQuoteFetchData, "f").call(this),
928
- ...propertiesFromClient,
929
- };
930
- }
931
- case UnifiedSwapBridgeEventName.AssetDetailTooltipClicked:
932
- case UnifiedSwapBridgeEventName.AssetPickerOpened:
933
- return baseProperties;
934
- // Inject `token_security_type_destination` from controller state so the
935
- // field is always present on this event. `baseProperties` (which spreads
936
- // `propertiesFromClient`) wins if the client supplies a value explicitly.
937
- case UnifiedSwapBridgeEventName.InputSourceDestinationSwitched:
938
- return {
939
- token_security_type_destination: this.state.tokenSecurityTypeDestination,
940
- ...baseProperties,
941
- };
942
- // These events may be published after the bridge-controller state is reset
943
- // So the BridgeStatusController populates all the properties
944
- case UnifiedSwapBridgeEventName.Submitted:
945
- case UnifiedSwapBridgeEventName.Completed:
946
- return propertiesFromClient;
947
- case UnifiedSwapBridgeEventName.InputChanged:
948
- default:
949
- return baseProperties;
860
+ case BatchSellMetricsEventName.BatchSellQuotePageViewed:
861
+ case BatchSellMetricsEventName.BatchSellQuotePageReviewClicked: {
862
+ const propsFromClient = propertiesFromClient;
863
+ return {
864
+ ...batchSellBaseProperties,
865
+ source_token_count: propsFromClient.source_token_addresses.length,
866
+ source_token_symbols: propsFromClient.source_token_symbols,
867
+ source_token_addresses: propsFromClient.source_token_addresses,
868
+ destination_token_symbol: propsFromClient.destination_token_symbol,
869
+ destination_token_address: propsFromClient.destination_token_address,
870
+ usd_amount_source_tokens: propsFromClient.usd_amount_source_tokens,
871
+ usd_amount_source_total: propsFromClient.usd_amount_source_total,
872
+ source_token_slippages: propsFromClient.source_token_slippages,
873
+ };
950
874
  }
951
- });
952
- _BridgeController_trackInputChangedEvents.set(this, (paramsToUpdate, featureId, quoteRequestIndex = 0) => {
953
- Object.entries(paramsToUpdate).forEach(([key, value]) => {
954
- const inputKey = toInputChangedPropertyKey[key];
955
- const inputValue = toInputChangedPropertyValue[key]?.(paramsToUpdate);
956
- if (inputKey &&
957
- inputValue !== undefined &&
958
- this.state.quoteRequest[quoteRequestIndex] &&
959
- value !==
960
- this.state.quoteRequest[quoteRequestIndex][key]) {
961
- this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.InputChanged, {
962
- input: inputKey,
963
- input_value: inputValue,
964
- location: __classPrivateFieldGet(this, _BridgeController_location, "f"),
965
- feature_id: featureId,
966
- });
967
- }
968
- });
969
- });
970
- /**
971
- * This method tracks cross-chain swaps events
972
- *
973
- * @param eventName - The name of the event to track
974
- * @param propertiesFromClient - Properties that can't be calculated from the event name and need to be provided by the client
975
- * @param quoteRequestIndex - The index of the quote request to track the event for
976
- * @example
977
- * this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.ActionOpened, {
978
- * location: MetaMetricsSwapsEventSource.MainView,
979
- * });
980
- */
981
- this.trackUnifiedSwapBridgeEvent = (eventName, propertiesFromClient, quoteRequestIndex = 0) => {
982
- try {
983
- const combinedPropertiesForEvent = __classPrivateFieldGet(this, _BridgeController_getEventProperties, "f").call(this, eventName, propertiesFromClient, quoteRequestIndex);
984
- __classPrivateFieldGet(this, _BridgeController_trackMetaMetricsFn, "f").call(this, eventName, combinedPropertiesForEvent);
875
+ case BatchSellMetricsEventName.BatchSellReviewModalSubmitted: {
876
+ const reviewModalProperties = propertiesFromClient;
877
+ return {
878
+ ...batchSellBaseProperties,
879
+ source_token_count: reviewModalProperties.source_token_addresses.length,
880
+ source_token_symbols: reviewModalProperties.source_token_symbols,
881
+ source_token_addresses: reviewModalProperties.source_token_addresses,
882
+ destination_token_symbol: reviewModalProperties.destination_token_symbol,
883
+ destination_token_address: reviewModalProperties.destination_token_address,
884
+ usd_amount_source_tokens: reviewModalProperties.usd_amount_source_tokens,
885
+ usd_amount_source_total: reviewModalProperties.usd_amount_source_total,
886
+ source_token_slippages: reviewModalProperties.source_token_slippages,
887
+ usd_quoted_gas: reviewModalProperties.usd_quoted_gas,
888
+ usd_quoted_return: reviewModalProperties.usd_quoted_return,
889
+ };
985
890
  }
986
- catch (error) {
987
- console.error(`Error tracking cross-chain swaps MetaMetrics event ${eventName}`, error);
891
+ case UnifiedSwapBridgeEventName.FiatCryptoToggleClicked:
892
+ return {
893
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
894
+ swap_type: getSwapTypeFromQuote(quoteRequest),
895
+ ...baseProperties,
896
+ };
897
+ case UnifiedSwapBridgeEventName.QuotesValidationFailed:
898
+ return {
899
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
900
+ refresh_count: this.state.quotesRefreshCount,
901
+ ...baseProperties,
902
+ };
903
+ case UnifiedSwapBridgeEventName.QuotesReceived:
904
+ return {
905
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
906
+ ...this.#getRequestMetadata(),
907
+ ...this.#getQuoteFetchData(),
908
+ refresh_count: this.state.quotesRefreshCount,
909
+ has_sufficient_funds: !quoteRequest.insufficientBal,
910
+ ...inputPrimaryDenominationProperties,
911
+ ...baseProperties,
912
+ };
913
+ case UnifiedSwapBridgeEventName.QuotesRequested:
914
+ return {
915
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
916
+ ...this.#getRequestMetadata(),
917
+ has_sufficient_funds: !quoteRequest.insufficientBal,
918
+ ...inputPrimaryDenominationProperties,
919
+ ...baseProperties,
920
+ };
921
+ case UnifiedSwapBridgeEventName.QuotesError:
922
+ return {
923
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
924
+ ...this.#getRequestMetadata(),
925
+ error_message: this.state.quoteFetchError,
926
+ has_sufficient_funds: !quoteRequest.insufficientBal,
927
+ ...baseProperties,
928
+ };
929
+ case UnifiedSwapBridgeEventName.AllQuotesOpened:
930
+ case UnifiedSwapBridgeEventName.AllQuotesSorted:
931
+ case UnifiedSwapBridgeEventName.QuoteSelected:
932
+ return {
933
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
934
+ ...this.#getRequestMetadata(),
935
+ ...this.#getQuoteFetchData(),
936
+ ...baseProperties,
937
+ };
938
+ case UnifiedSwapBridgeEventName.Failed: {
939
+ // Populate the properties that the error occurred before the tx was submitted
940
+ return {
941
+ ...baseProperties,
942
+ ...getRequestParams(quoteRequest, this.state.tokenSecurityTypeDestination),
943
+ ...this.#getRequestMetadata(),
944
+ ...this.#getQuoteFetchData(),
945
+ ...propertiesFromClient,
946
+ };
988
947
  }
989
- };
990
- /**
991
- *
992
- * @param walletAddress - The address of the account to get the allowance for
993
- * @param contractAddress - The address of the ERC20 token contract on mainnet
994
- * @param destinationChainId - The chain ID of the destination network
995
- * @returns The atomic allowance of the ERC20 token contract
996
- */
997
- _BridgeController_getUSDTMainnetAllowance.set(this, async (walletAddress, contractAddress, destinationChainId) => {
998
- const networkClient = __classPrivateFieldGet(this, _BridgeController_instances, "m", _BridgeController_getNetworkClientByChainId).call(this, CHAIN_IDS.MAINNET);
999
- const provider = networkClient?.provider;
1000
- if (!provider) {
1001
- throw new Error('No provider found');
948
+ case UnifiedSwapBridgeEventName.AssetDetailTooltipClicked:
949
+ case UnifiedSwapBridgeEventName.AssetPickerOpened:
950
+ return baseProperties;
951
+ // Inject `token_security_type_destination` from controller state so the
952
+ // field is always present on this event. `baseProperties` (which spreads
953
+ // `propertiesFromClient`) wins if the client supplies a value explicitly.
954
+ case UnifiedSwapBridgeEventName.InputSourceDestinationSwitched:
955
+ return {
956
+ token_security_type_destination: this.state.tokenSecurityTypeDestination,
957
+ ...baseProperties,
958
+ };
959
+ // These events may be published after the bridge-controller state is reset
960
+ // So the BridgeStatusController populates all the properties
961
+ case UnifiedSwapBridgeEventName.Submitted:
962
+ case UnifiedSwapBridgeEventName.Completed:
963
+ return propertiesFromClient;
964
+ case UnifiedSwapBridgeEventName.InputChanged:
965
+ default:
966
+ return baseProperties;
967
+ }
968
+ };
969
+ #trackInputChangedEvents = (paramsToUpdate, featureId, quoteRequestIndex = 0) => {
970
+ Object.entries(paramsToUpdate).forEach(([key, value]) => {
971
+ const inputKey = toInputChangedPropertyKey[key];
972
+ const inputValue = toInputChangedPropertyValue[key]?.(paramsToUpdate);
973
+ if (inputKey &&
974
+ inputValue !== undefined &&
975
+ this.state.quoteRequest[quoteRequestIndex] &&
976
+ value !==
977
+ this.state.quoteRequest[quoteRequestIndex][key]) {
978
+ this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.InputChanged, {
979
+ input: inputKey,
980
+ input_value: inputValue,
981
+ location: this.#location,
982
+ feature_id: featureId,
983
+ });
1002
984
  }
1003
- const ethersProvider = new Web3Provider(provider);
1004
- const contract = new Contract(contractAddress, abiERC20, ethersProvider);
1005
- const spenderAddress = isCrossChain(CHAIN_IDS.MAINNET, destinationChainId)
1006
- ? METABRIDGE_ETHEREUM_ADDRESS
1007
- : SWAPS_CONTRACT_ADDRESSES[CHAIN_IDS.MAINNET];
1008
- const allowance = await contract.allowance(walletAddress, spenderAddress);
1009
- return allowance.toString();
1010
985
  });
1011
- this.setIntervalLength(REFRESH_INTERVAL_MS);
1012
- __classPrivateFieldSet(this, _BridgeController_abortController, new AbortController(), "f");
1013
- __classPrivateFieldSet(this, _BridgeController_getLayer1GasFee, getLayer1GasFee, "f");
1014
- __classPrivateFieldSet(this, _BridgeController_clientId, clientId, "f");
1015
- __classPrivateFieldSet(this, _BridgeController_clientVersion, clientVersion, "f");
1016
- __classPrivateFieldSet(this, _BridgeController_fetchFn, fetchFn, "f");
1017
- __classPrivateFieldSet(this, _BridgeController_trackMetaMetricsFn, trackMetaMetricsFn, "f");
1018
- __classPrivateFieldSet(this, _BridgeController_config, config ?? {}, "f");
1019
- __classPrivateFieldSet(this, _BridgeController_trace, traceFn ?? ((_request, fn) => fn?.()), "f");
1020
- __classPrivateFieldSet(this, _BridgeController_getUseAssetsControllerForRates, getUseAssetsControllerForRates ?? (() => false), "f");
1021
- this.messenger.registerMethodActionHandlers(this, MESSENGER_EXPOSED_METHODS);
1022
- }
986
+ };
987
+ /**
988
+ * This method tracks cross-chain swaps events
989
+ *
990
+ * @param eventName - The name of the event to track
991
+ * @param propertiesFromClient - Properties that can't be calculated from the event name and need to be provided by the client
992
+ * @param quoteRequestIndex - The index of the quote request to track the event for
993
+ * @example
994
+ * this.trackUnifiedSwapBridgeEvent(UnifiedSwapBridgeEventName.ActionOpened, {
995
+ * location: MetaMetricsSwapsEventSource.MainView,
996
+ * });
997
+ */
998
+ trackUnifiedSwapBridgeEvent = (eventName, propertiesFromClient, quoteRequestIndex = 0) => {
999
+ try {
1000
+ const combinedPropertiesForEvent = this.#getEventProperties(eventName, propertiesFromClient, quoteRequestIndex);
1001
+ this.#trackMetaMetricsFn(eventName, combinedPropertiesForEvent);
1002
+ }
1003
+ catch (error) {
1004
+ console.error(`Error tracking cross-chain swaps MetaMetrics event ${eventName}`, error);
1005
+ }
1006
+ };
1007
+ /**
1008
+ *
1009
+ * @param walletAddress - The address of the account to get the allowance for
1010
+ * @param contractAddress - The address of the ERC20 token contract on mainnet
1011
+ * @param destinationChainId - The chain ID of the destination network
1012
+ * @returns The atomic allowance of the ERC20 token contract
1013
+ */
1014
+ #getUSDTMainnetAllowance = async (walletAddress, contractAddress, destinationChainId) => {
1015
+ const networkClient = this.#getNetworkClientByChainId(CHAIN_IDS.MAINNET);
1016
+ const provider = networkClient?.provider;
1017
+ if (!provider) {
1018
+ throw new Error('No provider found');
1019
+ }
1020
+ const ethersProvider = new Web3Provider(provider);
1021
+ const contract = new Contract(contractAddress, abiERC20, ethersProvider);
1022
+ const spenderAddress = isCrossChain(CHAIN_IDS.MAINNET, destinationChainId)
1023
+ ? METABRIDGE_ETHEREUM_ADDRESS
1024
+ : SWAPS_CONTRACT_ADDRESSES[CHAIN_IDS.MAINNET];
1025
+ const allowance = await contract.allowance(walletAddress, spenderAddress);
1026
+ return allowance.toString();
1027
+ };
1023
1028
  }
1024
- _BridgeController_abortController = new WeakMap(), _BridgeController_batchSellTradesAbortController = new WeakMap(), _BridgeController_quotesFirstFetched = new WeakMap(), _BridgeController_location = new WeakMap(), _BridgeController_clientId = new WeakMap(), _BridgeController_clientVersion = new WeakMap(), _BridgeController_getLayer1GasFee = new WeakMap(), _BridgeController_fetchFn = new WeakMap(), _BridgeController_trackMetaMetricsFn = new WeakMap(), _BridgeController_trace = new WeakMap(), _BridgeController_config = new WeakMap(), _BridgeController_getUseAssetsControllerForRates = new WeakMap(), _BridgeController_trackQuoteValidationFailures = new WeakMap(), _BridgeController_fetchAssetExchangeRates = new WeakMap(), _BridgeController_hasInsufficientBalance = new WeakMap(), _BridgeController_appendInsufficientBalAndResetApproval = new WeakMap(), _BridgeController_shouldResetApproval = new WeakMap(), _BridgeController_fetchBridgeQuotes = new WeakMap(), _BridgeController_handleQuoteStreaming = new WeakMap(), _BridgeController_setMinimumBalanceForRentExemptionInLamports = new WeakMap(), _BridgeController_getJwt = new WeakMap(), _BridgeController_getRequestMetadata = new WeakMap(), _BridgeController_getQuoteFetchData = new WeakMap(), _BridgeController_getEventProperties = new WeakMap(), _BridgeController_trackInputChangedEvents = new WeakMap(), _BridgeController_getUSDTMainnetAllowance = new WeakMap(), _BridgeController_instances = new WeakSet(), _BridgeController_getMultichainSelectedAccount = function _BridgeController_getMultichainSelectedAccount(walletAddress) {
1025
- // Assume that all quotes in a batch are for the same account
1026
- const addressToUse = walletAddress ?? this.state.quoteRequest[0].walletAddress;
1027
- if (!addressToUse) {
1028
- throw new Error('Account address is required');
1029
- }
1030
- const selectedAccount = this.messenger.call('AccountsController:getAccountByAddress', addressToUse);
1031
- return selectedAccount;
1032
- }, _BridgeController_getNetworkClientByChainId = function _BridgeController_getNetworkClientByChainId(chainId) {
1033
- const networkClientId = this.messenger.call('NetworkController:findNetworkClientIdByChainId', chainId);
1034
- if (!networkClientId) {
1035
- throw new Error(`No network client found for chainId: ${chainId}`);
1036
- }
1037
- const networkClient = this.messenger.call('NetworkController:getNetworkClientById', networkClientId);
1038
- return networkClient;
1039
- };
1040
1029
  //# sourceMappingURL=bridge-controller.js.map