mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 10:28:10 +01:00
Removed CryptoCompare + Added background thread for price fetching.
This commit is contained in:
@@ -100,10 +100,7 @@ from .util.network import is_private_ip_address
|
|||||||
from .util.smsg import smsgGetID
|
from .util.smsg import smsgGetID
|
||||||
from .interface.base import Curves
|
from .interface.base import Curves
|
||||||
from .interface.part import PARTInterface, PARTInterfaceAnon, PARTInterfaceBlind
|
from .interface.part import PARTInterface, PARTInterfaceAnon, PARTInterfaceBlind
|
||||||
from .explorers import (
|
from .explorers import default_coingecko_api_key
|
||||||
default_chart_api_key,
|
|
||||||
default_coingecko_api_key,
|
|
||||||
)
|
|
||||||
from .script import OpCodes
|
from .script import OpCodes
|
||||||
from .messages_npb import (
|
from .messages_npb import (
|
||||||
ADSBidIntentAcceptMessage,
|
ADSBidIntentAcceptMessage,
|
||||||
@@ -417,6 +414,21 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
self._is_encrypted = None
|
self._is_encrypted = None
|
||||||
self._is_locked = None
|
self._is_locked = None
|
||||||
|
|
||||||
|
self._price_cache = {}
|
||||||
|
self._volume_cache = {}
|
||||||
|
self._historical_cache = {}
|
||||||
|
self._price_cache_lock = threading.Lock()
|
||||||
|
self._price_fetch_thread = None
|
||||||
|
self._price_fetch_running = False
|
||||||
|
self._last_price_fetch = 0
|
||||||
|
self._last_volume_fetch = 0
|
||||||
|
self.price_fetch_interval = self.get_int_setting(
|
||||||
|
"price_fetch_interval", 5 * 60, 60, 60 * 60
|
||||||
|
)
|
||||||
|
self.volume_fetch_interval = self.get_int_setting(
|
||||||
|
"volume_fetch_interval", 5 * 60, 60, 60 * 60
|
||||||
|
)
|
||||||
|
|
||||||
self._max_transient_errors = self.settings.get(
|
self._max_transient_errors = self.settings.get(
|
||||||
"max_transient_errors", 100
|
"max_transient_errors", 100
|
||||||
) # Number of retries before a bid will stop when encountering transient errors.
|
) # Number of retries before a bid will stop when encountering transient errors.
|
||||||
@@ -599,6 +611,11 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
def finalise(self):
|
def finalise(self):
|
||||||
self.log.info("Finalising")
|
self.log.info("Finalising")
|
||||||
|
|
||||||
|
self._price_fetch_running = False
|
||||||
|
if self._price_fetch_thread and self._price_fetch_thread.is_alive():
|
||||||
|
self._price_fetch_thread.join(timeout=5)
|
||||||
|
self.log.info("Background price fetching stopped")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from basicswap.rpc_pool import close_all_pools
|
from basicswap.rpc_pool import close_all_pools
|
||||||
|
|
||||||
@@ -1099,6 +1116,19 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
elif self.coin_clients[coin]["connection_type"] == "passthrough":
|
elif self.coin_clients[coin]["connection_type"] == "passthrough":
|
||||||
self.coin_clients[coin]["interface"] = self.createPassthroughInterface(coin)
|
self.coin_clients[coin]["interface"] = self.createPassthroughInterface(coin)
|
||||||
|
|
||||||
|
def _cleanupOldSettings(self):
|
||||||
|
settings_changed = False
|
||||||
|
deprecated_keys = ["chart_api_key", "chart_api_key_enc"]
|
||||||
|
|
||||||
|
for key in deprecated_keys:
|
||||||
|
if key in self.settings:
|
||||||
|
self.log.info(f"Removing deprecated setting: {key}")
|
||||||
|
self.settings.pop(key)
|
||||||
|
settings_changed = True
|
||||||
|
|
||||||
|
if settings_changed:
|
||||||
|
self._save_settings()
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
@@ -1119,6 +1149,8 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
"SQLite {} or higher required.".format(".".join(MIN_SQLITE_VERSION))
|
"SQLite {} or higher required.".format(".".join(MIN_SQLITE_VERSION))
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._cleanupOldSettings()
|
||||||
|
|
||||||
upgradeDatabase(self, self.db_version)
|
upgradeDatabase(self, self.db_version)
|
||||||
upgradeDatabaseData(self, self.db_data_version)
|
upgradeDatabaseData(self, self.db_data_version)
|
||||||
|
|
||||||
@@ -1268,6 +1300,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
else:
|
else:
|
||||||
self.log.info("AMM autostart is disabled")
|
self.log.info("AMM autostart is disabled")
|
||||||
|
|
||||||
|
self._price_fetch_running = True
|
||||||
|
self._price_fetch_thread = threading.Thread(
|
||||||
|
target=self._backgroundPriceFetchLoop, daemon=True
|
||||||
|
)
|
||||||
|
self._price_fetch_thread.start()
|
||||||
|
self.log.info("Background price fetching started")
|
||||||
|
|
||||||
def stopDaemon(self, coin) -> None:
|
def stopDaemon(self, coin) -> None:
|
||||||
if coin in (Coins.XMR, Coins.DCR, Coins.WOW):
|
if coin in (Coins.XMR, Coins.DCR, Coins.WOW):
|
||||||
return
|
return
|
||||||
@@ -11073,27 +11112,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
settings_copy["show_chart"] = new_value
|
settings_copy["show_chart"] = new_value
|
||||||
settings_changed = True
|
settings_changed = True
|
||||||
|
|
||||||
if "chart_api_key" in data:
|
|
||||||
new_value = data["chart_api_key"]
|
|
||||||
ensure(
|
|
||||||
isinstance(new_value, str), "New chart_api_key value not a string"
|
|
||||||
)
|
|
||||||
ensure(len(new_value) <= 128, "New chart_api_key value too long")
|
|
||||||
if all(c in string.hexdigits for c in new_value):
|
|
||||||
if settings_copy.get("chart_api_key", "") != new_value:
|
|
||||||
settings_copy["chart_api_key"] = new_value
|
|
||||||
if "chart_api_key_enc" in settings_copy:
|
|
||||||
settings_copy.pop("chart_api_key_enc")
|
|
||||||
settings_changed = True
|
|
||||||
else:
|
|
||||||
# Encode value as hex to avoid escaping
|
|
||||||
new_value = new_value.encode("UTF-8").hex()
|
|
||||||
if settings_copy.get("chart_api_key_enc", "") != new_value:
|
|
||||||
settings_copy["chart_api_key_enc"] = new_value
|
|
||||||
if "chart_api_key" in settings_copy:
|
|
||||||
settings_copy.pop("chart_api_key")
|
|
||||||
settings_changed = True
|
|
||||||
|
|
||||||
if "coingecko_api_key" in data:
|
if "coingecko_api_key" in data:
|
||||||
new_value = data["coingecko_api_key"]
|
new_value = data["coingecko_api_key"]
|
||||||
ensure(
|
ensure(
|
||||||
@@ -11101,7 +11119,15 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
"New coingecko_api_key value not a string",
|
"New coingecko_api_key value not a string",
|
||||||
)
|
)
|
||||||
ensure(len(new_value) <= 128, "New coingecko_api_keyvalue too long")
|
ensure(len(new_value) <= 128, "New coingecko_api_keyvalue too long")
|
||||||
if all(c in string.hexdigits for c in new_value):
|
if new_value == "":
|
||||||
|
if (
|
||||||
|
"coingecko_api_key" in settings_copy
|
||||||
|
or "coingecko_api_key_enc" in settings_copy
|
||||||
|
):
|
||||||
|
settings_copy.pop("coingecko_api_key", None)
|
||||||
|
settings_copy.pop("coingecko_api_key_enc", None)
|
||||||
|
settings_changed = True
|
||||||
|
elif all(c in string.hexdigits for c in new_value):
|
||||||
if settings_copy.get("coingecko_api_key", "") != new_value:
|
if settings_copy.get("coingecko_api_key", "") != new_value:
|
||||||
settings_copy["coingecko_api_key"] = new_value
|
settings_copy["coingecko_api_key"] = new_value
|
||||||
if "coingecko_api_key_enc" in settings_copy:
|
if "coingecko_api_key_enc" in settings_copy:
|
||||||
@@ -12379,6 +12405,195 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
|
|
||||||
return chainparams[use_coinid]["name"]
|
return chainparams[use_coinid]["name"]
|
||||||
|
|
||||||
|
def _backgroundPriceFetchLoop(self):
|
||||||
|
while self._price_fetch_running:
|
||||||
|
try:
|
||||||
|
now = int(time.time())
|
||||||
|
if now - self._last_price_fetch >= self.price_fetch_interval:
|
||||||
|
self._fetchPricesBackground()
|
||||||
|
self._last_price_fetch = now
|
||||||
|
if now - self._last_volume_fetch >= self.volume_fetch_interval:
|
||||||
|
self._fetchVolumeBackground()
|
||||||
|
self._last_volume_fetch = now
|
||||||
|
except Exception as e:
|
||||||
|
self.log.error(f"Background price/volume fetch error: {e}")
|
||||||
|
|
||||||
|
for _ in range(60):
|
||||||
|
if not self._price_fetch_running:
|
||||||
|
break
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
def _fetchPricesBackground(self):
|
||||||
|
all_coins = [c for c in Coins if c in chainparams]
|
||||||
|
if not all_coins:
|
||||||
|
return
|
||||||
|
|
||||||
|
for rate_source in ["coingecko.com"]:
|
||||||
|
try:
|
||||||
|
self._fetchPricesForSource(all_coins, rate_source, Fiat.USD)
|
||||||
|
except Exception as e:
|
||||||
|
self.log.warning(
|
||||||
|
f"Background price fetch from {rate_source} failed: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _fetchVolumeBackground(self):
|
||||||
|
all_coins = [c for c in Coins if c in chainparams]
|
||||||
|
if not all_coins:
|
||||||
|
return
|
||||||
|
|
||||||
|
for rate_source in ["coingecko.com"]:
|
||||||
|
try:
|
||||||
|
self._fetchVolumeForSource(all_coins, rate_source)
|
||||||
|
except Exception as e:
|
||||||
|
self.log.warning(
|
||||||
|
f"Background volume fetch from {rate_source} failed: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def _fetchPricesForSource(self, coins_list, rate_source, currency_to):
|
||||||
|
now = int(time.time())
|
||||||
|
headers = {"User-Agent": "Mozilla/5.0", "Connection": "close"}
|
||||||
|
|
||||||
|
exchange_name_map = {}
|
||||||
|
coin_ids = ""
|
||||||
|
for coin_id in coins_list:
|
||||||
|
if len(coin_ids) > 0:
|
||||||
|
coin_ids += ","
|
||||||
|
exchange_name = self.getExchangeName(coin_id, rate_source)
|
||||||
|
coin_ids += exchange_name
|
||||||
|
exchange_name_map[exchange_name] = coin_id
|
||||||
|
|
||||||
|
if rate_source == "coingecko.com":
|
||||||
|
ticker_to = fiatTicker(currency_to).lower()
|
||||||
|
api_key = get_api_key_setting(
|
||||||
|
self.settings,
|
||||||
|
"coingecko_api_key",
|
||||||
|
default_coingecko_api_key,
|
||||||
|
escape=True,
|
||||||
|
)
|
||||||
|
url = f"https://api.coingecko.com/api/v3/simple/price?ids={coin_ids}&vs_currencies={ticker_to}"
|
||||||
|
if api_key != "":
|
||||||
|
url += f"&api_key={api_key}"
|
||||||
|
|
||||||
|
js = json.loads(self.readURL(url, timeout=3, headers=headers))
|
||||||
|
|
||||||
|
with self._price_cache_lock:
|
||||||
|
for k, v in js.items():
|
||||||
|
coin_id = exchange_name_map[k]
|
||||||
|
cache_key = (coin_id, currency_to, rate_source)
|
||||||
|
self._price_cache[cache_key] = {
|
||||||
|
"rate": v[ticker_to],
|
||||||
|
"timestamp": now,
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = self.openDB()
|
||||||
|
try:
|
||||||
|
update_query = """
|
||||||
|
UPDATE coinrates SET
|
||||||
|
rate=:rate,
|
||||||
|
last_updated=:last_updated
|
||||||
|
WHERE currency_from = :currency_from AND currency_to = :currency_to AND source = :rate_source
|
||||||
|
"""
|
||||||
|
|
||||||
|
insert_query = """INSERT INTO coinrates(currency_from, currency_to, rate, source, last_updated)
|
||||||
|
VALUES(:currency_from, :currency_to, :rate, :rate_source, :last_updated)"""
|
||||||
|
|
||||||
|
for k, v in js.items():
|
||||||
|
coin_id = exchange_name_map[k]
|
||||||
|
cursor.execute(
|
||||||
|
update_query,
|
||||||
|
{
|
||||||
|
"currency_from": coin_id,
|
||||||
|
"currency_to": currency_to,
|
||||||
|
"rate": v[ticker_to],
|
||||||
|
"rate_source": rate_source,
|
||||||
|
"last_updated": now,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if cursor.rowcount < 1:
|
||||||
|
cursor.execute(
|
||||||
|
insert_query,
|
||||||
|
{
|
||||||
|
"currency_from": coin_id,
|
||||||
|
"currency_to": currency_to,
|
||||||
|
"rate": v[ticker_to],
|
||||||
|
"rate_source": rate_source,
|
||||||
|
"last_updated": now,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.commitDB()
|
||||||
|
finally:
|
||||||
|
self.closeDB(cursor, commit=False)
|
||||||
|
|
||||||
|
def _fetchVolumeForSource(self, coins_list, rate_source):
|
||||||
|
now = int(time.time())
|
||||||
|
headers = {"User-Agent": "Mozilla/5.0", "Connection": "close"}
|
||||||
|
|
||||||
|
exchange_name_map = {}
|
||||||
|
coin_ids = ""
|
||||||
|
for coin_id in coins_list:
|
||||||
|
if len(coin_ids) > 0:
|
||||||
|
coin_ids += ","
|
||||||
|
exchange_name = self.getExchangeName(coin_id, rate_source)
|
||||||
|
coin_ids += exchange_name
|
||||||
|
exchange_name_map[exchange_name] = coin_id
|
||||||
|
|
||||||
|
if rate_source == "coingecko.com":
|
||||||
|
api_key = get_api_key_setting(
|
||||||
|
self.settings,
|
||||||
|
"coingecko_api_key",
|
||||||
|
default_coingecko_api_key,
|
||||||
|
escape=True,
|
||||||
|
)
|
||||||
|
url = f"https://api.coingecko.com/api/v3/simple/price?ids={coin_ids}&vs_currencies=usd&include_24hr_vol=true&include_24hr_change=true"
|
||||||
|
if api_key != "":
|
||||||
|
url += f"&api_key={api_key}"
|
||||||
|
|
||||||
|
js = json.loads(self.readURL(url, timeout=3, headers=headers))
|
||||||
|
|
||||||
|
with self._price_cache_lock:
|
||||||
|
for k, v in js.items():
|
||||||
|
coin_id = exchange_name_map[k]
|
||||||
|
cache_key = (coin_id, rate_source)
|
||||||
|
volume_24h = v.get("usd_24h_vol")
|
||||||
|
price_change_24h = v.get("usd_24h_change")
|
||||||
|
self._volume_cache[cache_key] = {
|
||||||
|
"volume_24h": (
|
||||||
|
float(volume_24h) if volume_24h is not None else None
|
||||||
|
),
|
||||||
|
"price_change_24h": (
|
||||||
|
float(price_change_24h)
|
||||||
|
if price_change_24h is not None
|
||||||
|
else 0.0
|
||||||
|
),
|
||||||
|
"timestamp": now,
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor = self.openDB()
|
||||||
|
try:
|
||||||
|
for k, v in js.items():
|
||||||
|
coin_id = exchange_name_map[k]
|
||||||
|
volume_24h = v.get("usd_24h_vol")
|
||||||
|
price_change_24h = v.get("usd_24h_change")
|
||||||
|
cursor.execute(
|
||||||
|
"INSERT OR REPLACE INTO coinvolume (coin_id, volume_24h, price_change_24h, source, last_updated) VALUES (:coin_id, :volume_24h, :price_change_24h, :rate_source, :last_updated)",
|
||||||
|
{
|
||||||
|
"coin_id": coin_id,
|
||||||
|
"volume_24h": (
|
||||||
|
str(volume_24h) if volume_24h is not None else "None"
|
||||||
|
),
|
||||||
|
"price_change_24h": (
|
||||||
|
str(price_change_24h)
|
||||||
|
if price_change_24h is not None
|
||||||
|
else "0.0"
|
||||||
|
),
|
||||||
|
"rate_source": rate_source,
|
||||||
|
"last_updated": now,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.commitDB()
|
||||||
|
finally:
|
||||||
|
self.closeDB(cursor, commit=False)
|
||||||
|
|
||||||
def lookupFiatRates(
|
def lookupFiatRates(
|
||||||
self,
|
self,
|
||||||
coins_list,
|
coins_list,
|
||||||
@@ -12395,7 +12610,13 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
oldest_time_valid: int = now - saved_ttl
|
oldest_time_valid: int = now - saved_ttl
|
||||||
return_rates = {}
|
return_rates = {}
|
||||||
|
|
||||||
headers = {"User-Agent": "Mozilla/5.0", "Connection": "close"}
|
with self._price_cache_lock:
|
||||||
|
for coin_id in coins_list:
|
||||||
|
cache_key = (coin_id, currency_to, rate_source)
|
||||||
|
if cache_key in self._price_cache:
|
||||||
|
cached = self._price_cache[cache_key]
|
||||||
|
if cached["timestamp"] >= oldest_time_valid:
|
||||||
|
return_rates[coin_id] = cached["rate"]
|
||||||
|
|
||||||
cursor = self.openDB()
|
cursor = self.openDB()
|
||||||
try:
|
try:
|
||||||
@@ -12421,109 +12642,10 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
rows = cursor.execute(query, parameters)
|
rows = cursor.execute(query, parameters)
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
return_rates[int(row[0])] = float(row[1])
|
coin_id = int(row[0])
|
||||||
|
|
||||||
need_coins = []
|
|
||||||
new_values = {}
|
|
||||||
exchange_name_map = {}
|
|
||||||
for coin_id in coins_list:
|
|
||||||
if coin_id not in return_rates:
|
if coin_id not in return_rates:
|
||||||
need_coins.append(coin_id)
|
return_rates[coin_id] = float(row[1])
|
||||||
|
|
||||||
if len(need_coins) < 1:
|
|
||||||
return return_rates
|
|
||||||
|
|
||||||
if rate_source == "coingecko.com":
|
|
||||||
ticker_to: str = fiatTicker(currency_to).lower()
|
|
||||||
# Update all requested coins
|
|
||||||
coin_ids: str = ""
|
|
||||||
for coin_id in coins_list:
|
|
||||||
if len(coin_ids) > 0:
|
|
||||||
coin_ids += ","
|
|
||||||
exchange_name: str = self.getExchangeName(coin_id, rate_source)
|
|
||||||
coin_ids += exchange_name
|
|
||||||
exchange_name_map[exchange_name] = coin_id
|
|
||||||
|
|
||||||
api_key: str = get_api_key_setting(
|
|
||||||
self.settings,
|
|
||||||
"coingecko_api_key",
|
|
||||||
default_coingecko_api_key,
|
|
||||||
escape=True,
|
|
||||||
)
|
|
||||||
url: str = (
|
|
||||||
f"https://api.coingecko.com/api/v3/simple/price?ids={coin_ids}&vs_currencies={ticker_to}"
|
|
||||||
)
|
|
||||||
if api_key != "":
|
|
||||||
url += f"&api_key={api_key}"
|
|
||||||
|
|
||||||
js = json.loads(self.readURL(url, timeout=10, headers=headers))
|
|
||||||
|
|
||||||
for k, v in js.items():
|
|
||||||
return_rates[int(exchange_name_map[k])] = v[ticker_to]
|
|
||||||
new_values[exchange_name_map[k]] = v[ticker_to]
|
|
||||||
elif rate_source == "cryptocompare.com":
|
|
||||||
ticker_to: str = fiatTicker(currency_to).upper()
|
|
||||||
api_key: str = get_api_key_setting(
|
|
||||||
self.settings,
|
|
||||||
"chart_api_key",
|
|
||||||
default_chart_api_key,
|
|
||||||
escape=True,
|
|
||||||
)
|
|
||||||
coin_ids: str = ""
|
|
||||||
for coin_id in coins_list:
|
|
||||||
if len(coin_ids) > 0:
|
|
||||||
coin_ids += ","
|
|
||||||
coin_ticker: str = chainparams[coin_id]["ticker"]
|
|
||||||
coin_ids += coin_ticker
|
|
||||||
exchange_name_map[coin_ticker] = coin_id
|
|
||||||
url: str = (
|
|
||||||
f"https://min-api.cryptocompare.com/data/pricemulti?fsyms={coin_ids}&tsyms={ticker_to}"
|
|
||||||
)
|
|
||||||
js = json.loads(self.readURL(url, timeout=10, headers=headers))
|
|
||||||
for k, v in js.items():
|
|
||||||
return_rates[int(exchange_name_map[k])] = v[ticker_to]
|
|
||||||
new_values[exchange_name_map[k]] = v[ticker_to]
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unknown rate source {rate_source}")
|
|
||||||
|
|
||||||
if len(new_values) < 1:
|
|
||||||
return return_rates
|
|
||||||
|
|
||||||
# ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint
|
|
||||||
update_query = """
|
|
||||||
UPDATE coinrates SET
|
|
||||||
rate=:rate,
|
|
||||||
last_updated=:last_updated
|
|
||||||
WHERE currency_from = :currency_from AND currency_to = :currency_to AND source = :rate_source
|
|
||||||
"""
|
|
||||||
|
|
||||||
insert_query = """INSERT INTO coinrates(currency_from, currency_to, rate, source, last_updated)
|
|
||||||
VALUES(:currency_from, :currency_to, :rate, :rate_source, :last_updated)"""
|
|
||||||
|
|
||||||
for k, v in new_values.items():
|
|
||||||
cursor.execute(
|
|
||||||
update_query,
|
|
||||||
{
|
|
||||||
"currency_from": k,
|
|
||||||
"currency_to": currency_to,
|
|
||||||
"rate": v,
|
|
||||||
"rate_source": rate_source,
|
|
||||||
"last_updated": now,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if cursor.rowcount < 1:
|
|
||||||
cursor.execute(
|
|
||||||
insert_query,
|
|
||||||
{
|
|
||||||
"currency_from": k,
|
|
||||||
"currency_to": currency_to,
|
|
||||||
"rate": v,
|
|
||||||
"rate_source": rate_source,
|
|
||||||
"last_updated": now,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
self.commitDB()
|
|
||||||
return return_rates
|
return return_rates
|
||||||
finally:
|
finally:
|
||||||
self.closeDB(cursor, commit=False)
|
self.closeDB(cursor, commit=False)
|
||||||
@@ -12543,7 +12665,16 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
oldest_time_valid: int = now - saved_ttl
|
oldest_time_valid: int = now - saved_ttl
|
||||||
return_data = {}
|
return_data = {}
|
||||||
|
|
||||||
headers = {"User-Agent": "Mozilla/5.0", "Connection": "close"}
|
with self._price_cache_lock:
|
||||||
|
for coin_id in coins_list:
|
||||||
|
cache_key = (coin_id, rate_source)
|
||||||
|
if cache_key in self._volume_cache:
|
||||||
|
cached = self._volume_cache[cache_key]
|
||||||
|
if cached["timestamp"] >= oldest_time_valid:
|
||||||
|
return_data[coin_id] = {
|
||||||
|
"volume_24h": cached["volume_24h"],
|
||||||
|
"price_change_24h": cached["price_change_24h"],
|
||||||
|
}
|
||||||
|
|
||||||
cursor = self.openDB()
|
cursor = self.openDB()
|
||||||
try:
|
try:
|
||||||
@@ -12568,6 +12699,9 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
rows = cursor.execute(query, parameters)
|
rows = cursor.execute(query, parameters)
|
||||||
|
|
||||||
for row in rows:
|
for row in rows:
|
||||||
|
coin_id = int(row[0])
|
||||||
|
if coin_id in return_data:
|
||||||
|
continue
|
||||||
volume_24h = None
|
volume_24h = None
|
||||||
price_change_24h = 0.0
|
price_change_24h = 0.0
|
||||||
try:
|
try:
|
||||||
@@ -12580,98 +12714,11 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
price_change_24h = float(row[2])
|
price_change_24h = float(row[2])
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
pass
|
pass
|
||||||
return_data[int(row[0])] = {
|
return_data[coin_id] = {
|
||||||
"volume_24h": volume_24h,
|
"volume_24h": volume_24h,
|
||||||
"price_change_24h": price_change_24h,
|
"price_change_24h": price_change_24h,
|
||||||
}
|
}
|
||||||
|
|
||||||
need_coins = []
|
|
||||||
new_values = {}
|
|
||||||
exchange_name_map = {}
|
|
||||||
for coin_id in coins_list:
|
|
||||||
if coin_id not in return_data:
|
|
||||||
need_coins.append(coin_id)
|
|
||||||
|
|
||||||
if len(need_coins) < 1:
|
|
||||||
return return_data
|
|
||||||
|
|
||||||
if rate_source == "coingecko.com":
|
|
||||||
try:
|
|
||||||
coin_ids: str = ""
|
|
||||||
for coin_id in coins_list:
|
|
||||||
if len(coin_ids) > 0:
|
|
||||||
coin_ids += ","
|
|
||||||
exchange_name: str = self.getExchangeName(coin_id, rate_source)
|
|
||||||
coin_ids += exchange_name
|
|
||||||
exchange_name_map[exchange_name] = coin_id
|
|
||||||
|
|
||||||
api_key: str = get_api_key_setting(
|
|
||||||
self.settings,
|
|
||||||
"coingecko_api_key",
|
|
||||||
default_coingecko_api_key,
|
|
||||||
escape=True,
|
|
||||||
)
|
|
||||||
url: str = (
|
|
||||||
f"https://api.coingecko.com/api/v3/simple/price?ids={coin_ids}&vs_currencies=usd&include_24hr_vol=true&include_24hr_change=true"
|
|
||||||
)
|
|
||||||
if api_key != "":
|
|
||||||
url += f"&api_key={api_key}"
|
|
||||||
|
|
||||||
js = json.loads(self.readURL(url, timeout=10, headers=headers))
|
|
||||||
|
|
||||||
for k, v in js.items():
|
|
||||||
coin_id = int(exchange_name_map[k])
|
|
||||||
volume_24h = v.get("usd_24h_vol")
|
|
||||||
price_change_24h = v.get("usd_24h_change")
|
|
||||||
|
|
||||||
# Convert to float if value exists, otherwise keep as None
|
|
||||||
volume_value = (
|
|
||||||
float(volume_24h) if volume_24h is not None else None
|
|
||||||
)
|
|
||||||
price_change_value = (
|
|
||||||
float(price_change_24h)
|
|
||||||
if price_change_24h is not None
|
|
||||||
else 0.0
|
|
||||||
)
|
|
||||||
|
|
||||||
return_data[coin_id] = {
|
|
||||||
"volume_24h": volume_value,
|
|
||||||
"price_change_24h": price_change_value,
|
|
||||||
}
|
|
||||||
new_values[coin_id] = {
|
|
||||||
"volume_24h": volume_value,
|
|
||||||
"price_change_24h": price_change_value,
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
self.log.warning(f"Could not fetch volume data: {e}")
|
|
||||||
for coin_id in need_coins:
|
|
||||||
return_data[coin_id] = {
|
|
||||||
"volume_24h": None,
|
|
||||||
"price_change_24h": 0.0,
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
raise ValueError(f"Unknown rate source {rate_source}")
|
|
||||||
|
|
||||||
if len(new_values) < 1:
|
|
||||||
return return_data
|
|
||||||
|
|
||||||
for coin_id, data in new_values.items():
|
|
||||||
cursor.execute(
|
|
||||||
"INSERT OR REPLACE INTO coinvolume (coin_id, volume_24h, price_change_24h, source, last_updated) VALUES (:coin_id, :volume_24h, :price_change_24h, :rate_source, :last_updated)",
|
|
||||||
{
|
|
||||||
"coin_id": coin_id,
|
|
||||||
"volume_24h": (
|
|
||||||
str(data["volume_24h"])
|
|
||||||
if data["volume_24h"] is not None
|
|
||||||
else "None"
|
|
||||||
),
|
|
||||||
"price_change_24h": str(data["price_change_24h"]),
|
|
||||||
"rate_source": rate_source,
|
|
||||||
"last_updated": now,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
self.commitDB()
|
|
||||||
return return_data
|
return return_data
|
||||||
finally:
|
finally:
|
||||||
self.closeDB(cursor, commit=False)
|
self.closeDB(cursor, commit=False)
|
||||||
@@ -12747,7 +12794,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
if api_key != "":
|
if api_key != "":
|
||||||
url += f"&api_key={api_key}"
|
url += f"&api_key={api_key}"
|
||||||
|
|
||||||
js = json.loads(self.readURL(url, timeout=10, headers=headers))
|
js = json.loads(self.readURL(url, timeout=5, headers=headers))
|
||||||
|
|
||||||
if "prices" in js:
|
if "prices" in js:
|
||||||
return_data[coin_id] = js["prices"]
|
return_data[coin_id] = js["prices"]
|
||||||
@@ -12798,14 +12845,35 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
|
|||||||
|
|
||||||
if rate_sources.get("coingecko.com", True):
|
if rate_sources.get("coingecko.com", True):
|
||||||
try:
|
try:
|
||||||
js = self.lookupFiatRates([int(coin_from), int(coin_to)])
|
price_coin_from = int(coin_from)
|
||||||
rate = float(js[int(coin_from)]) / float(js[int(coin_to)])
|
price_coin_to = int(coin_to)
|
||||||
js["rate_inferred"] = ci_to.format_amount(rate, conv_int=True, r=1)
|
|
||||||
|
|
||||||
js[name_from] = {"usd": js[int(coin_from)]}
|
if price_coin_from in (Coins.PART_BLIND, Coins.PART_ANON):
|
||||||
js.pop(int(coin_from))
|
price_coin_from = Coins.PART
|
||||||
js[name_to] = {"usd": js[int(coin_to)]}
|
elif price_coin_from == Coins.LTC_MWEB:
|
||||||
js.pop(int(coin_to))
|
price_coin_from = Coins.LTC
|
||||||
|
|
||||||
|
if price_coin_to in (Coins.PART_BLIND, Coins.PART_ANON):
|
||||||
|
price_coin_to = Coins.PART
|
||||||
|
elif price_coin_to == Coins.LTC_MWEB:
|
||||||
|
price_coin_to = Coins.LTC
|
||||||
|
|
||||||
|
if price_coin_from == price_coin_to:
|
||||||
|
rate = 1.0
|
||||||
|
js = self.lookupFiatRates([price_coin_from])
|
||||||
|
usd_price = js[price_coin_from]
|
||||||
|
js["rate_inferred"] = ci_to.format_amount(rate, conv_int=True, r=1)
|
||||||
|
js[name_from] = {"usd": usd_price}
|
||||||
|
js[name_to] = {"usd": usd_price}
|
||||||
|
js.pop(price_coin_from)
|
||||||
|
else:
|
||||||
|
js = self.lookupFiatRates([price_coin_from, price_coin_to])
|
||||||
|
rate = float(js[price_coin_from]) / float(js[price_coin_to])
|
||||||
|
js["rate_inferred"] = ci_to.format_amount(rate, conv_int=True, r=1)
|
||||||
|
js[name_from] = {"usd": js[price_coin_from]}
|
||||||
|
js.pop(price_coin_from)
|
||||||
|
js[name_to] = {"usd": js[price_coin_to]}
|
||||||
|
js.pop(price_coin_to)
|
||||||
|
|
||||||
rv["coingecko"] = js
|
rv["coingecko"] = js
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -8,9 +8,6 @@
|
|||||||
import json
|
import json
|
||||||
|
|
||||||
|
|
||||||
default_chart_api_key = (
|
|
||||||
"95dd900af910656e0e17c41f2ddc5dba77d01bf8b0e7d2787634a16bd976c553"
|
|
||||||
)
|
|
||||||
default_coingecko_api_key = "CG-8hm3r9iLfpEXv4ied8oLbeUj"
|
default_coingecko_api_key = "CG-8hm3r9iLfpEXv4ied8oLbeUj"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,8 +9,7 @@ const ApiManager = (function() {
|
|||||||
requestTimeout: 60000,
|
requestTimeout: 60000,
|
||||||
retryDelays: [5000, 15000, 30000],
|
retryDelays: [5000, 15000, 30000],
|
||||||
rateLimits: {
|
rateLimits: {
|
||||||
coingecko: { requestsPerMinute: 50, minInterval: 1200 },
|
coingecko: { requestsPerMinute: 50, minInterval: 1200 }
|
||||||
cryptocompare: { requestsPerMinute: 30, minInterval: 2000 }
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,20 +35,13 @@ const ConfigManager = (function() {
|
|||||||
},
|
},
|
||||||
itemsPerPage: 50,
|
itemsPerPage: 50,
|
||||||
apiEndpoints: {
|
apiEndpoints: {
|
||||||
cryptoCompare: 'https://min-api.cryptocompare.com/data/pricemultifull',
|
|
||||||
coinGecko: 'https://api.coingecko.com/api/v3',
|
coinGecko: 'https://api.coingecko.com/api/v3',
|
||||||
cryptoCompareHistorical: 'https://min-api.cryptocompare.com/data/v2/histoday',
|
|
||||||
cryptoCompareHourly: 'https://min-api.cryptocompare.com/data/v2/histohour',
|
|
||||||
volumeEndpoint: 'https://api.coingecko.com/api/v3/simple/price'
|
volumeEndpoint: 'https://api.coingecko.com/api/v3/simple/price'
|
||||||
},
|
},
|
||||||
rateLimits: {
|
rateLimits: {
|
||||||
coingecko: {
|
coingecko: {
|
||||||
requestsPerMinute: 50,
|
requestsPerMinute: 50,
|
||||||
minInterval: 1200
|
minInterval: 1200
|
||||||
},
|
|
||||||
cryptocompare: {
|
|
||||||
requestsPerMinute: 30,
|
|
||||||
minInterval: 2000
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
retryDelays: [5000, 15000, 30000],
|
retryDelays: [5000, 15000, 30000],
|
||||||
@@ -99,12 +92,10 @@ const ConfigManager = (function() {
|
|||||||
if (typeof window.getAPIKeys === 'function') {
|
if (typeof window.getAPIKeys === 'function') {
|
||||||
const apiKeys = window.getAPIKeys();
|
const apiKeys = window.getAPIKeys();
|
||||||
return {
|
return {
|
||||||
cryptoCompare: apiKeys.cryptoCompare || '',
|
|
||||||
coinGecko: apiKeys.coinGecko || ''
|
coinGecko: apiKeys.coinGecko || ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
cryptoCompare: '',
|
|
||||||
coinGecko: ''
|
coinGecko: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ const WalletManager = (function() {
|
|||||||
defaultTTL: 300,
|
defaultTTL: 300,
|
||||||
priceSource: {
|
priceSource: {
|
||||||
primary: 'coingecko.com',
|
primary: 'coingecko.com',
|
||||||
fallback: 'cryptocompare.com',
|
enabledSources: ['coingecko.com']
|
||||||
enabledSources: ['coingecko.com', 'cryptocompare.com']
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ const api = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const volumeData = await Api.fetchVolumeData({
|
const volumeData = await Api.fetchVolumeData({
|
||||||
cryptoCompare: apiKeys.cryptoCompare,
|
|
||||||
coinGecko: apiKeys.coinGecko
|
coinGecko: apiKeys.coinGecko
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -109,10 +108,7 @@ const api = {
|
|||||||
|
|
||||||
const historicalData = await Api.fetchHistoricalData(
|
const historicalData = await Api.fetchHistoricalData(
|
||||||
coinSymbols,
|
coinSymbols,
|
||||||
window.config.currentResolution,
|
window.config.currentResolution
|
||||||
{
|
|
||||||
cryptoCompare: window.config.getAPIKeys().cryptoCompare
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Object.keys(historicalData).forEach(coin => {
|
Object.keys(historicalData).forEach(coin => {
|
||||||
@@ -146,8 +142,7 @@ const api = {
|
|||||||
const rateLimiter = {
|
const rateLimiter = {
|
||||||
lastRequestTime: {},
|
lastRequestTime: {},
|
||||||
minRequestInterval: {
|
minRequestInterval: {
|
||||||
coingecko: window.config.rateLimits.coingecko.minInterval,
|
coingecko: window.config.rateLimits.coingecko.minInterval
|
||||||
cryptocompare: window.config.rateLimits.cryptocompare.minInterval
|
|
||||||
},
|
},
|
||||||
requestQueue: {},
|
requestQueue: {},
|
||||||
retryDelays: window.config.retryDelays,
|
retryDelays: window.config.retryDelays,
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
<script>
|
<script>
|
||||||
function getAPIKeys() {
|
function getAPIKeys() {
|
||||||
return {
|
return {
|
||||||
cryptoCompare: "{{ chart_api_key|safe }}",
|
|
||||||
coinGecko: "{{ coingecko_api_key|safe }}"
|
coinGecko: "{{ coingecko_api_key|safe }}"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,12 +394,6 @@
|
|||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Enable price charts in the interface</p>
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Enable price charts in the interface</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">CryptoCompare API Key</label>
|
|
||||||
<input type="text" class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none dark:bg-gray-700 dark:text-white border border-gray-300 dark:border-gray-600 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-1 focus:outline-none" name="chartapikey" value="{{ chart_settings.chart_api_key }}" placeholder="Enter API key">
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">API key for CryptoCompare price data</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="md:col-span-2">
|
<div class="md:col-span-2">
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">CoinGecko API Key</label>
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">CoinGecko API Key</label>
|
||||||
<div class="max-w-md">
|
<div class="max-w-md">
|
||||||
|
|||||||
@@ -43,10 +43,7 @@ from basicswap.chainparams import (
|
|||||||
Coins,
|
Coins,
|
||||||
ticker_map,
|
ticker_map,
|
||||||
)
|
)
|
||||||
from basicswap.explorers import (
|
from basicswap.explorers import default_coingecko_api_key
|
||||||
default_chart_api_key,
|
|
||||||
default_coingecko_api_key,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def value_or_none(v):
|
def value_or_none(v):
|
||||||
@@ -541,23 +538,9 @@ def page_newoffer(self, url_split, post_string):
|
|||||||
automation_filters = {"type_ind": Concepts.OFFER, "sort_by": "label"}
|
automation_filters = {"type_ind": Concepts.OFFER, "sort_by": "label"}
|
||||||
automation_strategies = swap_client.listAutomationStrategies(automation_filters)
|
automation_strategies = swap_client.listAutomationStrategies(automation_filters)
|
||||||
|
|
||||||
chart_api_key = swap_client.settings.get("chart_api_key", "")
|
coingecko_api_key = get_api_key_setting(
|
||||||
if chart_api_key == "":
|
swap_client.settings, "coingecko_api_key", default_coingecko_api_key
|
||||||
chart_api_key_enc = swap_client.settings.get("chart_api_key_enc", "")
|
)
|
||||||
chart_api_key = (
|
|
||||||
default_chart_api_key
|
|
||||||
if chart_api_key_enc == ""
|
|
||||||
else bytes.fromhex(chart_api_key_enc).decode("utf-8")
|
|
||||||
)
|
|
||||||
|
|
||||||
coingecko_api_key = swap_client.settings.get("coingecko_api_key", "")
|
|
||||||
if coingecko_api_key == "":
|
|
||||||
coingecko_api_key_enc = swap_client.settings.get("coingecko_api_key_enc", "")
|
|
||||||
coingecko_api_key = (
|
|
||||||
default_coingecko_api_key
|
|
||||||
if coingecko_api_key_enc == ""
|
|
||||||
else bytes.fromhex(coingecko_api_key_enc).decode("utf-8")
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.render_template(
|
return self.render_template(
|
||||||
template,
|
template,
|
||||||
@@ -575,7 +558,6 @@ def page_newoffer(self, url_split, post_string):
|
|||||||
(strSwapType(x), strSwapDesc(x)) for x in SwapTypes if strSwapType(x)
|
(strSwapType(x), strSwapDesc(x)) for x in SwapTypes if strSwapType(x)
|
||||||
],
|
],
|
||||||
"show_chart": swap_client.settings.get("show_chart", True),
|
"show_chart": swap_client.settings.get("show_chart", True),
|
||||||
"chart_api_key": chart_api_key,
|
|
||||||
"coingecko_api_key": coingecko_api_key,
|
"coingecko_api_key": coingecko_api_key,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -990,9 +972,6 @@ def page_offers(self, url_split, post_string, sent=False):
|
|||||||
|
|
||||||
coins_from, coins_to = listAvailableCoins(swap_client, split_from=True)
|
coins_from, coins_to = listAvailableCoins(swap_client, split_from=True)
|
||||||
|
|
||||||
chart_api_key = get_api_key_setting(
|
|
||||||
swap_client.settings, "chart_api_key", default_chart_api_key
|
|
||||||
)
|
|
||||||
coingecko_api_key = get_api_key_setting(
|
coingecko_api_key = get_api_key_setting(
|
||||||
swap_client.settings, "coingecko_api_key", default_coingecko_api_key
|
swap_client.settings, "coingecko_api_key", default_coingecko_api_key
|
||||||
)
|
)
|
||||||
@@ -1041,7 +1020,6 @@ def page_offers(self, url_split, post_string, sent=False):
|
|||||||
"show_chart": (
|
"show_chart": (
|
||||||
False if sent else swap_client.settings.get("show_chart", True)
|
False if sent else swap_client.settings.get("show_chart", True)
|
||||||
),
|
),
|
||||||
"chart_api_key": chart_api_key,
|
|
||||||
"coingecko_api_key": coingecko_api_key,
|
"coingecko_api_key": coingecko_api_key,
|
||||||
"coins_from": coins_from,
|
"coins_from": coins_from,
|
||||||
"coins": coins_to,
|
"coins": coins_to,
|
||||||
|
|||||||
@@ -49,9 +49,6 @@ def page_settings(self, url_split, post_string):
|
|||||||
active_tab = "general"
|
active_tab = "general"
|
||||||
data = {
|
data = {
|
||||||
"show_chart": toBool(get_data_entry(form_data, "showchart")),
|
"show_chart": toBool(get_data_entry(form_data, "showchart")),
|
||||||
"chart_api_key": html.unescape(
|
|
||||||
get_data_entry_or(form_data, "chartapikey", "")
|
|
||||||
),
|
|
||||||
"coingecko_api_key": html.unescape(
|
"coingecko_api_key": html.unescape(
|
||||||
get_data_entry_or(form_data, "coingeckoapikey", "")
|
get_data_entry_or(form_data, "coingeckoapikey", "")
|
||||||
),
|
),
|
||||||
@@ -213,16 +210,12 @@ def page_settings(self, url_split, post_string):
|
|||||||
"check_updates": swap_client.settings.get("check_updates", True),
|
"check_updates": swap_client.settings.get("check_updates", True),
|
||||||
}
|
}
|
||||||
|
|
||||||
chart_api_key = get_api_key_setting(
|
|
||||||
swap_client.settings, "chart_api_key", escape=True
|
|
||||||
)
|
|
||||||
coingecko_api_key = get_api_key_setting(
|
coingecko_api_key = get_api_key_setting(
|
||||||
swap_client.settings, "coingecko_api_key", escape=True
|
swap_client.settings, "coingecko_api_key", default_value="", escape=True
|
||||||
)
|
)
|
||||||
|
|
||||||
chart_settings = {
|
chart_settings = {
|
||||||
"show_chart": swap_client.settings.get("show_chart", True),
|
"show_chart": swap_client.settings.get("show_chart", True),
|
||||||
"chart_api_key": chart_api_key,
|
|
||||||
"coingecko_api_key": coingecko_api_key,
|
"coingecko_api_key": coingecko_api_key,
|
||||||
"enabled_chart_coins": swap_client.settings.get("enabled_chart_coins", ""),
|
"enabled_chart_coins": swap_client.settings.get("enabled_chart_coins", ""),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user