@rabbitio/ui-kit 1.0.0-beta.113 → 1.0.0-beta.114

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 (204) hide show
  1. package/coverage/clover.xml +2142 -1372
  2. package/coverage/coverage-final.json +17 -16
  3. package/coverage/index.html +29 -29
  4. package/coverage/ui-kit/index.html +1 -1
  5. package/coverage/ui-kit/index.js.html +1 -1
  6. package/coverage/ui-kit/src/common-apis/adapters/analyticsAdapters/googleAnalyticsAdapter.js.html +1 -1
  7. package/coverage/ui-kit/src/common-apis/adapters/analyticsAdapters/index.html +1 -1
  8. package/coverage/ui-kit/src/common-apis/adapters/analyticsAdapters/metrikaAdapter.js.html +1 -1
  9. package/coverage/ui-kit/src/common-apis/adapters/analyticsAdapters/mixpanelAdapter.js.html +1 -1
  10. package/coverage/ui-kit/src/common-apis/adapters/axiosAdapter.js.html +1 -1
  11. package/coverage/ui-kit/src/common-apis/adapters/index.html +1 -1
  12. package/coverage/ui-kit/src/common-apis/adapters/qrUtils.js.html +1 -1
  13. package/coverage/ui-kit/src/common-apis/external-apis/apiGroups.js.html +1 -1
  14. package/coverage/ui-kit/src/common-apis/external-apis/emailAPI.js.html +1 -1
  15. package/coverage/ui-kit/src/common-apis/external-apis/index.html +1 -1
  16. package/coverage/ui-kit/src/common-apis/external-apis/ipAddressProviders.js.html +1 -1
  17. package/coverage/ui-kit/src/common-apis/globalConstants.jsx.html +1 -1
  18. package/coverage/ui-kit/src/common-apis/index.html +1 -1
  19. package/coverage/ui-kit/src/common-apis/models/blockchain.js.html +1 -1
  20. package/coverage/ui-kit/src/common-apis/models/coin.js.html +221 -221
  21. package/coverage/ui-kit/src/common-apis/models/index.html +1 -1
  22. package/coverage/ui-kit/src/common-apis/models/protocol.js.html +3 -3
  23. package/coverage/ui-kit/src/common-apis/services/fiatCurrenciesService.js.html +1 -1
  24. package/coverage/ui-kit/src/common-apis/services/index.html +1 -1
  25. package/coverage/ui-kit/src/common-apis/utils/amountUtils.js.html +21 -21
  26. package/coverage/ui-kit/src/common-apis/utils/cache.js.html +6 -6
  27. package/coverage/ui-kit/src/common-apis/utils/errorUtils.js.html +13 -13
  28. package/coverage/ui-kit/src/common-apis/utils/index.html +15 -15
  29. package/coverage/ui-kit/src/common-apis/utils/logging/index.html +1 -1
  30. package/coverage/ui-kit/src/common-apis/utils/logging/logger.js.html +27 -27
  31. package/coverage/ui-kit/src/common-apis/utils/logging/logsStorage.js.html +3 -3
  32. package/coverage/ui-kit/src/common-apis/utils/postponeExecution.js.html +1 -1
  33. package/coverage/ui-kit/src/common-apis/utils/rabbitTicker.js.html +10 -10
  34. package/coverage/ui-kit/src/common-apis/utils/safeStringify.js.html +53 -53
  35. package/coverage/ui-kit/src/index.html +5 -5
  36. package/coverage/ui-kit/src/index.js.html +7 -4
  37. package/coverage/ui-kit/src/robust-api-caller/cacheAndConcurrentRequestsResolver.js.html +1 -1
  38. package/coverage/ui-kit/src/robust-api-caller/cachedRobustExternalApiCallerService.js.html +1 -1
  39. package/coverage/ui-kit/src/robust-api-caller/cancelProcessing.js.html +1 -1
  40. package/coverage/ui-kit/src/robust-api-caller/concurrentCalculationsMetadataHolder.js.html +1 -1
  41. package/coverage/ui-kit/src/robust-api-caller/externalApiProvider.js.html +1 -1
  42. package/coverage/ui-kit/src/robust-api-caller/externalServicesStatsCollector.js.html +1 -1
  43. package/coverage/ui-kit/src/robust-api-caller/index.html +1 -1
  44. package/coverage/ui-kit/src/robust-api-caller/robustExternalAPICallerService.js.html +1 -1
  45. package/coverage/ui-kit/src/swaps-lib/external-apis/changeNowSwapProvider.js.html +2323 -0
  46. package/coverage/ui-kit/src/swaps-lib/external-apis/exolixSwapProvider.js.html +1 -1
  47. package/coverage/ui-kit/src/swaps-lib/external-apis/goexmeSwapProvider.js.html +373 -313
  48. package/coverage/ui-kit/src/swaps-lib/external-apis/index.html +34 -19
  49. package/coverage/ui-kit/src/swaps-lib/external-apis/letsExchangeSwapProvider.js.html +1 -1
  50. package/coverage/ui-kit/src/swaps-lib/external-apis/swapProvider.js.html +327 -327
  51. package/coverage/ui-kit/src/swaps-lib/external-apis/swapspaceSwapProvider.js.html +1 -1
  52. package/coverage/ui-kit/src/swaps-lib/external-apis/utils.js.html +14 -14
  53. package/coverage/ui-kit/src/swaps-lib/models/baseSwapCreationInfo.js.html +1 -1
  54. package/coverage/ui-kit/src/swaps-lib/models/existingSwap.js.html +140 -140
  55. package/coverage/ui-kit/src/swaps-lib/models/existingSwapWithFiatData.js.html +1 -1
  56. package/coverage/ui-kit/src/swaps-lib/models/index.html +1 -1
  57. package/coverage/ui-kit/src/swaps-lib/models/partner.js.html +21 -21
  58. package/coverage/ui-kit/src/swaps-lib/models/swapProviderCoinInfo.js.html +73 -73
  59. package/coverage/ui-kit/src/swaps-lib/services/index.html +1 -1
  60. package/coverage/ui-kit/src/swaps-lib/services/publicSwapService.js.html +1 -1
  61. package/coverage/ui-kit/src/swaps-lib/utils/index.html +1 -1
  62. package/coverage/ui-kit/src/swaps-lib/utils/swapUtils.js.html +1 -1
  63. package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/arrowIcon.jsx.html +1 -1
  64. package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/arrowTosca.jsx.html +1 -1
  65. package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/arrowWhite.jsx.html +1 -1
  66. package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/darkRectangle.jsx.html +1 -1
  67. package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/determinedError.jsx.html +1 -1
  68. package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/failedValidationIcon.jsx.html +1 -1
  69. package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/index.html +1 -1
  70. package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/infoIcon.jsx.html +1 -1
  71. package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/messageIcon.jsx.html +1 -1
  72. package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/noticeQuestionIcon.jsx.html +1 -1
  73. package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/successfulValidationIcon.jsx.html +1 -1
  74. package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/supportDialogImage.jsx.html +1 -1
  75. package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/walletIcon.jsx.html +1 -1
  76. package/coverage/ui-kit/src/ui-kit/components/atoms/AssetIcon/AssetIcon.jsx.html +1 -1
  77. package/coverage/ui-kit/src/ui-kit/components/atoms/AssetIcon/index.html +1 -1
  78. package/coverage/ui-kit/src/ui-kit/components/atoms/AssetSelection/AssetSelection.jsx.html +1 -1
  79. package/coverage/ui-kit/src/ui-kit/components/atoms/AssetSelection/index.html +1 -1
  80. package/coverage/ui-kit/src/ui-kit/components/atoms/BackgroundTitle/BackgroundTitle.jsx.html +1 -1
  81. package/coverage/ui-kit/src/ui-kit/components/atoms/BackgroundTitle/index.html +1 -1
  82. package/coverage/ui-kit/src/ui-kit/components/atoms/InformationMessage/InformationMessage.jsx.html +1 -1
  83. package/coverage/ui-kit/src/ui-kit/components/atoms/InformationMessage/index.html +1 -1
  84. package/coverage/ui-kit/src/ui-kit/components/atoms/Input/Input.jsx.html +1 -1
  85. package/coverage/ui-kit/src/ui-kit/components/atoms/Input/index.html +1 -1
  86. package/coverage/ui-kit/src/ui-kit/components/atoms/LoadingDots/LoadingDots.jsx.html +1 -1
  87. package/coverage/ui-kit/src/ui-kit/components/atoms/LoadingDots/index.html +1 -1
  88. package/coverage/ui-kit/src/ui-kit/components/atoms/NoticeIcon/NoticeIcon.jsx.html +1 -1
  89. package/coverage/ui-kit/src/ui-kit/components/atoms/NoticeIcon/index.html +1 -1
  90. package/coverage/ui-kit/src/ui-kit/components/atoms/QrCode/QrCode.jsx.html +1 -1
  91. package/coverage/ui-kit/src/ui-kit/components/atoms/QrCode/index.html +1 -1
  92. package/coverage/ui-kit/src/ui-kit/components/atoms/RateSelector/RateSelector.jsx.html +1 -1
  93. package/coverage/ui-kit/src/ui-kit/components/atoms/RateSelector/index.html +1 -1
  94. package/coverage/ui-kit/src/ui-kit/components/atoms/SupportChat/SupportChat.jsx.html +1 -1
  95. package/coverage/ui-kit/src/ui-kit/components/atoms/SupportChat/index.html +1 -1
  96. package/coverage/ui-kit/src/ui-kit/components/atoms/Textarea/Textarea.jsx.html +1 -1
  97. package/coverage/ui-kit/src/ui-kit/components/atoms/Textarea/index.html +1 -1
  98. package/coverage/ui-kit/src/ui-kit/components/atoms/TitleBox/TitleBox.jsx.html +1 -1
  99. package/coverage/ui-kit/src/ui-kit/components/atoms/TitleBox/index.html +1 -1
  100. package/coverage/ui-kit/src/ui-kit/components/atoms/Tooltip/Tooltip.jsx.html +1 -1
  101. package/coverage/ui-kit/src/ui-kit/components/atoms/Tooltip/index.html +1 -1
  102. package/coverage/ui-kit/src/ui-kit/components/atoms/TwoLinesOfText/LinesOfText.jsx.html +1 -1
  103. package/coverage/ui-kit/src/ui-kit/components/atoms/TwoLinesOfText/index.html +1 -1
  104. package/coverage/ui-kit/src/ui-kit/components/atoms/Validation/Validation.jsx.html +1 -1
  105. package/coverage/ui-kit/src/ui-kit/components/atoms/Validation/index.html +1 -1
  106. package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/Button/Button.jsx.html +1 -1
  107. package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/Button/index.html +1 -1
  108. package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/Close/Close.jsx.html +1 -1
  109. package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/Close/index.html +1 -1
  110. package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/LinkButton/LinkButton.jsx.html +1 -1
  111. package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/LinkButton/index.html +1 -1
  112. package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/RadioButtonWithText/RadioButtonWithText.jsx.html +1 -1
  113. package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/RadioButtonWithText/index.html +1 -1
  114. package/coverage/ui-kit/src/ui-kit/components/molecules/AmountInput/AmountInput.jsx.html +1 -1
  115. package/coverage/ui-kit/src/ui-kit/components/molecules/AmountInput/index.html +1 -1
  116. package/coverage/ui-kit/src/ui-kit/components/molecules/CoinPicker/CoinPicker.jsx.html +1 -1
  117. package/coverage/ui-kit/src/ui-kit/components/molecules/CoinPicker/index.html +1 -1
  118. package/coverage/ui-kit/src/ui-kit/components/molecules/ColoredNotice/ColoredNotice.jsx.html +1 -1
  119. package/coverage/ui-kit/src/ui-kit/components/molecules/ColoredNotice/index.html +1 -1
  120. package/coverage/ui-kit/src/ui-kit/components/molecules/LineWithIconLink/LineWithIconLink.jsx.html +1 -1
  121. package/coverage/ui-kit/src/ui-kit/components/molecules/LineWithIconLink/index.html +1 -1
  122. package/coverage/ui-kit/src/ui-kit/components/molecules/LogoCarousel/LogoCarousel.jsx.html +1 -1
  123. package/coverage/ui-kit/src/ui-kit/components/molecules/LogoCarousel/index.html +1 -1
  124. package/coverage/ui-kit/src/ui-kit/components/molecules/SearchableCoinsList/SearchableCoinsList.jsx.html +1 -1
  125. package/coverage/ui-kit/src/ui-kit/components/molecules/SearchableCoinsList/index.html +1 -1
  126. package/coverage/ui-kit/src/ui-kit/components/molecules/TitledLineWithIconLink/TitledLineWithIconLink.jsx.html +1 -1
  127. package/coverage/ui-kit/src/ui-kit/components/molecules/TitledLineWithIconLink/index.html +1 -1
  128. package/coverage/ui-kit/src/ui-kit/components/organisms/CoinPickerDialogStep/CoinPickerDialogStep.jsx.html +1 -1
  129. package/coverage/ui-kit/src/ui-kit/components/organisms/CoinPickerDialogStep/index.html +1 -1
  130. package/coverage/ui-kit/src/ui-kit/components/organisms/Dialog/Dialog.jsx.html +1 -1
  131. package/coverage/ui-kit/src/ui-kit/components/organisms/Dialog/DialogButtons/DialogButtons.jsx.html +1 -1
  132. package/coverage/ui-kit/src/ui-kit/components/organisms/Dialog/DialogButtons/index.html +1 -1
  133. package/coverage/ui-kit/src/ui-kit/components/organisms/Dialog/DialogStep/DialogStep.jsx.html +1 -1
  134. package/coverage/ui-kit/src/ui-kit/components/organisms/Dialog/DialogStep/index.html +1 -1
  135. package/coverage/ui-kit/src/ui-kit/components/organisms/Dialog/index.html +1 -1
  136. package/coverage/ui-kit/src/ui-kit/components/organisms/SwapForm/SwapForm.jsx.html +1 -1
  137. package/coverage/ui-kit/src/ui-kit/components/organisms/SwapForm/index.html +1 -1
  138. package/coverage/ui-kit/src/ui-kit/components/organisms/WaitlistSubscription/WaitlistSubscription.jsx.html +1 -1
  139. package/coverage/ui-kit/src/ui-kit/components/organisms/WaitlistSubscription/index.html +1 -1
  140. package/coverage/ui-kit/src/ui-kit/components/templates/DeterminedErrorDialogStep/DeterminedErrorDialogStep.jsx.html +1 -1
  141. package/coverage/ui-kit/src/ui-kit/components/templates/DeterminedErrorDialogStep/index.html +1 -1
  142. package/coverage/ui-kit/src/ui-kit/hooks/index.html +1 -1
  143. package/coverage/ui-kit/src/ui-kit/hooks/useCallHandlingErrors.js.html +1 -1
  144. package/coverage/ui-kit/src/ui-kit/hooks/useIsHydrated.js.html +1 -1
  145. package/coverage/ui-kit/src/ui-kit/hooks/useReferredState.js.html +1 -1
  146. package/coverage/ui-kit/src/ui-kit/utils/index.html +1 -1
  147. package/coverage/ui-kit/src/ui-kit/utils/inputValueProviders.js.html +1 -1
  148. package/coverage/ui-kit/src/ui-kit/utils/searchCoins.js.html +1 -1
  149. package/coverage/ui-kit/src/ui-kit/utils/textUtils.js.html +1 -1
  150. package/coverage/ui-kit/src/ui-kit/utils/uiUtils.js.html +1 -1
  151. package/coverage/ui-kit/src/ui-kit/utils/urlQueryUtils.js.html +1 -1
  152. package/coverage/ui-kit/stories/atoms/BackgroundTitle.stories.jsx.html +1 -1
  153. package/coverage/ui-kit/stories/atoms/LinesOfText.stories.jsx.html +1 -1
  154. package/coverage/ui-kit/stories/atoms/LoadingDots.stories.jsx.html +1 -1
  155. package/coverage/ui-kit/stories/atoms/QrCode.stories.jsx.html +1 -1
  156. package/coverage/ui-kit/stories/atoms/RateSelector.stories.jsx.html +1 -1
  157. package/coverage/ui-kit/stories/atoms/Validation.stories.jsx.html +1 -1
  158. package/coverage/ui-kit/stories/atoms/buttons/Button.stories.jsx.html +1 -1
  159. package/coverage/ui-kit/stories/atoms/buttons/Close.stories.jsx.html +1 -1
  160. package/coverage/ui-kit/stories/atoms/buttons/LinkButton.stories.jsx.html +1 -1
  161. package/coverage/ui-kit/stories/atoms/buttons/index.html +1 -1
  162. package/coverage/ui-kit/stories/atoms/index.html +1 -1
  163. package/coverage/ui-kit/stories/molecules/AmountInput.stories.jsx.html +1 -1
  164. package/coverage/ui-kit/stories/molecules/CoinPicker.stories.jsx.html +1 -1
  165. package/coverage/ui-kit/stories/molecules/ColoredNotice.stories.jsx.html +1 -1
  166. package/coverage/ui-kit/stories/molecules/LineWithIconLink.stories.jsx.html +1 -1
  167. package/coverage/ui-kit/stories/molecules/LogoCarousel.stories.jsx.html +1 -1
  168. package/coverage/ui-kit/stories/molecules/TitledLineWithIconLink.stories.jsx.html +1 -1
  169. package/coverage/ui-kit/stories/molecules/index.html +1 -1
  170. package/coverage/ui-kit/stories/organisms/Dialog/Dialog.stories.jsx.html +1 -1
  171. package/coverage/ui-kit/stories/organisms/Dialog/DialogButtons/DialogButtons.stories.jsx.html +1 -1
  172. package/coverage/ui-kit/stories/organisms/Dialog/DialogButtons/index.html +1 -1
  173. package/coverage/ui-kit/stories/organisms/Dialog/DialogStep/DialogStep.stories.jsx.html +1 -1
  174. package/coverage/ui-kit/stories/organisms/Dialog/DialogStep/index.html +1 -1
  175. package/coverage/ui-kit/stories/organisms/Dialog/index.html +1 -1
  176. package/coverage/ui-kit/stories/organisms/WaitlistSubscription.stories.jsx.html +1 -1
  177. package/coverage/ui-kit/stories/organisms/index.html +1 -1
  178. package/coverage/ui-kit/stories/stubs/coins.jsx.html +1 -1
  179. package/coverage/ui-kit/stories/stubs/exampleContent.jsx.html +1 -1
  180. package/coverage/ui-kit/stories/stubs/index.html +1 -1
  181. package/coverage/ui-kit/stories/templates/DeterminedErrorDialogStep.stories.jsx.html +1 -1
  182. package/coverage/ui-kit/stories/templates/index.html +1 -1
  183. package/dist/index.cjs +1064 -151
  184. package/dist/index.cjs.map +1 -1
  185. package/dist/index.modern.js +702 -1
  186. package/dist/index.modern.js.map +1 -1
  187. package/dist/index.module.js +1064 -152
  188. package/dist/index.module.js.map +1 -1
  189. package/dist/index.umd.js +1064 -151
  190. package/dist/index.umd.js.map +1 -1
  191. package/package.json +1 -1
  192. package/src/index.js +1 -0
  193. package/src/swaps-lib/external-apis/changeNowSwapProvider.js +746 -0
  194. package/src/swaps-lib/external-apis/goexmeSwapProvider.js +20 -0
  195. package/src/swaps-lib/test/external-apis/changeNowSwapProvider/_fetchSupportedCurrenciesIfNeeded.test.js +83 -0
  196. package/src/swaps-lib/test/external-apis/changeNowSwapProvider/_providerHelpers.test.js +54 -0
  197. package/src/swaps-lib/test/external-apis/changeNowSwapProvider/_validateAddressWithProvider.test.js +49 -0
  198. package/src/swaps-lib/test/external-apis/changeNowSwapProvider/createSwap.test.js +886 -0
  199. package/src/swaps-lib/test/external-apis/changeNowSwapProvider/getExistingSwapsDetailsAndStatus.test.js +348 -0
  200. package/src/swaps-lib/test/external-apis/changeNowSwapProvider/getSwapInfo.test.js +342 -0
  201. package/src/swaps-lib/test/external-apis/goexmeSwapProvider/createSwap.test.js +1 -15
  202. package/src/swaps-lib/test/external-apis/goexmeSwapProvider/getSwapInfo.test.js +0 -8
  203. package/src/swaps-lib/test/external-apis/goexmeSwapProvider/integration/PairSupport.int.test.js +1 -1
  204. package/src/swaps-lib/test/external-apis/goexmeSwapProvider/integration/getSwapInfo.int.test.js +13 -10
