@rabbitio/ui-kit 1.0.0-beta.41 → 1.0.0-beta.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/.gitlab-ci.yml +29 -0
  2. package/.husky/commit-msg +8 -0
  3. package/.husky/pre-push +1 -0
  4. package/README.md +13 -4
  5. package/coverage/base.css +224 -0
  6. package/coverage/block-navigation.js +87 -0
  7. package/coverage/clover.xml +6516 -0
  8. package/coverage/coverage-final.json +43 -0
  9. package/coverage/favicon.png +0 -0
  10. package/coverage/index.html +416 -0
  11. package/coverage/prettify.css +1 -0
  12. package/coverage/prettify.js +2 -0
  13. package/coverage/rabbit-ui-kit/index.html +116 -0
  14. package/coverage/rabbit-ui-kit/index.js.html +88 -0
  15. package/coverage/rabbit-ui-kit/src/common/adapters/axiosAdapter.js.html +190 -0
  16. package/coverage/rabbit-ui-kit/src/common/adapters/index.html +116 -0
  17. package/coverage/rabbit-ui-kit/src/common/amountUtils.js.html +1393 -0
  18. package/coverage/rabbit-ui-kit/src/common/errorUtils.js.html +211 -0
  19. package/coverage/rabbit-ui-kit/src/common/external-apis/apiGroups.js.html +250 -0
  20. package/coverage/rabbit-ui-kit/src/common/external-apis/index.html +131 -0
  21. package/coverage/rabbit-ui-kit/src/common/external-apis/ipAddressProviders.js.html +499 -0
  22. package/coverage/rabbit-ui-kit/src/common/fiatCurrenciesService.js.html +568 -0
  23. package/coverage/rabbit-ui-kit/src/common/index.html +146 -0
  24. package/coverage/rabbit-ui-kit/src/common/models/blockchain.js.html +115 -0
  25. package/coverage/rabbit-ui-kit/src/common/models/coin.js.html +556 -0
  26. package/coverage/rabbit-ui-kit/src/common/models/index.html +146 -0
  27. package/coverage/rabbit-ui-kit/src/common/models/protocol.js.html +100 -0
  28. package/coverage/rabbit-ui-kit/src/common/utils/cache.js.html +889 -0
  29. package/coverage/rabbit-ui-kit/src/common/utils/emailAPI.js.html +139 -0
  30. package/coverage/rabbit-ui-kit/src/common/utils/index.html +161 -0
  31. package/coverage/rabbit-ui-kit/src/common/utils/logging/index.html +131 -0
  32. package/coverage/rabbit-ui-kit/src/common/utils/logging/logger.js.html +229 -0
  33. package/coverage/rabbit-ui-kit/src/common/utils/logging/logsStorage.js.html +268 -0
  34. package/coverage/rabbit-ui-kit/src/common/utils/postponeExecution.js.html +118 -0
  35. package/coverage/rabbit-ui-kit/src/common/utils/safeStringify.js.html +235 -0
  36. package/coverage/rabbit-ui-kit/src/components/atoms/AssetIcon/AssetIcon.jsx.html +250 -0
  37. package/coverage/rabbit-ui-kit/src/components/atoms/AssetIcon/index.html +116 -0
  38. package/coverage/rabbit-ui-kit/src/components/atoms/LoadingDots/LoadingDots.jsx.html +256 -0
  39. package/coverage/rabbit-ui-kit/src/components/atoms/LoadingDots/index.html +116 -0
  40. package/coverage/rabbit-ui-kit/src/components/atoms/SupportChat/SupportChat.jsx.html +229 -0
  41. package/coverage/rabbit-ui-kit/src/components/atoms/SupportChat/index.html +116 -0
  42. package/coverage/rabbit-ui-kit/src/components/atoms/buttons/Button/Button.jsx.html +802 -0
  43. package/coverage/rabbit-ui-kit/src/components/atoms/buttons/Button/index.html +116 -0
  44. package/coverage/rabbit-ui-kit/src/components/hooks/index.html +131 -0
  45. package/coverage/rabbit-ui-kit/src/components/hooks/useCallHandlingErrors.js.html +163 -0
  46. package/coverage/rabbit-ui-kit/src/components/hooks/useReferredState.js.html +157 -0
  47. package/coverage/rabbit-ui-kit/src/components/utils/index.html +146 -0
  48. package/coverage/rabbit-ui-kit/src/components/utils/inputValueProviders.js.html +259 -0
  49. package/coverage/rabbit-ui-kit/src/components/utils/uiUtils.js.html +127 -0
  50. package/coverage/rabbit-ui-kit/src/components/utils/urlQueryUtils.js.html +346 -0
  51. package/coverage/rabbit-ui-kit/src/index.html +116 -0
  52. package/coverage/rabbit-ui-kit/src/index.js.html +250 -0
  53. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/cacheAndConcurrentRequestsResolver.js.html +1762 -0
  54. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/cachedRobustExternalApiCallerService.js.html +649 -0
  55. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/cancelProcessing.js.html +172 -0
  56. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/concurrentCalculationsMetadataHolder.js.html +394 -0
  57. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/externalApiProvider.js.html +553 -0
  58. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/externalServicesStatsCollector.js.html +331 -0
  59. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/index.html +206 -0
  60. package/coverage/rabbit-ui-kit/src/robustExteranlApiCallerService/robustExternalAPICallerService.js.html +1249 -0
  61. package/coverage/rabbit-ui-kit/src/swaps-lib/external-apis/index.html +131 -0
  62. package/coverage/rabbit-ui-kit/src/swaps-lib/external-apis/swapProvider.js.html +727 -0
  63. package/coverage/rabbit-ui-kit/src/swaps-lib/external-apis/swapspaceSwapProvider.js.html +2899 -0
  64. package/coverage/rabbit-ui-kit/src/swaps-lib/models/baseSwapCreationInfo.js.html +214 -0
  65. package/coverage/rabbit-ui-kit/src/swaps-lib/models/existingSwap.js.html +304 -0
  66. package/coverage/rabbit-ui-kit/src/swaps-lib/models/existingSwapWithFiatData.js.html +487 -0
  67. package/coverage/rabbit-ui-kit/src/swaps-lib/models/index.html +146 -0
  68. package/coverage/rabbit-ui-kit/src/swaps-lib/services/index.html +116 -0
  69. package/coverage/rabbit-ui-kit/src/swaps-lib/services/publicSwapService.js.html +2191 -0
  70. package/coverage/rabbit-ui-kit/src/swaps-lib/utils/index.html +116 -0
  71. package/coverage/rabbit-ui-kit/src/swaps-lib/utils/swapUtils.js.html +742 -0
  72. package/coverage/rabbit-ui-kit/stories/atoms/LoadingDots.stories.jsx.html +226 -0
  73. package/coverage/rabbit-ui-kit/stories/atoms/buttons/Button.stories.jsx.html +946 -0
  74. package/coverage/rabbit-ui-kit/stories/atoms/buttons/index.html +116 -0
  75. package/coverage/rabbit-ui-kit/stories/atoms/index.html +116 -0
  76. package/coverage/sort-arrow-sprite.png +0 -0
  77. package/coverage/sorter.js +196 -0
  78. package/dist/index.cjs +1706 -1498
  79. package/dist/index.cjs.map +1 -1
  80. package/dist/index.modern.js +817 -666
  81. package/dist/index.modern.js.map +1 -1
  82. package/dist/index.module.js +1704 -1498
  83. package/dist/index.module.js.map +1 -1
  84. package/dist/index.umd.js +1664 -1456
  85. package/dist/index.umd.js.map +1 -1
  86. package/package.json +11 -3
  87. package/src/common/amountUtils.js +4 -2
  88. package/src/common/external-apis/ipAddressProviders.js +138 -0
  89. package/src/common/tests/integration/external-apis/ipAddressProviders/getClientIpAddress.test.js +14 -0
  90. package/src/common/utils/cache.js +4 -4
  91. package/src/components/tests/utils/inputValueProviders/provideFormatOfFloatValueByInputString.test.js +139 -0
  92. package/src/components/tests/utils/urlQueryUtils/getQueryParameterValues.test.js +71 -0
  93. package/src/components/tests/utils/urlQueryUtils/saveQueryParameterAndValues.test.js +144 -0
  94. package/src/components/utils/inputValueProviders.js +58 -0
  95. package/src/index.js +2 -0
  96. package/src/robustExteranlApiCallerService/robustExternalAPICallerService.js +4 -2
  97. package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/callExternalAPI/_performCallAttempt.test.js +787 -0
  98. package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/callExternalAPI/callExternalAPI.test.js +745 -0
  99. package/src/robustExteranlApiCallerService/tests/robustExternalAPICallerService/robustExternalAPICallerService/constructor.test.js +31 -0
  100. package/src/swaps-lib/external-apis/swapProvider.js +17 -4
  101. package/src/swaps-lib/external-apis/swapspaceSwapProvider.js +91 -30
  102. package/src/swaps-lib/models/baseSwapCreationInfo.js +4 -1
  103. package/src/swaps-lib/models/existingSwap.js +3 -0
  104. package/src/swaps-lib/models/existingSwapWithFiatData.js +4 -0
  105. package/src/swaps-lib/services/publicSwapService.js +32 -4
  106. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/_fetchSupportedCurrenciesIfNeeded.test.js +506 -0
  107. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/createSwap.test.js +1311 -0
  108. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getAllSupportedCurrencies.test.js +76 -0
  109. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getDepositCurrencies.test.js +82 -0
  110. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getSwapInfo.test.js +1892 -0
  111. package/src/swaps-lib/test/external-apis/swapspaceSwapProvider/getWithdrawalCurrencies.test.js +111 -0
  112. package/src/swaps-lib/test/utils/swapUtils/safeHandleRequestsLimitExceeding.test.js +88 -0
@@ -1,9 +1,9 @@
1
1
  import React, { useState, useRef, useEffect, useCallback } from 'react';
2
2
  import { BigNumber } from 'bignumber.js';
3
3
  import axios from 'axios';
4
- import { v4 } from 'uuid';
5
- import Hashes from 'jshashes';
6
4
  import EventBusInstance from 'eventbusjs';
5
+ import Hashes from 'jshashes';
6
+ import { v4 } from 'uuid';
7
7
 
