@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.
- package/coverage/clover.xml +2142 -1372
- package/coverage/coverage-final.json +17 -16
- package/coverage/index.html +29 -29
- package/coverage/ui-kit/index.html +1 -1
- package/coverage/ui-kit/index.js.html +1 -1
- package/coverage/ui-kit/src/common-apis/adapters/analyticsAdapters/googleAnalyticsAdapter.js.html +1 -1
- package/coverage/ui-kit/src/common-apis/adapters/analyticsAdapters/index.html +1 -1
- package/coverage/ui-kit/src/common-apis/adapters/analyticsAdapters/metrikaAdapter.js.html +1 -1
- package/coverage/ui-kit/src/common-apis/adapters/analyticsAdapters/mixpanelAdapter.js.html +1 -1
- package/coverage/ui-kit/src/common-apis/adapters/axiosAdapter.js.html +1 -1
- package/coverage/ui-kit/src/common-apis/adapters/index.html +1 -1
- package/coverage/ui-kit/src/common-apis/adapters/qrUtils.js.html +1 -1
- package/coverage/ui-kit/src/common-apis/external-apis/apiGroups.js.html +1 -1
- package/coverage/ui-kit/src/common-apis/external-apis/emailAPI.js.html +1 -1
- package/coverage/ui-kit/src/common-apis/external-apis/index.html +1 -1
- package/coverage/ui-kit/src/common-apis/external-apis/ipAddressProviders.js.html +1 -1
- package/coverage/ui-kit/src/common-apis/globalConstants.jsx.html +1 -1
- package/coverage/ui-kit/src/common-apis/index.html +1 -1
- package/coverage/ui-kit/src/common-apis/models/blockchain.js.html +1 -1
- package/coverage/ui-kit/src/common-apis/models/coin.js.html +221 -221
- package/coverage/ui-kit/src/common-apis/models/index.html +1 -1
- package/coverage/ui-kit/src/common-apis/models/protocol.js.html +3 -3
- package/coverage/ui-kit/src/common-apis/services/fiatCurrenciesService.js.html +1 -1
- package/coverage/ui-kit/src/common-apis/services/index.html +1 -1
- package/coverage/ui-kit/src/common-apis/utils/amountUtils.js.html +21 -21
- package/coverage/ui-kit/src/common-apis/utils/cache.js.html +6 -6
- package/coverage/ui-kit/src/common-apis/utils/errorUtils.js.html +13 -13
- package/coverage/ui-kit/src/common-apis/utils/index.html +15 -15
- package/coverage/ui-kit/src/common-apis/utils/logging/index.html +1 -1
- package/coverage/ui-kit/src/common-apis/utils/logging/logger.js.html +27 -27
- package/coverage/ui-kit/src/common-apis/utils/logging/logsStorage.js.html +3 -3
- package/coverage/ui-kit/src/common-apis/utils/postponeExecution.js.html +1 -1
- package/coverage/ui-kit/src/common-apis/utils/rabbitTicker.js.html +10 -10
- package/coverage/ui-kit/src/common-apis/utils/safeStringify.js.html +53 -53
- package/coverage/ui-kit/src/index.html +5 -5
- package/coverage/ui-kit/src/index.js.html +7 -4
- package/coverage/ui-kit/src/robust-api-caller/cacheAndConcurrentRequestsResolver.js.html +1 -1
- package/coverage/ui-kit/src/robust-api-caller/cachedRobustExternalApiCallerService.js.html +1 -1
- package/coverage/ui-kit/src/robust-api-caller/cancelProcessing.js.html +1 -1
- package/coverage/ui-kit/src/robust-api-caller/concurrentCalculationsMetadataHolder.js.html +1 -1
- package/coverage/ui-kit/src/robust-api-caller/externalApiProvider.js.html +1 -1
- package/coverage/ui-kit/src/robust-api-caller/externalServicesStatsCollector.js.html +1 -1
- package/coverage/ui-kit/src/robust-api-caller/index.html +1 -1
- package/coverage/ui-kit/src/robust-api-caller/robustExternalAPICallerService.js.html +1 -1
- package/coverage/ui-kit/src/swaps-lib/external-apis/changeNowSwapProvider.js.html +2323 -0
- package/coverage/ui-kit/src/swaps-lib/external-apis/exolixSwapProvider.js.html +1 -1
- package/coverage/ui-kit/src/swaps-lib/external-apis/goexmeSwapProvider.js.html +373 -313
- package/coverage/ui-kit/src/swaps-lib/external-apis/index.html +34 -19
- package/coverage/ui-kit/src/swaps-lib/external-apis/letsExchangeSwapProvider.js.html +1 -1
- package/coverage/ui-kit/src/swaps-lib/external-apis/swapProvider.js.html +327 -327
- package/coverage/ui-kit/src/swaps-lib/external-apis/swapspaceSwapProvider.js.html +1 -1
- package/coverage/ui-kit/src/swaps-lib/external-apis/utils.js.html +14 -14
- package/coverage/ui-kit/src/swaps-lib/models/baseSwapCreationInfo.js.html +1 -1
- package/coverage/ui-kit/src/swaps-lib/models/existingSwap.js.html +140 -140
- package/coverage/ui-kit/src/swaps-lib/models/existingSwapWithFiatData.js.html +1 -1
- package/coverage/ui-kit/src/swaps-lib/models/index.html +1 -1
- package/coverage/ui-kit/src/swaps-lib/models/partner.js.html +21 -21
- package/coverage/ui-kit/src/swaps-lib/models/swapProviderCoinInfo.js.html +73 -73
- package/coverage/ui-kit/src/swaps-lib/services/index.html +1 -1
- package/coverage/ui-kit/src/swaps-lib/services/publicSwapService.js.html +1 -1
- package/coverage/ui-kit/src/swaps-lib/utils/index.html +1 -1
- package/coverage/ui-kit/src/swaps-lib/utils/swapUtils.js.html +1 -1
- package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/arrowIcon.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/arrowTosca.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/arrowWhite.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/darkRectangle.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/determinedError.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/failedValidationIcon.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/infoIcon.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/messageIcon.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/noticeQuestionIcon.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/successfulValidationIcon.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/supportDialogImage.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/assets/wrappedImages/walletIcon.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/AssetIcon/AssetIcon.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/AssetIcon/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/AssetSelection/AssetSelection.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/AssetSelection/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/BackgroundTitle/BackgroundTitle.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/BackgroundTitle/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/InformationMessage/InformationMessage.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/InformationMessage/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/Input/Input.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/Input/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/LoadingDots/LoadingDots.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/LoadingDots/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/NoticeIcon/NoticeIcon.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/NoticeIcon/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/QrCode/QrCode.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/QrCode/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/RateSelector/RateSelector.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/RateSelector/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/SupportChat/SupportChat.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/SupportChat/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/Textarea/Textarea.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/Textarea/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/TitleBox/TitleBox.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/TitleBox/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/Tooltip/Tooltip.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/Tooltip/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/TwoLinesOfText/LinesOfText.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/TwoLinesOfText/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/Validation/Validation.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/Validation/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/Button/Button.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/Button/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/Close/Close.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/Close/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/LinkButton/LinkButton.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/LinkButton/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/RadioButtonWithText/RadioButtonWithText.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/atoms/buttons/RadioButtonWithText/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/AmountInput/AmountInput.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/AmountInput/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/CoinPicker/CoinPicker.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/CoinPicker/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/ColoredNotice/ColoredNotice.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/ColoredNotice/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/LineWithIconLink/LineWithIconLink.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/LineWithIconLink/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/LogoCarousel/LogoCarousel.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/LogoCarousel/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/SearchableCoinsList/SearchableCoinsList.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/SearchableCoinsList/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/TitledLineWithIconLink/TitledLineWithIconLink.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/molecules/TitledLineWithIconLink/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/organisms/CoinPickerDialogStep/CoinPickerDialogStep.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/organisms/CoinPickerDialogStep/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/organisms/Dialog/Dialog.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/organisms/Dialog/DialogButtons/DialogButtons.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/organisms/Dialog/DialogButtons/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/organisms/Dialog/DialogStep/DialogStep.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/organisms/Dialog/DialogStep/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/organisms/Dialog/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/organisms/SwapForm/SwapForm.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/organisms/SwapForm/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/organisms/WaitlistSubscription/WaitlistSubscription.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/organisms/WaitlistSubscription/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/templates/DeterminedErrorDialogStep/DeterminedErrorDialogStep.jsx.html +1 -1
- package/coverage/ui-kit/src/ui-kit/components/templates/DeterminedErrorDialogStep/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/hooks/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/hooks/useCallHandlingErrors.js.html +1 -1
- package/coverage/ui-kit/src/ui-kit/hooks/useIsHydrated.js.html +1 -1
- package/coverage/ui-kit/src/ui-kit/hooks/useReferredState.js.html +1 -1
- package/coverage/ui-kit/src/ui-kit/utils/index.html +1 -1
- package/coverage/ui-kit/src/ui-kit/utils/inputValueProviders.js.html +1 -1
- package/coverage/ui-kit/src/ui-kit/utils/searchCoins.js.html +1 -1
- package/coverage/ui-kit/src/ui-kit/utils/textUtils.js.html +1 -1
- package/coverage/ui-kit/src/ui-kit/utils/uiUtils.js.html +1 -1
- package/coverage/ui-kit/src/ui-kit/utils/urlQueryUtils.js.html +1 -1
- package/coverage/ui-kit/stories/atoms/BackgroundTitle.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/atoms/LinesOfText.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/atoms/LoadingDots.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/atoms/QrCode.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/atoms/RateSelector.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/atoms/Validation.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/atoms/buttons/Button.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/atoms/buttons/Close.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/atoms/buttons/LinkButton.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/atoms/buttons/index.html +1 -1
- package/coverage/ui-kit/stories/atoms/index.html +1 -1
- package/coverage/ui-kit/stories/molecules/AmountInput.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/molecules/CoinPicker.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/molecules/ColoredNotice.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/molecules/LineWithIconLink.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/molecules/LogoCarousel.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/molecules/TitledLineWithIconLink.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/molecules/index.html +1 -1
- package/coverage/ui-kit/stories/organisms/Dialog/Dialog.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/organisms/Dialog/DialogButtons/DialogButtons.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/organisms/Dialog/DialogButtons/index.html +1 -1
- package/coverage/ui-kit/stories/organisms/Dialog/DialogStep/DialogStep.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/organisms/Dialog/DialogStep/index.html +1 -1
- package/coverage/ui-kit/stories/organisms/Dialog/index.html +1 -1
- package/coverage/ui-kit/stories/organisms/WaitlistSubscription.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/organisms/index.html +1 -1
- package/coverage/ui-kit/stories/stubs/coins.jsx.html +1 -1
- package/coverage/ui-kit/stories/stubs/exampleContent.jsx.html +1 -1
- package/coverage/ui-kit/stories/stubs/index.html +1 -1
- package/coverage/ui-kit/stories/templates/DeterminedErrorDialogStep.stories.jsx.html +1 -1
- package/coverage/ui-kit/stories/templates/index.html +1 -1
- package/dist/index.cjs +1064 -151
- package/dist/index.cjs.map +1 -1
- package/dist/index.modern.js +702 -1
- package/dist/index.modern.js.map +1 -1
- package/dist/index.module.js +1064 -152
- package/dist/index.module.js.map +1 -1
- package/dist/index.umd.js +1064 -151
- package/dist/index.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/index.js +1 -0
- package/src/swaps-lib/external-apis/changeNowSwapProvider.js +746 -0
- package/src/swaps-lib/external-apis/goexmeSwapProvider.js +20 -0
- package/src/swaps-lib/test/external-apis/changeNowSwapProvider/_fetchSupportedCurrenciesIfNeeded.test.js +83 -0
- package/src/swaps-lib/test/external-apis/changeNowSwapProvider/_providerHelpers.test.js +54 -0
- package/src/swaps-lib/test/external-apis/changeNowSwapProvider/_validateAddressWithProvider.test.js +49 -0
- package/src/swaps-lib/test/external-apis/changeNowSwapProvider/createSwap.test.js +886 -0
- package/src/swaps-lib/test/external-apis/changeNowSwapProvider/getExistingSwapsDetailsAndStatus.test.js +348 -0
- package/src/swaps-lib/test/external-apis/changeNowSwapProvider/getSwapInfo.test.js +342 -0
- package/src/swaps-lib/test/external-apis/goexmeSwapProvider/createSwap.test.js +1 -15
- package/src/swaps-lib/test/external-apis/goexmeSwapProvider/getSwapInfo.test.js +0 -8
- package/src/swaps-lib/test/external-apis/goexmeSwapProvider/integration/PairSupport.int.test.js +1 -1
- 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
|
+
}
|