@rabbitio/ui-kit 1.0.0-beta.10 → 1.0.0-beta.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,6 @@
1
1
  import React, { useState, useRef, useEffect } from 'react';
2
2
  import { BigNumber } from 'bignumber.js';
3
+ import axios from 'axios';
3
4
 
4
5
  function createCommonjsModule(fn) {
5
6
  var module = { exports: {} };
@@ -1396,7 +1397,7 @@ var s = {"container":"_DYS-g","m-0":"_85R9d","p-0":"_wi0-U","m-1":"_YoewO","p-1"
1396
1397
 
1397
1398
  const AssetIcon = ({
1398
1399
  assetIconSrc,
1399
- assetIconProtocolScr: _assetIconProtocolScr = null,
1400
+ assetIconProtocolSrc: _assetIconProtocolSrc = null,
1400
1401
  fallbackSrc: _fallbackSrc = null,
1401
1402
  small: _small = false
1402
1403
  }) => {
@@ -1411,8 +1412,8 @@ const AssetIcon = ({
1411
1412
  className: s["asset-icon-primary"] + (_small ? " " + s["small"] : ""),
1412
1413
  alt: " ",
1413
1414
  onError: handleFailedLoad
1414
- }), _assetIconProtocolScr ? /*#__PURE__*/React.createElement("img", {
1415
- src: _assetIconProtocolScr,
1415
+ }), _assetIconProtocolSrc ? /*#__PURE__*/React.createElement("img", {
1416
+ src: _assetIconProtocolSrc,
1416
1417
  className: s["asset-icon-secondary"] + (_small ? " " + s["small"] : ""),
1417
1418
  alt: " ",
1418
1419
  onError: handleFailedLoad
@@ -1420,12 +1421,12 @@ const AssetIcon = ({
1420
1421
  };
1421
1422
  AssetIcon.propTypes = {
1422
1423
  assetIconSrc: PropTypes.string.isRequired,
1423
- assetIconProtocolScr: PropTypes.string,
1424
+ assetIconProtocolSrc: PropTypes.string,
1424
1425
  fallbackSrc: PropTypes.string,
1425
1426
  small: PropTypes.bool
1426
1427
  };
1427
1428
  AssetIcon.defaultProps = {
1428
- assetIconProtocolScr: null,
1429
+ assetIconProtocolSrc: null,
1429
1430
  fallbackSrc: null,
1430
1431
  small: false
1431
1432
  };
@@ -1805,5 +1806,1197 @@ AmountUtils.defaultCryptoParams = {
1805
1806
  periods: true // Whether we add periods ("..") as suffix for trimmed numbers
1806
1807
  };
1807
1808
 
1808
- export { AmountUtils, AssetIcon, Button, FiatCurrenciesService, LoadingDots, SupportChat, improveAndRethrow };
1809
+ class Blockchain {
1810
+ /**
1811
+ * @param name {string} latin printable name of blockchain
1812
+ * @param supportedProtocols {Protocol[]}
1813
+ */
1814
+ constructor(name, supportedProtocols = []) {
1815
+ this.name = name;
1816
+ this.supportedProtocols = supportedProtocols;
1817
+ }
1818
+ }
1819
+
1820
+ class Protocol {
1821
+ constructor(protocolName) {
1822
+ this.protocol = protocolName;
1823
+ }
1824
+ }
1825
+
1826
+ /**
1827
+ * The model for cryptocurrency coins.
1828
+ *
1829
+ * WARNING: this class should not be instantiated directly. Use only predefined singleton Coin (or descendants) instances.
1830
+ */
1831
+ class Coin {
1832
+ /**
1833
+ * Creates new coin
1834
+ *
1835
+ * @param latinName {string} the coin name in latin symbols like "Bitcoin"
1836
+ * @param ticker {string} the coin symbol/ticker/code like 'BTC'. Always upper case. A unique coin identifier
1837
+ * @param tickerPrintable {string} ticker but in printable format. Useful for tokens based on external blockchains
1838
+ * like ERC20 or TRC20. It is not friendly to display USDTERC20 or BUSDTRC20 - more neat options are just
1839
+ * USDT and BUSD. Note that you should always care about user's understanding of what coin he/she is working
1840
+ * with as printable ticker for USDTERC20 and USDTTRC20 are the same.
1841
+ * @param digitsCountAfterComma {number} count of digits after the comma. E.g. 8 for bitcoin
1842
+ * @param maxValue {number|null} max possible value for cryptocurrency. Null means that the currency has no max possible value
1843
+ * @param atomName {string} name of the coin's atomic value. Like 'satoshi' for bitcoin
1844
+ * @param mainnet {Network} main network for this coin
1845
+ * @param testnet {Network} test network for this coin
1846
+ * @param minConfirmations {number} min confirmations count to treat the coin's transaction confirmed
1847
+ * @param payableEntityStringForFeeRate {string|null} the payable fee entity like byte for bitcoin or gas for ether if present
1848
+ * @param feeOptionsTimeStringsSortedDesc {string[]} array of 4 strings for fee options when sending coins. Should be sorted from the highest time to the smallest
1849
+ * @param feeRatesExpirationTimeMs {number} number of milliseconds to treat the fee rates as expired
1850
+ * @param blockchain {Blockchain} blockchain object
1851
+ * @param [protocol] {Protocol|null} token/coin protocol if relevant
1852
+ * @param [tokenAddress] {string|null} address of contract of this token (if the coin is token)
1853
+ * @param [doesUseLowerCaseAddresses] {boolean} flag to clarify whether we can use lower case addresses to ensure more robust comparisons
1854
+ * @param [doesUseOutputs=false] {boolean} true if this coin uses inputs/outputs concept and false if it uses just balances
1855
+ */
1856
+ constructor(latinName, ticker, tickerPrintable, digitsCountAfterComma, maxValue, atomName, mainnet, testnet, minConfirmations, payableEntityStringForFeeRate, feeOptionsTimeStringsSortedDesc, feeRatesExpirationTimeMs, blockchain, protocol = null, tokenAddress = null, doesUseLowerCaseAddresses = true, doesUseOutputs = false) {
1857
+ this.latinName = latinName;
1858
+ this.ticker = ticker;
1859
+ this.tickerPrintable = tickerPrintable;
1860
+ this.digits = digitsCountAfterComma;
1861
+ this.maxValue = maxValue;
1862
+ this.atomName = atomName;
1863
+ this.mainnet = mainnet;
1864
+ this.testnet = testnet;
1865
+ this.minConfirmations = minConfirmations;
1866
+ this.payableEntityStringForFeeRate = payableEntityStringForFeeRate;
1867
+ this.feeOptionsTimeStringsSortedDesc = feeOptionsTimeStringsSortedDesc;
1868
+ this.feeRatesExpirationTimeMs = feeRatesExpirationTimeMs;
1869
+ this.protocol = protocol;
1870
+ this.blockchain = blockchain;
1871
+ // TODO: [bug, critical] use testnet property for testnet contract address as it blocks the app work in testnets
1872
+ this.tokenAddress = tokenAddress;
1873
+ this.feeCoin = this;
1874
+ this._significantDigits = 8;
1875
+ this.doesUseLowerCaseAddresses = doesUseLowerCaseAddresses;
1876
+ this.doesUseOutputs = doesUseOutputs;
1877
+ }
1878
+
1879
+ /**
1880
+ * Sets fee coin
1881
+ *
1882
+ * @param feeCoin {Coin} some tokens use another coin to charge transaction fee as they work on top of some external
1883
+ * blockchain. So pass here the coin the token uses for fee charging. Like for ERC20 token the fee coin is ETH.
1884
+ * By default, the creating coin will be set as a value for this field.
1885
+ */
1886
+ setFeeCoin(feeCoin) {
1887
+ this.feeCoin = feeCoin;
1888
+ }
1889
+
1890
+ /**
1891
+ * Checks whether this coin uses another coin (blockchain) to charge fee for transactions (means works on base of
1892
+ * some external blockchain).
1893
+ *
1894
+ * @return {boolean} true if this coin uses external blockchain to perform transactions and charge fee
1895
+ */
1896
+ doesUseDifferentCoinFee() {
1897
+ return this.feeCoin !== this;
1898
+ }
1899
+
1900
+ /**
1901
+ * Converts the given atoms string/number to string representing the same amount in coin itself - floating point number
1902
+ *
1903
+ * @param atoms {string} atoms positive integer amount
1904
+ * @return {string} coin amount floating point number as a string
1905
+ */
1906
+ atomsToCoinAmount(atoms) {
1907
+ throw new Error("Not implemented in base Coin");
1908
+ }
1909
+
1910
+ /**
1911
+ * Converts the given coins amount string/number to string representing the same amount in coin atoms - integer number
1912
+ *
1913
+ * @param coinsAmount {string} coins positive floating point amount
1914
+ * @return {string} coin atoms amount integer number as a string
1915
+ */
1916
+ coinAmountToAtoms(coinsAmount) {
1917
+ throw new Error("Not implemented in base Coin");
1918
+ }
1919
+
1920
+ /**
1921
+ * Composes URL to view the tx with given id in the external blockchain explorer
1922
+ *
1923
+ * @param txId {string} id of transaction
1924
+ * @return {string} URL string
1925
+ */
1926
+ composeUrlToTransactionExplorer(txId) {
1927
+ throw new Error("Not implemented in base Coin");
1928
+ }
1929
+
1930
+ /**
1931
+ * Most of the cryptocurrencies has specific fee rate or fee price metric. This value usually has specific measure
1932
+ * like satoshi/byte or gWei/gas. This function adds the described denomination string to the given amount
1933
+ * as a suffix and returns the result string ready to be show to a user.
1934
+ *
1935
+ * @param coinAtomsString {string} coin atoms positive integer amount
1936
+ * @return {string} string of coin amount and fee rate units
1937
+ */
1938
+ coinAtomsFeeRateToCommonlyUsedAmountFormatWithDenominationString(coinAtomsString) {
1939
+ throw new Error("Not implemented in base Coin");
1940
+ }
1941
+
1942
+ /**
1943
+ * Check whether this coin support transaction prioritisation during the sending process.
1944
+ *
1945
+ * @return {boolean} true if support transaction prioritisation and false otherwise
1946
+ */
1947
+ doesSupportTransactionPrioritisation() {
1948
+ return Array.isArray(this.feeOptionsTimeStringsSortedDesc);
1949
+ }
1950
+ tickerAndProtocol() {
1951
+ try {
1952
+ var _ref;
1953
+ return `${this.tickerPrintable}${this.protocol ? (_ref = " " + this.protocol.protocol) != null ? _ref : "" : ""}`;
1954
+ } catch (e) {
1955
+ improveAndRethrow(e, "tickerAndProtocol");
1956
+ }
1957
+ }
1958
+ }
1959
+
1960
+ class LogsStorage {
1961
+ static saveLog(log) {
1962
+ this._inMemoryStorage.push(log);
1963
+ }
1964
+ static getInMemoryLogs() {
1965
+ return this._inMemoryStorage;
1966
+ }
1967
+ static getAllLogs() {
1968
+ let storedLogs = "";
1969
+ if (typeof window !== "undefined") {
1970
+ storedLogs = localStorage.getItem(this._logsStorageId);
1971
+ }
1972
+ return `${storedLogs}\n${this._inMemoryStorage.join("\n")}`;
1973
+ }
1974
+
1975
+ /**
1976
+ * @param logger {Logger}
1977
+ */
1978
+ static saveToTheDisk(logger) {
1979
+ try {
1980
+ const MAX_LOCAL_STORAGE_VOLUME_BYTES = 5 * 1024 * 1024;
1981
+ const MAX_LOGS_STORAGE_BYTES = MAX_LOCAL_STORAGE_VOLUME_BYTES * 0.65;
1982
+ if (typeof window !== "undefined") {
1983
+ const existingLogs = localStorage.getItem(this._logsStorageId);
1984
+ const logsString = `${existingLogs}\n${this._inMemoryStorage.join("\n")}`;
1985
+ const lettersCountToRemove = logsString.length - Math.round(MAX_LOGS_STORAGE_BYTES / 2);
1986
+ if (lettersCountToRemove > 0) {
1987
+ localStorage.setItem(this._logsStorageId, logsString.slice(lettersCountToRemove, logsString.length));
1988
+ } else {
1989
+ localStorage.setItem(this._logsStorageId, logsString);
1990
+ }
1991
+ this._inMemoryStorage = [];
1992
+ }
1993
+ } catch (e) {
1994
+ logger == null || logger.logError(e, "saveToTheDisk", "Failed to save logs to disk");
1995
+ }
1996
+ }
1997
+ static removeAllClientLogs() {
1998
+ if (typeof window !== "undefined") {
1999
+ if (localStorage.getItem("doNotRemoveClientLogsWhenSignedOut") !== "true") {
2000
+ localStorage.removeItem(this._logsStorageId);
2001
+ }
2002
+ }
2003
+ this._inMemoryStorage = [];
2004
+ }
2005
+ static setDoNotRemoveClientLogsWhenSignedOut(value) {
2006
+ if (typeof window !== "undefined") {
2007
+ localStorage.setItem("doNotRemoveClientLogsWhenSignedOut", value);
2008
+ }
2009
+ }
2010
+ }
2011
+ LogsStorage._inMemoryStorage = [];
2012
+ LogsStorage._logsStorageId = "clietnLogs_j203fj2D0n-d1";
2013
+
2014
+ /**
2015
+ * Stringify given object by use of JSON.stringify but handles circular structures and "response", "request" properties
2016
+ * to avoid stringing redundant data when printing errors containing request/response objects.
2017
+ *
2018
+ * @param object - object to be stringed
2019
+ * @param indent - custom indentation
2020
+ * @return {string} - stringed object
2021
+ */
2022
+ function safeStringify(object, indent = 2) {
2023
+ let cache = [];
2024
+ if (typeof object === "string" || typeof object === "function" || typeof object === "number" || typeof object === "undefined" || typeof object === "boolean") {
2025
+ return String(object);
2026
+ }
2027
+ const retVal = JSON.stringify(object, (key, value) => {
2028
+ if (key.toLowerCase().includes("request")) {
2029
+ return JSON.stringify({
2030
+ body: value == null ? void 0 : value.body,
2031
+ query: value == null ? void 0 : value.query,
2032
+ headers: value == null ? void 0 : value.headers
2033
+ });
2034
+ }
2035
+ if (key.toLowerCase().includes("response")) {
2036
+ return JSON.stringify({
2037
+ statusText: value == null ? void 0 : value.statusText,
2038
+ status: value == null ? void 0 : value.status,
2039
+ data: value == null ? void 0 : value.data,
2040
+ headers: value == null ? void 0 : value.headers
2041
+ });
2042
+ }
2043
+ return typeof value === "object" && value !== null ? cache.includes(value) ? "duplicated reference" // Duplicated references were found, discarding this key
2044
+ : cache.push(value) && value // Store value in our collection
2045
+ : value;
2046
+ }, indent);
2047
+ cache = null;
2048
+ return retVal;
2049
+ }
2050
+
2051
+ class Logger {
2052
+ /**
2053
+ * Logs to client logs storage.
2054
+ *
2055
+ * WARNING! this method should ce used carefully for critical logging as we have the restriction for storing logs
2056
+ * on client side as we store them inside the local storage. Please see details inside storage.js
2057
+ * @param logString {string} log string
2058
+ * @param source {string} source of the log entry
2059
+ */
2060
+ static log(logString, source) {
2061
+ const timestamp = new Date().toISOString();
2062
+ LogsStorage.saveLog(`${timestamp}|${source}:${logString}`);
2063
+ }
2064
+ static logError(e, settingFunction, additionalMessage = "", onlyToConsole = false) {
2065
+ var _e$errorDescription, _e$howToFix;
2066
+ let message = `\nFunction call ${settingFunction != null ? settingFunction : ""} failed. Error message: ${e == null ? void 0 : e.message}. ${additionalMessage} `;
2067
+ message += `${(_e$errorDescription = e == null ? void 0 : e.errorDescription) != null ? _e$errorDescription : ""}${(_e$howToFix = e == null ? void 0 : e.howToFix) != null ? _e$howToFix : ""}` + ((e == null ? void 0 : e.httpStatus) === 403 ? "Authentication has expired or was lost. " : "");
2068
+ if (e != null && e.response) {
2069
+ try {
2070
+ const responseData = safeStringify({
2071
+ response: e.response
2072
+ });
2073
+ responseData && (message += `\n${responseData}. `);
2074
+ } catch (e) {}
2075
+ }
2076
+ const finalErrorText = message + ". " + safeStringify(e);
2077
+ // eslint-disable-next-line no-console
2078
+ console.error(finalErrorText);
2079
+ if (!onlyToConsole) {
2080
+ this.log(finalErrorText, "logError");
2081
+ }
2082
+ }
2083
+ }
2084
+
2085
+ /**
2086
+ * TODO: [tests, critical] Ued by payments logic
2087
+ *
2088
+ * Simple cache based on Map.
2089
+ * Provides ability to store event-dependent data.
2090
+ */
2091
+ class Cache {
2092
+ /**
2093
+ * @param eventBus {EventBus} EventBus.js lib instance
2094
+ * @param [noSessionEvents=[]] {string[]} array of events that will be treated as "no session"
2095
+ */
2096
+ constructor(eventBus, noSessionEvents = []) {
2097
+ this._cache = new Map();
2098
+ this._eventDependentDataKeys = [];
2099
+ this._noSessionEvents = noSessionEvents;
2100
+ this._eventBus = eventBus;
2101
+ }
2102
+ _setupIntervalClearingExpired() {
2103
+ let _cleaner = function cleaner() {
2104
+ try {
2105
+ for (const key of this._cache.keys()) {
2106
+ const item = this._cache.get(key);
2107
+ if (item && item.ttlMs && item.addedMsTimestamp + item.ttlMs < Date.now()) {
2108
+ this._cache.delete(key);
2109
+ }
2110
+ }
2111
+ } catch (e) {
2112
+ improveAndRethrow(e, "_intervalClearingExpiredCache");
2113
+ }
2114
+ };
2115
+ _cleaner = _cleaner.bind(this);
2116
+ setInterval(_cleaner, 1000);
2117
+ }
2118
+
2119
+ /**
2120
+ * Puts data to cache
2121
+ *
2122
+ * @param key {string} string key for this data
2123
+ * @param data {any} any data
2124
+ * @param ttlMs {number|null} optional milliseconds number for cache lifetime
2125
+ * @throws {Error} when the data is null/undefined because these values for data are reserved for internal logic
2126
+ */
2127
+ put(key, data, ttlMs = null) {
2128
+ try {
2129
+ if (typeof key !== "string" || data == null) {
2130
+ throw new Error(`Trying to cache corrupted data: ${key}, ${data}`);
2131
+ }
2132
+ this._cache.set(key, {
2133
+ data: data,
2134
+ addedMsTimestamp: Date.now(),
2135
+ ttlMs: ttlMs
2136
+ });
2137
+ } catch (e) {
2138
+ improveAndRethrow(e, "cache.put");
2139
+ }
2140
+ }
2141
+ putSessionDependentData(key, data, ttlMs = null) {
2142
+ this._putEventDependentData(key, data, this._noSessionEvents, ttlMs);
2143
+ }
2144
+
2145
+ /**
2146
+ * Puts data to cache and adds its key to list of keys that should be related by each of given events.
2147
+ *
2148
+ * @param key {string} key for cache
2149
+ * @param data {any} any caching data
2150
+ * @param events {string[]} list of events forcing putting data to be removed when triggered
2151
+ * @param ttlMs {|null} optional time to live for this cache item
2152
+ * @throws {Error} when the data is null/undefined because these values for data are reserved for internal logic
2153
+ */
2154
+ putEventDependentData(key, data, events, ttlMs = null) {
2155
+ this._putEventDependentData(key, data, events, ttlMs);
2156
+ }
2157
+ _putEventDependentData(key, data, events, ttlMs = null) {
2158
+ try {
2159
+ if (typeof key !== "string" || data == null) {
2160
+ throw new Error(`Trying to cache corrupted data: ${key}, ${data}`);
2161
+ }
2162
+ this._cache.set(key, {
2163
+ data: data,
2164
+ addedMsTimestamp: Date.now(),
2165
+ ttlMs: ttlMs
2166
+ });
2167
+ for (let event of events) {
2168
+ const eventAndKeys = this._eventDependentDataKeys.find(item => item[0] === event);
2169
+ if (eventAndKeys) {
2170
+ eventAndKeys.push(key);
2171
+ } else {
2172
+ this._eventDependentDataKeys.push([event, key]);
2173
+ this._eventBus.addEventListener(event, () => {
2174
+ try {
2175
+ const keys = this._eventDependentDataKeys.find(item => item[0] === event);
2176
+ (keys != null ? keys : [event]).slice(1).forEach(key => this._cache.delete(key));
2177
+ } catch (e) {
2178
+ Logger.logError(e, "cache.removing-for-event", `Event: ${event}`);
2179
+ }
2180
+ });
2181
+ }
2182
+ }
2183
+ } catch (e) {
2184
+ improveAndRethrow(e, "cache.putEventDependentData");
2185
+ }
2186
+ }
2187
+
2188
+ // TODO: [feature, low] add clearing of expired data by schedule
2189
+ get(key) {
2190
+ try {
2191
+ const item = this._cache.get(key);
2192
+ if (item) {
2193
+ if (item.addedMsTimestamp && item.ttlMs !== null && item.addedMsTimestamp + item.ttlMs < Date.now()) {
2194
+ this._cache.delete(key);
2195
+ return null;
2196
+ } else {
2197
+ return item.data;
2198
+ }
2199
+ }
2200
+ return null;
2201
+ } catch (e) {
2202
+ improveAndRethrow(e, "cache.get");
2203
+ }
2204
+ }
2205
+ getLastUpdateTimestamp(key) {
2206
+ var _this$_cache$get$adde, _this$_cache$get;
2207
+ return (_this$_cache$get$adde = (_this$_cache$get = this._cache.get(key)) == null ? void 0 : _this$_cache$get.addedMsTimestamp) != null ? _this$_cache$get$adde : null;
2208
+ }
2209
+
2210
+ /**
2211
+ * Updates the timestamp of the last update for specified key to the provided value.
2212
+ * Can be useful when TTL is controlled outside this class.
2213
+ *
2214
+ * @param key {string}
2215
+ * @param timestamp {number}
2216
+ * @return {boolean}
2217
+ */
2218
+ setLastUpdateTimestamp(key, timestamp) {
2219
+ try {
2220
+ const item = this._cache.get(key);
2221
+ if (item != null && typeof timestamp === "number") {
2222
+ this._cache.set(key, _extends({}, item, {
2223
+ addedTimestampMs: timestamp
2224
+ }));
2225
+ return true;
2226
+ }
2227
+ return false;
2228
+ } catch (e) {
2229
+ improveAndRethrow("cache.setLastUpdateTimestamp");
2230
+ }
2231
+ }
2232
+ invalidate(key) {
2233
+ try {
2234
+ this._cache.delete(key);
2235
+ } catch (e) {
2236
+ improveAndRethrow(e, "cache.invalidate");
2237
+ }
2238
+ }
2239
+ invalidateContaining(keyPart) {
2240
+ if (typeof keyPart !== "string" || keyPart === "") {
2241
+ throw new Error("Trying to invalidate containing wrong key or empty key: " + keyPart);
2242
+ }
2243
+ try {
2244
+ const matchedKeys = Array.from(this._cache.keys()).filter(key => typeof key === "string" && new RegExp(keyPart).test(key));
2245
+ for (let i = 0; i < matchedKeys.length; ++i) {
2246
+ this._cache.delete(matchedKeys[i]);
2247
+ }
2248
+ } catch (e) {
2249
+ improveAndRethrow(e, "invalidateContaining");
2250
+ }
2251
+ }
2252
+ clear() {
2253
+ this._cache.clear();
2254
+ this._sessionDependentDataKeys = [];
2255
+ }
2256
+
2257
+ /**
2258
+ * Saves given data string to persistent cache.
2259
+ * NOTE: we have no TTL here, implement if needed.
2260
+ *
2261
+ * WARNING: use only when really needed and don't store big data as we use localStorage
2262
+ * under the hood and its capacity is restricted.
2263
+ *
2264
+ * @param uniqueKey {string} the key should be unique
2265
+ * @param data {string} only string data allowed
2266
+ */
2267
+ putClientPersistentData(uniqueKey, data) {
2268
+ try {
2269
+ if (typeof window !== "undefined") {
2270
+ localStorage.setItem(uniqueKey, data);
2271
+ }
2272
+ } catch (e) {
2273
+ improveAndRethrow(e, "cache.putClientPersistentData");
2274
+ }
2275
+ }
2276
+
2277
+ /**
2278
+ * @param uniqueKey {string}
2279
+ * @return {string|null}
2280
+ */
2281
+ getClientPersistentData(uniqueKey) {
2282
+ try {
2283
+ if (typeof window !== "undefined") {
2284
+ return localStorage.getItem(uniqueKey);
2285
+ }
2286
+ return null;
2287
+ } catch (e) {
2288
+ improveAndRethrow(e, "cache.getClientPersistentData");
2289
+ }
2290
+ }
2291
+
2292
+ /**
2293
+ * Only makes effect if the TTL is not null.
2294
+ *
2295
+ * @param key {string}
2296
+ * @param ttlMs {number|null}
2297
+ */
2298
+ markCacheItemAsExpiredButDontRemove(key, ttlMs = null) {
2299
+ try {
2300
+ const item = this._cache.get(key);
2301
+ const ttlFinalMs = ttlMs != null ? ttlMs : item == null ? void 0 : item.ttlMs;
2302
+ if (item != null && ttlFinalMs) {
2303
+ this._cache.set(key, {
2304
+ data: item.data,
2305
+ addedMsTimestamp: Date.now() - ttlFinalMs - 1,
2306
+ ttlMs: ttlFinalMs
2307
+ });
2308
+ }
2309
+ } catch (e) {
2310
+ improveAndRethrow(e, "cache.markCacheItemAsExpiredButDontRemove");
2311
+ }
2312
+ }
2313
+ }
2314
+
2315
+ class ExistingSwap {
2316
+ /**
2317
+ * @param swapId {string}
2318
+ * @param status {SwapProvider.SWAP_STATUSES}
2319
+ * @param createdAt {number}
2320
+ * @param expiresAt {number}
2321
+ * @param confirmations {number}
2322
+ * @param rate {string}
2323
+ * @param refundAddress {string}
2324
+ * @param fromCoin {Coin}
2325
+ * @param fromAmount {string}
2326
+ * @param fromTransactionId {string}
2327
+ * @param toCoin {Coin}
2328
+ * @param toAmount {string}
2329
+ * @param toTransactionId {string|null}
2330
+ * @param toAddress {string}
2331
+ * @param partner {string}
2332
+ */
2333
+ constructor(swapId, status, createdAt, expiresAt, confirmations, rate, refundAddress, payToAddress, fromCoin, fromAmount, fromTransactionId, fromTransactionLink, toCoin, toAmount, toTransactionId, toTransactionLink, toAddress,
2334
+ // TODO: [refactoring, moderate] toAddress is not quite clear. How about recipientAddress? task_id=0815a111c99543b78d374217eadbde4f
2335
+ partner) {
2336
+ this.swapId = swapId;
2337
+ this.status = status;
2338
+ this.createdAt = createdAt;
2339
+ this.expiresAt = expiresAt;
2340
+ this.confirmations = confirmations;
2341
+ this.rate = rate;
2342
+ this.refundAddress = refundAddress;
2343
+ this.payToAddress = payToAddress;
2344
+ this.fromCoin = fromCoin;
2345
+ this.fromTransactionId = fromTransactionId;
2346
+ this.fromAmount = fromAmount;
2347
+ this.fromTransactionLink = fromTransactionLink;
2348
+ this.toCoin = toCoin;
2349
+ this.toTransactionId = toTransactionId;
2350
+ this.toTransactionLink = toTransactionLink;
2351
+ this.toAmount = toAmount;
2352
+ this.toAddress = toAddress;
2353
+ this.partner = partner;
2354
+ }
2355
+ }
2356
+
2357
+ class SwapProvider {
2358
+ /**
2359
+ * @return {Promise<void>}
2360
+ */
2361
+ async initialize() {
2362
+ throw new Error("Not implemented in base");
2363
+ }
2364
+
2365
+ /**
2366
+ * @return {number} milliseconds TTL
2367
+ */
2368
+ getSwapCreationInfoTtlMs() {
2369
+ throw new Error("Not implemented in base");
2370
+ }
2371
+
2372
+ /**
2373
+ * Retrieves all deposit currencies supported by this swap provider.
2374
+ * Returns one of SwapProvider.COMMON_ERRORS in case of processable fail.
2375
+ *
2376
+ * @return {Promise<({ result: true, coins: Coin[] }|{ result: false, reason: string })>}
2377
+ */
2378
+ async getDepositCurrencies() {
2379
+ throw new Error("Not implemented in base");
2380
+ }
2381
+
2382
+ /**
2383
+ * Retrieves all withdrawable currencies supported by this swap provider.
2384
+ * Returns one of SwapProvider.COMMON_ERRORS in case of processable fail.
2385
+ *
2386
+ * @param [exceptCurrency=null] {Coin|null}
2387
+ * @return {Promise<({ result: true, coins: Coin[] }|{ result: false, reason: string })>}
2388
+ */
2389
+ async getWithdrawalCurrencies(exceptCurrency = null) {
2390
+ throw new Error("Not implemented in base");
2391
+ }
2392
+
2393
+ /**
2394
+ * Retrieves URL for coin icon or fallback if not found.
2395
+ *
2396
+ * @param coin {Coin|string} coin or rabbit-format of coin ticker
2397
+ * @return {string}
2398
+ */
2399
+ getIconUrl(coin) {
2400
+ throw new Error("Not implemented in base");
2401
+ }
2402
+
2403
+ /**
2404
+ * Retrieves coin to USDT rate.
2405
+ *
2406
+ * @param coin {Coin}
2407
+ * @return {{result: true, rate: string}|{result: false}}
2408
+ */
2409
+ async getCoinToUSDTRate(coin) {
2410
+ throw new Error("Not implemented in base");
2411
+ }
2412
+
2413
+ /**
2414
+ * Retrieves estimation for swapping giving coins amount.
2415
+ * null min or max signals there is no corresponding limitation. undefined means that the limits were not retrieved.
2416
+ * For fail result on of SwapProvider.NO_SWAPS_REASONS or SwapProvider.COMMON_ERRORS reasons will be returned.
2417
+ *
2418
+ * @param fromCoin {Coin}
2419
+ * @param toCoin {Coin}
2420
+ * @param amountCoins {string}
2421
+ * @param [fromCoinToUsdRate=null] pass if you want to increase the min amount returned
2422
+ * by provider with some fixed "insurance" amount to cover min amount fluctuations.
2423
+ * @return {Promise<({
2424
+ * result: false,
2425
+ * reason: string,
2426
+ * smallestMin: (string|null|undefined),
2427
+ * greatestMax: (string|null|undefined),
2428
+ * }|{
2429
+ * result: true,
2430
+ * min: (string|null),
2431
+ * max: (string|null),
2432
+ * smallestMin: (string|null),
2433
+ * greatestMax: (string|null),
2434
+ * rate: (string|null),
2435
+ * durationMinutesRange: string,
2436
+ * [rawSwapData]: Object
2437
+ * })>}
2438
+ */
2439
+ async getSwapInfo(fromCoin, toCoin, amountCoins, fromCoinToUsdRate = null) {
2440
+ throw new Error("Not implemented in base");
2441
+ }
2442
+
2443
+ /**
2444
+ * For fail result we return one of SwapProvider.CREATION_FAIL_REASONS or SwapProvider.COMMON_ERRORS.
2445
+ *
2446
+ * @param fromCoin {Coin}
2447
+ * @param toCoin {Coin}
2448
+ * @param amount {string}
2449
+ * @param toAddress {string}
2450
+ * @param refundAddress {string}
2451
+ * @param rawSwapData {Object|null}
2452
+ * @param clientIpAddress {string}
2453
+ * @return {Promise<({
2454
+ * result: true,
2455
+ * swapId: string,
2456
+ * fromCoin: Coin,
2457
+ * fromAmount: string,
2458
+ * fromAddress: string,
2459
+ * toCoin: Coin,
2460
+ * toAmount: string,
2461
+ * toAddress: string,
2462
+ * rate: string
2463
+ * }|{
2464
+ * result: false,
2465
+ * reason: string,
2466
+ * partner: string
2467
+ * })>}
2468
+ */
2469
+ async createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData = null, clientIpAddress) {
2470
+ throw new Error("Not implemented in base");
2471
+ }
2472
+
2473
+ /**
2474
+ * Retrieves details and status for swaps by given ids.
2475
+ * If some swap is not found by id then there is no item in return list.
2476
+ *
2477
+ * @param swapIds {string[]}
2478
+ * @return {Promise<{result: false, reason: string}|{result:true, swaps: ExistingSwap[]}>}
2479
+ */
2480
+ async getExistingSwapsDetailsAndStatus(swapIds) {
2481
+ throw new Error("Not implemented in base");
2482
+ }
2483
+
2484
+ /**
2485
+ * @param ticker {string}
2486
+ * @return {Coin|null}
2487
+ */
2488
+ getCoinByTickerIfPresent(ticker) {
2489
+ throw new Error("Not implemented in base");
2490
+ }
2491
+
2492
+ /**
2493
+ * @param asset {Coin}
2494
+ * @param address {string}
2495
+ * @return {boolean}
2496
+ */
2497
+ isAddressValidForAsset(asset, address) {
2498
+ throw new Error("Not implemented in base");
2499
+ }
2500
+ }
2501
+ SwapProvider.COMMON_ERRORS = {
2502
+ REQUESTS_LIMIT_EXCEEDED: "requestsLimitExceeded"
2503
+ };
2504
+ SwapProvider.NO_SWAPS_REASONS = {
2505
+ TOO_LOW: "tooLow",
2506
+ TOO_HIGH: "tooHigh",
2507
+ NOT_SUPPORTED: "notSupported"
2508
+ };
2509
+ SwapProvider.CREATION_FAIL_REASONS = {
2510
+ RETRIABLE_FAIL: "retriableFail"
2511
+ };
2512
+ SwapProvider.SWAP_STATUSES = {
2513
+ WAITING_FOR_PAYMENT: "waiting_for_payment",
2514
+ // public +
2515
+ CONFIRMING: "confirming",
2516
+ PAYMENT_RECEIVED: "payment_received",
2517
+ // public +
2518
+ EXCHANGING: "exchanging",
2519
+ // session full // public +
2520
+ COMPLETED: "completed",
2521
+ // session full // public +
2522
+ REFUNDED: "refunded",
2523
+ // session full // public +
2524
+ EXPIRED: "expired",
2525
+ // public +
2526
+ FAILED: "failed" // public +
2527
+ };
2528
+
2529
+ const BANNED_PARTNERS = ["stealthex", "changee", "coincraddle"];
2530
+ const FALLBACK_ICON_URL = "https://rabbit.io/asset-icons/fallback.svg";
2531
+ class SwapspaceSwapProvider extends SwapProvider {
2532
+ constructor(apiKeysProxyUrl, cache, customCoinBuilder = (coin, network) => null, useRestrictedCoinsSet = true) {
2533
+ super();
2534
+ this._supportedCoins = [];
2535
+ this._URL = `${apiKeysProxyUrl}/swapspace`;
2536
+ this._maxRateDigits = 20;
2537
+ this.useRestrictedCoinsSet = useRestrictedCoinsSet;
2538
+ this._customCoinBuilder = customCoinBuilder;
2539
+ this._cache = cache;
2540
+ }
2541
+ getSwapCreationInfoTtlMs() {
2542
+ /* Actually 2 minutes and only relevant for some partners, but we use it
2543
+ * (and even a bit smaller value) for better consistency */
2544
+ return 110000;
2545
+ }
2546
+ async getDepositCurrencies() {
2547
+ const loggerSource = "getDepositCurrencies";
2548
+ try {
2549
+ var _this$_supportedCoins;
2550
+ await this._fetchSupportedCurrenciesIfNeeded();
2551
+ Logger.log(`We have ${(_this$_supportedCoins = this._supportedCoins) == null ? void 0 : _this$_supportedCoins.length} supported coins, getting depositable`, loggerSource);
2552
+ return {
2553
+ result: true,
2554
+ coins: this._supportedCoins.filter(item => item.deposit).map(item => item.coin)
2555
+ };
2556
+ } catch (e) {
2557
+ var _e$response;
2558
+ if ((e == null || (_e$response = e.response) == null ? void 0 : _e$response.status) === 429) {
2559
+ return {
2560
+ result: false,
2561
+ reason: SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED
2562
+ };
2563
+ }
2564
+ improveAndRethrow(e, loggerSource);
2565
+ }
2566
+ }
2567
+ async getWithdrawalCurrencies(exceptCurrency = null) {
2568
+ const loggerSource = "getWithdrawalCurrencies";
2569
+ try {
2570
+ var _this$_supportedCoins2;
2571
+ await this._fetchSupportedCurrenciesIfNeeded();
2572
+ Logger.log(`We have ${(_this$_supportedCoins2 = this._supportedCoins) == null ? void 0 : _this$_supportedCoins2.length} supported coins, getting withdrawable`, loggerSource);
2573
+ return {
2574
+ result: true,
2575
+ coins: this._supportedCoins.filter(item => item.withdrawal && (!exceptCurrency || item.coin !== exceptCurrency)).map(item => item.coin)
2576
+ };
2577
+ } catch (e) {
2578
+ var _e$response2;
2579
+ if ((e == null || (_e$response2 = e.response) == null ? void 0 : _e$response2.status) === 429) {
2580
+ return {
2581
+ result: false,
2582
+ reason: SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED
2583
+ };
2584
+ }
2585
+ improveAndRethrow(e, loggerSource);
2586
+ }
2587
+ }
2588
+ async initialize() {
2589
+ await this._fetchSupportedCurrenciesIfNeeded();
2590
+ }
2591
+ getIconUrl(coinOrTicker) {
2592
+ const loggerSource = "getIconUrl";
2593
+ try {
2594
+ var _this$_supportedCoins4, _this$_supportedCoins5;
2595
+ let coin = coinOrTicker;
2596
+ if (!(coinOrTicker instanceof Coin)) {
2597
+ var _this$_supportedCoins3;
2598
+ coin = (_this$_supportedCoins3 = this._supportedCoins.find(i => i.coin.ticker === coinOrTicker)) == null ? void 0 : _this$_supportedCoins3.coin;
2599
+ }
2600
+ return (_this$_supportedCoins4 = (_this$_supportedCoins5 = this._supportedCoins.find(item => item.coin === coin)) == null ? void 0 : _this$_supportedCoins5.iconURL) != null ? _this$_supportedCoins4 : FALLBACK_ICON_URL;
2601
+ } catch (e) {
2602
+ improveAndRethrow(e, loggerSource);
2603
+ }
2604
+ }
2605
+ async _fetchSupportedCurrenciesIfNeeded() {
2606
+ const loggerSource = "_fetchSupportedCurrenciesIfNeeded";
2607
+ try {
2608
+ var _this$_supportedCoins6;
2609
+ if (!((_this$_supportedCoins6 = this._supportedCoins) != null && _this$_supportedCoins6.length)) {
2610
+ var _rawResponse$data, _rawResponse$data2;
2611
+ const rawResponse = await axios.get(`${this._URL}/api/v2/currencies`);
2612
+ Logger.log(`Retrieved ${rawResponse == null || (_rawResponse$data = rawResponse.data) == null ? void 0 : _rawResponse$data.length} currencies`, loggerSource);
2613
+ this._supportedCoins = ((_rawResponse$data2 = rawResponse == null ? void 0 : rawResponse.data) != null ? _rawResponse$data2 : []).map(item => {
2614
+ let coin = this._customCoinBuilder(item.code, item.network);
2615
+ if (!coin && !this.useRestrictedCoinsSet) {
2616
+ /** Building coin object for coin that isn't supported OOB in Rabbit.
2617
+ * We are doing this way to be able to use extended coins set for swaps.
2618
+ * These temporary built coins are only for in-swap use, and we omit some usual
2619
+ * coin details here.
2620
+ * Ideally we should add some new abstractions e.g. BaseCoin:
2621
+ * Coin will extend BaseCoin, SwapCoin will extend BaseCoin etc.
2622
+ * But for now it is reasonable to use this simpler approach.
2623
+ */
2624
+ const code = item.code.toUpperCase();
2625
+ const network = item.network.toUpperCase();
2626
+ const ticker = `${code}${code === network ? "" : network}`;
2627
+ const defaultDecimalPlacesForCoinNotSupportedOOB = 8;
2628
+ const defaultMinConfirmationsForCoinNotSupportedOOB = 1;
2629
+ coin = new Coin(item.name, ticker, code, defaultDecimalPlacesForCoinNotSupportedOOB, null, "", null, null, defaultMinConfirmationsForCoinNotSupportedOOB, null, [], 60000, null,
2630
+ // We cannot recognize blockchain from swapspace data
2631
+ new Protocol(network),
2632
+ // TODO: [dev] maybe we should recognize standard protocols?
2633
+ item.contractAddress || null, false);
2634
+ }
2635
+ if (coin) {
2636
+ var _item$deposit, _item$withdrawal, _item$validationRegex;
2637
+ return {
2638
+ coin: coin,
2639
+ code: item.code,
2640
+ network: item.network,
2641
+ extraId: item.extraIdName,
2642
+ isPopular: !!(item != null && item.popular),
2643
+ iconURL: item.icon ? `https://storage.swapspace.co${item.icon}` : FALLBACK_ICON_URL,
2644
+ deposit: (_item$deposit = item.deposit) != null ? _item$deposit : false,
2645
+ withdrawal: (_item$withdrawal = item.withdrawal) != null ? _item$withdrawal : false,
2646
+ validationRegexp: (_item$validationRegex = item.validationRegexp) != null ? _item$validationRegex : null
2647
+ };
2648
+ }
2649
+ return [];
2650
+ }).flat();
2651
+ this._putPopularCoinsFirst();
2652
+ }
2653
+ } catch (e) {
2654
+ improveAndRethrow(e, loggerSource);
2655
+ }
2656
+ }
2657
+
2658
+ /**
2659
+ * This method sort internal list putting popular (as swapspace thinks) coins to the top.
2660
+ * This is just for users of this API if they don't care about the sorting - we just improve a list a bit this way.
2661
+ * @private
2662
+ */
2663
+ _putPopularCoinsFirst() {
2664
+ this._supportedCoins.sort((i1, i2) => {
2665
+ if (i1.isPopular && !i2.isPopular) return -1;
2666
+ if (i2.isPopular && !i1.isPopular) return 1;
2667
+ return i1.coin.ticker > i2.coin.ticker ? 1 : i1.coin.ticker < i2.coin.ticker ? -1 : 0;
2668
+ });
2669
+ }
2670
+ async getCoinToUSDTRate(coin) {
2671
+ const loggerSource = "getCoinToUSDTRate";
2672
+ try {
2673
+ var _this$_supportedCoins7;
2674
+ await this._fetchSupportedCurrenciesIfNeeded();
2675
+
2676
+ // Using USDT TRC20 as usually fee in this network is smaller than ERC20 and this network is widely used for USDT
2677
+ const usdtTrc20 = (_this$_supportedCoins7 = this._supportedCoins.find(i => i.code === "usdt" && i.network === "trc20")) == null ? void 0 : _this$_supportedCoins7.coin;
2678
+ if (!usdtTrc20) {
2679
+ return {
2680
+ result: false
2681
+ };
2682
+ }
2683
+ const cached = this._cache.get("swapspace_usdt_rate_" + coin.ticker);
2684
+ if (cached != null) {
2685
+ return {
2686
+ result: true,
2687
+ rate: cached
2688
+ };
2689
+ }
2690
+ Logger.log("Loading USDT -> coin rate as not found in cache:", coin == null ? void 0 : coin.ticker);
2691
+ const result = await this.getSwapInfo(usdtTrc20, coin, "5000");
2692
+ if (!result.result) {
2693
+ return {
2694
+ result: false
2695
+ };
2696
+ }
2697
+
2698
+ // This calculation is not precise as we cannot recognize the actual fee and network fee. Just approximate.
2699
+ const standardSwapspaceFeeMultiplier = 1.002; // usually 0.2%
2700
+ const rate = BigNumber(1).div(BigNumber(result.rate).times(standardSwapspaceFeeMultiplier)).toString();
2701
+ this._cache.put("swapspace_usdt_rate_" + coin.ticker, rate, 15 * 60000);
2702
+ return {
2703
+ result: true,
2704
+ rate: rate
2705
+ };
2706
+ } catch (e) {
2707
+ improveAndRethrow(e, loggerSource);
2708
+ }
2709
+ }
2710
+ getCoinByTickerIfPresent(ticker) {
2711
+ try {
2712
+ var _item$coin;
2713
+ const item = this._supportedCoins.find(i => i.coin.ticker === ticker);
2714
+ return (_item$coin = item == null ? void 0 : item.coin) != null ? _item$coin : null;
2715
+ } catch (e) {
2716
+ improveAndRethrow(e, "getCoinByTickerIfPresent");
2717
+ }
2718
+ }
2719
+ async getSwapInfo(fromCoin, toCoin, amountCoins, fromCoinToUsdRate = null) {
2720
+ const loggerSource = "getSwapInfo";
2721
+ try {
2722
+ var _response$data;
2723
+ if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof amountCoins !== "string" || BigNumber(amountCoins).lt("0")) {
2724
+ throw new Error(`Wrong input params: ${amountCoins} ${fromCoin.ticker} -> ${toCoin.ticker}` + (fromCoin instanceof Coin) + (toCoin instanceof Coin));
2725
+ }
2726
+ const fromCoinSwapspaceDetails = this._supportedCoins.find(i => i.coin === fromCoin);
2727
+ const toCoinSwapspaceDetails = this._supportedCoins.find(i => i.coin === toCoin);
2728
+ if (!fromCoinSwapspaceDetails || !toCoinSwapspaceDetails) {
2729
+ throw new Error("Failed to find swapspace coin details for: " + fromCoin.ticker + " -> " + toCoin.ticker);
2730
+ }
2731
+ /* Here we use not documented parameter 'estimated=false'. This parameter controls whether we want to use
2732
+ * cached rate values stored in swapspace cache. Their support says they store at most for 30 sec.
2733
+ * But we are better off using the most actual rates.
2734
+ */
2735
+ const response = await axios.get(`${this._URL}/api/v2/amounts?fromCurrency=${fromCoinSwapspaceDetails.code}&fromNetwork=${fromCoinSwapspaceDetails.network}&toNetwork=${toCoinSwapspaceDetails.network}&toCurrency=${toCoinSwapspaceDetails.code}&amount=${amountCoins}&float=true&estimated=false`);
2736
+ Logger.log(`Retrieved ${response == null || (_response$data = response.data) == null ? void 0 : _response$data.length} options`, loggerSource);
2737
+ const options = Array.isArray(response.data) ? response.data : [];
2738
+ const exchangesSupportingThePair = options.filter(exchange => (exchange == null ? void 0 : exchange.exists) && !BANNED_PARTNERS.find(bannedPartner => bannedPartner === (exchange == null ? void 0 : exchange.partner)) && (exchange == null ? void 0 : exchange.fixed) === false && (exchange.min === 0 || exchange.max === 0 || exchange.max > exchange.min || (typeof exchange.min !== "number" || typeof exchange.max !== "number") && exchange.toAmount > 0));
2739
+ Logger.log(`${exchangesSupportingThePair == null ? void 0 : exchangesSupportingThePair.length} of them have exist=true`, loggerSource);
2740
+ if (!exchangesSupportingThePair.length) {
2741
+ return {
2742
+ result: false,
2743
+ reason: SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED
2744
+ };
2745
+ }
2746
+ const availableExchanges = exchangesSupportingThePair.filter(exchange => typeof (exchange == null ? void 0 : exchange.toAmount) === "number" && exchange.toAmount > 0);
2747
+ Logger.log(`Available (having amountTo): ${safeStringify(availableExchanges)}`, loggerSource);
2748
+ // min=0 or max=0 means there is no limit for the partner
2749
+ let smallestMin = null;
2750
+ if (exchangesSupportingThePair.find(ex => BigNumber(ex.min).isZero()) == null) {
2751
+ smallestMin = exchangesSupportingThePair.reduce((prev, cur) => {
2752
+ if (typeof cur.min === "number" && (prev === null || BigNumber(cur.min).lt(prev))) return BigNumber(cur.min);
2753
+ return prev;
2754
+ }, null);
2755
+ }
2756
+ let greatestMax = null;
2757
+ if (exchangesSupportingThePair.find(ex => BigNumber(ex.max).isZero()) == null) {
2758
+ greatestMax = exchangesSupportingThePair.reduce((prev, cur) => {
2759
+ if (typeof cur.max === "number" && (prev === null || BigNumber(cur.max).gt(prev))) return BigNumber(cur.max);
2760
+ return prev;
2761
+ }, null);
2762
+ }
2763
+ let extraCoinsToFitMinMax = "0";
2764
+ if (typeof fromCoinToUsdRate === "string" && BigNumber(fromCoinToUsdRate).gt("0")) {
2765
+ const extraUsdToFitMinMax = BigNumber("1"); // We correct the limits as the exact limit can fluctuate and cause failed swap creation
2766
+ extraCoinsToFitMinMax = AmountUtils.trim(extraUsdToFitMinMax.div(fromCoinToUsdRate), fromCoin.digits);
2767
+ }
2768
+ if (smallestMin instanceof BigNumber) {
2769
+ smallestMin = AmountUtils.trim(smallestMin.plus(extraCoinsToFitMinMax), fromCoin.digits);
2770
+ }
2771
+ if (greatestMax instanceof BigNumber) {
2772
+ if (greatestMax > extraCoinsToFitMinMax) {
2773
+ greatestMax = AmountUtils.trim(greatestMax.minus(extraCoinsToFitMinMax), fromCoin.digits);
2774
+ } else {
2775
+ greatestMax = "0";
2776
+ }
2777
+ }
2778
+ if (availableExchanges.length) {
2779
+ var _bestOpt$duration;
2780
+ const sorted = availableExchanges.sort((op1, op2) => op2.toAmount - op1.toAmount);
2781
+ const bestOpt = sorted[0];
2782
+ Logger.log(`Returning first option after sorting: ${safeStringify(bestOpt)}`, loggerSource);
2783
+ let max = null;
2784
+ let min = null;
2785
+ if (extraCoinsToFitMinMax != null) {
2786
+ if (typeof bestOpt.max === "number" && bestOpt.max !== 0) {
2787
+ max = BigNumber(bestOpt.max).minus(extraCoinsToFitMinMax);
2788
+ max = AmountUtils.trim(max.lt(0) ? "0" : max, fromCoin.digits);
2789
+ }
2790
+ if (typeof bestOpt.min === "number" && bestOpt.min !== 0) {
2791
+ min = AmountUtils.trim(BigNumber(bestOpt.min).plus(extraCoinsToFitMinMax), fromCoin.digits);
2792
+ }
2793
+ }
2794
+ const rate = bestOpt.toAmount && bestOpt.fromAmount ? BigNumber(bestOpt.toAmount).div(bestOpt.fromAmount) : null;
2795
+ return {
2796
+ result: true,
2797
+ min: min,
2798
+ max: max,
2799
+ smallestMin: smallestMin,
2800
+ greatestMax: greatestMax,
2801
+ rate: rate != null ? AmountUtils.trim(rate, this._maxRateDigits) : null,
2802
+ durationMinutesRange: (_bestOpt$duration = bestOpt.duration) != null ? _bestOpt$duration : null,
2803
+ rawSwapData: bestOpt
2804
+ };
2805
+ }
2806
+ const result = {
2807
+ result: false,
2808
+ reason: smallestMin && BigNumber(amountCoins).lt(smallestMin) ? SwapProvider.NO_SWAPS_REASONS.TOO_LOW : greatestMax && BigNumber(amountCoins).gt(greatestMax) ? SwapProvider.NO_SWAPS_REASONS.TOO_HIGH : SwapProvider.NO_SWAPS_REASONS.NOT_SUPPORTED,
2809
+ smallestMin: smallestMin,
2810
+ greatestMax: greatestMax
2811
+ };
2812
+ Logger.log(`Returning result ${safeStringify(result)}`, loggerSource);
2813
+ return result;
2814
+ } catch (e) {
2815
+ var _e$response3;
2816
+ if ((e == null || (_e$response3 = e.response) == null ? void 0 : _e$response3.status) === 429) {
2817
+ return {
2818
+ result: false,
2819
+ reason: SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED
2820
+ };
2821
+ }
2822
+ Logger.log(`Internal swapspace/rabbit error when getting swap options ${safeStringify(e)}`, loggerSource);
2823
+ improveAndRethrow(e, loggerSource);
2824
+ }
2825
+ }
2826
+ async createSwap(fromCoin, toCoin, amount, toAddress, refundAddress, rawSwapData, clientIpAddress) {
2827
+ const loggerSource = "createSwap";
2828
+ const partner = rawSwapData == null ? void 0 : rawSwapData.partner;
2829
+ try {
2830
+ var _this$_supportedCoins8, _this$_supportedCoins9;
2831
+ if (!(fromCoin instanceof Coin) || !(toCoin instanceof Coin) || typeof amount !== "string" || typeof toAddress !== "string" || typeof refundAddress !== "string") {
2832
+ throw new Error(`Invalid input: ${fromCoin} ${toCoin} ${amount} ${toAddress} ${refundAddress}`);
2833
+ }
2834
+ if (typeof partner !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.fromCurrency) !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.fromNetwork) !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.toCurrency) !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.toNetwork) !== "string" || typeof (rawSwapData == null ? void 0 : rawSwapData.id) !== "string" // can be just empty
2835
+ ) {
2836
+ throw new Error(`Invalid raw swap data: ${safeStringify(rawSwapData)}`);
2837
+ }
2838
+ await this._fetchSupportedCurrenciesIfNeeded();
2839
+ const toCurrencyExtraId = (_this$_supportedCoins8 = (_this$_supportedCoins9 = this._supportedCoins.find(item => item.coin === toCoin)) == null ? void 0 : _this$_supportedCoins9.extraId) != null ? _this$_supportedCoins8 : "";
2840
+ const requestData = {
2841
+ partner: partner,
2842
+ fromCurrency: rawSwapData == null ? void 0 : rawSwapData.fromCurrency,
2843
+ fromNetwork: rawSwapData == null ? void 0 : rawSwapData.fromNetwork,
2844
+ toCurrency: rawSwapData == null ? void 0 : rawSwapData.toCurrency,
2845
+ toNetwork: rawSwapData == null ? void 0 : rawSwapData.toNetwork,
2846
+ address: toAddress,
2847
+ amount: amount,
2848
+ fixed: false,
2849
+ extraId: toCurrencyExtraId != null ? toCurrencyExtraId : "",
2850
+ rateId: rawSwapData == null ? void 0 : rawSwapData.id,
2851
+ userIp: clientIpAddress,
2852
+ refund: refundAddress
2853
+ };
2854
+ Logger.log(`Sending create request: ${safeStringify(requestData)}`, loggerSource);
2855
+ const response = await axios.post(`${this._URL}/api/v2/exchange`, requestData);
2856
+ const result = response.data;
2857
+ Logger.log(`Creation result ${safeStringify(result)}`, loggerSource);
2858
+ if (result != null && result.id) {
2859
+ var _result$from, _result$from2, _result$to, _result$to2, _result$from4, _result$from5, _result$to4, _result$to5;
2860
+ if (typeof (result == null || (_result$from = result.from) == null ? void 0 : _result$from.amount) !== "number" || typeof (result == null || (_result$from2 = result.from) == null ? void 0 : _result$from2.address) !== "string" || typeof (result == null || (_result$to = result.to) == null ? void 0 : _result$to.amount) !== "number" || typeof (result == null || (_result$to2 = result.to) == null ? void 0 : _result$to2.address) !== "string") throw new Error(`Wrong swap creation result ${result}`);
2861
+ /* We use the returned rate preferably but if the retrieved
2862
+ * rate 0/null/undefined we calculate it manually */
2863
+ let rate = result.rate;
2864
+ if (typeof rate !== "number" || BigNumber(rate).isZero()) {
2865
+ var _result$to3, _result$from3;
2866
+ rate = BigNumber(result == null || (_result$to3 = result.to) == null ? void 0 : _result$to3.amount).div(result == null || (_result$from3 = result.from) == null ? void 0 : _result$from3.amount);
2867
+ } else {
2868
+ rate = BigNumber(rate);
2869
+ }
2870
+ return {
2871
+ result: true,
2872
+ swapId: result == null ? void 0 : result.id,
2873
+ fromCoin: fromCoin,
2874
+ fromAmount: AmountUtils.trim(result == null || (_result$from4 = result.from) == null ? void 0 : _result$from4.amount, fromCoin.digits),
2875
+ fromAddress: result == null || (_result$from5 = result.from) == null ? void 0 : _result$from5.address,
2876
+ toCoin: toCoin,
2877
+ toAmount: AmountUtils.trim(result == null || (_result$to4 = result.to) == null ? void 0 : _result$to4.amount, toCoin.digits),
2878
+ toAddress: result == null || (_result$to5 = result.to) == null ? void 0 : _result$to5.address,
2879
+ rate: AmountUtils.trim(rate, this._maxRateDigits)
2880
+ };
2881
+ }
2882
+ const errorMessage = `Swap creation succeeded but the response is wrong: ${safeStringify(response)}`;
2883
+ Logger.log(errorMessage, loggerSource);
2884
+ throw new Error(errorMessage);
2885
+ } catch (e) {
2886
+ var _e$response4, _e$response5;
2887
+ Logger.log(`Failed to create swap. Error is: ${safeStringify(e)}`, loggerSource);
2888
+ const composeFailResult = reason => ({
2889
+ result: false,
2890
+ reason: reason,
2891
+ partner: partner
2892
+ });
2893
+ const status = e == null || (_e$response4 = e.response) == null ? void 0 : _e$response4.status;
2894
+ const data = e == null || (_e$response5 = e.response) == null ? void 0 : _e$response5.data;
2895
+ if (status === 429) {
2896
+ Logger.log(`Returning fail - RPS limit exceeded ${data}`, loggerSource);
2897
+ return composeFailResult(SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED);
2898
+ }
2899
+ const texts422 = ["Pair cannot be processed by", "Currency not found", "Amount maximum is", "Amount minimum is"];
2900
+ const text403 = "IP address is forbidden";
2901
+ if (typeof data === "string" && (status === 403 && data.includes(text403) || status === 422 && texts422.find(text => data.includes(text)))) {
2902
+ Logger.log(`Returning retriable fail: ${status} - ${data}, ${partner}`, loggerSource);
2903
+ return composeFailResult(SwapProvider.CREATION_FAIL_REASONS.RETRIABLE_FAIL);
2904
+ }
2905
+ Logger.log(`Internal swapspace/rabbit error for ${partner}: ${safeStringify(e)}`, loggerSource);
2906
+ improveAndRethrow(e, loggerSource);
2907
+ }
2908
+ }
2909
+ _mapSwapspaceStatusToRabbitStatus(status) {
2910
+ switch (status) {
2911
+ case "waiting":
2912
+ return SwapProvider.SWAP_STATUSES.WAITING_FOR_PAYMENT;
2913
+ case "confirming":
2914
+ return SwapProvider.SWAP_STATUSES.CONFIRMING;
2915
+ case "exchanging":
2916
+ return SwapProvider.SWAP_STATUSES.EXCHANGING;
2917
+ case "sending":
2918
+ return SwapProvider.SWAP_STATUSES.PAYMENT_RECEIVED;
2919
+ case "finished":
2920
+ return SwapProvider.SWAP_STATUSES.COMPLETED;
2921
+ case "verifying":
2922
+ return SwapProvider.SWAP_STATUSES.EXCHANGING;
2923
+ case "refunded":
2924
+ return SwapProvider.SWAP_STATUSES.REFUNDED;
2925
+ case "expired":
2926
+ return SwapProvider.SWAP_STATUSES.EXPIRED;
2927
+ case "failed":
2928
+ return SwapProvider.SWAP_STATUSES.FAILED;
2929
+ default:
2930
+ throw new Error(`Unknown swapspace status: ${status}`);
2931
+ }
2932
+ }
2933
+ async getExistingSwapsDetailsAndStatus(swapIds) {
2934
+ var _this = this;
2935
+ const loggerSource = "getExistingSwapsDetailsAndStatus";
2936
+ try {
2937
+ if (swapIds.find(id => typeof id !== "string")) {
2938
+ throw new Error("Swap id is not string: " + safeStringify(swapIds));
2939
+ }
2940
+ const getNotFailingOn404 = async function getNotFailingOn404(swapId) {
2941
+ try {
2942
+ return await axios.get(`${_this._URL}/api/v2/exchange/${swapId}`);
2943
+ } catch (error) {
2944
+ var _error$response;
2945
+ if ((error == null || (_error$response = error.response) == null ? void 0 : _error$response.status) === 404) return [];
2946
+ throw error;
2947
+ }
2948
+ };
2949
+ const responses = await Promise.all(swapIds.map(swapId => getNotFailingOn404(swapId)));
2950
+ const wo404 = responses.flat();
2951
+ const swaps = wo404.map(r => r.data).map((swap, index) => {
2952
+ var _this$_supportedCoins10, _this$_supportedCoins11;
2953
+ const fromCoin = (_this$_supportedCoins10 = this._supportedCoins.find(i => i.code === swap.from.code && i.network === swap.from.network)) == null ? void 0 : _this$_supportedCoins10.coin;
2954
+ const toCoin = (_this$_supportedCoins11 = this._supportedCoins.find(i => i.code === swap.to.code && i.network === swap.to.network)) == null ? void 0 : _this$_supportedCoins11.coin;
2955
+ if (!fromCoin || !toCoin) {
2956
+ return []; // We skip swaps with not supported coins for now
2957
+ }
2958
+ const status = this._mapSwapspaceStatusToRabbitStatus(swap.status);
2959
+ const toDigits = status === SwapProvider.SWAP_STATUSES.REFUNDED ? fromCoin.digits : toCoin.digits;
2960
+ const addressToSendCoinsToSwapspace = swap.from.address;
2961
+ const toUtcTimestamp = timeStr => Date.parse(timeStr.match(/.+[Zz]$/) ? timeStr : `${timeStr}Z`);
2962
+ return new ExistingSwap(swapIds[index], status, toUtcTimestamp(swap.timestamps.createdAt), toUtcTimestamp(swap.timestamps.expiresAt), swap.confirmations, AmountUtils.trim(swap.rate, this._maxRateDigits), swap.refundAddress, addressToSendCoinsToSwapspace, fromCoin, AmountUtils.trim(swap.from.amount, fromCoin.digits), swap.from.transactionHash, swap.blockExplorerTransactionUrl.from, toCoin, AmountUtils.trim(swap.to.amount, toDigits), swap.to.transactionHash, swap.blockExplorerTransactionUrl.to, swap.to.address, swap.partner);
2963
+ }).flat();
2964
+ Logger.log(`Swap details result ${safeStringify(swaps)}`, loggerSource);
2965
+ return {
2966
+ result: true,
2967
+ swaps: swaps
2968
+ };
2969
+ } catch (e) {
2970
+ var _e$response6, _e$response7;
2971
+ Logger.log(`Failed to get swap details. Error is: ${safeStringify(e)}`, loggerSource);
2972
+ const composeFailResult = reason => ({
2973
+ result: false,
2974
+ reason: reason
2975
+ });
2976
+ const status = e == null || (_e$response6 = e.response) == null ? void 0 : _e$response6.status;
2977
+ const data = e == null || (_e$response7 = e.response) == null ? void 0 : _e$response7.data;
2978
+ if (status === 429) {
2979
+ Logger.log(`Returning fail - RPS limit exceeded ${data}`, loggerSource);
2980
+ return composeFailResult(SwapProvider.COMMON_ERRORS.REQUESTS_LIMIT_EXCEEDED);
2981
+ }
2982
+ improveAndRethrow(e, loggerSource);
2983
+ }
2984
+ }
2985
+ isAddressValidForAsset(asset, address) {
2986
+ try {
2987
+ const assetData = this._supportedCoins.find(i => i.coin === asset);
2988
+ if (assetData) {
2989
+ let corrected = assetData.validationRegexp.trim();
2990
+ corrected = corrected[0] === "/" ? corrected.slice(1) : corrected;
2991
+ corrected = corrected[corrected.length - 1] === "/" ? corrected.slice(0, corrected.length - 1) : corrected;
2992
+ return address.match(corrected) != null;
2993
+ }
2994
+ } catch (e) {
2995
+ Logger.logError(e, "isAddressValidForAsset");
2996
+ }
2997
+ return false;
2998
+ }
2999
+ }
3000
+
3001
+ export { AmountUtils, AssetIcon, Blockchain, Button, Cache, Coin, ExistingSwap, FiatCurrenciesService, LoadingDots, Logger, LogsStorage, Protocol, SupportChat, SwapProvider, SwapspaceSwapProvider, improveAndRethrow, safeStringify };
1809
3002
  //# sourceMappingURL=index.modern.js.map