@cowprotocol/sdk-bridging 0.3.2-beta.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -122,6 +122,83 @@ function isAppDoc(appData) {
122
122
  return typeof appData === "object" && appData !== null && "version" in appData && "metadata" in appData;
123
123
  }
124
124
 
125
+ // src/const.ts
126
+ var import_sdk_config = require("@cowprotocol/sdk-config");
127
+ var RAW_PROVIDERS_FILES_PATH = `${import_sdk_config.RAW_FILES_PATH}/src/bridging/providers`;
128
+ var DEFAULT_GAS_COST_FOR_HOOK_ESTIMATION = 24e4;
129
+ var DEFAULT_EXTRA_GAS_FOR_HOOK_ESTIMATION = 2e5;
130
+ var COW_SHED_PROXY_CREATION_GAS = 36e4;
131
+ var DEFAULT_EXTRA_GAS_PROXY_CREATION = 4e5;
132
+ var HOOK_DAPP_BRIDGE_PROVIDER_PREFIX = "cow-sdk://bridging/providers";
133
+
134
+ // src/BridgingSdk/findBridgeProviderFromHook.ts
135
+ function findBridgeProviderFromHook(fullAppData, providers) {
136
+ const postHooks = getPostHooks(fullAppData);
137
+ const bridgingHook = postHooks.find((hook) => {
138
+ return hook.dappId?.startsWith(HOOK_DAPP_BRIDGE_PROVIDER_PREFIX);
139
+ });
140
+ if (!bridgingHook) {
141
+ return void 0;
142
+ }
143
+ const bridgeProviderDappId = bridgingHook.dappId;
144
+ return providers.find((provider) => provider.info.dappId === bridgeProviderDappId);
145
+ }
146
+
147
+ // src/BridgingSdk/getCrossChainOrder.ts
148
+ async function getCrossChainOrder(params) {
149
+ const { chainId, orderId, orderBookApi, providers, env } = params;
150
+ const chainContext = { chainId, env };
151
+ const order = await orderBookApi.getOrder(orderId, chainContext);
152
+ const provider = order.fullAppData && findBridgeProviderFromHook(order.fullAppData, providers);
153
+ if (!provider) {
154
+ throw new BridgeOrderParsingError(
155
+ `Unknown Bridge provider in order ${order.uid}. Add provider to the SDK config to be able to decode the order`
156
+ );
157
+ }
158
+ const trades = await orderBookApi.getTrades({ orderUid: order.uid }, chainContext);
159
+ if (trades.length > 0) {
160
+ const firstTrade = trades[0];
161
+ const tradeTxHash = firstTrade?.txHash;
162
+ if (!tradeTxHash) {
163
+ throw new BridgeOrderParsingError(
164
+ `No tx hash found for order ${orderId} . First trade, with log index ${firstTrade?.logIndex}`
165
+ );
166
+ }
167
+ const { params: bridgingParams, status: statusResult } = await provider.getBridgingParams(chainId, orderId, tradeTxHash) || {};
168
+ if (!bridgingParams || !statusResult) {
169
+ throw new BridgeOrderParsingError(`Bridging params cannot be derived from transaction: ${tradeTxHash}`);
170
+ }
171
+ const state = {
172
+ provider,
173
+ chainId,
174
+ order,
175
+ statusResult: {
176
+ status: "unknown" /* UNKNOWN */
177
+ },
178
+ bridgingParams,
179
+ tradeTxHash
180
+ };
181
+ try {
182
+ const explorerUrl = provider.getExplorerUrl(bridgingParams.bridgingId);
183
+ return {
184
+ ...state,
185
+ statusResult,
186
+ explorerUrl
187
+ };
188
+ } catch (e) {
189
+ console.error("Cannot get bridging status", e);
190
+ return state;
191
+ }
192
+ }
193
+ return null;
194
+ }
195
+
196
+ // src/BridgingSdk/BridgingSdk.ts
197
+ var import_sdk_trading2 = require("@cowprotocol/sdk-trading");
198
+ var import_sdk_order_book2 = require("@cowprotocol/sdk-order-book");
199
+ var import_sdk_config2 = require("@cowprotocol/sdk-config");
200
+ var import_sdk_common4 = require("@cowprotocol/sdk-common");
201
+
125
202
  // src/BridgingSdk/getQuoteWithoutBridge.ts
126
203
  var import_sdk_common = require("@cowprotocol/sdk-common");