8
8
  function createCommonjsModule(fn) {
9
9
  var module = { exports: {} };
@@ -1612,6 +1612,52 @@ const handleClickOutside = (exceptionsRefs, callback) => {
1612
1612
  return () => document.removeEventListener("click", handleClick);
1613
1613
  };
1614
1614
 
1615
+ class InputValuesProviders {
1616
+ /**
1617
+ * Designed to be called onKeyUp event of html input field for float value
1618
+ * Removes all prohibited stuff from the given float string and remains only allowed.
1619
+ * Removes digits before and after the dot.
1620
+ *
1621
+ * @param inputString {string} string to be corrected
1622
+ * @param maxValue {string} max value for the correcting float value
1623
+ * @param digitsAfterDot {number} count of digits after the dot that this method should provide, min 0
1624
+ * @return {string} corrected float value string
1625
+ */
1626
+ static provideFormatOfFloatValueByInputString(inputString, digitsAfterDot = 2, maxValue = null) {
1627
+ var _parts$2;
1628
+ let value = inputString;
1629
+ if (!value) {
1630
+ return "";
1631
+ }
1632
+ if (digitsAfterDot < 0) {
1633
+ throw new Error("Min suffix length is 0, got " + digitsAfterDot);
1634
+ }
1635
+ value = value.replace(/[,]/g, "."); // replaces commas with dots
1636
+ value = value.replace(/[^0-9.]/g, ""); // remove non digits/dots
1637
+ value = value.replace(/^\./g, "0."); // adds leading zero
1638
+ value = value.replace(/\.+/g, "."); // replaces series of dots with single dot
1639
+
1640
+ let parts = value.split(".");
1641
+ if (parts.length > 2) {
1642
+ // removes all after second dot and itself
1643
+ parts = [parts[0], parts[1]];
1644
+ }
1645
+ if (maxValue != null) {
1646
+ var _parts$;
1647
+ const maxDigitsCountBeforeTheDot = BigNumber(maxValue).toFixed(0).length;
1648
+ if (((_parts$ = parts[0]) == null ? void 0 : _parts$.length) > maxDigitsCountBeforeTheDot) {
1649
+ // removes redundant prefix digits
1650
+ parts[0] = parts[0].substring(parts[0].length - maxDigitsCountBeforeTheDot, parts[0].length);
1651
+ }
1652
+ }
1653
+ if (((_parts$2 = parts[1]) == null ? void 0 : _parts$2.length) > digitsAfterDot) {
1654
+ // removes redundant suffix digits
1655
+ parts[1] = parts[1].substring(0, digitsAfterDot);
1656
+ }
1657
+ return parts.join(".");
1658
+ }
1659
+ }
1660
+
1615
1661
  const PARAMETER_VALUES_SEPARATOR = "|*|"; // Sting that with high probability will not be in the user's data
1616
1662
 
1617
1663
  /**
@@ -1919,11 +1965,13 @@ class AmountUtils {
1919
1965
  }
1920
1966
 
1921
1967
  /**
1968
+ * Returns integer part of number as a string.
1969
+ *
1922
1970
  * @param amount {BigNumber|number|string|null|undefined} The number value to be trimmed.
1923
1971
  * HEX strings also allowed "0x..." and JS hex numbers
1924
1972
  * @return {string|null}
1925
1973
  */
1926
- static intStr(amount) {
1974
+ static toIntegerString(amount) {
1927
1975
  return this.trim(amount, 0);
1928
1976
  }
1929
1977
 
@@ -2004,7 +2052,7 @@ class AmountUtils {
2004
2052
  leftNumber = leftNumber.times(multiplier);
2005
2053
  }
2006
2054
  }
2007
- const leftAmountString = AmountUtils.intStr(leftNumber);
2055
+ const leftAmountString = AmountUtils.toIntegerString(leftNumber);
2008
2056
  const rightAmountString = right != null ? right.toFixed(rightCurrencyDigitsAfterDots, BigNumber.ROUND_FLOOR) : null;
2009
2057
  return `${leftAmountString} ${leftTicker} ~ ${rightAmountString != null ? rightAmountString : "?"} ${rightTicker}`;
2010
2058
  } catch (e) {
@@ -2225,10 +2273,10 @@ class Coin {
2225
2273
  */
2226
2274
  class Cache {
2227
2275
  /**
2228
- * @param eventBus {EventBus} EventBus.js lib instance
2229
- * @param [noSessionEvents=[]] {string[]} array of events that will be treated as "no session"
2276
+ * @param [eventBus=null] {EventBus} EventBus.js lib instance if you plan to use Cache with events handling
2277
+ * @param [noSessionEvents=[]] {string[]} array of events that will be treated as "no session", you should pass EventBus to make it work
2230
2278
  */
2231
- constructor(eventBus, noSessionEvents = []) {
2279
+ constructor(eventBus = null, noSessionEvents = []) {
2232
2280
  this._cache = new Map();
2233
2281
  this._eventDependentDataKeys = [];
2234
2282
  this._noSessionEvents = noSessionEvents;
@@ -2303,7 +2351,7 @@ class Cache {
2303
2351
  const eventAndKeys = this._eventDependentDataKeys.find(item => item[0] === event);
2304
2352
  if (eventAndKeys) {
2305
2353
  eventAndKeys.push(key);
2306
- } else {
2354
+ } else if (this._eventBus) {
2307
2355
  this._eventDependentDataKeys.push([event, key]);
2308
2356
  this._eventBus.addEventListener(event, () => {
2309
2357
  try {
@@ -2501,456 +2549,220 @@ class EmailsApi {
2501
2549
  }
2502
2550
  EmailsApi.serverEndpointEntity = "emails";
2503
2551
 
2504
- /**
2505
- * This util helps to avoid duplicated calls to a shared resource.
2506
- * It tracks is there currently active calculation for the specific cache id and make all other requests
2507
- * with the same cache id waiting for this active calculation to be finished. When the calculation ends
2508
- * the resolver allows all the waiting requesters to get the data from cache and start their own calculations.
2509
- *
2510
- * This class should be instantiated inside some other service where you need to request some resource concurrently.
2511
- * Rules:
2512
- * 1. When you need to make a request inside your main service call 'getCachedOrWaitForCachedOrAcquireLock'
2513
- * on the instance of this class and await for the result. If the flag allowing to start calculation is true
2514
- * then you can request data inside your main service. Otherwise you should use the cached data as an another
2515
- * requester just finished the most resent requesting and there is actual data in the cache that
2516
- * is returned to you here.
2517
- * 1.1 Also you can acquire a lock directly if you don't want to get cached data. Use the corresponding method 'acquireLock'.
2518
- *
2519
- * 2. If you start requesting (when you successfully acquired the lock) then after receiving the result of your
2520
- * requesting you should call the 'saveCachedData' so the retrieved data will appear in the cache.
2521
- *
2522
- * 3. If you successfully acquired the lock then you should after calling the 'saveCachedData' call
2523
- * the 'releaseLock' - this is mandatory to release the lock and allow other requesters to perform their requests.
2524
- * WARNING: If for any reason you forget to call this method then this class instance will wait perpetually for
2525
- * the lock releasing and all your attempts to request the data will constantly fail. So usually call it
2526
- * inside the 'finally' block.
2527
- *
2528
- * TODO: [tests, critical++] add unit tests - massively used logic and can produce sophisticated concurrency bugs
2529
- */
2530
- class CacheAndConcurrentRequestsResolver {
2552
+ class ExternalApiProvider {
2531
2553
  /**
2532
- * @param bio {string} unique identifier for the exact service
2533
- * @param cache {Cache} cache
2534
- * @param cacheTtl {number|null} time to live for cache ms. 0 or null means the cache cannot expire
2535
- * @param [maxCallAttemptsToWaitForAlreadyRunningRequest=100] {number} number of request allowed to do waiting for
2536
- * result before we fail the original request. Use custom value only if you need to make the attempts count
2537
- * and polling interval changes.
2538
- * @param [timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished=1000] {number}
2539
- * timeout ms for polling for a result. if you change maxCallAttemptsToWaitForAlreadyRunningRequest
2540
- * then this parameter maybe also require the custom value.
2541
- * @param [removeExpiredCacheAutomatically=true] {boolean}
2554
+ * Creates an instance of external api provider.
2555
+ *
2556
+ * If you need sub-request then use 'subRequestIndex' to check current request index in functions below.
2557
+ * Also use array for 'httpMethod'.
2558
+ *
2559
+ * If the endpoint of dedicated provider has pagination then you should customize the behavior using
2560
+ * "changeQueryParametersForPageNumber", "checkWhetherResponseIsForLastPage".
2561
+ *
2562
+ * We perform RPS counting all over the App to avoid blocking our clients due to abuses of the providers.
2563
+ *
2564
+ * @param endpoint {string} URL to the provider's endpoint. Note: you can customize it using composeQueryString
2565
+ * @param [httpMethod] {string|string[]} one of "get", "post", "put", "patch", "delete" or an array of these values
2566
+ * for request having sub-requests
2567
+ * @param [timeout] {number} number of milliseconds to wait for the response
2568
+ * @param [apiGroup] {ApiGroup} singleton object containing parameters of API group. Helpful when you use the same
2569
+ * api for different providers to avoid hardcoding RPS inside each provider what can cause mistakes
2570
+ * @param [specificHeaders] {Object} contains specific keys (headers) and values (their content) if needed for this provider
2571
+ * @param [maxPageLength] {number} optional number of items per page if the request supports pagination
2542
2572
  */
2543
- constructor(bio, cache, cacheTtl, removeExpiredCacheAutomatically = true, maxCallAttemptsToWaitForAlreadyRunningRequest = 100, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished = 1000) {
2544
- if (cacheTtl != null && cacheTtl < timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished * 2) {
2545
- /*
2546
- * During the lifetime of this service e.g. if the data is being retrieved slowly we can get
2547
- * RACE CONDITION when we constantly retrieve data and during retrieval it is expired, so we are trying
2548
- * to retrieve it again and again.
2549
- * We have a protection mechanism that we will wait no more than
2550
- * maxCallAttemptsToWaitForAlreadyRunningRequest * timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished
2551
- * but this additional check is aimed to reduce potential loading time for some requests.
2552
- */
2553
- throw new Error(`DEV: Wrong parameters passed to construct ${bio} - TTL ${cacheTtl} should be 2 times greater than ${timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished}`);
2554
- }
2555
- this._bio = bio;
2556
- this._cache = cache;
2557
- this._cacheTtlMs = cacheTtl != null ? cacheTtl : null;
2558
- this._maxExecutionTimeMs = maxCallAttemptsToWaitForAlreadyRunningRequest * timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished;
2559
- this._removeExpiredCacheAutomatically = removeExpiredCacheAutomatically;
2560
- this._requestsManager = new ManagerOfRequestsToTheSameResource(bio, maxCallAttemptsToWaitForAlreadyRunningRequest, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished);
2573
+ constructor(endpoint, httpMethod, timeout, apiGroup, specificHeaders = {}, maxPageLength = Number.MAX_SAFE_INTEGER) {
2574
+ this.endpoint = endpoint;
2575
+ this.httpMethod = httpMethod != null ? httpMethod : "get";
2576
+ // TODO: [refactoring, critical] We have two timeouts for robust data retrieval - here and inside the service method call, need to remain the only
2577
+ this.timeout = timeout != null ? timeout : 10000;
2578
+ // TODO: [refactoring, critical] We need single place for all RPSes as we use them as hardcoded constants now inside different services
2579
+ this.apiGroup = apiGroup;
2580
+ this.maxPageLength = maxPageLength != null ? maxPageLength : Number.MAX_SAFE_INTEGER;
2581
+ this.niceFactor = 1;
2582
+ this.specificHeaders = specificHeaders != null ? specificHeaders : {};
2583
+ }
2584
+ getRps() {
2585
+ var _this$apiGroup$rps;
2586
+ return (_this$apiGroup$rps = this.apiGroup.rps) != null ? _this$apiGroup$rps : 2;
2587
+ }
2588
+ isRpsExceeded() {
2589
+ return this.apiGroup.isRpsExceeded();
2590
+ }
2591
+ actualizeLastCalledTimestamp() {
2592
+ this.apiGroup.actualizeLastCalledTimestamp();
2593
+ }
2594
+ getApiGroupId() {
2595
+ return this.apiGroup.id;
2561
2596
  }
2562
2597
 
2563
2598
  /**
2564
- * When using this service this is the major method you should call to get data by cache id.
2565
- * This method checks is there cached data and ether
2566
- * - returns you flag that you can start requesting data from the shared resource
2567
- * - or if there is already started calculation waits until it is finished (removed from this service)
2568
- * and returns you the retrieved data
2569
- * - or just returns you the cached data
2570
- *
2571
- * 'canStartDataRetrieval' equal true means that the lock was acquired, and you should manually call 'saveCachedData'
2572
- * if needed and then 'releaseLock' to mark this calculation as finished so other
2573
- * requesters can take their share of the resource.
2599
+ * Some endpoint can require several sub requests. Example is one request to get confirmed transactions
2600
+ * and another request for unconfirmed transactions. You should override this method to return true for such requests.
2574
2601
  *
2575
- * @param cacheId {string}
2576
- * @return {Promise<({
2577
- * canStartDataRetrieval: true,
2578
- * cachedData: any,
2579
- * lockId: string
2580
- * }|{
2581
- * canStartDataRetrieval: false,
2582
- * cachedData: any
2583
- * })>}
2602
+ * @return {boolean} true if this provider requires several requests to retrieve the data
2584
2603
  */
2585
- async getCachedOrWaitForCachedOrAcquireLock(cacheId) {
2586
- try {
2587
- var _cached2;
2588
- const startedAtTimestamp = Date.now();
2589
- let cached = this._cache.get(cacheId);
2590
- let cachedDataBackupIsPresentButExpired = null;
2591
- if (cached != null && !this._removeExpiredCacheAutomatically) {
2592
- const lastUpdateTimestamp = this._cache.getLastUpdateTimestamp(cacheId);
2593
- if ((lastUpdateTimestamp != null ? lastUpdateTimestamp : 0) + this._cacheTtlMs < Date.now()) {
2594
- /*
2595
- * Here we are manually clearing 'cached' value retrieved from cache to force the data loading.
2596
- * But we save its value first to the backup variable to be able to return this value if ongoing
2597
- * requesting fails.
2598
- */
2599
- cachedDataBackupIsPresentButExpired = cached;
2600
- cached = null;
2601
- }
2602
- }
2603
- let calculationId = null;
2604
- let isRetrievedCacheExpired = true;
2605
- let isWaitingForActiveCalculationSucceeded;
2606
- let weStillHaveSomeTimeToProceedExecution = true;
2607
- while (calculationId == null && cached == null && isRetrievedCacheExpired && weStillHaveSomeTimeToProceedExecution) {
2608
- const result = await this._requestsManager.startCalculationOrWaitForActiveToFinish(cacheId);
2609
- calculationId = typeof result === "string" ? result : null;
2610
- isWaitingForActiveCalculationSucceeded = typeof result === "boolean" ? result : null;
2611
- cached = this._cache.get(cacheId);
2612
- isRetrievedCacheExpired = isWaitingForActiveCalculationSucceeded && cached == null;
2613
- weStillHaveSomeTimeToProceedExecution = Date.now() - startedAtTimestamp < this._maxExecutionTimeMs;
2614
- }
2615
- if (calculationId) {
2616
- var _cached;
2617
- return {
2618
- canStartDataRetrieval: true,
2619
- cachedData: (_cached = cached) != null ? _cached : cachedDataBackupIsPresentButExpired,
2620
- lockId: calculationId
2621
- };
2622
- }
2623
- return {
2624
- canStartDataRetrieval: false,
2625
- cachedData: (_cached2 = cached) != null ? _cached2 : cachedDataBackupIsPresentButExpired
2626
- };
2627
- } catch (e) {
2628
- improveAndRethrow(e, `${this._bio}.getCachedOrWaitForCachedOrAcquireLock`);
2629
- }
2604
+ doesRequireSubRequests() {
2605
+ return false;
2630
2606
  }
2631
2607
 
2632
2608
  /**
2633
- * Returns just the current cache value for the given id.
2634
- * Doesn't wait for the active calculation, doesn't acquire lock, just retrieves the current cache as it is.
2609
+ * Some endpoint support pagination. Override this method if so and implement corresponding methods.
2635
2610
  *
2636
- * @param cacheId {string}
2637
- * @return {any}
2611
+ * @return {boolean} true if this provider requires several requests to retrieve the data
2638
2612
  */
2639
- getCached(cacheId) {
2640
- try {
2641
- return this._cache.get(cacheId);
2642
- } catch (e) {
2643
- improveAndRethrow(e, "getCached");
2644
- }
2645
- }
2646
- _getTtl() {
2647
- return this._removeExpiredCacheAutomatically ? this._cacheTtlMs : null;
2613
+ doesSupportPagination() {
2614
+ return false;
2648
2615
  }
2649
2616
 
2650
2617
  /**
2651
- * Directly acquires the lock despite on cached data availability.
2652
- * So if this method returns result === true you can start the data retrieval.
2618
+ * Composes a query string to be added to the endpoint of this provider.
2653
2619
  *
2654
- * @param cacheId {string}
2655
- * @return {Promise<{ result: true, lockId: string }|{ result: false }>}
2620
+ * @param params {any[]} params array passed to the RobustExternalAPICallerService
2621
+ * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
2622
+ * @returns {string} query string to be concatenated with endpoint
2656
2623
  */
2657
- async acquireLock(cacheId) {
2658
- try {
2659
- return await this._requestsManager.acquireLock(cacheId);
2660
- } catch (e) {
2661
- improveAndRethrow(e, "acquireLock");
2662
- }
2624
+ composeQueryString(params, subRequestIndex = 0) {
2625
+ return "";
2663
2626
  }
2664
2627
 
2665
2628
  /**
2666
- * This method should be called only if you acquired a lock successfully.
2667
- *
2668
- * If the current lock id is not equal to the passed one the passed data will be ignored.
2669
- * Or you can do the synchronous data merging on your side and pass the
2670
- * wasDataMergedSynchronouslyWithMostRecentCacheState=true so your data will be stored
2671
- * despite on the lockId.
2672
- * WARNING: you should do this only if you are sure you perform the synchronous update.
2629
+ * Composes a body to be added to the request
2673
2630
  *
2674
- * @param cacheId {string}
2675
- * @param lockId {string}
2676
- * @param data {any}
2677
- * @param [sessionDependentData=true] {boolean}
2678
- * @param [wasDataMergedSynchronouslyWithMostRecentCacheState=false]
2631
+ * @param params {any[]} params array passed to the RobustExternalAPICallerService
2632
+ * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
2633
+ * @returns {string}
2679
2634
  */
2680
- saveCachedData(cacheId, lockId, data, sessionDependentData = true, wasDataMergedSynchronouslyWithMostRecentCacheState = false) {
2681
- try {
2682
- if (wasDataMergedSynchronouslyWithMostRecentCacheState || this._requestsManager.isTheLockActiveOne(cacheId, lockId)) {
2683
- /* We save passed data only if the <caller> has the currently acquired lockId.
2684
- * If the passed lockId is not the active one it means that other code cleared/stopped the lock
2685
- * acquired by the <caller> recently due to some urgent/more prior changes.
2686
- *
2687
- * But we allow user to pass the 'wasDataMergedSynchronouslyWithMostRecentCacheState' flag
2688
- * that tells us that the user had taken the most recent cache value and merged his new data
2689
- * with that cached value (AFTER possibly performing async data retrieval). This means that we
2690
- * can ignore the fact that his lockId is no more relevant and save the passed data
2691
- * as it is synchronously merged with the most recent cached data. (Synchronously merged means that
2692
- * the lost update cannot occur during the merge time as JS execute the synchronous functions\
2693
- * till the end).
2694
- */
2695
- if (sessionDependentData) {
2696
- this._cache.putSessionDependentData(cacheId, data, this._getTtl());
2697
- } else {
2698
- this._cache.put(cacheId, data, this._getTtl());
2699
- }
2700
- }
2701
- } catch (e) {
2702
- improveAndRethrow(e, `${this._bio}.saveCachedData`);
2703
- }
2635
+ composeBody(params, subRequestIndex = 0) {
2636
+ return "";
2704
2637
  }
2705
2638
 
2706
2639
  /**
2707
- * Should be called then and only then if you successfully acquired a lock with the lock id.
2640
+ * Extracts data from the response and returns it
2708
2641
  *
2709
- * @param cacheId {string}
2710
- * @param lockId {string}
2642
+ * @param response {Object} HTTP response returned by provider
2643
+ * @param [params] {any[]} params array passed to the RobustExternalAPICallerService
2644
+ * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
2645
+ * @param iterationsData {any[]} array of data retrieved from previous sub-requests
2646
+ * @returns {any}
2711
2647
  */
2712
- releaseLock(cacheId, lockId) {
2713
- try {
2714
- if (this._requestsManager.isTheLockActiveOne(cacheId, lockId)) {
2715
- this._requestsManager.finishActiveCalculation(cacheId);
2716
- }
2717
- } catch (e) {
2718
- improveAndRethrow(e, `${this._bio}.releaseLock`);
2719
- }
2648
+ getDataByResponse(response, params = [], subRequestIndex = 0, iterationsData = []) {
2649
+ return [];
2720
2650
  }
2721
2651
 
2722
2652
  /**
2723
- * Actualized currently present cached data by key. Applies the provided function to the cached data.
2653
+ * Function changing the query string according to page number and previous response
2654
+ * Only for endpoints supporting pagination
2724
2655
  *
2725
- * @param cacheId {string} id of cache entry
2726
- * @param synchronousCurrentCacheProcessor (function|null} synchronous function accepting cache entry. Should return
2727
- * an object in following format:
2728
- * {
2729
- * isModified: boolean,
2730
- * data: any
2731
- * }
2732
- * the flag signals whether data was changed during the processing or not
2733
- * @param [sessionDependent=true] {boolean} whether to mark the cache entry as session-dependent
2656
+ * @param params {any[]} params array passed to the RobustExternalAPICallerService
2657
+ * @param previousResponse {Object} HTTP response returned by provider for previous call (previous page)
2658
+ * @param pageNumber {number} new page number. We count from 0. You need to manually increment with 1 if your
2659
+ * provider counts pages starting with 1
2660
+ * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
2661
+ * @returns {any[]}
2734
2662
  */
2735
- actualizeCachedData(cacheId, synchronousCurrentCacheProcessor, sessionDependent = true) {
2736
- try {
2737
- const cached = this._cache.get(cacheId);
2738
- const result = synchronousCurrentCacheProcessor(cached);
2739
- if (result != null && result.isModified && (result == null ? void 0 : result.data) != null) {
2740
- if (sessionDependent) {
2741
- this._cache.putSessionDependentData(cacheId, result == null ? void 0 : result.data, this._getTtl());
2742
- } else {
2743
- this._cache.put(cacheId, result == null ? void 0 : result.data, this._getTtl());
2744
- }
2745
-
2746
- /* Here we call the lock releasing to ensure the currently active calculation will be ignored.
2747
- * This is needed to ensure no 'lost update'.
2748
- * Lost update can occur if we change data in this method and after that some calculation finishes
2749
- * having the earlier data as its base to calculate its data set result. And the earlier data
2750
- * has no changes applied inside this method, so we will lose them.
2751
- *
2752
- * This is not so good solution: ideally, we should acquire lock before performing any data updating.
2753
- * But the goal of this method is to provide an instant ability to update the cached data.
2754
- * And if we start acquiring the lock here the data update can be postponed significantly.
2755
- * And this kills the desired nature of this method.
2756
- * So we better lose some data retrieval (means abusing the resource a bit) than lose
2757
- * the instant update expected after this method execution.
2758
- */
2759
- this._requestsManager.finishActiveCalculation(cacheId);
2760
- }
2761
- } catch (e) {
2762
- improveAndRethrow(e, `${this._bio}.actualizeCachedData`);
2763
- }
2764
- }
2765
- invalidate(key) {
2766
- this._cache.invalidate(key);
2767
- this._requestsManager.finishActiveCalculation(key);
2768
- }
2769
- invalidateContaining(keyPart) {
2770
- this._cache.invalidateContaining(keyPart);
2771
- this._requestsManager.finishAllActiveCalculations(keyPart);
2772
- }
2773
- markAsExpiredButDontRemove(key) {
2774
- if (this._removeExpiredCacheAutomatically) {
2775
- this._cache.markCacheItemAsExpiredButDontRemove(key, this._cacheTtlMs);
2776
- } else {
2777
- this._cache.setLastUpdateTimestamp(key, Date.now() - this._cacheTtlMs - 1);
2778
- }
2779
- this._requestsManager.finishAllActiveCalculations(key);
2663
+ changeQueryParametersForPageNumber(params, previousResponse, pageNumber, subRequestIndex = 0) {
2664
+ return params;
2780
2665
  }
2781
- }
2782
2666
 
2783
- /**
2784
- * Util class to control access to a resource when it can be called in parallel for the same result.
2785
- * (E.g. getting today coins-fiat rates from some API).
2786
- */
2787
- class ManagerOfRequestsToTheSameResource {
2788
2667
  /**
2789
- * @param bio {string} resource-related identifier for logging
2790
- * @param [maxPollsCount=100] {number} max number of attempts to wait when waiting for a lock acquisition
2791
- * @param [timeoutDuration=1000] {number} timeout between the polls for a lock acquisition
2668
+ * Function checking whether the response is for the last page to stop requesting for a next page.
2669
+ * Only for endpoints supporting pagination.
2670
+ *
2671
+ * @param previousResponse {Object} HTTP response returned by provider for previous call (previous page)
2672
+ * @param currentResponse {Object} HTTP response returned by provider for current call (current page, next after the previous)
2673
+ * @param currentPageNumber {number} current page number (for current response)
2674
+ * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
2675
+ * @returns {boolean}
2792
2676
  */
2793
- constructor(bio, maxPollsCount = 100, timeoutDuration = 1000) {
2794
- this.bio = bio;
2795
- this.maxPollsCount = maxPollsCount;
2796
- this.timeoutDuration = timeoutDuration;
2797
- this._activeCalculationsIds = new Map();
2798
- this._nextCalculationIds = new Map();
2677
+ checkWhetherResponseIsForLastPage(previousResponse, currentResponse, currentPageNumber, subRequestIndex = 0) {
2678
+ return true;
2799
2679
  }
2800
2680
 
2801
2681
  /**
2802
- * If there is no active calculation just creates uuid and returns it.
2803
- * If there is active calculation waits until it removed from the active calculation uuid variable.
2804
- *
2805
- * @param requestHash {string}
2806
- * @return {Promise<string|boolean>} returns uuid of new active calculation or true if waiting for active
2807
- * calculation succeed or false if max attempts count exceeded
2682
+ * Resets the nice factor to default value
2808
2683
  */
2809
- async startCalculationOrWaitForActiveToFinish(requestHash) {
2810
- try {
2811
- const activeCalculationIdForHash = this._activeCalculationsIds.get(requestHash);
2812
- if (activeCalculationIdForHash == null) {
2813
- const id = v4();
2814
- this._activeCalculationsIds.set(requestHash, id);
2815
- return id;
2816
- }
2817
- return await this._waitForCalculationIdToFinish(requestHash, activeCalculationIdForHash, 0);
2818
- } catch (e) {
2819
- Logger.logError(e, `startCalculationOrWaitForActiveToFinish_${this.bio}`);
2820
- }
2821
- return null;
2684
+ resetNiceFactor() {
2685
+ this.niceFactor = 1;
2822
2686
  }
2823
2687
 
2824
2688
  /**
2825
- * Acquires lock to the resource by the provided hash.
2689
+ * Internal method used for requests requiring sub-requests.
2826
2690
  *
2827
- * @param requestHash {string}
2828
- * @return {Promise<{ result: true, lockId: string }|{ result: false }>} result is true if the lock is successfully
2829
- * acquired, false if the max allowed time to wait for acquisition expired or any unexpected error occurs
2830
- * during the waiting.
2691
+ * @param iterationsData {any[]} iterations data retrieved from getDataByResponse called per sub-request.
2692
+ * @return {any} by default flatten the passed iterations data array. Should be redefined if you need another logic.
2831
2693
  */
2832
- async acquireLock(requestHash) {
2833
- try {
2834
- var _this$_nextCalculatio;
2835
- const activeId = this._activeCalculationsIds.get(requestHash);
2836
- const nextId = v4();
2837
- if (activeId == null) {
2838
- this._activeCalculationsIds.set(requestHash, nextId);
2839
- return {
2840
- result: true,
2841
- lockId: nextId
2842
- };
2843
- }
2844
- const currentNext = (_this$_nextCalculatio = this._nextCalculationIds.get(requestHash)) != null ? _this$_nextCalculatio : [];
2845
- currentNext.push(nextId);
2846
- this._nextCalculationIds.set(requestHash, currentNext);
2847
- const waitingResult = await this._waitForCalculationIdToFinish(requestHash, activeId, 0, nextId);
2848
- return {
2849
- result: waitingResult,
2850
- lockId: waitingResult ? nextId : undefined
2851
- };
2852
- } catch (e) {
2853
- improveAndRethrow(e, "acquireLock");
2854
- }
2694
+ incorporateIterationsData(iterationsData) {
2695
+ return iterationsData.flat();
2855
2696
  }
2697
+ }
2856
2698
 
2857
- /**
2858
- * Clears active calculation id.
2859
- * WARNING: if you forget to call this method the start* one will perform maxPollsCount attempts before finishing
2860
- * @param requestHash {string} hash of request. Helps to distinct the request for the same resource but
2861
- * having different request parameters and hold a dedicated calculation id per this hash
2862
- */
2863
- finishActiveCalculation(requestHash = "default") {
2864
- try {
2865
- var _this$_nextCalculatio2;
2866
- this._activeCalculationsIds.delete(requestHash);
2867
- const next = (_this$_nextCalculatio2 = this._nextCalculationIds.get(requestHash)) != null ? _this$_nextCalculatio2 : [];
2868
- if (next.length) {
2869
- this._activeCalculationsIds.set(requestHash, next[0]);
2870
- this._nextCalculationIds.set(requestHash, next.slice(1));
2871
- }
2872
- } catch (e) {
2873
- improveAndRethrow(e, "finishActiveCalculation");
2874
- }
2699
+ /**
2700
+ * Models a group of APIs provided by the same owner and used for different services in our app.
2701
+ * It means we need to mention RPS several times for each usage and also have some holder of last call timestamp per
2702
+ * api group. So this concept allows to use it for exact ExternalApiProvider and make sure that you use the same
2703
+ * RPS value and make decisions on base of the same timestamp of last call to the API group owner.
2704
+ */
2705
+ class ApiGroup {
2706
+ constructor(id, rps, backendProxyIdGenerator = null) {
2707
+ this.id = id;
2708
+ this.rps = rps;
2709
+ this.lastCalledTimestamp = null;
2710
+ this.backendProxyIdGenerator = backendProxyIdGenerator;
2875
2711
  }
2876
- finishAllActiveCalculations(keyPart = "") {
2877
- try {
2878
- Array.from(this._activeCalculationsIds.keys()).forEach(hash => {
2879
- if (typeof hash === "string" && new RegExp(keyPart).test(hash)) {
2880
- this.finishActiveCalculation(hash);
2881
- }
2882
- });
2883
- } catch (e) {
2884
- improveAndRethrow(e, "finishAllActiveCalculations");
2885
- }
2712
+ isRpsExceeded() {
2713
+ var _this$lastCalledTimes;
2714
+ return ((_this$lastCalledTimes = this.lastCalledTimestamp) != null ? _this$lastCalledTimes : 0) + Math.floor(1000 / this.rps) > Date.now();
2886
2715
  }
2887
-
2888
- /**
2889
- * @param requestHash {string}
2890
- * @param lockId {string}
2891
- * @return {boolean}
2892
- */
2893
- isTheLockActiveOne(requestHash, lockId) {
2894
- try {
2895
- return this._activeCalculationsIds.get(requestHash) === lockId;
2896
- } catch (e) {
2897
- improveAndRethrow(e, "isTheLockActiveOne");
2898
- }
2716
+ actualizeLastCalledTimestamp() {
2717
+ this.lastCalledTimestamp = Date.now();
2899
2718
  }
2900
-
2719
+ }
2720
+ const ApiGroups = {
2901
2721
  /**
2902
- * @param requestHash {string}
2903
- * @param activeCalculationId {string|null}
2904
- * @param [attemptIndex=0] {number}
2905
- * @param waitForCalculationId {string|null} if you want to wait for an exact id to appear as active then pass this parameter
2906
- * @return {Promise<boolean>} true
2907
- * - if the given calculation id is no more an active one
2908
- * - or it is equal to waitForCalculationId
2909
- * false
2910
- * - if waiting period exceeds the max allowed waiting time or unexpected error occurs
2911
- * @private
2722
+ * Currently we use free version of etherscan provider with 0.2 RPS. But we have API key with 100k requests free
2723
+ * per month. So we can add it if not enough current RPS.
2912
2724
  */
2913
- async _waitForCalculationIdToFinish(requestHash, activeCalculationId, attemptIndex = 0, waitForCalculationId = null) {
2914
- try {
2915
- if (attemptIndex + 1 > this.maxPollsCount) {
2916
- // Max number of polls for active calculation id change is achieved. So we return false.
2917
- return false;
2918
- }
2919
- const currentId = this._activeCalculationsIds.get(requestHash);
2920
- if (waitForCalculationId == null ? currentId !== activeCalculationId : currentId === waitForCalculationId) {
2921
- /* We return true depending on the usage of this function:
2922
- * 1. if there is calculation id that we should wait for to become an active then we return true only
2923
- * if this id becomes the active one.
2924
- *
2925
- * Theoretically we can fail to wait for the desired calculation id. This can be caused by wrong use of
2926
- * this service or by any other mistakes/errors. But this waiting function will return false anyway if
2927
- * the number of polls done exceeds the max allowed.
2928
- *
2929
- * 2. if we just wait for the currently active calculation id to be finished then we return true
2930
- * when we notice that the current active id differs from the original passed into this function.
2931
- */
2932
- return true;
2933
- } else {
2934
- /* The original calculation id is still the active one, so we are scheduling a new attempt to check
2935
- * whether the active calculation id changed or not in timeoutDuration milliseconds.
2936
- */
2937
- const it = this;
2938
- return new Promise((resolve, reject) => {
2939
- setTimeout(function () {
2940
- try {
2941
- resolve(it._waitForCalculationIdToFinish(requestHash, activeCalculationId, attemptIndex + 1));
2942
- } catch (e) {
2943
- reject(e);
2944
- }
2945
- }, this.timeoutDuration);
2946
- });
2947
- }
2948
- } catch (e) {
2949
- Logger.logError(e, "_waitForCalculationIdToFinish", "Failed to wait for active calculation id change.");
2950
- return false;
2951
- }
2952
- }
2953
- }
2725
+ ETHERSCAN: new ApiGroup("etherscan", 0.17),
2726
+ // Actually 0.2 but fails sometime, so we use smaller
2727
+ ALCHEMY: new ApiGroup("alchemy", 0.3, networkKey => `alchemy-${networkKey}`),
2728
+ BLOCKSTREAM: new ApiGroup("blockstream", 0.2),
2729
+ BLOCKCHAIN_INFO: new ApiGroup("blockchain.info", 1),
2730
+ BLOCKNATIVE: new ApiGroup("blocknative", 0.5),
2731
+ ETHGASSTATION: new ApiGroup("ethgasstation", 0.5),
2732
+ TRONGRID: new ApiGroup("trongrid", 0.3, networkKey => `trongrid-${networkKey}`),
2733
+ TRONSCAN: new ApiGroup("tronscan", 0.3),
2734
+ GETBLOCK: new ApiGroup("getblock", 0.3),
2735
+ COINCAP: new ApiGroup("coincap", 0.5),
2736
+ // 200 per minute without API key
2737
+ COINGECKO: new ApiGroup("coingecko", 0.9),
2738
+ // actually 0.13-0.5 according to the docs but we use smaller due to expirienced frequent abuses
2739
+ MESSARI: new ApiGroup("messari", 0.2),
2740
+ BTCCOM: new ApiGroup("btccom", 0.2),
2741
+ BITAPS: new ApiGroup("bitaps", 0.25),
2742
+ // Docs say that RPS is 3 but using it causes frequent 429 HTTP errors
2743
+ CEX: new ApiGroup("cex", 0.5),
2744
+ // Just assumption for RPS
2745
+ BIGDATACLOUD: new ApiGroup("bigdatacloud", 1),
2746
+ // Just assumption for RPS
2747
+ TRACKIP: new ApiGroup("trackip", 1),
2748
+ // Just assumption for RPS
2749
+ IPIFY: new ApiGroup("ipify", 1),
2750
+ // Just assumption for RPS
2751
+ WHATISMYIPADDRESS: new ApiGroup("whatismyipaddress", 1),
2752
+ // Just assumption for RPS
2753
+ EXCHANGERATE: new ApiGroup("exchangerate", 1),
2754
+ // Just assumption for RPS
2755
+ FRANKFURTER: new ApiGroup("frankfurter", 1),
2756
+ // Just assumption for RPS
2757
+ BITGO: new ApiGroup("bitgo", 1),
2758
+ // Just assumption for RPS
2759
+ BITCOINER: new ApiGroup("bitcoiner", 1),
2760
+ // Just assumption for RPS
2761
+ BITCORE: new ApiGroup("bitcore", 1),
2762
+ // Just assumption for RPS
2763
+ // BLOCKCHAIR: new ApiGroup("blockchair", 0.04), // this provider require API key for commercial use (10usd 10000 reqs), we will add it later
2764
+ MEMPOOL: new ApiGroup("mempool", 0.2) // Just assumption for RPS
2765
+ };
2954
2766
 
2955
2767
  // TODO: [refactoring, low] Consider removing this logic task_id=c360f2af75764bde8badd9ff1cc00d48
2956
2768
  class ConcurrentCalculationsMetadataHolder {
@@ -3089,7 +2901,7 @@ class ExternalServicesStatsCollector {
3089
2901
  */
3090
2902
  class RobustExternalAPICallerService {
3091
2903
  static getStats() {
3092
- this.statsCollector.getStats();
2904
+ return this.statsCollector.getStats();
3093
2905
  }
3094
2906
 
3095
2907
  /**
@@ -3109,7 +2921,9 @@ class RobustExternalAPICallerService {
3109
2921
  this.providers = providersData;
3110
2922
  providersData.forEach(provider => provider.resetNiceFactor());
3111
2923
  this.bio = bio;
3112
- this._logger = Logger.logError;
2924
+ this._logger = (...args) => {
2925
+ Logger.logError(...args);
2926
+ };
3113
2927
  }
3114
2928
  /**
3115
2929
  * Performs data retrieval from external APIs. Tries providers till the data is retrieved.
@@ -3301,12 +3115,463 @@ class RobustExternalAPICallerService {
3301
3115
  return providersCopy.sort((p1, p2) => p2.niceFactor - p1.niceFactor);
3302
3116
  }
3303
3117
  }
3304
- RobustExternalAPICallerService.statsCollector = new ExternalServicesStatsCollector();
3305
- RobustExternalAPICallerService.defaultRPSFactor = 1;
3306
- RobustExternalAPICallerService.rpsMultiplier = 1.05;
3307
- function punishProvider(provider) {
3308
- provider.niceFactor = provider.niceFactor - 1;
3309
- }
3118
+ RobustExternalAPICallerService.statsCollector = new ExternalServicesStatsCollector();
3119
+ RobustExternalAPICallerService.defaultRPSFactor = 1;
3120
+ RobustExternalAPICallerService.rpsMultiplier = 1.05;
3121
+ function punishProvider(provider) {
3122
+ provider.niceFactor = provider.niceFactor - 1;
3123
+ }
3124
+
3125
+ /**
3126
+ * This util helps to avoid duplicated calls to a shared resource.
3127
+ * It tracks is there currently active calculation for the specific cache id and make all other requests
3128
+ * with the same cache id waiting for this active calculation to be finished. When the calculation ends
3129
+ * the resolver allows all the waiting requesters to get the data from cache and start their own calculations.
3130
+ *
3131
+ * This class should be instantiated inside some other service where you need to request some resource concurrently.
3132
+ * Rules:
3133
+ * 1. When you need to make a request inside your main service call 'getCachedOrWaitForCachedOrAcquireLock'
3134
+ * on the instance of this class and await for the result. If the flag allowing to start calculation is true
3135
+ * then you can request data inside your main service. Otherwise you should use the cached data as an another
3136
+ * requester just finished the most resent requesting and there is actual data in the cache that
3137
+ * is returned to you here.
3138
+ * 1.1 Also you can acquire a lock directly if you don't want to get cached data. Use the corresponding method 'acquireLock'.
3139
+ *
3140
+ * 2. If you start requesting (when you successfully acquired the lock) then after receiving the result of your
3141
+ * requesting you should call the 'saveCachedData' so the retrieved data will appear in the cache.
3142
+ *
3143
+ * 3. If you successfully acquired the lock then you should after calling the 'saveCachedData' call
3144
+ * the 'releaseLock' - this is mandatory to release the lock and allow other requesters to perform their requests.
3145
+ * WARNING: If for any reason you forget to call this method then this class instance will wait perpetually for
3146
+ * the lock releasing and all your attempts to request the data will constantly fail. So usually call it
3147
+ * inside the 'finally' block.
3148
+ *
3149
+ * TODO: [tests, critical++] add unit tests - massively used logic and can produce sophisticated concurrency bugs
3150
+ */
3151
+ class CacheAndConcurrentRequestsResolver {
3152
+ /**
3153
+ * @param bio {string} unique identifier for the exact service
3154
+ * @param cache {Cache} cache
3155
+ * @param cacheTtl {number|null} time to live for cache ms. 0 or null means the cache cannot expire
3156
+ * @param [maxCallAttemptsToWaitForAlreadyRunningRequest=100] {number} number of request allowed to do waiting for
3157
+ * result before we fail the original request. Use custom value only if you need to make the attempts count
3158
+ * and polling interval changes.
3159
+ * @param [timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished=1000] {number}
3160
+ * timeout ms for polling for a result. if you change maxCallAttemptsToWaitForAlreadyRunningRequest
3161
+ * then this parameter maybe also require the custom value.
3162
+ * @param [removeExpiredCacheAutomatically=true] {boolean}
3163
+ */
3164
+ constructor(bio, cache, cacheTtl, removeExpiredCacheAutomatically = true, maxCallAttemptsToWaitForAlreadyRunningRequest = 100, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished = 1000) {
3165
+ if (cacheTtl != null && cacheTtl < timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished * 2) {
3166
+ /*
3167
+ * During the lifetime of this service e.g. if the data is being retrieved slowly we can get
3168
+ * RACE CONDITION when we constantly retrieve data and during retrieval it is expired, so we are trying
3169
+ * to retrieve it again and again.
3170
+ * We have a protection mechanism that we will wait no more than
3171
+ * maxCallAttemptsToWaitForAlreadyRunningRequest * timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished
3172
+ * but this additional check is aimed to reduce potential loading time for some requests.
3173
+ */
3174
+ throw new Error(`DEV: Wrong parameters passed to construct ${bio} - TTL ${cacheTtl} should be 2 times greater than ${timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished}`);
3175
+ }
3176
+ this._bio = bio;
3177
+ this._cache = cache;
3178
+ this._cacheTtlMs = cacheTtl != null ? cacheTtl : null;
3179
+ this._maxExecutionTimeMs = maxCallAttemptsToWaitForAlreadyRunningRequest * timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished;
3180
+ this._removeExpiredCacheAutomatically = removeExpiredCacheAutomatically;
3181
+ this._requestsManager = new ManagerOfRequestsToTheSameResource(bio, maxCallAttemptsToWaitForAlreadyRunningRequest, timeoutBetweenAttemptsToCheckWhetherAlreadyRunningRequestFinished);
3182
+ }
3183
+
3184
+ /**
3185
+ * When using this service this is the major method you should call to get data by cache id.
3186
+ * This method checks is there cached data and ether
3187
+ * - returns you flag that you can start requesting data from the shared resource
3188
+ * - or if there is already started calculation waits until it is finished (removed from this service)
3189
+ * and returns you the retrieved data
3190
+ * - or just returns you the cached data
3191
+ *
3192
+ * 'canStartDataRetrieval' equal true means that the lock was acquired, and you should manually call 'saveCachedData'
3193
+ * if needed and then 'releaseLock' to mark this calculation as finished so other
3194
+ * requesters can take their share of the resource.
3195
+ *
3196
+ * @param cacheId {string}
3197
+ * @return {Promise<({
3198
+ * canStartDataRetrieval: true,
3199
+ * cachedData: any,
3200
+ * lockId: string
3201
+ * }|{
3202
+ * canStartDataRetrieval: false,
3203
+ * cachedData: any
3204
+ * })>}
3205
+ */
3206
+ async getCachedOrWaitForCachedOrAcquireLock(cacheId) {
3207
+ try {
3208
+ var _cached2;
3209
+ const startedAtTimestamp = Date.now();
3210
+ let cached = this._cache.get(cacheId);
3211
+ let cachedDataBackupIsPresentButExpired = null;
3212
+ if (cached != null && !this._removeExpiredCacheAutomatically) {
3213
+ const lastUpdateTimestamp = this._cache.getLastUpdateTimestamp(cacheId);
3214
+ if ((lastUpdateTimestamp != null ? lastUpdateTimestamp : 0) + this._cacheTtlMs < Date.now()) {
3215
+ /*
3216
+ * Here we are manually clearing 'cached' value retrieved from cache to force the data loading.
3217
+ * But we save its value first to the backup variable to be able to return this value if ongoing
3218
+ * requesting fails.
3219
+ */
3220
+ cachedDataBackupIsPresentButExpired = cached;
3221
+ cached = null;
3222
+ }
3223
+ }
3224
+ let calculationId = null;
3225
+ let isRetrievedCacheExpired = true;
3226
+ let isWaitingForActiveCalculationSucceeded;
3227
+ let weStillHaveSomeTimeToProceedExecution = true;
3228
+ while (calculationId == null && cached == null && isRetrievedCacheExpired && weStillHaveSomeTimeToProceedExecution) {
3229
+ const result = await this._requestsManager.startCalculationOrWaitForActiveToFinish(cacheId);
3230
+ calculationId = typeof result === "string" ? result : null;
3231
+ isWaitingForActiveCalculationSucceeded = typeof result === "boolean" ? result : null;
3232
+ cached = this._cache.get(cacheId);
3233
+ isRetrievedCacheExpired = isWaitingForActiveCalculationSucceeded && cached == null;
3234
+ weStillHaveSomeTimeToProceedExecution = Date.now() - startedAtTimestamp < this._maxExecutionTimeMs;
3235
+ }
3236
+ if (calculationId) {
3237
+ var _cached;
3238
+ return {
3239
+ canStartDataRetrieval: true,
3240
+ cachedData: (_cached = cached) != null ? _cached : cachedDataBackupIsPresentButExpired,
3241
+ lockId: calculationId
3242
+ };
3243
+ }
3244
+ return {
3245
+ canStartDataRetrieval: false,
3246
+ cachedData: (_cached2 = cached) != null ? _cached2 : cachedDataBackupIsPresentButExpired
3247
+ };
3248
+ } catch (e) {
3249
+ improveAndRethrow(e, `${this._bio}.getCachedOrWaitForCachedOrAcquireLock`);
3250
+ }
3251
+ }
3252
+
3253
+ /**
3254
+ * Returns just the current cache value for the given id.
3255
+ * Doesn't wait for the active calculation, doesn't acquire lock, just retrieves the current cache as it is.
3256
+ *
3257
+ * @param cacheId {string}
3258
+ * @return {any}
3259
+ */
3260
+ getCached(cacheId) {
3261
+ try {
3262
+ return this._cache.get(cacheId);
3263
+ } catch (e) {
3264
+ improveAndRethrow(e, "getCached");
3265
+ }
3266
+ }
3267
+ _getTtl() {
3268
+ return this._removeExpiredCacheAutomatically ? this._cacheTtlMs : null;
3269
+ }
3270
+
3271
+ /**
3272
+ * Directly acquires the lock despite on cached data availability.
3273
+ * So if this method returns result === true you can start the data retrieval.
3274
+ *
3275
+ * @param cacheId {string}
3276
+ * @return {Promise<{ result: true, lockId: string }|{ result: false }>}
3277
+ */
3278
+ async acquireLock(cacheId) {
3279
+ try {
3280
+ return await this._requestsManager.acquireLock(cacheId);
3281
+ } catch (e) {
3282
+ improveAndRethrow(e, "acquireLock");
3283
+ }
3284
+ }
3285
+
3286
+ /**
3287
+ * This method should be called only if you acquired a lock successfully.
3288
+ *
3289
+ * If the current lock id is not equal to the passed one the passed data will be ignored.
3290
+ * Or you can do the synchronous data merging on your side and pass the
3291
+ * wasDataMergedSynchronouslyWithMostRecentCacheState=true so your data will be stored
3292
+ * despite on the lockId.
3293
+ * WARNING: you should do this only if you are sure you perform the synchronous update.
3294
+ *
3295
+ * @param cacheId {string}
3296
+ * @param lockId {string}
3297
+ * @param data {any}
3298
+ * @param [sessionDependentData=true] {boolean}
3299
+ * @param [wasDataMergedSynchronouslyWithMostRecentCacheState=false]
3300
+ */
3301
+ saveCachedData(cacheId, lockId, data, sessionDependentData = true, wasDataMergedSynchronouslyWithMostRecentCacheState = false) {
3302
+ try {
3303
+ if (wasDataMergedSynchronouslyWithMostRecentCacheState || this._requestsManager.isTheLockActiveOne(cacheId, lockId)) {
3304
+ /* We save passed data only if the <caller> has the currently acquired lockId.
3305
+ * If the passed lockId is not the active one it means that other code cleared/stopped the lock
3306
+ * acquired by the <caller> recently due to some urgent/more prior changes.
3307
+ *
3308
+ * But we allow user to pass the 'wasDataMergedSynchronouslyWithMostRecentCacheState' flag
3309
+ * that tells us that the user had taken the most recent cache value and merged his new data
3310
+ * with that cached value (AFTER possibly performing async data retrieval). This means that we
3311
+ * can ignore the fact that his lockId is no more relevant and save the passed data
3312
+ * as it is synchronously merged with the most recent cached data. (Synchronously merged means that
3313
+ * the lost update cannot occur during the merge time as JS execute the synchronous functions\
3314
+ * till the end).
3315
+ */
3316
+ if (sessionDependentData) {
3317
+ this._cache.putSessionDependentData(cacheId, data, this._getTtl());
3318
+ } else {
3319
+ this._cache.put(cacheId, data, this._getTtl());
3320
+ }
3321
+ }
3322
+ } catch (e) {
3323
+ improveAndRethrow(e, `${this._bio}.saveCachedData`);
3324
+ }
3325
+ }
3326
+
3327
+ /**
3328
+ * Should be called then and only then if you successfully acquired a lock with the lock id.
3329
+ *
3330
+ * @param cacheId {string}
3331
+ * @param lockId {string}
3332
+ */
3333
+ releaseLock(cacheId, lockId) {
3334
+ try {
3335
+ if (this._requestsManager.isTheLockActiveOne(cacheId, lockId)) {
3336
+ this._requestsManager.finishActiveCalculation(cacheId);
3337
+ }
3338
+ } catch (e) {
3339
+ improveAndRethrow(e, `${this._bio}.releaseLock`);
3340
+ }
3341
+ }
3342
+
3343
+ /**
3344
+ * Actualized currently present cached data by key. Applies the provided function to the cached data.
3345
+ *
3346
+ * @param cacheId {string} id of cache entry
3347
+ * @param synchronousCurrentCacheProcessor (function|null} synchronous function accepting cache entry. Should return
3348
+ * an object in following format:
3349
+ * {
3350
+ * isModified: boolean,
3351
+ * data: any
3352
+ * }
3353
+ * the flag signals whether data was changed during the processing or not
3354
+ * @param [sessionDependent=true] {boolean} whether to mark the cache entry as session-dependent
3355
+ */
3356
+ actualizeCachedData(cacheId, synchronousCurrentCacheProcessor, sessionDependent = true) {
3357
+ try {
3358
+ const cached = this._cache.get(cacheId);
3359
+ const result = synchronousCurrentCacheProcessor(cached);
3360
+ if (result != null && result.isModified && (result == null ? void 0 : result.data) != null) {
3361
+ if (sessionDependent) {
3362
+ this._cache.putSessionDependentData(cacheId, result == null ? void 0 : result.data, this._getTtl());
3363
+ } else {
3364
+ this._cache.put(cacheId, result == null ? void 0 : result.data, this._getTtl());
3365
+ }
3366
+
3367
+ /* Here we call the lock releasing to ensure the currently active calculation will be ignored.
3368
+ * This is needed to ensure no 'lost update'.
3369
+ * Lost update can occur if we change data in this method and after that some calculation finishes
3370
+ * having the earlier data as its base to calculate its data set result. And the earlier data
3371
+ * has no changes applied inside this method, so we will lose them.
3372
+ *
3373
+ * This is not so good solution: ideally, we should acquire lock before performing any data updating.
3374
+ * But the goal of this method is to provide an instant ability to update the cached data.
3375
+ * And if we start acquiring the lock here the data update can be postponed significantly.
3376
+ * And this kills the desired nature of this method.
3377
+ * So we better lose some data retrieval (means abusing the resource a bit) than lose
3378
+ * the instant update expected after this method execution.
3379
+ */
3380
+ this._requestsManager.finishActiveCalculation(cacheId);
3381
+ }
3382
+ } catch (e) {
3383
+ improveAndRethrow(e, `${this._bio}.actualizeCachedData`);
3384
+ }
3385
+ }
3386
+ invalidate(key) {
3387
+ this._cache.invalidate(key);
3388
+ this._requestsManager.finishActiveCalculation(key);
3389
+ }
3390
+ invalidateContaining(keyPart) {
3391
+ this._cache.invalidateContaining(keyPart);
3392
+ this._requestsManager.finishAllActiveCalculations(keyPart);
3393
+ }
3394
+ markAsExpiredButDontRemove(key) {
3395
+ if (this._removeExpiredCacheAutomatically) {
3396
+ this._cache.markCacheItemAsExpiredButDontRemove(key, this._cacheTtlMs);
3397
+ } else {
3398
+ this._cache.setLastUpdateTimestamp(key, Date.now() - this._cacheTtlMs - 1);
3399
+ }
3400
+ this._requestsManager.finishAllActiveCalculations(key);
3401
+ }
3402
+ }
3403
+
3404
+ /**
3405
+ * Util class to control access to a resource when it can be called in parallel for the same result.
3406
+ * (E.g. getting today coins-fiat rates from some API).
3407
+ */
3408
+ class ManagerOfRequestsToTheSameResource {
3409
+ /**
3410
+ * @param bio {string} resource-related identifier for logging
3411
+ * @param [maxPollsCount=100] {number} max number of attempts to wait when waiting for a lock acquisition
3412
+ * @param [timeoutDuration=1000] {number} timeout between the polls for a lock acquisition
3413
+ */
3414
+ constructor(bio, maxPollsCount = 100, timeoutDuration = 1000) {
3415
+ this.bio = bio;
3416
+ this.maxPollsCount = maxPollsCount;
3417
+ this.timeoutDuration = timeoutDuration;
3418
+ this._activeCalculationsIds = new Map();
3419
+ this._nextCalculationIds = new Map();
3420
+ }
3421
+
3422
+ /**
3423
+ * If there is no active calculation just creates uuid and returns it.
3424
+ * If there is active calculation waits until it removed from the active calculation uuid variable.
3425
+ *
3426
+ * @param requestHash {string}
3427
+ * @return {Promise<string|boolean>} returns uuid of new active calculation or true if waiting for active
3428
+ * calculation succeed or false if max attempts count exceeded
3429
+ */
3430
+ async startCalculationOrWaitForActiveToFinish(requestHash) {
3431
+ try {
3432
+ const activeCalculationIdForHash = this._activeCalculationsIds.get(requestHash);
3433
+ if (activeCalculationIdForHash == null) {
3434
+ const id = v4();
3435
+ this._activeCalculationsIds.set(requestHash, id);
3436
+ return id;
3437
+ }
3438
+ return await this._waitForCalculationIdToFinish(requestHash, activeCalculationIdForHash, 0);
3439
+ } catch (e) {
3440
+ Logger.logError(e, `startCalculationOrWaitForActiveToFinish_${this.bio}`);
3441
+ }
3442
+ return null;
3443
+ }
3444
+
3445
+ /**
3446
+ * Acquires lock to the resource by the provided hash.
3447
+ *
3448
+ * @param requestHash {string}
3449
+ * @return {Promise<{ result: true, lockId: string }|{ result: false }>} result is true if the lock is successfully
3450
+ * acquired, false if the max allowed time to wait for acquisition expired or any unexpected error occurs
3451
+ * during the waiting.
3452
+ */
3453
+ async acquireLock(requestHash) {
3454
+ try {
3455
+ var _this$_nextCalculatio;
3456
+ const activeId = this._activeCalculationsIds.get(requestHash);
3457
+ const nextId = v4();
3458
+ if (activeId == null) {
3459
+ this._activeCalculationsIds.set(requestHash, nextId);
3460
+ return {
3461
+ result: true,
3462
+ lockId: nextId
3463
+ };
3464
+ }
3465
+ const currentNext = (_this$_nextCalculatio = this._nextCalculationIds.get(requestHash)) != null ? _this$_nextCalculatio : [];
3466
+ currentNext.push(nextId);
3467
+ this._nextCalculationIds.set(requestHash, currentNext);
3468
+ const waitingResult = await this._waitForCalculationIdToFinish(requestHash, activeId, 0, nextId);
3469
+ return {
3470
+ result: waitingResult,
3471
+ lockId: waitingResult ? nextId : undefined
3472
+ };
3473
+ } catch (e) {
3474
+ improveAndRethrow(e, "acquireLock");
3475
+ }
3476
+ }
3477
+
3478
+ /**
3479
+ * Clears active calculation id.
3480
+ * WARNING: if you forget to call this method the start* one will perform maxPollsCount attempts before finishing
3481
+ * @param requestHash {string} hash of request. Helps to distinct the request for the same resource but
3482
+ * having different request parameters and hold a dedicated calculation id per this hash
3483
+ */
3484
+ finishActiveCalculation(requestHash = "default") {
3485
+ try {
3486
+ var _this$_nextCalculatio2;
3487
+ this._activeCalculationsIds.delete(requestHash);
3488
+ const next = (_this$_nextCalculatio2 = this._nextCalculationIds.get(requestHash)) != null ? _this$_nextCalculatio2 : [];
3489
+ if (next.length) {
3490
+ this._activeCalculationsIds.set(requestHash, next[0]);
3491
+ this._nextCalculationIds.set(requestHash, next.slice(1));
3492
+ }
3493
+ } catch (e) {
3494
+ improveAndRethrow(e, "finishActiveCalculation");
3495
+ }
3496
+ }
3497
+ finishAllActiveCalculations(keyPart = "") {
3498
+ try {
3499
+ Array.from(this._activeCalculationsIds.keys()).forEach(hash => {
3500
+ if (typeof hash === "string" && new RegExp(keyPart).test(hash)) {
3501
+ this.finishActiveCalculation(hash);
3502
+ }
3503
+ });
3504
+ } catch (e) {
3505
+ improveAndRethrow(e, "finishAllActiveCalculations");
3506
+ }
3507
+ }
3508
+
3509
+ /**
3510
+ * @param requestHash {string}
3511
+ * @param lockId {string}
3512
+ * @return {boolean}
3513
+ */
3514
+ isTheLockActiveOne(requestHash, lockId) {
3515
+ try {
3516
+ return this._activeCalculationsIds.get(requestHash) === lockId;
3517
+ } catch (e) {
3518
+ improveAndRethrow(e, "isTheLockActiveOne");
3519
+ }
3520
+ }
3521
+
3522
+ /**
3523
+ * @param requestHash {string}
3524
+ * @param activeCalculationId {string|null}
3525
+ * @param [attemptIndex=0] {number}
3526
+ * @param waitForCalculationId {string|null} if you want to wait for an exact id to appear as active then pass this parameter
3527
+ * @return {Promise<boolean>} true
3528
+ * - if the given calculation id is no more an active one
3529
+ * - or it is equal to waitForCalculationId
3530
+ * false
3531
+ * - if waiting period exceeds the max allowed waiting time or unexpected error occurs
3532
+ * @private
3533
+ */
3534
+ async _waitForCalculationIdToFinish(requestHash, activeCalculationId, attemptIndex = 0, waitForCalculationId = null) {
3535
+ try {
3536
+ if (attemptIndex + 1 > this.maxPollsCount) {
3537
+ // Max number of polls for active calculation id change is achieved. So we return false.
3538
+ return false;
3539
+ }
3540
+ const currentId = this._activeCalculationsIds.get(requestHash);
3541
+ if (waitForCalculationId == null ? currentId !== activeCalculationId : currentId === waitForCalculationId) {
3542
+ /* We return true depending on the usage of this function:
3543
+ * 1. if there is calculation id that we should wait for to become an active then we return true only
3544
+ * if this id becomes the active one.
3545
+ *
3546
+ * Theoretically we can fail to wait for the desired calculation id. This can be caused by wrong use of
3547
+ * this service or by any other mistakes/errors. But this waiting function will return false anyway if
3548
+ * the number of polls done exceeds the max allowed.
3549
+ *
3550
+ * 2. if we just wait for the currently active calculation id to be finished then we return true
3551
+ * when we notice that the current active id differs from the original passed into this function.
3552
+ */
3553
+ return true;
3554
+ } else {
3555
+ /* The original calculation id is still the active one, so we are scheduling a new attempt to check
3556
+ * whether the active calculation id changed or not in timeoutDuration milliseconds.
3557
+ */
3558
+ const it = this;
3559
+ return new Promise((resolve, reject) => {
3560
+ setTimeout(function () {
3561
+ try {
3562
+ resolve(it._waitForCalculationIdToFinish(requestHash, activeCalculationId, attemptIndex + 1));
3563
+ } catch (e) {
3564
+ reject(e);
3565
+ }
3566
+ }, this.timeoutDuration);
3567
+ });
3568
+ }
3569
+ } catch (e) {
3570
+ Logger.logError(e, "_waitForCalculationIdToFinish", "Failed to wait for active calculation id change.");
3571
+ return false;
3572
+ }
3573
+ }
3574
+ }
3310
3575
 
3311
3576
  /**
3312
3577
  * Extended edit of RobustExternalApiCallerService supporting cache and management of concurrent requests
@@ -3402,244 +3667,93 @@ class CachedRobustExternalApiCallerService {
3402
3667
  }
3403
3668
  }
3404
3669
 
3405
- /**
3406
- * Utils class needed to perform cancelling of axios request inside some process.
3407
- * Provides cancel state and axios token for HTTP requests
3408
- */
3409
- class CancelProcessing {
3670
+ class BigdatacloudIpAddressProvider extends ExternalApiProvider {
3410
3671
  constructor() {
3411
- this._cancelToken = axios.CancelToken.source();
3412
- this._isCanceled = false;
3413
- }
3414
- cancel() {
3415
- this._isCanceled = true;
3416
- this._cancelToken.cancel();
3417
- }
3418
- isCanceled() {
3419
- return this._isCanceled;
3672
+ super("https://api.bigdatacloud.net/data/client-ip", "get", 15000, ApiGroups.BIGDATACLOUD);
3420
3673
  }
3421
- getToken() {
3422
- return this._cancelToken.token;
3423
- }
3424
- static instance() {
3425
- return new CancelProcessing();
3674
+ getDataByResponse(response, params = [], subRequestIndex = 0, iterationsData = []) {
3675
+ var _response$data;
3676
+ return (response == null ? void 0 : response.data) && ((_response$data = response.data) == null ? void 0 : _response$data.ipString);
3426
3677
  }
3427
3678
  }
3428
-
3429
- class ExternalApiProvider {
3430
- /**
3431
- * Creates an instance of external api provider.
3432
- *
3433
- * If you need sub-request then use 'subRequestIndex' to check current request index in functions below.
3434
- * Also use array for 'httpMethod'.
3435
- *
3436
- * If the endpoint of dedicated provider has pagination then you should customize the behavior using
3437
- * "changeQueryParametersForPageNumber", "checkWhetherResponseIsForLastPage".
3438
- *
3439
- * We perform RPS counting all over the App to avoid blocking our clients due to abuses of the providers.
3440
- *
3441
- * @param endpoint {string} URL to the provider's endpoint. Note: you can customize it using composeQueryString
3442
- * @param [httpMethod] {string|string[]} one of "get", "post", "put", "patch", "delete" or an array of these values
3443
- * for request having sub-requests
3444
- * @param [timeout] {number} number of milliseconds to wait for the response
3445
- * @param [apiGroup] {ApiGroup} singleton object containing parameters of API group. Helpful when you use the same
3446
- * api for different providers to avoid hardcoding RPS inside each provider what can cause mistakes
3447
- * @param [specificHeaders] {Object} contains specific keys (headers) and values (their content) if needed for this provider
3448
- * @param [maxPageLength] {number} optional number of items per page if the request supports pagination
3449
- */
3450
- constructor(endpoint, httpMethod, timeout, apiGroup, specificHeaders = {}, maxPageLength = Number.MAX_SAFE_INTEGER) {
3451
- this.endpoint = endpoint;
3452
- this.httpMethod = httpMethod != null ? httpMethod : "get";
3453
- // TODO: [refactoring, critical] We have two timeouts for robust data retrieval - here and inside the service method call, need to remain the only
3454
- this.timeout = timeout != null ? timeout : 10000;
3455
- // TODO: [refactoring, critical] We need single place for all RPSes as we use them as hardcoded constants now inside different services
3456
- this.apiGroup = apiGroup;
3457
- this.maxPageLength = maxPageLength != null ? maxPageLength : Number.MAX_SAFE_INTEGER;
3458
- this.niceFactor = 1;
3459
- this.specificHeaders = specificHeaders != null ? specificHeaders : {};
3460
- }
3461
- getRps() {
3462
- var _this$apiGroup$rps;
3463
- return (_this$apiGroup$rps = this.apiGroup.rps) != null ? _this$apiGroup$rps : 2;
3464
- }
3465
- isRpsExceeded() {
3466
- return this.apiGroup.isRpsExceeded();
3679
+ class TrackipIpAddressProvider extends ExternalApiProvider {
3680
+ constructor() {
3681
+ super("https://www.trackip.net/ip", "get", 15000, ApiGroups.TRACKIP);
3467
3682
  }
3468
- actualizeLastCalledTimestamp() {
3469
- this.apiGroup.actualizeLastCalledTimestamp();
3683
+ getDataByResponse(response, params = [], subRequestIndex = 0, iterationsData = []) {
3684
+ return response == null ? void 0 : response.data;
3470
3685
  }
3471
- getApiGroupId() {
3472
- return this.apiGroup.id;
3686
+ }
3687
+ class IpifyV6IpAddressProvider extends ExternalApiProvider {
3688
+ constructor() {
3689
+ super("https://api6.ipify.org/?format=json", "get", 15000, ApiGroups.IPIFY);
3473
3690
  }
3474
-
3475
- /**
3476
- * Some endpoint can require several sub requests. Example is one request to get confirmed transactions
3477
- * and another request for unconfirmed transactions. You should override this method to return true for such requests.
3478
- *
3479
- * @return {boolean} true if this provider requires several requests to retrieve the data
3480
- */
3481
- doesRequireSubRequests() {
3482
- return false;
3691
+ getDataByResponse(response, params = [], subRequestIndex = 0, iterationsData = []) {
3692
+ var _response$data2;
3693
+ return (response == null ? void 0 : response.data) && ((_response$data2 = response.data) == null ? void 0 : _response$data2.ip);
3483
3694
  }
3484
-
3485
- /**
3486
- * Some endpoint support pagination. Override this method if so and implement corresponding methods.
3487
- *
3488
- * @return {boolean} true if this provider requires several requests to retrieve the data
3489
- */
3490
- doesSupportPagination() {
3491
- return false;
3695
+ }
3696
+ class IpifyIpAddressProvider extends ExternalApiProvider {
3697
+ constructor() {
3698
+ super("https://api.ipify.org/?format=json", "get", 15000, ApiGroups.IPIFY);
3492
3699
  }
3493
-
3494
- /**
3495
- * Composes a query string to be added to the endpoint of this provider.
3496
- *
3497
- * @param params {any[]} params array passed to the RobustExternalAPICallerService
3498
- * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
3499
- * @returns {string} query string to be concatenated with endpoint
3500
- */
3501
- composeQueryString(params, subRequestIndex = 0) {
3502
- return "";
3700
+ getDataByResponse(response, params = [], subRequestIndex = 0, iterationsData = []) {
3701
+ var _response$data3;
3702
+ return (response == null ? void 0 : response.data) && ((_response$data3 = response.data) == null ? void 0 : _response$data3.ip);
3503
3703
  }
3504
-
3505
- /**
3506
- * Composes a body to be added to the request
3507
- *
3508
- * @param params {any[]} params array passed to the RobustExternalAPICallerService
3509
- * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
3510
- * @returns {string}
3511
- */
3512
- composeBody(params, subRequestIndex = 0) {
3513
- return "";
3704
+ }
3705
+ class WhatismyipaddressIpAddressProvider extends ExternalApiProvider {
3706
+ constructor() {
3707
+ super("http://bot.whatismyipaddress.com/", "get", 15000, ApiGroups.WHATISMYIPADDRESS);
3514
3708
  }
3515
-
3516
- /**
3517
- * Extracts data from the response and returns it
3518
- *
3519
- * @param response {Object} HTTP response returned by provider
3520
- * @param [params] {any[]} params array passed to the RobustExternalAPICallerService
3521
- * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
3522
- * @param iterationsData {any[]} array of data retrieved from previous sub-requests
3523
- * @returns {any}
3524
- */
3525
3709
  getDataByResponse(response, params = [], subRequestIndex = 0, iterationsData = []) {
3526
- return [];
3527
- }
3528
-
3529
- /**
3530
- * Function changing the query string according to page number and previous response
3531
- * Only for endpoints supporting pagination
3532
- *
3533
- * @param params {any[]} params array passed to the RobustExternalAPICallerService
3534
- * @param previousResponse {Object} HTTP response returned by provider for previous call (previous page)
3535
- * @param pageNumber {number} new page number. We count from 0. You need to manually increment with 1 if your
3536
- * provider counts pages starting with 1
3537
- * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
3538
- * @returns {any[]}
3539
- */
3540
- changeQueryParametersForPageNumber(params, previousResponse, pageNumber, subRequestIndex = 0) {
3541
- return params;
3710
+ return response == null ? void 0 : response.data;
3542
3711
  }
3543
-
3712
+ }
3713
+ class IpAddressProvider {
3544
3714
  /**
3545
- * Function checking whether the response is for the last page to stop requesting for a next page.
3546
- * Only for endpoints supporting pagination.
3715
+ * Returns current public IP address identified by one of external services.
3547
3716
  *
3548
- * @param previousResponse {Object} HTTP response returned by provider for previous call (previous page)
3549
- * @param currentResponse {Object} HTTP response returned by provider for current call (current page, next after the previous)
3550
- * @param currentPageNumber {number} current page number (for current response)
3551
- * @param [subRequestIndex] {number} optional number of the sub-request the call is performed for
3552
- * @returns {boolean}
3553
- */
3554
- checkWhetherResponseIsForLastPage(previousResponse, currentResponse, currentPageNumber, subRequestIndex = 0) {
3555
- return true;
3556
- }
3557
-
3558
- /**
3559
- * Resets the nice factor to default value
3560
- */
3561
- resetNiceFactor() {
3562
- this.niceFactor = 1;
3563
- }
3564
-
3565
- /**
3566
- * Internal method used for requests requiring sub-requests.
3717
+ * It is easier than manual identification and also (as ip needed for server side to check it) it saves us from
3718
+ * issues related to changes of infrastructure configurations (like adding proxies etc.) so we should not configure
3719
+ * anything on server side to get correct client's IP.
3567
3720
  *
3568
- * @param iterationsData {any[]} iterations data retrieved from getDataByResponse called per sub-request.
3569
- * @return {any} by default flatten the passed iterations data array. Should be redefined if you need another logic.
3721
+ * @returns {Promise<String>} IP address
3722
+ * @throws {Error} if fails to retrieve IP address from all the services
3570
3723
  */
3571
- incorporateIterationsData(iterationsData) {
3572
- return iterationsData.flat();
3724
+ static async getClientIpAddress() {
3725
+ try {
3726
+ return await this.externalIPAddressAPICaller.callExternalAPICached([], 7000);
3727
+ } catch (e) {
3728
+ improveAndRethrow(e, "getClientIpAddress");
3729
+ }
3573
3730
  }
3574
3731
  }
3732
+ IpAddressProvider.externalIPAddressAPICaller = new CachedRobustExternalApiCallerService("externalIPAddressAPICaller", new Cache(EventBusInstance), [new BigdatacloudIpAddressProvider(), new TrackipIpAddressProvider(), new IpifyV6IpAddressProvider(), new IpifyIpAddressProvider(), new WhatismyipaddressIpAddressProvider()], 300000);
3575
3733
 
3576
3734
  /**
3577
- * Models a group of APIs provided by the same owner and used for different services in our app.
3578
- * It means we need to mention RPS several times for each usage and also have some holder of last call timestamp per
3579
- * api group. So this concept allows to use it for exact ExternalApiProvider and make sure that you use the same
3580
- * RPS value and make decisions on base of the same timestamp of last call to the API group owner.
3735
+ * Utils class needed to perform cancelling of axios request inside some process.
3736
+ * Provides cancel state and axios token for HTTP requests
3581
3737
  */
3582
- class ApiGroup {
3583
- constructor(id, rps, backendProxyIdGenerator = null) {
3584
- this.id = id;
3585
- this.rps = rps;
3586
- this.lastCalledTimestamp = null;
3587
- this.backendProxyIdGenerator = backendProxyIdGenerator;
3738
+ class CancelProcessing {
3739
+ constructor() {
3740
+ this._cancelToken = axios.CancelToken.source();
3741
+ this._isCanceled = false;
3588
3742
  }
3589
- isRpsExceeded() {
3590
- var _this$lastCalledTimes;
3591
- return ((_this$lastCalledTimes = this.lastCalledTimestamp) != null ? _this$lastCalledTimes : 0) + Math.floor(1000 / this.rps) > Date.now();
3743
+ cancel() {
3744
+ this._isCanceled = true;
3745
+ this._cancelToken.cancel();
3592
3746
  }
3593
- actualizeLastCalledTimestamp() {
3594
- this.lastCalledTimestamp = Date.now();
3747
+ isCanceled() {
3748
+ return this._isCanceled;
3749
+ }
3750
+ getToken() {
3751
+ return this._cancelToken.token;
3752
+ }
3753
+ static instance() {
3754
+ return new CancelProcessing();
3595
3755
  }
3596
3756
  }
3597
- const ApiGroups = {
3598
- /**
3599
- * Currently we use free version of etherscan provider with 0.2 RPS. But we have API key with 100k requests free
3600
- * per month. So we can add it if not enough current RPS.
3601
- */
3602
- ETHERSCAN: new ApiGroup("etherscan", 0.17),
3603
- // Actually 0.2 but fails sometime, so we use smaller
3604
- ALCHEMY: new ApiGroup("alchemy", 0.3, networkKey => `alchemy-${networkKey}`),
3605
- BLOCKSTREAM: new ApiGroup("blockstream", 0.2),
3606
- BLOCKCHAIN_INFO: new ApiGroup("blockchain.info", 1),
3607
- BLOCKNATIVE: new ApiGroup("blocknative", 0.5),
3608
- ETHGASSTATION: new ApiGroup("ethgasstation", 0.5),
3609
- TRONGRID: new ApiGroup("trongrid", 0.3, networkKey => `trongrid-${networkKey}`),
3610
- TRONSCAN: new ApiGroup("tronscan", 0.3),
3611
- GETBLOCK: new ApiGroup("getblock", 0.3),
3612
- COINCAP: new ApiGroup("coincap", 0.5),
3613
- // 200 per minute without API key
3614
- COINGECKO: new ApiGroup("coingecko", 0.9),
3615
- // actually 0.13-0.5 according to the docs but we use smaller due to expirienced frequent abuses
3616
- MESSARI: new ApiGroup("messari", 0.2),
3617
- BTCCOM: new ApiGroup("btccom", 0.2),
3618
- BITAPS: new ApiGroup("bitaps", 0.25),
3619
- // Docs say that RPS is 3 but using it causes frequent 429 HTTP errors
3620
- CEX: new ApiGroup("cex", 0.5),
3621
- // Just assumption for RPS
3622
- BIGDATACLOUD: new ApiGroup("bigdatacloud", 1),
3623
- // Just assumption for RPS
3624
- TRACKIP: new ApiGroup("trackip", 1),
3625
- // Just assumption for RPS
3626
- IPIFY: new ApiGroup("ipify", 1),
3627
- // Just assumption for RPS
3628
- WHATISMYIPADDRESS: new ApiGroup("whatismyipaddress", 1),
3629
- // Just assumption for RPS
3630
- EXCHANGERATE: new ApiGroup("exchangerate", 1),
3631
- // Just assumption for RPS
3632
- FRANKFURTER: new ApiGroup("frankfurter", 1),
3633
- // Just assumption for RPS
3634
- BITGO: new ApiGroup("bitgo", 1),
3635
- // Just assumption for RPS
3636
- BITCOINER: new ApiGroup("bitcoiner", 1),
3637
- // Just assumption for RPS
3638
- BITCORE: new ApiGroup("bitcore", 1),
3639
- // Just assumption for RPS
3640
- // BLOCKCHAIR: new ApiGroup("blockchair", 0.04), // this provider require API key for commercial use (10usd 10000 reqs), we will add it later
3641
- MEMPOOL: new ApiGroup("mempool", 0.2) // Just assumption for RPS
3642
- };
3643
3757
 
3644
3758
  class ExistingSwap {
3645
3759
  /**
@@ -3649,6 +3763,7 @@ class ExistingSwap {
3649
3763
  * @param expiresAt {number}
3650
3764
  * @param confirmations {number}
3651
3765
  * @param rate {string}
3766
+ * @param fixed {boolean}
3652
3767
  * @param refundAddress {string}
3653
3768
  * @param payToAddress {string}
3654
3769
  * @param fromCoin {Coin}
@@ -3665,7 +3780,7 @@ class ExistingSwap {
3665
3780
  * @param toExtraId {string}
3666
3781
  * @param refundExtraId {string}
3667
3782
  */
3668
- constructor(swapId, status, createdAt, expiresAt, confirmations, rate, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress,
3783
+ constructor(swapId, status, createdAt, expiresAt, confirmations, rate, fixed, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress,
3669
3784
  // TODO: [refactoring, moderate] toAddress is not quite clear. How about recipientAddress? task_id=0815a111c99543b78d374217eadbde4f
3670
3785
  partner, fromExtraId, toExtraId, refundExtraId) {
3671
3786
  this.swapId = swapId;
@@ -3674,6 +3789,7 @@ class ExistingSwap {
3674
3789
  this.expiresAt = expiresAt;
3675
3790
  this.confirmations = confirmations;
3676
3791
  this.rate = rate;
3792
+ this.fixed = fixed;
3677
3793
  this.refundAddress = refundAddress;
3678
3794
  this.payToAddress = payToAddress;
3679
3795
  this.fromCoin = fromCoin;
@@ -3700,6 +3816,7 @@ class ExistingSwapWithFiatData extends ExistingSwap {
3700
3816
  * @param expiresAt {number}
3701
3817
  * @param confirmations {number}
3702
3818
  * @param rate {string}
3819
+ * @param fixed {boolean}
3703
3820
  * @param refundAddress {string}
3704
3821
  * @param payToAddress {string}
3705
3822
  * @param fromCoin {Coin}
@@ -3720,8 +3837,8 @@ class ExistingSwapWithFiatData extends ExistingSwap {
3720
3837
  * @param fiatCurrencyCode {string}
3721
3838
  * @param fiatCurrencyDecimals {number}
3722
3839
  */
3723
- constructor(swapId, status, createdAt, expiresAt, confirmations, rate, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress, partner, fromExtraId, toExtraId, refundExtraId, fromAmountFiat, toAmountFiat, fiatCurrencyCode, fiatCurrencyDecimals) {
3724
- super(swapId, status, createdAt, expiresAt, confirmations, rate, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress, partner, fromExtraId, toExtraId, refundExtraId);
3840
+ constructor(swapId, status, createdAt, expiresAt, confirmations, rate, fixed, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress, partner, fromExtraId, toExtraId, refundExtraId, fromAmountFiat, toAmountFiat, fiatCurrencyCode, fiatCurrencyDecimals) {
3841
+ super(swapId, status, createdAt, expiresAt, confirmations, rate, fixed, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress, partner, fromExtraId, toExtraId, refundExtraId);
3725
3842
  this.fromAmountFiat = fromAmountFiat;
3726
3843
  this.toAmountFiat = toAmountFiat;
3727
3844
  this.fiatCurrencyCode = fiatCurrencyCode;
@@ -3737,7 +3854,7 @@ class ExistingSwapWithFiatData extends ExistingSwap {
3737
3854
  * @return {ExistingSwapWithFiatData}
3738
3855
  */
3739
3856
  static fromExistingSwap(existingSwap, fromAmountFiat, toAmountFiat, fiatCurrencyCode, fiatCurrencyDecimals) {
3740
- return new ExistingSwapWithFiatData(existingSwap.swapId, existingSwap.status, existingSwap.createdAt, existingSwap.expiresAt, existingSwap.confirmations, existingSwap.rate, existingSwap.refundAddress, existingSwap.payToAddress, existingSwap.fromCoin, existingSwap.fromAmount, existingSwap.fromTransactionId, existingSwap.fromTransactionLink, existingSwap.toCoin, existingSwap.toAmount, existingSwap.toTransactionId, existingSwap.toTransactionLink, existingSwap.toAddress, existingSwap.partner, existingSwap.fromExtraId, existingSwap.toExtraId, existingSwap.refundExtraId, fromAmountFiat, toAmountFiat, fiatCurrencyCode, fiatCurrencyDecimals);
3857
+ return new ExistingSwapWithFiatData(existingSwap.swapId, existingSwap.status, existingSwap.createdAt, existingSwap.expiresAt, existingSwap.confirmations, existingSwap.rate, existingSwap.fixed, existingSwap.refundAddress, existingSwap.payToAddress, existingSwap.fromCoin, existingSwap.fromAmount, existingSwap.fromTransactionId, existingSwap.fromTransactionLink, existingSwap.toCoin, existingSwap.toAmount, existingSwap.toTransactionId, existingSwap.toTransactionLink, existingSwap.toAddress, existingSwap.partner, existingSwap.fromExtraId, existingSwap.toExtraId, existingSwap.refundExtraId, fromAmountFiat, toAmountFiat, fiatCurrencyCode, fiatCurrencyDecimals);
3741
3858
  }
3742
3859
  }
3743
3860
 
@@ -3754,8 +3871,9 @@ class BaseSwapCreationInfo {
3754
3871
  * @param max {string}
3755
3872
  * @param fiatMax {number}
3756
3873
  * @param durationMinutesRange {string}
3874
+ * @param fixed {boolean}
3757
3875
  */
3758
- constructor(fromCoin, toCoin, fromAmountCoins, toAmountCoins, rate, rawSwapData, min, fiatMin, max, fiatMax, durationMinutesRange) {
3876
+ constructor(fromCoin, toCoin, fromAmountCoins, toAmountCoins, rate, rawSwapData, min, fiatMin, max, fiatMax, durationMinutesRange, fixed) {
3759
3877
  this.fromCoin = fromCoin;
3760
3878
  this.toCoin = toCoin;
3761
3879
  this.fromAmountCoins = fromAmountCoins;
@@ -3767,6 +3885,7 @@ class BaseSwapCreationInfo {
3767
3885
  this.max = max;
3768
3886
  this.fiatMax = fiatMax;
3769
3887
  this.durationMinutesRange = durationMinutesRange;
3888
+ this.fixed = fixed;
3770
3889
  }
3771
3890
  }
3772
3891
 
@@ -3845,6 +3964,7 @@ class SwapProvider {
3845
3964
  * @param fromCoin {Coin}
3846
3965
  * @param toCoin {Coin}
3847
3966
  * @param amountCoins {string}
3967
+ * @param [fixed=false] {boolean|null} null means fixed or float doesn't matter
3848
3968
  * @param [fromCoinToUsdRate=null] pass if you want to increase the min amount returned
3849
3969
  * by provider with some fixed "insurance" amount to cover min amount fluctuations.
3850
3970
  * @return {Promise<({
@@ -3860,15 +3980,16 @@ class SwapProvider {
3860
3980
  * greatestMax: (string|null),
3861
3981
  * rate: (string|null),
3862
3982
  * durationMinutesRange: string,
3983
+ * fixed: boolean,
3863
3984
  * [rawSwapData]: Object
3864
3985
  * })>}
3865
3986
  */
3866
- async getSwapInfo(fromCoin, toCoin, amountCoins, fromCoinToUsdRate = null) {
3987
+ async getSwapInfo(fromCoin, toCoin, amountCoins, fixed = false, fromCoinToUsdRate = null) {
3867
3988
  throw new Error("Not implemented in base");
3868
3989
  }
3869
3990
 
3870
3991
  /**
3871
- * For fail result we return one of SwapProvider.CREATION_FAIL_REASONS or SwapProvider.COMMON_ERRORS.
3992
+ * For fail result we return one of SwapProvider.CREATION_FAIL_REASONS or SwapProvider.COMMON_ERRORS.*
3872
3993
  *
3873
3994
  * @param fromCoin {Coin}
3874
3995
  * @param toCoin {Coin}
@@ -3877,6 +3998,7 @@ class SwapProvider {
3877
3998
  * @param refundAddress {string}
3878
3999
  * @param rawSwapData {Object|null}
3879
4000
  * @param clientIpAddress {string}
4001
+ * @param fixed {boolean}
3880
4002
  * @param [toCurrencyExtraId=""] {string} optional extra ID
3881
4003
  * @param [refundExtraId=""] {string} optional extra ID for refund address
3882
4004
  * @return {Promise<({
@@ -3889,14 +4011,15 @@ class SwapProvider {
3889
4011
  * toAmount: string,
3890
4012
  * toAddress: string,
3891
4013
  * rate: string,
3892
- * fromCurrencyExtraId: string|undefined
4014
+ * fromCurrencyExtraId: string|undefined,
4015
+ * fixed: boolean
3893
4016
  * }|{
3894
4017
  * result: false,
3895
4018
  * reason: string,
3896
4019
  * partner: string
3897
4020
  * })>}
3898
4021
  */
3899
- async createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData = null, clientIpAddress, toCurrencyExtraId = "", refundExtraId = "") {
4022
+ async createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData, clientIpAddress, fixed, toCurrencyExtraId = "", refundExtraId = "") {
3900
4023
  throw new Error("Not implemented in base");
3901
4024
  }
3902
4025
 
@@ -3942,7 +4065,9 @@ SwapProvider.COMMON_ERRORS = {
3942
4065
  SwapProvider.NO_SWAPS_REASONS = {
3943
4066
  TOO_LOW: "tooLow",
3944
4067
  TOO_HIGH: "tooHigh",
3945
- NOT_SUPPORTED: "notSupported"
4068
+ NOT_SUPPORTED: "notSupported",
4069
+ NO_FIXED_BUT_HAVE_FLOATING: "noFixedButHaveFloating",
4070
+ NO_FLOATING_BUT_HAVE_FIXED: "noFloatingButHaveFixed"
3946
4071
  };
3947
4072
  SwapProvider.CREATION_FAIL_REASONS = {
3948
4073
  RETRIABLE_FAIL: "retriableFail"
@@ -4093,7 +4218,6 @@ class SwapspaceSwapProvider extends SwapProvider {
4093
4218
  const ticker = `${code}${code === network ? "" : network}`;
4094
4219
  const defaultDecimalPlacesForCoinNotSupportedOOB = 8;
4095
4220
  const defaultMinConfirmationsForCoinNotSupportedOOB = 1;
4096
- // TODO: [dev] maybe we should recognize standard protocols?
4097
4221
  coin = new Coin(item.name, ticker, code, defaultDecimalPlacesForCoinNotSupportedOOB, null, "", null, null, defaultMinConfirmationsForCoinNotSupportedOOB, null, [], 60000, null,
4098
4222
  // We cannot recognize blockchain from swapspace data
4099
4223
  code !== network ? new Protocol(network) : null, item.contractAddress || null, false);
@@ -4106,7 +4230,7 @@ class SwapspaceSwapProvider extends SwapProvider {
4106
4230
  network: item.network,
4107
4231
  hasExtraId: item.hasExtraId,
4108
4232
  extraIdName: item.extraIdName,
4109
- isPopular: !!(item != null && item.popular),
4233
+ isPopular: !!item.popular,
4110
4234
  iconURL: item.icon ? `https://storage.swapspace.co${item.icon}` : FALLBACK_ICON_URL,
4111
4235
  deposit: (_item$deposit = item.deposit) != null ? _item$deposit : false,
4112
4236
  withdrawal: (_item$withdrawal = item.withdrawal) != null ? _item$withdrawal : false,
@@ -4156,7 +4280,7 @@ class SwapspaceSwapProvider extends SwapProvider {
4156
4280
  };
4157
4281
  }
4158
4282
  Logger.log("Loading USDT->coin rate as not found in cache:", coin == null ? void 0 : coin.ticker);
4159
- const result = await this.getSwapInfo(usdtTrc20, coin, "5000");
4283
+ const result = await this.getSwapInfo(usdtTrc20, coin, "5000", false);
4160
4284
  if (!result.result) {
4161
4285
  return {
4162
4286
  result: false
@@ -4164,7 +4288,7 @@ class SwapspaceSwapProvider extends SwapProvider {
4164
4288
  }
4165
4289
 
4166
4290
  // This calculation is not precise as we cannot recognize the actual fee and network fee. Just approximate.
4167
- const standardSwapspaceFeeMultiplier = 1.004; // usually 0.2%
4291
+ const standardSwapspaceFeeMultiplier = 1.004; // fee is usually 0.4%
4168
4292
  const rate = BigNumber(1).div(BigNumber(result.rate).times(standardSwapspaceFeeMultiplier)).toString();
4169
4293
  this._cache.put("swapspace_usdt_rate_" + coin.ticker, rate, 15 * 60000 // 15 minutes
4170
4294
  );
@@ -4185,12 +4309,12 @@ class SwapspaceSwapProvider extends SwapProvider {
4185
4309
  improveAndRethrow(e, "getCoinByTickerIfPresent");
4186
4310
  }
4187
4311
  }
4188
- async getSwapInfo(fromCoin, toCoin, amountCoins, fromCoinToUsdRate = null) {
4312
+ async getSwapInfo(fromCoin, toCoin, amountCoins, fixed = false, fromCoinToUsdRate = null) {
4189
4313
  const loggerSource = "getSwapInfo";
4190
4314
  try {
4191
- var _response$data;
4192
- if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof amountCoins !== "string" || BigNumber(amountCoins).lt("0")) {
4193
- throw new Error(`Wrong input params: ${amountCoins} ${fromCoin.ticker} -> ${toCoin.ticker}` + (fromCoin instanceof Coin) + (toCoin instanceof Coin));
4315
+ var _response$data, _exchangesSupportingT;
4316
+ if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof amountCoins !== "string" || BigNumber(amountCoins).lt("0") || fixed !== null && typeof fixed !== "boolean") {
4317
+ throw new Error(`Wrong input params: ${amountCoins} ${fromCoin.ticker} -> ${toCoin.ticker}, ${fromCoin instanceof Coin}, ${toCoin instanceof Coin}, ${typeof fixed} ${fixed}`);
4194
4318
  }
4195
4319
  const fromCoinSwapspaceDetails = this._supportedCoins.find(i => {
4196
4320
  var _i$coin;
@@ -4213,12 +4337,22 @@ class SwapspaceSwapProvider extends SwapProvider {
4213
4337
  * cached rate values stored in swapspace cache. Their support says they store at most for 30 sec.
4214
4338
  * But we are better off using the most actual rates.
4215
4339
  */
4216
- const response = await axios.get(`${this._URL}/api/v2/amounts?fromCurrency=${fromCoinSwapspaceDetails.code}&fromNetwork=${fromCoinSwapspaceDetails.network}&toNetwork=${toCoinSwapspaceDetails.network}&toCurrency=${toCoinSwapspaceDetails.code}&amount=${amountCoins}&float=true&estimated=false`);
4340
+ const response = await axios.get(`${this._URL}/api/v2/amounts?fromCurrency=${fromCoinSwapspaceDetails.code}&fromNetwork=${fromCoinSwapspaceDetails.network}&toNetwork=${toCoinSwapspaceDetails.network}&toCurrency=${toCoinSwapspaceDetails.code}&amount=${amountCoins}&estimated=false`);
4217
4341
  Logger.log(`Retrieved ${response == null || (_response$data = response.data) == null ? void 0 : _response$data.length} options`, loggerSource);
4218
4342
  const options = Array.isArray(response.data) ? response.data : [];
4219
- const exchangesSupportingThePair = options.filter(exchange => (exchange == null ? void 0 : exchange.exists) && !BANNED_PARTNERS.find(bannedPartner => bannedPartner === (exchange == null ? void 0 : exchange.partner)) && (exchange == null ? void 0 : exchange.fixed) === false && (exchange.min === 0 || exchange.max === 0 || exchange.max > exchange.min || (typeof exchange.min !== "number" || typeof exchange.max !== "number") && exchange.toAmount > 0));
4220
- Logger.log(`${exchangesSupportingThePair == null ? void 0 : exchangesSupportingThePair.length} of them have exist=true`, loggerSource);
4221
- if (!exchangesSupportingThePair.length) {
4343
+ let exchangesSupportingThePairDespiteFixedOrFloating = options.filter(exchange => (exchange == null ? void 0 : exchange.exists) && !BANNED_PARTNERS.find(bannedPartner => bannedPartner === (exchange == null ? void 0 : exchange.partner)) && ((exchange == null ? void 0 : exchange.fixed) === false || (exchange == null ? void 0 : exchange.fixed) === true) && (exchange.min === 0 || exchange.max === 0 || exchange.max > exchange.min || (typeof exchange.min !== "number" || typeof exchange.max !== "number") && exchange.toAmount > 0));
4344
+ let exchangesSupportingThePair = exchangesSupportingThePairDespiteFixedOrFloating;
4345
+ if (fixed != null) {
4346
+ exchangesSupportingThePair = exchangesSupportingThePairDespiteFixedOrFloating.filter(option => option.fixed === fixed);
4347
+ }
4348
+ Logger.log(`${(_exchangesSupportingT = exchangesSupportingThePair) == null ? void 0 : _exchangesSupportingT.length} of them have exist=true`, loggerSource);
4349
+ if (exchangesSupportingThePair.length === 0) {
4350
+ if (exchangesSupportingThePairDespiteFixedOrFloating.length > 0 && fixed !== null) {
4351
+ return {
4352
+ result: false,
4353
+ reason: fixed ? SwapProvider.NO_SWAPS_REASONS.NO_FIXED_BUT_HAVE_FLOATING : SwapProvider.NO_SWAPS_REASONS.NO_FLOATING_BUT_HAVE_FIXED
4354
+ };
4355
+ }
4222
4356
  return {
4223
4357
  result: false,
4224
4358
  reason: SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED
@@ -4281,6 +4415,7 @@ class SwapspaceSwapProvider extends SwapProvider {
4281
4415
  greatestMax: greatestMax,
4282
4416
  rate: rate != null ? AmountUtils.trim(rate, this._maxRateDigits) : null,
4283
4417
  durationMinutesRange: (_bestOpt$duration = bestOpt.duration) != null ? _bestOpt$duration : null,
4418
+ fixed: bestOpt.fixed,
4284
4419
  rawSwapData: bestOpt
4285
4420
  };
4286
4421
  }
@@ -4304,17 +4439,18 @@ class SwapspaceSwapProvider extends SwapProvider {
4304
4439
  improveAndRethrow(e, loggerSource);
4305
4440
  }
4306
4441
  }
4307
- async createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData, clientIpAddress, toCurrencyExtraId = "", refundExtraId = "") {
4442
+ async createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData, clientIpAddress, fixed, toCurrencyExtraId = "", refundExtraId = "") {
4308
4443
  const loggerSource = "createSwap";
4309
4444
  const partner = rawSwapData == null ? void 0 : rawSwapData.partner;
4310
4445
  try {
4311
- if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof amount !== "string" || typeof toAddress !== "string" || typeof refundAddress !== "string") {
4312
- throw new Error(`Invalid input: ${fromCoin} ${toCoin} ${amount} ${toAddress} ${refundAddress}`);
4446
+ if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof amount !== "string" || typeof toAddress !== "string" || typeof refundAddress !== "string" || typeof clientIpAddress != "string" || typeof fixed != "boolean" || clientIpAddress.length === 0) {
4447
+ throw new Error(`Invalid input: ${fromCoin} ${toCoin} ${amount} ${toAddress} ${refundAddress} ${clientIpAddress == null ? void 0 : clientIpAddress.length} ${fixed}`);
4313
4448
  }
4314
4449
  if (typeof partner !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.fromCurrency) !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.fromNetwork) !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.toCurrency) !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.toNetwork) !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.id) !== "string" // can be just empty
4315
4450
  ) {
4316
4451
  throw new Error(`Invalid raw swap data: ${safeStringify(rawSwapData)}`);
4317
4452
  }
4453
+ const [fromCurrencyHasExtraId, toCurrencyHasExtraId] = this._supportedCoins.reduce((prev, coinData) => [coinData.coin.ticker === fromCoin.ticker ? coinData.hasExtraId : prev[0], coinData.coin.ticker === toCoin.ticker ? coinData.hasExtraId : prev[1]], [false, false]);
4318
4454
  await this._fetchSupportedCurrenciesIfNeeded();
4319
4455
  const requestData = {
4320
4456
  partner: partner,
@@ -4324,10 +4460,10 @@ class SwapspaceSwapProvider extends SwapProvider {
4324
4460
  toNetwork: rawSwapData == null ? void 0 : rawSwapData.toNetwork,
4325
4461
  address: toAddress,
4326
4462
  amount: amount,
4327
- fixed: false,
4328
- extraId: toCurrencyExtraId != null ? toCurrencyExtraId : "",
4329
- refundExtraId: refundExtraId != null ? refundExtraId : "",
4463
+ fixed: fixed,
4464
+ extraId: toCurrencyHasExtraId ? toCurrencyExtraId != null ? toCurrencyExtraId : "" : "",
4330
4465
  // This param is not documented. But the refund is usually manual so this is not critical.
4466
+ refundExtraId: fromCurrencyHasExtraId ? refundExtraId != null ? refundExtraId : "" : "",
4331
4467
  rateId: rawSwapData == null ? void 0 : rawSwapData.id,
4332
4468
  userIp: clientIpAddress,
4333
4469
  refund: refundAddress
@@ -4338,7 +4474,7 @@ class SwapspaceSwapProvider extends SwapProvider {
4338
4474
  Logger.log(`Creation result ${safeStringify(result)}`, loggerSource);
4339
4475
  if (result != null && result.id) {
4340
4476
  var _result$from, _result$from2, _result$to, _result$to2, _result$from4, _result$from5, _result$to4, _result$to5, _result$from$extraId, _result$from6;
4341
- if (typeof (result == null || (_result$from = result.from) == null ? void 0 : _result$from.amount) !== "number" || typeof (result == null || (_result$from2 = result.from) == null ? void 0 : _result$from2.address) !== "string" || typeof (result == null || (_result$to = result.to) == null ? void 0 : _result$to.amount) !== "number" || typeof (result == null || (_result$to2 = result.to) == null ? void 0 : _result$to2.address) !== "string") throw new Error(`Wrong swap creation result ${result}`);
4477
+ if (typeof (result == null || (_result$from = result.from) == null ? void 0 : _result$from.amount) !== "number" || typeof (result == null || (_result$from2 = result.from) == null ? void 0 : _result$from2.address) !== "string" || typeof (result == null ? void 0 : result.fixed) !== "boolean" || typeof (result == null || (_result$to = result.to) == null ? void 0 : _result$to.amount) !== "number" || typeof (result == null || (_result$to2 = result.to) == null ? void 0 : _result$to2.address) !== "string") throw new Error(`Wrong swap creation result ${result}`);
4342
4478
  /* We use the returned rate preferably but if the retrieved
4343
4479
  * rate 0/null/undefined we calculate it manually */
4344
4480
  let rate = result.rate;
@@ -4358,7 +4494,8 @@ class SwapspaceSwapProvider extends SwapProvider {
4358
4494
  toAmount: AmountUtils.trim(result == null || (_result$to4 = result.to) == null ? void 0 : _result$to4.amount, toCoin.digits),
4359
4495
  toAddress: result == null || (_result$to5 = result.to) == null ? void 0 : _result$to5.address,
4360
4496
  fromCurrencyExtraId: (_result$from$extraId = result == null || (_result$from6 = result.from) == null ? void 0 : _result$from6.extraId) != null ? _result$from$extraId : "",
4361
- rate: AmountUtils.trim(rate, this._maxRateDigits)
4497
+ rate: AmountUtils.trim(rate, this._maxRateDigits),
4498
+ fixed: result.fixed
4362
4499
  };
4363
4500
  }
4364
4501
  const errorMessage = `Swap creation succeeded but the response is wrong: ${safeStringify(response)}`;
@@ -4447,7 +4584,7 @@ class SwapspaceSwapProvider extends SwapProvider {
4447
4584
  const status = this._mapSwapspaceStatusToRabbitStatus(swap.status, isExpiredByTime);
4448
4585
  const toDigits = status === SwapProvider.SWAP_STATUSES.REFUNDED ? fromCoin.digits : toCoin.digits;
4449
4586
  const addressToSendCoinsToSwapspace = swap.from.address;
4450
- return new ExistingSwap(swapIds[index], status, toUtcTimestamp(swap.timestamps.createdAt), expiresAt, swap.confirmations, AmountUtils.trim(swap.rate, this._maxRateDigits), swap.refundAddress, addressToSendCoinsToSwapspace, fromCoin, AmountUtils.trim(swap.from.amount, fromCoin.digits), swap.from.transactionHash, swap.blockExplorerTransactionUrl.from, toCoin, AmountUtils.trim(swap.to.amount, toDigits), swap.to.transactionHash, swap.blockExplorerTransactionUrl.to, swap.to.address, swap.partner, (_swap$from$extraId = swap.from.extraId) != null ? _swap$from$extraId : null, (_swap$to$extraId = swap.to.extraId) != null ? _swap$to$extraId : null, (_swap$refundExtraId = swap.refundExtraId) != null ? _swap$refundExtraId : null);
4587
+ return new ExistingSwap(swapIds[index], status, toUtcTimestamp(swap.timestamps.createdAt), expiresAt, swap.confirmations, AmountUtils.trim(swap.rate, this._maxRateDigits), swap.fixed, swap.refundAddress, addressToSendCoinsToSwapspace, fromCoin, AmountUtils.trim(swap.from.amount, fromCoin.digits), swap.from.transactionHash, swap.blockExplorerTransactionUrl.from, toCoin, AmountUtils.trim(swap.to.amount, toDigits), swap.to.transactionHash, swap.blockExplorerTransactionUrl.to, swap.to.address, swap.partner, (_swap$from$extraId = swap.from.extraId) != null ? _swap$from$extraId : null, (_swap$to$extraId = swap.to.extraId) != null ? _swap$to$extraId : null, (_swap$refundExtraId = swap.refundExtraId) != null ? _swap$refundExtraId : null);
4451
4588
  }).flat();
4452
4589
  Logger.log(`Swap details result ${safeStringify(swaps)}`, loggerSource);
4453
4590
  return {
@@ -4744,6 +4881,7 @@ class PublicSwapService {
4744
4881
  * @param fromCoin {Coin}
4745
4882
  * @param toCoin {Coin}
4746
4883
  * @param fromAmountCoins {string}
4884
+ * @param [fixed=false] {boolean|null} null means fixed or float doesn't matter
4747
4885
  * @param [withoutFiat=false] {boolean} pass true if you don't need the fiat equivalent - this will diminish requests count
4748
4886
  * @return {Promise<{
4749
4887
  * result: false,
@@ -4758,12 +4896,12 @@ class PublicSwapService {
4758
4896
  * swapCreationInfo: BaseSwapCreationInfo
4759
4897
  * }>}
4760
4898
  */
4761
- async getPublicSwapDetails(fromCoin, toCoin, fromAmountCoins, withoutFiat = false) {
4899
+ async getPublicSwapDetails(fromCoin, toCoin, fromAmountCoins, fixed = false, withoutFiat = false) {
4762
4900
  const loggerSource = "getPublicSwapDetails";
4763
4901
  try {
4764
4902
  var _await$this$_swapProv, _await$this$_swapProv2, _result$swapCreationI, _result$swapCreationI2;
4765
4903
  const coinUsdtRate = withoutFiat ? null : (_await$this$_swapProv = (_await$this$_swapProv2 = await this._swapProvider.getCoinToUSDTRate(fromCoin)) == null ? void 0 : _await$this$_swapProv2.rate) != null ? _await$this$_swapProv : null;
4766
- const details = await this._swapProvider.getSwapInfo(fromCoin, toCoin, fromAmountCoins, coinUsdtRate);
4904
+ const details = await this._swapProvider.getSwapInfo(fromCoin, toCoin, fromAmountCoins, fixed, coinUsdtRate);
4767
4905
  const min = details.result ? details.min : details.smallestMin;
4768
4906
  const max = details.result ? details.max : details.greatestMax;
4769
4907
  let fiatMin = null,
@@ -4789,9 +4927,15 @@ class PublicSwapService {
4789
4927
  };
4790
4928
  };
4791
4929
  if (!details.result) {
4792
- if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED) return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.PAIR_NOT_SUPPORTED);else if ((details == null ? void 0 : details.reason) === SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED) {
4930
+ if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED) {
4931
+ return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.PAIR_NOT_SUPPORTED);
4932
+ } else if ((details == null ? void 0 : details.reason) === SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED) {
4793
4933
  SwapUtils.safeHandleRequestsLimitExceeding();
4794
4934
  return composeFailResult(PublicSwapService.PUBLIC_SWAPS_COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED);
4935
+ } else if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NO_FLOATING_BUT_HAVE_FIXED) {
4936
+ return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.NO_FLOATING_BUT_HAVE_FIXED_PUBLIC_SWAP_OPTION);
4937
+ } else if ((details == null ? void 0 : details.reason) === SwapProvider.NO_SWAPS_REASONS.NO_FIXED_BUT_HAVE_FLOATING) {
4938
+ return composeFailResult(PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS.NO_FIXED_BUT_HAVE_FLOATING_PUBLIC_SWAP_OPTION);
4795
4939
  }
4796
4940
  }
4797
4941
  const fromAmountBigNumber = BigNumber(fromAmountCoins);
@@ -4803,7 +4947,7 @@ class PublicSwapService {
4803
4947
  const toAmountCoins = AmountUtils.trim(fromAmountBigNumber.times(details.rate), fromCoin.digits);
4804
4948
  const result = {
4805
4949
  result: true,
4806
- swapCreationInfo: new BaseSwapCreationInfo(fromCoin, toCoin, fromAmountCoins, toAmountCoins, details.rate, details.rawSwapData, min, fiatMin, max, fiatMax, details.durationMinutesRange)
4950
+ swapCreationInfo: new BaseSwapCreationInfo(fromCoin, toCoin, fromAmountCoins, toAmountCoins, details.rate, details.rawSwapData, min, fiatMin, max, fiatMax, details.durationMinutesRange, details.fixed)
4807
4951
  };
4808
4952
  Logger.log(`Result: ${safeStringify({
4809
4953
  result: result.result,
@@ -4844,7 +4988,8 @@ class PublicSwapService {
4844
4988
  * fromCoin: Coin,
4845
4989
  * rate: string,
4846
4990
  * swapId: string,
4847
- * fromCurrencyExtraId: string
4991
+ * fromCurrencyExtraId: string,
4992
+ * fixed: boolean
4848
4993
  * }|{
4849
4994
  * result: false,
4850
4995
  * reason: string
@@ -4861,7 +5006,7 @@ class PublicSwapService {
4861
5006
  fromCoin: swapCreationInfo == null || (_swapCreationInfo$fro = swapCreationInfo.fromCoin) == null ? void 0 : _swapCreationInfo$fro.ticker,
4862
5007
  toCoin: swapCreationInfo == null || (_swapCreationInfo$toC = swapCreationInfo.toCoin) == null ? void 0 : _swapCreationInfo$toC.ticker
4863
5008
  }))}`, loggerSource);
4864
- const result = await this._swapProvider.createSwap(fromCoin, toCoin, fromAmount, toAddress, refundAddress, swapCreationInfo.rawSwapData, clientIp, toCurrencyExtraId, refundExtraId);
5009
+ const result = await this._swapProvider.createSwap(fromCoin, toCoin, fromAmount, toAddress, refundAddress, swapCreationInfo.rawSwapData, clientIp, swapCreationInfo.fixed, toCurrencyExtraId, refundExtraId);
4865
5010
  Logger.log(`Created:${safeStringify(_extends({}, result, {
4866
5011
  fromCoin: fromCoin == null ? void 0 : fromCoin.ticker,
4867
5012
  toCoin: toCoin == null ? void 0 : toCoin.ticker
@@ -4899,6 +5044,8 @@ class PublicSwapService {
4899
5044
  } catch (e) {
4900
5045
  Logger.logError(e, loggerSource, "Failed to calculate fiat amounts for result");
4901
5046
  }
5047
+
5048
+ // TODO: feature, cirtical] add GA event. task_id=tbd
4902
5049
  EventBusInstance.dispatch(PublicSwapService.PUBLIC_SWAP_CREATED_EVENT, null, fromCoin.ticker, toCoin.ticker, fromAmountFiat);
4903
5050
  const toReturn = {
4904
5051
  result: true,
@@ -4915,7 +5062,9 @@ class PublicSwapService {
4915
5062
  durationMinutesRange: swapCreationInfo.durationMinutesRange,
4916
5063
  address: result.fromAddress,
4917
5064
  // CRITICAL: this is the address to send coins to swaps provider
4918
- fromCurrencyExtraId: (_result$fromCurrencyE = result.fromCurrencyExtraId) != null ? _result$fromCurrencyE : "" // CRITICAL: this is the extra ID for address to send coins to swaps provider
5065
+ fromCurrencyExtraId: (_result$fromCurrencyE = result.fromCurrencyExtraId) != null ? _result$fromCurrencyE : "",
5066
+ // CRITICAL: this is the extra ID for address to send coins to swaps provider
5067
+ fixed: result.fixed
4919
5068
  };
4920
5069
  this._savePublicSwapIdLocally(result.swapId);
4921
5070
  Logger.log(`Returning: ${safeStringify(_extends({}, toReturn, {
@@ -5104,9 +5253,11 @@ PublicSwapService.PUBLIC_SWAPS_COMMON_ERRORS = {
5104
5253
  PublicSwapService.PUBLIC_SWAP_DETAILS_FAIL_REASONS = {
5105
5254
  AMOUNT_LESS_THAN_MIN_SWAPPABLE: "amountLessThanMinSwappable",
5106
5255
  AMOUNT_HIGHER_THAN_MAX_SWAPPABLE: "amountHigherThanMaxSwappable",
5107
- PAIR_NOT_SUPPORTED: "pairNotSupported"
5256
+ PAIR_NOT_SUPPORTED: "pairNotSupported",
5257
+ NO_FIXED_BUT_HAVE_FLOATING_PUBLIC_SWAP_OPTION: "noFixedButHaveFloatingPublicSwapOption",
5258
+ NO_FLOATING_BUT_HAVE_FIXED_PUBLIC_SWAP_OPTION: "noFloatingButHaveFixedPublicSwapOption"
5108
5259
  };
5109
5260
  PublicSwapService._fiatDecimalsCount = FiatCurrenciesService.getCurrencyDecimalCountByCode("USD");
5110
5261
 
5111
- export { AmountUtils, ApiGroup, ApiGroups, AssetIcon, AxiosAdapter, BaseSwapCreationInfo, Blockchain, Button, Cache, CacheAndConcurrentRequestsResolver, CachedRobustExternalApiCallerService, CancelProcessing, Coin, ConcurrentCalculationsMetadataHolder, EmailsApi, ExistingSwap, ExistingSwapWithFiatData, ExternalApiProvider, FiatCurrenciesService, LoadingDots, Logger, LogsStorage, Protocol, PublicSwapService, RobustExternalAPICallerService, SupportChat, SwapProvider, SwapUtils, SwapspaceSwapProvider, getQueryParameterSingleValue, getQueryParameterValues, handleClickOutside, improveAndRethrow, logErrorOrOutputToConsole, postponeExecution, removeQueryParameterAndValues, safeStringify, saveQueryParameterAndValues, useCallHandlingErrors, useReferredState };
5262
+ export { AmountUtils, ApiGroup, ApiGroups, AssetIcon, AxiosAdapter, BaseSwapCreationInfo, Blockchain, Button, Cache, CacheAndConcurrentRequestsResolver, CachedRobustExternalApiCallerService, CancelProcessing, Coin, ConcurrentCalculationsMetadataHolder, EmailsApi, ExistingSwap, ExistingSwapWithFiatData, ExternalApiProvider, FiatCurrenciesService, InputValuesProviders, IpAddressProvider, LoadingDots, Logger, LogsStorage, Protocol, PublicSwapService, RobustExternalAPICallerService, SupportChat, SwapProvider, SwapUtils, SwapspaceSwapProvider, getQueryParameterSingleValue, getQueryParameterValues, handleClickOutside, improveAndRethrow, logErrorOrOutputToConsole, postponeExecution, removeQueryParameterAndValues, safeStringify, saveQueryParameterAndValues, useCallHandlingErrors, useReferredState };
5112
5263
  //# sourceMappingURL=index.modern.js.map