@@ -0,0 +1,746 @@
1
+ import axios from "axios";
2
+ import BigNumber from "bignumber.js";
3
+
4
+ import { improveAndRethrow } from "../../common-apis/utils/errorUtils.js";
5
+ import { DEFAULT_CRYPTO_DECIMAL_COUNT, FALLBACK_ASSET_ICON_URL } from "../../common-apis/globalConstants.jsx";
6
+ import { Logger } from "../../common-apis/utils/logging/logger.js";
7
+ import { Coin } from "../../common-apis/models/coin.js";
8
+ import { Protocol } from "../../common-apis/models/protocol.js";
9
+ import { safeStringify } from "../../common-apis/utils/safeStringify.js";
10
+ import { AmountUtils } from "../../common-apis/utils/amountUtils.js";
11
+
12
+ import { SwapProvider } from "./swapProvider.js";
13
+ import { SwapProviderCoinInfo } from "../models/swapProviderCoinInfo.js";
14
+ import { ExistingSwap } from "../models/existingSwap.js";
15
+ import { Partner, KYC_LEVELS } from "../models/partner.js";
16
+ import { createRobustTimeoutSetupForAxios } from "./utils.js";
17
+
18
+ /**
19
+ * ChangeNowSwapProvider wraps ChangeNOW API v2 to provide swap functionality.
20
+ *
21
+ * IMPORTANT: this adapter operates strictly on a hardcoded whitelist of
22
+ * Rabbit tickers mapped to (ticker, network) pairs in ChangeNOW API. Any
23
+ * asset outside of the map below is considered unsupported by this adapter
24
+ * and will not be used at all.
25
+ *
26
+ * Mapping rule:
27
+ * Rabbit ticker (our internal) → { code, network } for ChangeNOW.
28
+ * Example: USDTTRC20 → { code: "usdt", network: "trx" }.
29
+ *
30
+ * We propagate the Rabbit ticker as the internal coin identifier everywhere,
31
+ * and when calling ChangeNOW we use the corresponding code/network from the
32
+ * map.
33
+ *
34
+ * Flows:
35
+ * - Floating rate → flow=standard
36
+ * - Fixed rate → flow=fixed-rate (you MUST pass the rateId obtained
37
+ * from /exchange/estimated-amount when creating a swap). The presence
38
+ * of "validUntil" in transaction details indicates a fixed‑rate swap.
39
+ *
40
+ * Expiration:
41
+ * ChangeNOW does not auto-expire swaps. For UI simplicity we treat swaps
42
+ * as expired after 4 hours (see _swapExpirationMs).
43
+ */
44
+
45
+ // Strict whitelist of supported assets: RabbitTicker -> { code: ChangeNOW ticker, network: ChangeNOW network }
46
+ const RABBIT_TO_CHANGENOW_MAP = Object.freeze({
47
+ BNBBEP20: { code: "bnb", network: "bsc", tickerPrintable: "BNB", protocol: "BEP20" },
48
+ BNBOPBNB: { code: "bnb", network: "opbnb", tickerPrintable: "BNB", protocol: "OPBNB" },
49
+ BTC: { code: "btc", network: "btc", tickerPrintable: "BTC", protocol: null },
50
+ DOGE: { code: "doge", network: "doge", tickerPrintable: "DOGE", protocol: null },
51
+ DOT: { code: "dot", network: "dot", tickerPrintable: "DOT", protocol: null },
52
+ ETH: { code: "eth", network: "eth", tickerPrintable: "ETH", protocol: null },
53
+ ETHBASE: { code: "eth", network: "base", tickerPrintable: "ETH", protocol: "BASE" },
54
+ GMTSOL: { code: "gmt", network: "sol", tickerPrintable: "GMT", protocol: "SOL" },
55
+ KAS: { code: "kas", network: "kas", tickerPrintable: "KAS", protocol: null },
56
+ LTC: { code: "ltc", network: "ltc", tickerPrintable: "LTC", protocol: null },
57
+ PYUSDSOL: { code: "pyusd", network: "sol", tickerPrintable: "PYUSD", protocol: "SOL" },
58
+ SOL: { code: "sol", network: "sol", tickerPrintable: "SOL", protocol: null },
59
+ TON: { code: "ton", network: "ton", tickerPrintable: "TON", protocol: null },
60
+ TRX: { code: "trx", network: "trx", tickerPrintable: "TRX", protocol: null },
61
+ "USDC.EARBITRUM": { code: "arbusdce", network: "arbitrum", tickerPrintable: "USDC", protocol: "EARBITRUM" },
62
+ USDCARBITRUM: { code: "usdc", network: "arbitrum", tickerPrintable: "USDC", protocol: "ARBITRUM" },
63
+ USDCERC20: { code: "usdc", network: "eth", tickerPrintable: "USDC", protocol: "ERC20" },
64
+ USDTBEP20: { code: "usdt", network: "bsc", tickerPrintable: "USDT", protocol: "BEP20" },
65
+ USDTERC20: { code: "usdt", network: "eth", tickerPrintable: "USDT", protocol: "ERC20" },
66
+ USDTPOLYGON: { code: "usdt", network: "matic", tickerPrintable: "USDT", protocol: "POLYGON" },
67
+ USDTSOL: { code: "usdt", network: "sol", tickerPrintable: "USDT", protocol: "SOL" },
68
+ USDTTRC20: { code: "usdt", network: "trx", tickerPrintable: "USDT", protocol: "TRC20" },
69
+ WBTCERC20: { code: "wbtc", network: "eth", tickerPrintable: "WBTC", protocol: "ERC20" },
70
+ XMR: { code: "xmr", network: "xmr", tickerPrintable: "XMR", protocol: null },
71
+ XRP: { code: "xrp", network: "xrp", tickerPrintable: "XRP", protocol: null },
72
+ });
73
+
74
+ export class ChangeNowSwapProvider extends SwapProvider {
75
+ constructor(
76
+ apiKeysProxyUrl = null,
77
+ cache,
78
+ customCoinBuilder = (ticker, network) => null,
79
+ useRestrictedCoinsSet = true,
80
+ apiKey = null
81
+ ) {
82
+ super("changenow", cache, customCoinBuilder, useRestrictedCoinsSet, false, true);
83
+ this._apiKey = apiKey;
84
+ this._URL = apiKey ? "https://api.changenow.io/v2" : `${apiKeysProxyUrl}/${this.id}`;
85
+ this._commonHeaders = apiKey ? { "x-changenow-api-key": apiKey } : {};
86
+ this._axiosGetPreset = {
87
+ headers: this._commonHeaders,
88
+ validateStatus: status => [200, 400, 404].includes(status),
89
+ };
90
+ this._axiosPostPreset = {
91
+ headers: { ...this._commonHeaders, "Content-Type": "application/json" },
92
+ validateStatus: status => [200, 201, 400, 404].includes(status),
93
+ };
94
+
95
+ this._swapExpirationMs = 4 * 60 * 60 * 1000;
96
+
97
+ this._partners = [];
98
+ }
99
+
100
+ getSwapCreationInfoTtlMs() {
101
+ return 110000;
102
+ }
103
+
104
+ /**
105
+ * Loads ChangeNOW currencies and maps ONLY assets from RABBIT_TO_CHANGENOW_MAP.
106
+ *
107
+ * - coin.ticker in our system is EXACTLY the Rabbit ticker (e.g., USDTTRC20).
108
+ * - code/network used for ChangeNOW requests come from the map (e.g., usdt + trx).
109
+ * - Any asset not present in the map is ignored entirely.
110
+ */
111
+ async _fetchSupportedCurrenciesIfNeeded() {
112
+ const loggerSource = "ChangeNow::_fetchSupportedCurrenciesIfNeeded";
113
+ if (!this._shouldCoinsListBeLoaded()) {
114
+ return;
115
+ }
116
+ try {
117
+ const response = await axios.get(`${this._URL}/exchange/currencies`, {
118
+ ...this._axiosGetPreset,
119
+ ...createRobustTimeoutSetupForAxios(),
120
+ params: {},
121
+ });
122
+ if (response.status !== 200 || !Array.isArray(response.data)) {
123
+ throw new Error(
124
+ `Unexpected /exchange/currencies response: ${response.status}, ${safeStringify(response.data)}`
125
+ );
126
+ }
127
+
128
+ const currencies = response.data;
129
+
130
+ // Index currencies by "ticker|network" (both lowercased) for quick lookup.
131
+ const byTickerAndNetwork = new Map();
132
+ for (const item of currencies) {
133
+ try {
134
+ const t = (item.ticker ?? "").toLowerCase();
135
+ const n = (item.network ?? "").toLowerCase();
136
+ if (!t) continue;
137
+ byTickerAndNetwork.set(`${t}|${n}`, item);
138
+ } catch (e) {
139
+ Logger.logError(e, `${loggerSource} while indexing currencies`);
140
+ }
141
+ }
142
+
143
+ const mappedCoins = Object.entries(RABBIT_TO_CHANGENOW_MAP).map(([rabbitTicker, mapping]) => {
144
+ try {
145
+ const codeLower = (mapping.code ?? "").toLowerCase();
146
+ const networkLower = (mapping.network ?? "").toLowerCase();
147
+ const item = byTickerAndNetwork.get(`${codeLower}|${networkLower}`);
148
+
149
+ // If ChangeNOW doesn't return this (ticker, network) pair — skip the asset.
150
+ if (!item) return null;
151
+
152
+ const internalTicker = rabbitTicker;
153
+ const providerCurrencyCode = (mapping.code ?? "").toUpperCase();
154
+ const providerNetwork = networkLower || null;
155
+
156
+ const tickerPrintable = mapping.tickerPrintable;
157
+ const protocol = mapping.protocol ? new Protocol(mapping.protocol) : null;
158
+
159
+ let coinObject = null;
160
+ if (this._customCoinBuilder) {
161
+ coinObject = this._customCoinBuilder(internalTicker, mapping.protocol ?? null);
162
+ }
163
+ if (!coinObject && !this.useRestrictedCoinsSet) {
164
+ const name = item.name ?? internalTicker;
165
+ const decimals =
166
+ typeof item.decimals === "number" ? item.decimals : DEFAULT_CRYPTO_DECIMAL_COUNT;
167
+ const contract = item.tokenContract || null;
168
+ coinObject = new Coin(
169
+ name,
170
+ internalTicker,
171
+ tickerPrintable,
172
+ decimals,
173
+ null,
174
+ "",
175
+ null,
176
+ null,
177
+ 1,
178
+ null,
179
+ [],
180
+ 60000,
181
+ null,
182
+ protocol,
183
+ contract,
184
+ false
185
+ );
186
+ }
187
+ if (!coinObject) return null;
188
+
189
+ const hasExtraId = item.isExtraIdSupported ?? false;
190
+ const extraIdName = item.extraIdName ?? null;
191
+ const depositAvailable = item.sell === true;
192
+ const withdrawalAvailable = item.buy === true;
193
+ const popularity = item.featured ? 1 : 0;
194
+ const validationRegex = item.validationRegexp ?? null;
195
+ const iconUrl = item.image ?? FALLBACK_ASSET_ICON_URL;
196
+
197
+ return new SwapProviderCoinInfo(
198
+ coinObject,
199
+ providerCurrencyCode,
200
+ providerNetwork,
201
+ !!hasExtraId,
202
+ extraIdName,
203
+ popularity,
204
+ iconUrl,
205
+ depositAvailable,
206
+ withdrawalAvailable,
207
+ validationRegex,
208
+ true
209
+ );
210
+ } catch (error) {
211
+ Logger.logError(error, `${loggerSource} while processing mapped currency ${rabbitTicker}`);
212
+ return null;
213
+ }
214
+ });
215
+
216
+ this._supportedCoins = mappedCoins.filter(Boolean);
217
+ this._supportedCoinsLastUpdateTimestamp = Date.now();
218
+ this._putPopularCoinsFirst();
219
+ await super._fetchSupportedCurrenciesIfNeeded();
220
+ } catch (error) {
221
+ improveAndRethrow(error, loggerSource);
222
+ }
223
+ }
224
+
225
+ async _validateAddressWithProvider(network, baseCode, address, extraId = null) {
226
+ const loggerSource = "ChangeNow::_validateAddressWithProvider";
227
+ try {
228
+ if (!this._apiKey) {
229
+ return this.isAddressValidForAsset({ ticker: baseCode }, address);
230
+ }
231
+
232
+ // Compose request parameters: currency (ticker or network) and address.
233
+ const currencyParameter = (network || baseCode).toLowerCase();
234
+ const parameters = { currency: currencyParameter, address };
235
+
236
+ // Some currencies (e.g. XRP) require a destination tag/memo for validation.
237
+ // Passing extraId will prevent "Extra Id is required" errors.
238
+ if (extraId) {
239
+ parameters.extraId = extraId;
240
+ }
241
+
242
+ const response = await axios.get(`${this._URL}/validate/address`, {
243
+ ...this._axiosGetPreset,
244
+ ...createRobustTimeoutSetupForAxios(),
245
+ params: parameters,
246
+ });
247
+
248
+ if (response.status === 200 && typeof response.data === "object") {
249
+ const isValid = response.data?.result === true;
250
+ if (!isValid) {
251
+ Logger.log(
252
+ `Address validation failed: ${address}, reason: ${response.data?.message}`,
253
+ loggerSource
254
+ );
255
+ }
256
+ return isValid;
257
+ }
258
+
259
+ // Unexpected response: return true to avoid blocking the swap.
260
+ Logger.log(
261
+ `Unexpected address validation response for ${address}: ${safeStringify(response.data)}`,
262
+ loggerSource
263
+ );
264
+ return true;
265
+ } catch (error) {
266
+ Logger.logError(error, loggerSource);
267
+ return true;
268
+ }
269
+ }
270
+
271
+ async getSwapInfo(
272
+ fromCoin,
273
+ toCoin,
274
+ amountCoins,
275
+ fixed = false,
276
+ fromCoinToUsdRate = null,
277
+ amountIsToReceive = false,
278
+ bannedPartners = [],
279
+ whitelistedPartners = null
280
+ ) {
281
+ const loggerSource = "ChangeNow::getSwapInfo";
282
+ try {
283
+ if (
284
+ !(fromCoin instanceof Coin) ||
285
+ !(toCoin instanceof Coin) ||
286
+ typeof amountCoins !== "string" ||
287
+ BigNumber(amountCoins).lte("0")
288
+ ) {
289
+ throw new Error(`Invalid swap parameters: ${amountCoins} ${fromCoin?.ticker} -> ${toCoin?.ticker}`);
290
+ }
291
+ await this._fetchSupportedCurrenciesIfNeeded();
292
+ const fromInfo = this._supportedCoins.find(info => info.coin.ticker === fromCoin.ticker);
293
+ const toInfo = this._supportedCoins.find(info => info.coin.ticker === toCoin.ticker);
294
+ if (
295
+ !fromInfo ||
296
+ !toInfo ||
297
+ !fromInfo.deposit ||
298
+ !fromInfo.isAvailable ||
299
+ !toInfo.withdrawal ||
300
+ !toInfo.isAvailable
301
+ ) {
302
+ return {
303
+ result: false,
304
+ reason: SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED,
305
+ smallestMin: null,
306
+ greatestMax: null,
307
+ };
308
+ }
309
+ const fromCode = fromInfo.code;
310
+ const toCode = toInfo.code;
311
+ const fromNetwork = fromInfo.network;
312
+ const toNetwork = toInfo.network;
313
+
314
+ let minAmount = null;
315
+ let maxAmount = null;
316
+ try {
317
+ const rangeResponse = await axios.get(`${this._URL}/exchange/range`, {
318
+ ...this._axiosGetPreset,
319
+ ...createRobustTimeoutSetupForAxios(),
320
+ params: {
321
+ fromCurrency: fromCode,
322
+ toCurrency: toCode,
323
+ ...(fromNetwork ? { fromNetwork } : {}),
324
+ ...(toNetwork ? { toNetwork } : {}),
325
+ ...(fixed ? { flow: "fixed-rate" } : {}),
326
+ },
327
+ });
328
+ if (rangeResponse.status === 200 && typeof rangeResponse.data === "object") {
329
+ const data = rangeResponse.data;
330
+ if (data.minAmount != null && BigNumber(data.minAmount).gt("0")) {
331
+ minAmount = BigNumber(
332
+ AmountUtils.trim(data.minAmount, amountIsToReceive ? toCoin.digits : fromCoin.digits)
333
+ ).toString();
334
+ }
335
+ if (data.maxAmount != null && BigNumber(data.maxAmount).gt("0")) {
336
+ maxAmount = BigNumber(
337
+ AmountUtils.trim(data.maxAmount, amountIsToReceive ? toCoin.digits : fromCoin.digits)
338
+ ).toString();
339
+ }
340
+ } else {
341
+ Logger.log(
342
+ `Unexpected /exchange/range response: ${rangeResponse.status}, ${safeStringify(
343
+ rangeResponse.data
344
+ )}`,
345
+ loggerSource
346
+ );
347
+ }
348
+ } catch (error) {
349
+ Logger.logError(error, `${loggerSource} while fetching range`);
350
+ }
351
+ const requestedAmount = BigNumber(amountCoins);
352
+ if (minAmount != null && requestedAmount.lt(minAmount)) {
353
+ return {
354
+ result: false,
355
+ reason: SwapProvider.NO_SWAPS_REASONS.TOO_LOW,
356
+ smallestMin: minAmount,
357
+ greatestMax: maxAmount,
358
+ };
359
+ }
360
+ if (maxAmount != null && requestedAmount.gt(maxAmount)) {
361
+ return {
362
+ result: false,
363
+ reason: SwapProvider.NO_SWAPS_REASONS.TOO_HIGH,
364
+ smallestMin: minAmount,
365
+ greatestMax: maxAmount,
366
+ };
367
+ }
368
+
369
+ const estimateParameters = {
370
+ fromCurrency: fromCode,
371
+ toCurrency: toCode,
372
+ ...(fromNetwork ? { fromNetwork } : {}),
373
+ ...(toNetwork ? { toNetwork } : {}),
374
+ flow: fixed ? "fixed-rate" : "standard",
375
+ };
376
+ if (amountIsToReceive) {
377
+ estimateParameters.toAmount = amountCoins;
378
+ } else {
379
+ estimateParameters.fromAmount = amountCoins;
380
+ }
381
+ const estimateResponse = await axios.get(`${this._URL}/exchange/estimated-amount`, {
382
+ ...this._axiosGetPreset,
383
+ ...createRobustTimeoutSetupForAxios(),
384
+ params: estimateParameters,
385
+ });
386
+ if (estimateResponse.status !== 200 || typeof estimateResponse.data !== "object") {
387
+ throw new Error(
388
+ `Unexpected estimate response: ${estimateResponse.status} ${safeStringify(estimateResponse.data)}`
389
+ );
390
+ }
391
+ const estimation = estimateResponse.data;
392
+ const fromAmountResult =
393
+ estimation.fromAmount != null ? BigNumber(estimation.fromAmount).toString() : amountCoins;
394
+ const toAmountResult =
395
+ estimation.toAmount != null ? BigNumber(estimation.toAmount).toString() : amountCoins;
396
+ const rateValue = BigNumber(toAmountResult).div(BigNumber(fromAmountResult)).toString();
397
+ const durationMinutesRange = estimation.transactionSpeedForecast ?? "";
398
+ const rateId = estimation.rateId ?? null;
399
+ const rawSwapData = {
400
+ fromAmount: fromAmountResult,
401
+ toAmount: toAmountResult,
402
+ rate: rateValue,
403
+ rateId,
404
+ fromCode,
405
+ toCode,
406
+ fromNetwork,
407
+ toNetwork,
408
+ };
409
+ return {
410
+ result: true,
411
+ min: minAmount,
412
+ max: maxAmount,
413
+ smallestMin: minAmount,
414
+ greatestMax: maxAmount,
415
+ rate: rateValue,
416
+ durationMinutesRange: durationMinutesRange ? `${durationMinutesRange}` : "",
417
+ fixed: !!fixed,
418
+ isRefundAddressRequired: false,
419
+ rawSwapData,
420
+ };
421
+ } catch (error) {
422
+ if (error?.response?.status === 429) {
423
+ return {
424
+ result: false,
425
+ reason: SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED,
426
+ smallestMin: null,
427
+ greatestMax: null,
428
+ };
429
+ }
430
+ Logger.logError(error, loggerSource);
431
+ improveAndRethrow(error, loggerSource);
432
+ }
433
+ }
434
+
435
+ async createSwap(
436
+ fromCoin,
437
+ toCoin,
438
+ amount,
439
+ toAddress,
440
+ refundAddress,
441
+ proprietarySwapEstimationData,
442
+ clientIpAddress,
443
+ fixed,
444
+ toCurrencyExtraId = "",
445
+ refundExtraId = "",
446
+ amountIsToReceive = false
447
+ ) {
448
+ const loggerSource = "ChangeNow::createSwap";
449
+ try {
450
+ if (
451
+ !(fromCoin instanceof Coin) ||
452
+ !(toCoin instanceof Coin) ||
453
+ typeof amount !== "string" ||
454
+ BigNumber(amount).lte("0")
455
+ ) {
456
+ throw new Error(`Invalid swap parameters: ${amount} ${fromCoin?.ticker} -> ${toCoin?.ticker}`);
457
+ }
458
+
459
+ if (typeof toAddress !== "string" || !toAddress.trim()) {
460
+ throw new Error(`Invalid destination address: ${safeStringify(toAddress)}`);
461
+ }
462
+
463
+ await this._fetchSupportedCurrenciesIfNeeded();
464
+
465
+ const fromInfo = this._supportedCoins.find(info => info.coin.ticker === fromCoin.ticker);
466
+ const toInfo = this._supportedCoins.find(info => info.coin.ticker === toCoin.ticker);
467
+
468
+ if (!fromInfo || !toInfo) {
469
+ throw new Error(
470
+ `Internal inconsistency: supported coin info not found (${
471
+ !fromInfo ? `from=${fromCoin?.ticker}` : ""
472
+ }${!fromInfo && !toInfo ? ", " : ""}${!toInfo ? `to=${toCoin?.ticker}` : ""})`
473
+ );
474
+ }
475
+
476
+ const fromCode = fromInfo.code;
477
+ const toCode = toInfo.code;
478
+ const fromNetwork = fromInfo.network;
479
+ const toNetwork = toInfo.network;
480
+
481
+ const addressValid = await this._validateAddressWithProvider(
482
+ toNetwork,
483
+ toCode,
484
+ toAddress,
485
+ toCurrencyExtraId
486
+ );
487
+ if (!addressValid) {
488
+ return {
489
+ result: false,
490
+ reason: SwapProvider.CREATION_FAIL_REASONS.RETRIABLE_FAIL,
491
+ partner: this.id,
492
+ };
493
+ }
494
+ if (refundAddress && refundAddress.trim()) {
495
+ const refundValid = await this._validateAddressWithProvider(
496
+ fromNetwork,
497
+ fromCode,
498
+ refundAddress,
499
+ refundExtraId
500
+ );
501
+ if (!refundValid) {
502
+ return {
503
+ result: false,
504
+ reason: SwapProvider.CREATION_FAIL_REASONS.RETRIABLE_FAIL,
505
+ partner: this.id,
506
+ };
507
+ }
508
+ }
509
+
510
+ const payload = {
511
+ fromCurrency: fromCode,
512
+ toCurrency: toCode,
513
+ address: toAddress,
514
+ };
515
+
516
+ if (fromNetwork) payload.fromNetwork = fromNetwork;
517
+ if (toNetwork) payload.toNetwork = toNetwork;
518
+
519
+ if (amountIsToReceive) {
520
+ payload.toAmount = amount;
521
+ } else {
522
+ payload.fromAmount = amount;
523
+ }
524
+ if (fixed) {
525
+ payload.flow = "fixed-rate";
526
+ const rateId = proprietarySwapEstimationData?.rateId ?? null;
527
+ if (rateId) {
528
+ payload.rateId = rateId;
529
+ } else {
530
+ throw new Error("Fixed-rate flow requires rateId from estimation");
531
+ }
532
+ }
533
+ if (toCurrencyExtraId) payload.extraId = toCurrencyExtraId;
534
+ if (refundAddress) payload.refundAddress = refundAddress;
535
+ if (refundExtraId) payload.refundExtraId = refundExtraId;
536
+
537
+ Logger.log(`Sending ChangeNOW create payload: ${safeStringify(payload)}`, loggerSource);
538
+
539
+ const response = await axios.post(`${this._URL}/exchange`, payload, {
540
+ ...this._axiosPostPreset,
541
+ ...createRobustTimeoutSetupForAxios(),
542
+ });
543
+
544
+ Logger.log(`ChangeNOW create response: ${safeStringify(response.data)}`, loggerSource);
545
+
546
+ const data = response.data;
547
+
548
+ if (response.status !== 200 && response.status !== 201) {
549
+ const errorMessageFromProvider =
550
+ data?.error || data?.message || `Unexpected response status ${response.status}`;
551
+ Logger.log(
552
+ `ChangeNOW swap creation failed. status=${response.status}, message=${errorMessageFromProvider}`,
553
+ loggerSource
554
+ );
555
+
556
+ return {
557
+ result: false,
558
+ reason: SwapProvider.CREATION_FAIL_REASONS.RETRIABLE_FAIL,
559
+ partner: this.id,
560
+ };
561
+ }
562
+
563
+ const swapId = data?.id ?? null;
564
+ const payinAddress = data?.payinAddress ?? null;
565
+ const payinExtraId = data?.payinExtraId ?? null;
566
+ const returnedFromAmount = data?.fromAmount ?? null;
567
+ const returnedToAmount = data?.toAmount ?? null;
568
+
569
+ const computedRate =
570
+ proprietarySwapEstimationData?.rate ||
571
+ (returnedFromAmount && returnedToAmount
572
+ ? BigNumber(returnedToAmount).div(BigNumber(returnedFromAmount)).toString()
573
+ : null);
574
+
575
+ return {
576
+ result: true,
577
+ swapId: swapId,
578
+ fromCoin,
579
+ fromAmount: returnedFromAmount != null ? BigNumber(returnedFromAmount).toString() : undefined,
580
+ fromAddress: payinAddress,
581
+ toCoin,
582
+ toAmount: returnedToAmount != null ? BigNumber(returnedToAmount).toString() : undefined,
583
+ toAddress: toAddress,
584
+ fromCurrencyExtraId: payinExtraId,
585
+ rate: computedRate,
586
+ fixed: !!fixed,
587
+ };
588
+ } catch (error) {
589
+ const status = error?.response?.status;
590
+ if (status === 429) {
591
+ return { result: false, reason: SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED, partner: this.id };
592
+ }
593
+ improveAndRethrow(error, loggerSource);
594
+ }
595
+ }
596
+
597
+ async getExistingSwapsDetailsAndStatus(swapIds) {
598
+ const loggerSource = "ChangeNow::getExistingSwapsDetailsAndStatus";
599
+ try {
600
+ if (!Array.isArray(swapIds) || swapIds.length === 0) {
601
+ return { result: true, swaps: [] };
602
+ }
603
+ await this._fetchSupportedCurrenciesIfNeeded();
604
+ const swaps = new Array(swapIds.length).fill(null);
605
+ for (let index = 0; index < swapIds.length; index++) {
606
+ const id = swapIds[index];
607
+ try {
608
+ const response = await axios.get(`${this._URL}/exchange/by-id`, {
609
+ ...this._axiosGetPreset,
610
+ ...createRobustTimeoutSetupForAxios(),
611
+ params: { id },
612
+ });
613
+ if (response.status !== 200 || typeof response.data !== "object") {
614
+ continue;
615
+ }
616
+ const transactionData = response.data;
617
+ const statusRaw = (transactionData.status || "").toLowerCase();
618
+
619
+ const createdAtMilliseconds =
620
+ transactionData.createdAt != null ? new Date(transactionData.createdAt).getTime() : null;
621
+ let expiresAtMilliseconds;
622
+ if (transactionData.validUntil != null) {
623
+ expiresAtMilliseconds = new Date(transactionData.validUntil).getTime();
624
+ } else {
625
+ expiresAtMilliseconds =
626
+ createdAtMilliseconds != null ? createdAtMilliseconds + this._swapExpirationMs : null;
627
+ }
628
+ const isExpiredByTime = expiresAtMilliseconds != null ? expiresAtMilliseconds < Date.now() : false;
629
+ const status = this._mapChangeNowStatusToRabbitStatus(statusRaw, isExpiredByTime);
630
+
631
+ const fromAmountRaw = transactionData.amountFrom ?? transactionData.expectedAmountFrom ?? null;
632
+ const toAmountRaw = transactionData.amountTo ?? transactionData.expectedAmountTo ?? null;
633
+ const fromAmountString = fromAmountRaw != null ? BigNumber(fromAmountRaw).toString() : null;
634
+ const toAmountString = toAmountRaw != null ? BigNumber(toAmountRaw).toString() : null;
635
+ const computedRate =
636
+ fromAmountString != null && toAmountString != null
637
+ ? BigNumber(toAmountString).div(BigNumber(fromAmountString)).toString()
638
+ : null;
639
+ const payinAddress = transactionData.payinAddress ?? null;
640
+ const payinTransactionHash = transactionData.payinHash ?? null;
641
+ const payoutAddress = transactionData.payoutAddress ?? null;
642
+ const payoutTransactionHash = transactionData.payoutHash ?? null;
643
+
644
+ // Determine from/to currency codes and networks (base ticker + network).
645
+ const fromCurrencyCode = transactionData.fromCurrency?.toUpperCase?.() ?? null;
646
+ const toCurrencyCode = transactionData.toCurrency?.toUpperCase?.() ?? null;
647
+ const fromNetwork = transactionData.fromNetwork?.toLowerCase() ?? null;
648
+ const toNetwork = transactionData.toNetwork?.toLowerCase() ?? null;
649
+
650
+ const fromInfo = this._supportedCoins.find(info => {
651
+ const matchBase =
652
+ fromCurrencyCode &&
653
+ info.code.toUpperCase() === fromCurrencyCode &&
654
+ (!fromNetwork || info.network === fromNetwork);
655
+ return matchBase;
656
+ });
657
+ const toInfoMatch = this._supportedCoins.find(info => {
658
+ const matchBase =
659
+ toCurrencyCode &&
660
+ info.code.toUpperCase() === toCurrencyCode &&
661
+ (!toNetwork || info.network === toNetwork);
662
+ return matchBase;
663
+ });
664
+
665
+ const fromCoinObject = fromInfo ? fromInfo.coin : null;
666
+ const toCoinObject = toInfoMatch ? toInfoMatch.coin : null;
667
+
668
+ const isFixed = transactionData.validUntil != null; // validUntil denotes fixed‑rate swap
669
+
670
+ const existingSwap = new ExistingSwap(
671
+ id,
672
+ status,
673
+ createdAtMilliseconds,
674
+ expiresAtMilliseconds,
675
+ -1,
676
+ computedRate,
677
+ isFixed,
678
+ transactionData.refundAddress ?? null,
679
+ payinAddress,
680
+ fromCoinObject,
681
+ fromAmountString,
682
+ payinTransactionHash,
683
+ null,
684
+ toCoinObject,
685
+ toAmountString,
686
+ payoutTransactionHash,
687
+ null,
688
+ payoutAddress,
689
+ this.id,
690
+ transactionData.payinExtraId ?? null,
691
+ transactionData.payoutExtraId ?? null,
692
+ transactionData.refundExtraId ?? null
693
+ );
694
+ swaps[index] = existingSwap;
695
+ } catch (error) {
696
+ Logger.logError(error, `${loggerSource} for id ${id}`);
697
+ }
698
+ }
699
+ return { result: true, swaps };
700
+ } catch (error) {
701
+ Logger.logError(error, loggerSource);
702
+ if (error?.response?.status === 429) {
703
+ return { result: false, reason: SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED };
704
+ }
705
+ improveAndRethrow(error, loggerSource);
706
+ }
707
+ }
708
+
709
+ _mapChangeNowStatusToRabbitStatus(status, isExpiredByTime) {
710
+ switch (status) {
711
+ case "new":
712
+ case "waiting":
713
+ if (isExpiredByTime) {
714
+ return SwapProvider.SWAP_STATUSES.EXPIRED;
715
+ }
716
+ return SwapProvider.SWAP_STATUSES.WAITING_FOR_PAYMENT;
717
+ case "confirming":
718
+ return SwapProvider.SWAP_STATUSES.CONFIRMING;
719
+ case "exchanging":
720
+ return SwapProvider.SWAP_STATUSES.EXCHANGING;
721
+ case "sending":
722
+ return SwapProvider.SWAP_STATUSES.EXCHANGING;
723
+ case "finished":
724
+ return SwapProvider.SWAP_STATUSES.COMPLETED;
725
+ case "failed":
726
+ return SwapProvider.SWAP_STATUSES.FAILED;
727
+ case "refunded":
728
+ return SwapProvider.SWAP_STATUSES.REFUNDED;
729
+ case "verifying":
730
+ return SwapProvider.SWAP_STATUSES.VERIFYING;
731
+ default:
732
+ throw new Error(`Unknown ChangeNOW status: ${status}`);
733
+ }
734
+ }
735
+
736
+ async getPartnersList() {
737
+ return [
738
+ new Partner(
739
+ this.id,
740
+ "ChangeNOW",
741
+ "https://storage.swapspace.co/static/wrzph_gI7rsmSgYt3x5f1kAuz.svg",
742
+ KYC_LEVELS.LOW
743
+ ),
744
+ ];
745
+ }
746
+ }