127
204
  function getQuoteWithoutBridge(params) {
@@ -168,15 +245,6 @@ async function getBridgeSignedHook(bridgeRequest, { provider, signer, hookGasLim
168
245
  };
169
246
  }
170
247
 
171
- // src/const.ts
172
- var import_sdk_config = require("@cowprotocol/sdk-config");
173
- var RAW_PROVIDERS_FILES_PATH = `${import_sdk_config.RAW_FILES_PATH}/src/bridging/providers`;
174
- var DEFAULT_GAS_COST_FOR_HOOK_ESTIMATION = 24e4;
175
- var DEFAULT_EXTRA_GAS_FOR_HOOK_ESTIMATION = 2e5;
176
- var COW_SHED_PROXY_CREATION_GAS = 36e4;
177
- var DEFAULT_EXTRA_GAS_PROXY_CREATION = 4e5;
178
- var HOOK_DAPP_BRIDGE_PROVIDER_PREFIX = "cow-sdk://bridging/providers";
179
-
180
248
  // src/BridgingSdk/getQuoteWithBridge.ts
181
249
  var import_sdk_order_book = require("@cowprotocol/sdk-order-book");
182
250
  var import_sdk_common3 = require("@cowprotocol/sdk-common");
@@ -404,73 +472,274 @@ async function getBridgeResult(context) {
404
472
  return { bridgeResult, bridgeHook, appDataInfo };
405
473
  }
406
474
 
407
- // src/BridgingSdk/findBridgeProviderFromHook.ts
408
- function findBridgeProviderFromHook(fullAppData, providers) {
409
- const postHooks = getPostHooks(fullAppData);
410
- const bridgingHook = postHooks.find((hook) => {
411
- return hook.dappId?.startsWith(HOOK_DAPP_BRIDGE_PROVIDER_PREFIX);
412
- });
413
- if (!bridgingHook) {
414
- return void 0;
475
+ // src/BridgingSdk/strategies/SingleQuoteStrategy.ts
476
+ var SingleQuoteStrategyImpl = class {
477
+ strategyName = "SingleQuoteStrategy";
478
+ async execute(request, config) {
479
+ const { quoteBridgeRequest, advancedSettings } = request;
480
+ const { sellTokenChainId, buyTokenChainId } = quoteBridgeRequest;
481
+ const { tradingSdk, providers } = config;
482
+ if (sellTokenChainId !== buyTokenChainId) {
483
+ const provider = providers[0];
484
+ if (!provider) {
485
+ throw new Error("No provider found for cross-chain swap");
486
+ }
487
+ return getQuoteWithBridge({
488
+ swapAndBridgeRequest: quoteBridgeRequest,
489
+ advancedSettings,
490
+ tradingSdk,
491
+ provider,
492
+ bridgeHookSigner: advancedSettings?.quoteSigner
493
+ });
494
+ } else {
495
+ return getQuoteWithoutBridge({
496
+ quoteBridgeRequest,
497
+ advancedSettings,
498
+ tradingSdk
499
+ });
500
+ }
415
501
  }
416
- const bridgeProviderDappId = bridgingHook.dappId;
417
- return providers.find((provider) => provider.info.dappId === bridgeProviderDappId);
418
- }
502
+ };
419
503
 
420
- // src/BridgingSdk/getCrossChainOrder.ts
421
- async function getCrossChainOrder(params) {
422
- const { chainId, orderId, orderBookApi, providers, env } = params;
423
- const chainContext = { chainId, env };
424
- const order = await orderBookApi.getOrder(orderId, chainContext);
425
- const provider = order.fullAppData && findBridgeProviderFromHook(order.fullAppData, providers);
426
- if (!provider) {
427
- throw new BridgeOrderParsingError(
428
- `Unknown Bridge provider in order ${order.uid}. Add provider to the SDK config to be able to decode the order`
504
+ // src/BridgingSdk/utils.ts
505
+ function validateCrossChainRequest(sellTokenChainId, buyTokenChainId) {
506
+ if (sellTokenChainId === buyTokenChainId) {
507
+ throw new BridgeProviderError(
508
+ "getMultiQuotes() and getBestQuote() are only for cross-chain bridging. For single-chain swaps, use getQuote() instead.",
509
+ { sellTokenChainId, buyTokenChainId }
429
510
  );
430
511
  }
431
- const trades = await orderBookApi.getTrades({ orderUid: order.uid }, chainContext);
432
- if (trades.length > 0) {
433
- const firstTrade = trades[0];
434
- const tradeTxHash = firstTrade?.txHash;
435
- if (!tradeTxHash) {
436
- throw new BridgeOrderParsingError(
437
- `No tx hash found for order ${orderId} . First trade, with log index ${firstTrade?.logIndex}`
512
+ }
513
+ function createBridgeQuoteTimeoutPromise(timeoutMs, prefix) {
514
+ return new Promise((_, reject) => {
515
+ setTimeout(() => {
516
+ reject(new BridgeProviderError(`${prefix} timeout after ${timeoutMs}ms`, {}));
517
+ }, timeoutMs);
518
+ });
519
+ }
520
+ function safeCallProgressiveCallback(onQuoteResult, result) {
521
+ if (!onQuoteResult) {
522
+ return;
523
+ }
524
+ try {
525
+ onQuoteResult(result);
526
+ } catch (callbackError) {
527
+ console.warn("Error in onQuoteResult callback:", callbackError);
528
+ }
529
+ }
530
+ function fillTimeoutResults(results, providersToQuery) {
531
+ for (let i = 0; i < providersToQuery.length; i++) {
532
+ const provider = providersToQuery[i];
533
+ if (!results[i] && provider) {
534
+ results[i] = {
535
+ providerDappId: provider.info.dappId,
536
+ quote: null,
537
+ error: new BridgeProviderError("Provider request timed out", {})
538
+ };
539
+ }
540
+ }
541
+ }
542
+ async function executeProviderQuotes(promises, timeout, config) {
543
+ try {
544
+ await Promise.race([
545
+ Promise.allSettled(promises),
546
+ createBridgeQuoteTimeoutPromise(timeout, `Multi-quote with ${config.providers.length}`)
547
+ ]);
548
+ } catch {
549
+ console.warn("getMultiQuotes timeout occurred, returning partial results");
550
+ }
551
+ }
552
+ function isBetterQuote(quote1, quote2) {
553
+ if (!quote2 || !quote2.quote) {
554
+ return !!quote1.quote;
555
+ }
556
+ if (!quote1.quote) {
557
+ return false;
558
+ }
559
+ const quote1BuyAmount = quote1.quote.bridge.amountsAndCosts.afterSlippage.buyAmount;
560
+ const quote2BuyAmount = quote2.quote.bridge.amountsAndCosts.afterSlippage.buyAmount;
561
+ return quote1BuyAmount > quote2BuyAmount;
562
+ }
563
+ function safeCallBestQuoteCallback(onQuoteResult, result) {
564
+ if (!onQuoteResult) {
565
+ return;
566
+ }
567
+ try {
568
+ onQuoteResult(result);
569
+ } catch (callbackError) {
570
+ console.warn("Error in onQuoteResult callback:", callbackError);
571
+ }
572
+ }
573
+ function resolveProvidersToQuery(providerDappIds, providers) {
574
+ if (!providerDappIds) {
575
+ return providers;
576
+ }
577
+ return providerDappIds.map((dappId) => {
578
+ const provider = providers.find((p) => p.info.dappId === dappId);
579
+ if (!provider) {
580
+ throw new BridgeProviderError(
581
+ `Provider with dappId '${dappId}' not found. Available providers: ${providers.map((p) => p.info.dappId).join(", ")}`,
582
+ { providers }
438
583
  );
439
584
  }
440
- const { params: bridgingParams, status: statusResult } = await provider.getBridgingParams(chainId, orderId, tradeTxHash) || {};
441
- if (!bridgingParams || !statusResult) {
442
- throw new BridgeOrderParsingError(`Bridging params cannot be derived from transaction: ${tradeTxHash}`);
585
+ return provider;
586
+ });
587
+ }
588
+
589
+ // src/BridgingSdk/strategies/MultiQuoteStrategy.ts
590
+ var DEFAULT_TOTAL_TIMEOUT_MS = 4e4;
591
+ var DEFAULT_PROVIDER_TIMEOUT_MS = 2e4;
592
+ var MultiQuoteStrategyImpl = class {
593
+ strategyName = "MultiQuoteStrategy";
594
+ async execute(request, config) {
595
+ const { quoteBridgeRequest, providerDappIds, advancedSettings, options } = request;
596
+ const { sellTokenChainId, buyTokenChainId } = quoteBridgeRequest;
597
+ validateCrossChainRequest(sellTokenChainId, buyTokenChainId);
598
+ const providersToQuery = resolveProvidersToQuery(providerDappIds, config.providers);
599
+ const {
600
+ onQuoteResult,
601
+ totalTimeout = DEFAULT_TOTAL_TIMEOUT_MS,
602
+ providerTimeout = DEFAULT_PROVIDER_TIMEOUT_MS
603
+ } = options || {};
604
+ const results = [];
605
+ const promises = [];
606
+ for (let i = 0; i < providersToQuery.length; i++) {
607
+ const provider = providersToQuery[i];
608
+ if (!provider) {
609
+ continue;
610
+ }
611
+ const context = {
612
+ provider,
613
+ quoteBridgeRequest,
614
+ advancedSettings,
615
+ providerTimeout,
616
+ onQuoteResult,
617
+ results,
618
+ index: i
619
+ };
620
+ const promise = this.createProviderQuotePromise(context, config);
621
+ promises.push(promise);
443
622
  }
444
- const state = {
445
- provider,
446
- chainId,
447
- order,
448
- statusResult: {
449
- status: "unknown" /* UNKNOWN */
450
- },
451
- bridgingParams,
452
- tradeTxHash
453
- };
454
- try {
455
- const explorerUrl = provider.getExplorerUrl(bridgingParams.bridgingId);
456
- return {
457
- ...state,
458
- statusResult,
459
- explorerUrl
623
+ await executeProviderQuotes(promises, totalTimeout, config);
624
+ fillTimeoutResults(results, providersToQuery);
625
+ results.sort((a, b) => {
626
+ if (isBetterQuote(a, b))
627
+ return -1;
628
+ if (isBetterQuote(b, a))
629
+ return 1;
630
+ return 0;
631
+ });
632
+ return results;
633
+ }
634
+ createProviderQuotePromise(context, config) {
635
+ const { provider, quoteBridgeRequest, advancedSettings, providerTimeout, onQuoteResult, results, index } = context;
636
+ return (async () => {
637
+ try {
638
+ const quote = await Promise.race([
639
+ getQuoteWithBridge({
640
+ swapAndBridgeRequest: quoteBridgeRequest,
641
+ advancedSettings,
642
+ tradingSdk: config.tradingSdk,
643
+ provider,
644
+ bridgeHookSigner: advancedSettings?.quoteSigner
645
+ }),
646
+ createBridgeQuoteTimeoutPromise(providerTimeout, `Provider ${provider.info.dappId}`)
647
+ ]);
648
+ const result = {
649
+ providerDappId: provider.info.dappId,
650
+ quote,
651
+ error: void 0
652
+ };
653
+ results[index] = result;
654
+ safeCallProgressiveCallback(onQuoteResult, result);
655
+ } catch (error) {
656
+ const result = {
657
+ providerDappId: provider.info.dappId,
658
+ quote: null,
659
+ error: error instanceof Error ? error : new BridgeProviderError(String(error), {})
660
+ };
661
+ results[index] = result;
662
+ safeCallProgressiveCallback(onQuoteResult, result);
663
+ }
664
+ })();
665
+ }
666
+ };
667
+
668
+ // src/BridgingSdk/strategies/BestQuoteStrategy.ts
669
+ var DEFAULT_TOTAL_TIMEOUT_MS2 = 4e4;
670
+ var DEFAULT_PROVIDER_TIMEOUT_MS2 = 2e4;
671
+ var BestQuoteStrategyImpl = class {
672
+ strategyName = "BestQuoteStrategy";
673
+ async execute(request, config) {
674
+ const { quoteBridgeRequest, providerDappIds, advancedSettings, options } = request;
675
+ const { sellTokenChainId, buyTokenChainId } = quoteBridgeRequest;
676
+ validateCrossChainRequest(sellTokenChainId, buyTokenChainId);
677
+ const providersToQuery = resolveProvidersToQuery(providerDappIds, config.providers);
678
+ const {
679
+ onQuoteResult,
680
+ totalTimeout = DEFAULT_TOTAL_TIMEOUT_MS2,
681
+ providerTimeout = DEFAULT_PROVIDER_TIMEOUT_MS2
682
+ } = options || {};
683
+ const bestResult = { current: null };
684
+ const firstError = { current: null };
685
+ const promises = [];
686
+ for (const provider of providersToQuery) {
687
+ const context = {
688
+ provider,
689
+ quoteBridgeRequest,
690
+ advancedSettings,
691
+ providerTimeout,
692
+ onQuoteResult,
693
+ bestResult,
694
+ firstError
460
695
  };
461
- } catch (e) {
462
- console.error("Cannot get bridging status", e);
463
- return state;
696
+ const promise = this.createBestQuoteProviderPromise(context, config);
697
+ promises.push(promise);
464
698
  }
699
+ await executeProviderQuotes(promises, totalTimeout, config);
700
+ return bestResult.current || firstError.current;
465
701
  }
466
- return null;
467
- }
702
+ createBestQuoteProviderPromise(context, config) {
703
+ const { provider, quoteBridgeRequest, advancedSettings, providerTimeout, onQuoteResult, bestResult, firstError } = context;
704
+ return (async () => {
705
+ try {
706
+ const quote = await Promise.race([
707
+ getQuoteWithBridge({
708
+ swapAndBridgeRequest: quoteBridgeRequest,
709
+ advancedSettings,
710
+ tradingSdk: config.tradingSdk,
711
+ provider,
712
+ bridgeHookSigner: advancedSettings?.quoteSigner
713
+ }),
714
+ createBridgeQuoteTimeoutPromise(providerTimeout, `Provider ${provider.info.dappId}`)
715
+ ]);
716
+ const result = {
717
+ providerDappId: provider.info.dappId,
718
+ quote,
719
+ error: void 0
720
+ };
721
+ if (isBetterQuote(result, bestResult.current)) {
722
+ bestResult.current = result;
723
+ safeCallBestQuoteCallback(onQuoteResult, result);
724
+ }
725
+ } catch (error) {
726
+ const errorResult = {
727
+ providerDappId: provider.info.dappId,
728
+ quote: null,
729
+ error: error instanceof Error ? error : new BridgeProviderError(String(error), {})
730
+ };
731
+ if (!firstError.current) {
732
+ firstError.current = errorResult;
733
+ }
734
+ }
735
+ })();
736
+ }
737
+ };
468
738
 
469
739
  // src/BridgingSdk/BridgingSdk.ts
470
- var import_sdk_trading2 = require("@cowprotocol/sdk-trading");
471
- var import_sdk_order_book2 = require("@cowprotocol/sdk-order-book");
472
- var import_sdk_config2 = require("@cowprotocol/sdk-config");
473
- var import_sdk_common4 = require("@cowprotocol/sdk-common");
740
+ var singleQuoteStrategy = new SingleQuoteStrategyImpl();
741
+ var multiQuoteStrategy = new MultiQuoteStrategyImpl();
742
+ var bestQuoteStrategy = new BestQuoteStrategyImpl();
474
743
  var BridgingSdk = class {
475
744
  constructor(options, adapter) {
476
745
  this.options = options;
@@ -478,8 +747,8 @@ var BridgingSdk = class {
478
747
  (0, import_sdk_common4.setGlobalAdapter)(adapter);
479
748
  }
480
749
  const { providers, ...restOptions } = options;
481
- if (!providers || providers.length !== 1) {
482
- throw new Error("Current implementation only supports a single bridge provider");
750
+ if (!providers || providers.length === 0) {
751
+ throw new Error("At least one bridge provider is required");
483
752
  }
484
753
  if (options.enableLogging !== void 0) {
485
754
  (0, import_sdk_common4.enableLogging)(options.enableLogging);
@@ -543,23 +812,47 @@ var BridgingSdk = class {
543
812
  * @throws Error if no path is found
544
813
  */
545
814
  async getQuote(quoteBridgeRequest, advancedSettings) {
546
- const { sellTokenChainId, buyTokenChainId } = quoteBridgeRequest;
547
- const tradingSdk = this.config.tradingSdk;
548
- if (sellTokenChainId !== buyTokenChainId) {
549
- return getQuoteWithBridge({
550
- swapAndBridgeRequest: quoteBridgeRequest,
551
- advancedSettings,
552
- tradingSdk,
553
- provider: this.provider,
554
- bridgeHookSigner: advancedSettings?.quoteSigner
555
- });
556
- } else {
557
- return getQuoteWithoutBridge({
558
- quoteBridgeRequest,
559
- advancedSettings,
560
- tradingSdk
561
- });
562
- }
815
+ const request = {
816
+ quoteBridgeRequest,
817
+ advancedSettings
818
+ };
819
+ return singleQuoteStrategy.execute(request, this.config);
820
+ }
821
+ /**
822
+ * Get quotes from multiple bridge providers in parallel with progressive results.
823
+ *
824
+ * This method is specifically for cross-chain bridging quotes. For single-chain swaps, use getQuote() instead.
825
+ *
826
+ * Features:
827
+ * - Progressive results: Use the `onQuoteResult` callback to receive quotes as soon as each provider responds
828
+ * - Timeout support: Configure maximum wait time for all providers and individual provider timeouts
829
+ * - Parallel execution: All providers are queried simultaneously for best performance
830
+ *
831
+ * @param request - The multi-quote request containing quote parameters, provider dappIds, and options
832
+ * @returns Array of results, one for each provider (successful quotes or errors)
833
+ * @throws Error if the request is for a single-chain swap (sellTokenChainId === buyTokenChainId)
834
+ * ```
835
+ */
836
+ async getMultiQuotes(request) {
837
+ return multiQuoteStrategy.execute(request, this.config);
838
+ }
839
+ /**
840
+ * Get the best quote from multiple bridge providers with progressive updates.
841
+ *
842
+ * This method is specifically for cross-chain bridging quotes. For single-chain swaps, use getQuote() instead.
843
+ *
844
+ * Features:
845
+ * - Returns only the best quote based on buyAmount after slippage
846
+ * - Progressive updates: Use the `onQuoteResult` callback to receive updates whenever a better quote is found
847
+ * - Timeout support: Configure maximum wait time for all providers and individual provider timeouts
848
+ * - Parallel execution: All providers are queried simultaneously for best performance
849
+ *
850
+ * @param request - The best quote request containing quote parameters, provider dappIds, and options
851
+ * @returns The best quote result found, or null if no successful quotes were obtained
852
+ * @throws Error if the request is for a single-chain swap (sellTokenChainId === buyTokenChainId)
853
+ */
854
+ async getBestQuote(request) {
855
+ return bestQuoteStrategy.execute(request, this.config);
563
856
  }
564
857
  async getOrder(params) {
565
858
  const { orderBookApi } = this.config;
@@ -578,6 +871,9 @@ var BridgingSdk = class {
578
871
  getProviderFromAppData(fullAppData) {
579
872
  return findBridgeProviderFromHook(fullAppData, this.getProviders());
580
873
  }
874
+ getProviderByDappId(dappId) {
875
+ return this.config.providers.find((provider) => provider.info.dappId === dappId);
876
+ }
581
877
  };
582
878
 
583
879
  // src/providers/across/AcrossApi.ts
@@ -3431,9 +3727,13 @@ var BUNGEE_APPROVE_AND_BRIDGE_V1_ABI = [
3431
3727
 
3432
3728
  // src/providers/bungee/BungeeApi.ts
3433
3729
  var import_sdk_common12 = require("@cowprotocol/sdk-common");
3730
+
3731
+ // src/providers/bungee/consts.ts
3732
+ var BUNGEE_API_PATH = "/api/v1/bungee";
3733
+ var BUNGEE_MANUAL_API_PATH = "/api/v1/bungee-manual";
3434
3734
  var BUNGEE_BASE_URL = "https://public-backend.bungee.exchange";
3435
- var BUNGEE_API_URL = `${BUNGEE_BASE_URL}/api/v1/bungee`;
3436
- var BUNGEE_MANUAL_API_URL = `${BUNGEE_BASE_URL}/api/v1/bungee-manual`;
3735
+ var BUNGEE_API_URL = `${BUNGEE_BASE_URL}${BUNGEE_API_PATH}`;
3736
+ var BUNGEE_MANUAL_API_URL = `${BUNGEE_BASE_URL}${BUNGEE_MANUAL_API_PATH}`;
3437
3737
  var BUNGEE_EVENTS_API_URL = "https://microservices.socket.tech/loki";
3438
3738
  var ACROSS_API_URL2 = "https://app.across.to/api";
3439
3739
  var SUPPORTED_BRIDGES = ["across", "cctp", "gnosis-native-bridge"];
@@ -3443,16 +3743,98 @@ var errorMessageMap = {
3443
3743
  across: "Across Api Error",
3444
3744
  "bungee-manual": "Bungee Manual Api Error"
3445
3745
  };
3746
+ var DEFAULT_API_OPTIONS = {
3747
+ apiBaseUrl: BUNGEE_API_URL,
3748
+ eventsApiBaseUrl: BUNGEE_EVENTS_API_URL,
3749
+ acrossApiBaseUrl: ACROSS_API_URL2,
3750
+ manualApiBaseUrl: BUNGEE_MANUAL_API_URL
3751
+ };
3752
+ var BUNGEE_API_FALLBACK_TIMEOUT = 3e5;
3753
+
3754
+ // src/providers/bungee/apiUtils.ts
3755
+ function isValidQuoteResponse(response) {
3756
+ if (typeof response !== "object" || response === null) {
3757
+ return false;
3758
+ }
3759
+ const resp = response;
3760
+ if (!("success" in resp) || !("statusCode" in resp) || !("result" in resp) || typeof resp.success !== "boolean" || typeof resp.statusCode !== "number") {
3761
+ return false;
3762
+ }
3763
+ const result = resp.result;
3764
+ if (typeof result !== "object" || result === null) {
3765
+ return false;
3766
+ }
3767
+ const res = result;
3768
+ if (!("originChainId" in res) || !("destinationChainId" in res) || !("userAddress" in res) || !("receiverAddress" in res) || !("manualRoutes" in res) || !Array.isArray(res.manualRoutes)) {
3769
+ return false;
3770
+ }
3771
+ return res.manualRoutes.every((route) => {
3772
+ if (typeof route !== "object" || route === null) {
3773
+ return false;
3774
+ }
3775
+ const r = route;
3776
+ if (!("routeDetails" in r) || typeof r.routeDetails !== "object" || r.routeDetails === null) {
3777
+ return false;
3778
+ }
3779
+ if (!("routeFee" in r.routeDetails)) {
3780
+ return false;
3781
+ }
3782
+ const routeFee = r.routeDetails.routeFee;
3783
+ if (typeof routeFee !== "object" || routeFee === null) {
3784
+ return false;
3785
+ }
3786
+ if (!("amount" in routeFee)) {
3787
+ return false;
3788
+ }
3789
+ return "quoteId" in r && "quoteExpiry" in r && "output" in r && "gasFee" in r && "slippage" in r && "estimatedTime" in r && "routeDetails" in r;
3790
+ });
3791
+ }
3792
+ function isValidBungeeEventsResponse(response) {
3793
+ if (typeof response !== "object" || response === null) {
3794
+ return false;
3795
+ }
3796
+ const resp = response;
3797
+ if (!("success" in resp) || !("result" in resp) || typeof resp.success !== "boolean" || !Array.isArray(resp.result)) {
3798
+ return false;
3799
+ }
3800
+ return resp.result.every((event) => {
3801
+ if (typeof event !== "object" || event === null) {
3802
+ return false;
3803
+ }
3804
+ const e = event;
3805
+ return "identifier" in e && "bridgeName" in e && "fromChainId" in e && "isCowswapTrade" in e && "orderId" in e && // 'recipient' in e &&
3806
+ "sender" in e && "srcTxStatus" in e && "destTxStatus" in e;
3807
+ });
3808
+ }
3809
+ function isValidAcrossStatusResponse(response) {
3810
+ if (typeof response !== "object" || response === null) {
3811
+ return false;
3812
+ }
3813
+ const resp = response;
3814
+ if (!("status" in resp)) {
3815
+ return false;
3816
+ }
3817
+ return true;
3818
+ }
3819
+ function isInfrastructureError(status) {
3820
+ return status >= 500 || status === 429;
3821
+ }
3822
+ function isClientFetchError(error) {
3823
+ return error instanceof TypeError || error instanceof Error && error.message?.includes("fetch");
3824
+ }
3825
+ function resolveApiEndpointFromOptions(key, options, useFallback, customUrl) {
3826
+ return useFallback ? DEFAULT_API_OPTIONS[key] : customUrl || options[key] || DEFAULT_API_OPTIONS[key];
3827
+ }
3828
+
3829
+ // src/providers/bungee/BungeeApi.ts
3446
3830
  var BungeeApi = class {
3447
- constructor(options = {
3448
- apiBaseUrl: BUNGEE_API_URL,
3449
- eventsApiBaseUrl: BUNGEE_EVENTS_API_URL,
3450
- acrossApiBaseUrl: ACROSS_API_URL2,
3451
- includeBridges: SUPPORTED_BRIDGES
3452
- }) {
3831
+ constructor(options = DEFAULT_API_OPTIONS) {
3453
3832
  this.options = options;
3454
3833
  this.validateBridges(this.getSupportedBridges());
3834
+ this.fallbackTimeoutMs = this.options.fallbackTimeoutMs ?? BUNGEE_API_FALLBACK_TIMEOUT;
3455
3835
  }
3836
+ fallbackStates = /* @__PURE__ */ new Map();
3837
+ fallbackTimeoutMs;
3456
3838
  // TODO: why do we need options.includeBridges then? Practically, you cannot add more bridges dynamically
3457
3839
  validateBridges(includeBridges) {
3458
3840
  if (includeBridges?.some((bridge) => !SUPPORTED_BRIDGES.includes(bridge))) {
@@ -3680,100 +4062,94 @@ var BungeeApi = class {
3680
4062
  getSupportedBridges(bridges) {
3681
4063
  return bridges ?? this.options.includeBridges ?? SUPPORTED_BRIDGES;
3682
4064
  }
4065
+ isBungeeApi(apiType) {
4066
+ return apiType === "bungee" || apiType === "bungee-manual";
4067
+ }
4068
+ shouldAddApiKey(apiType) {
4069
+ return this.isBungeeApi(apiType) && !!this.options.apiKey && !!this.options.customApiBaseUrl;
4070
+ }
4071
+ shouldUseFallback(apiType) {
4072
+ const fallbackState = this.fallbackStates.get(apiType);
4073
+ if (!fallbackState)
4074
+ return false;
4075
+ const now = Date.now();
4076
+ if (now > fallbackState.fallbackExpiresAt) {
4077
+ this.fallbackStates.delete(apiType);
4078
+ return false;
4079
+ }
4080
+ return fallbackState.isUsingFallback;
4081
+ }
4082
+ enableFallback(apiType) {
4083
+ const now = Date.now();
4084
+ this.fallbackStates.set(apiType, {
4085
+ isUsingFallback: true,
4086
+ fallbackExpiresAt: now + this.fallbackTimeoutMs
4087
+ });
4088
+ }
3683
4089
  shouldAddAffiliate(apiType, baseUrl) {
3684
- const isBungeeApi = apiType === "bungee" || apiType === "bungee-manual";
3685
- return !baseUrl.includes(BUNGEE_BASE_URL) && isBungeeApi;
4090
+ if (!this.isBungeeApi(apiType))
4091
+ return false;
4092
+ const defaultHost = new URL(BUNGEE_BASE_URL).host;
4093
+ const baseHost = new URL(baseUrl).host;
4094
+ return this.shouldAddApiKey(apiType) || baseHost !== defaultHost;
3686
4095
  }
3687
4096
  async makeApiCall(apiType, path, params, isValidResponse) {
4097
+ const useFallback = this.shouldUseFallback(apiType);
4098
+ const customApiBaseUrl = this.options.apiKey ? this.options.customApiBaseUrl : void 0;
3688
4099
  const baseUrlMap = {
3689
- bungee: this.options.apiBaseUrl || BUNGEE_API_URL,
3690
- events: this.options.eventsApiBaseUrl || BUNGEE_EVENTS_API_URL,
3691
- across: this.options.acrossApiBaseUrl || ACROSS_API_URL2,
3692
- "bungee-manual": this.options.manualApiBaseUrl || BUNGEE_MANUAL_API_URL
4100
+ bungee: resolveApiEndpointFromOptions(
4101
+ "apiBaseUrl",
4102
+ this.options,
4103
+ useFallback,
4104
+ customApiBaseUrl ? `${customApiBaseUrl}${BUNGEE_API_PATH}` : void 0
4105
+ ),
4106
+ "bungee-manual": resolveApiEndpointFromOptions(
4107
+ "manualApiBaseUrl",
4108
+ this.options,
4109
+ useFallback,
4110
+ customApiBaseUrl ? `${customApiBaseUrl}${BUNGEE_MANUAL_API_PATH}` : void 0
4111
+ ),
4112
+ events: resolveApiEndpointFromOptions("eventsApiBaseUrl", this.options, useFallback),
4113
+ across: resolveApiEndpointFromOptions("acrossApiBaseUrl", this.options, useFallback)
3693
4114
  };
3694
4115
  const baseUrl = baseUrlMap[apiType];
3695
4116
  const url = `${baseUrl}${path}?${new URLSearchParams(params).toString()}`;
3696
4117
  const headers = {};
4118
+ if (this.shouldAddApiKey(apiType) && this.options.apiKey) {
4119
+ headers["x-api-key"] = this.options.apiKey;
4120
+ }
3697
4121
  if (this.shouldAddAffiliate(apiType, baseUrl) && this.options.affiliate) {
3698
4122
  headers["affiliate"] = this.options.affiliate;
3699
4123
  }
3700
4124
  (0, import_sdk_common12.log)(`Fetching ${apiType} API: GET ${url}. Params: ${JSON.stringify(params)}`);
3701
- const response = await fetch(url, { method: "GET", headers });
3702
- if (!response.ok) {
3703
- const errorBody = await response.json();
3704
- throw new BridgeProviderQuoteError("API_ERROR" /* API_ERROR */, { errorBody, type: errorMessageMap[apiType] });
3705
- }
3706
- const json = await response.json();
3707
- if (isValidResponse && !isValidResponse(json)) {
3708
- throw new BridgeProviderQuoteError("INVALID_API_JSON_RESPONSE" /* INVALID_API_JSON_RESPONSE */, { json, apiType, params });
4125
+ try {
4126
+ const response = await fetch(url, { method: "GET", headers });
4127
+ if (!response.ok) {
4128
+ if (isInfrastructureError(response.status) && !useFallback) {
4129
+ this.enableFallback(apiType);
4130
+ (0, import_sdk_common12.log)(
4131
+ `Infrastructure error (${response.status}) detected for ${apiType} API. Enabling fallback for ${this.fallbackTimeoutMs}ms`
4132
+ );
4133
+ return this.makeApiCall(apiType, path, params, isValidResponse);
4134
+ }
4135
+ const errorBody = await response.json();
4136
+ throw new BridgeProviderQuoteError("API_ERROR" /* API_ERROR */, { errorBody, type: errorMessageMap[apiType] });
4137
+ }
4138
+ const json = await response.json();
4139
+ if (isValidResponse && !isValidResponse(json)) {
4140
+ throw new BridgeProviderQuoteError("INVALID_API_JSON_RESPONSE" /* INVALID_API_JSON_RESPONSE */, { json, apiType, params });
4141
+ }
4142
+ return json;
4143
+ } catch (error) {
4144
+ if (!useFallback && isClientFetchError(error)) {
4145
+ this.enableFallback(apiType);
4146
+ (0, import_sdk_common12.log)(`Network error detected for ${apiType} API. Enabling fallback for ${this.fallbackTimeoutMs}ms`);
4147
+ return this.makeApiCall(apiType, path, params, isValidResponse);
4148
+ }
4149
+ throw error;
3709
4150
  }
3710
- return json;
3711
4151
  }
3712
4152
  };
3713
- function isValidQuoteResponse(response) {
3714
- if (typeof response !== "object" || response === null) {
3715
- return false;
3716
- }
3717
- const resp = response;
3718
- if (!("success" in resp) || !("statusCode" in resp) || !("result" in resp) || typeof resp.success !== "boolean" || typeof resp.statusCode !== "number") {
3719
- return false;
3720
- }
3721
- const result = resp.result;
3722
- if (typeof result !== "object" || result === null) {
3723
- return false;
3724
- }
3725
- const res = result;
3726
- if (!("originChainId" in res) || !("destinationChainId" in res) || !("userAddress" in res) || !("receiverAddress" in res) || !("manualRoutes" in res) || !Array.isArray(res.manualRoutes)) {
3727
- return false;
3728
- }
3729
- return res.manualRoutes.every((route) => {
3730
- if (typeof route !== "object" || route === null) {
3731
- return false;
3732
- }
3733
- const r = route;
3734
- if (!("routeDetails" in r) || typeof r.routeDetails !== "object" || r.routeDetails === null) {
3735
- return false;
3736
- }
3737
- if (!("routeFee" in r.routeDetails)) {
3738
- return false;
3739
- }
3740
- const routeFee = r.routeDetails.routeFee;
3741
- if (typeof routeFee !== "object" || routeFee === null) {
3742
- return false;
3743
- }
3744
- if (!("amount" in routeFee)) {
3745
- return false;
3746
- }
3747
- return "quoteId" in r && "quoteExpiry" in r && "output" in r && "gasFee" in r && "slippage" in r && "estimatedTime" in r && "routeDetails" in r;
3748
- });
3749
- }
3750
- function isValidBungeeEventsResponse(response) {
3751
- if (typeof response !== "object" || response === null) {
3752
- return false;
3753
- }
3754
- const resp = response;
3755
- if (!("success" in resp) || !("result" in resp) || typeof resp.success !== "boolean" || !Array.isArray(resp.result)) {
3756
- return false;
3757
- }
3758
- return resp.result.every((event) => {
3759
- if (typeof event !== "object" || event === null) {
3760
- return false;
3761
- }
3762
- const e = event;
3763
- return "identifier" in e && "bridgeName" in e && "fromChainId" in e && "isCowswapTrade" in e && "orderId" in e && // 'recipient' in e &&
3764
- "sender" in e && "srcTxStatus" in e && "destTxStatus" in e;
3765
- });
3766
- }
3767
- function isValidAcrossStatusResponse(response) {
3768
- if (typeof response !== "object" || response === null) {
3769
- return false;
3770
- }
3771
- const resp = response;
3772
- if (!("status" in resp)) {
3773
- return false;
3774
- }
3775
- return true;
3776
- }
3777
4153
 
3778
4154
  // src/providers/bungee/createBungeeDepositCall.ts
3779
4155
  var import_sdk_config7 = require("@cowprotocol/sdk-config");