mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2024-11-19 09:54:21 +01:00
abstract exchange rates code into a "util".
This commit is contained in:
parent
5142f1e29f
commit
adc3e62573
@ -1,48 +0,0 @@
|
|||||||
import trio # type: ignore
|
|
||||||
import httpx
|
|
||||||
|
|
||||||
|
|
||||||
async def get_fiat_rate(currency: str):
|
|
||||||
assert currency == "USD", "Only USD is supported as fiat currency."
|
|
||||||
return await get_usd_rate()
|
|
||||||
|
|
||||||
|
|
||||||
async def get_usd_rate():
|
|
||||||
"""
|
|
||||||
Returns an average satoshi price from multiple sources.
|
|
||||||
"""
|
|
||||||
|
|
||||||
satoshi_prices = [None, None, None]
|
|
||||||
|
|
||||||
async def fetch_price(index, url, getter):
|
|
||||||
try:
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
r = await client.get(url)
|
|
||||||
r.raise_for_status()
|
|
||||||
satoshi_price = int(100_000_000 / float(getter(r.json())))
|
|
||||||
satoshi_prices[index] = satoshi_price
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async with trio.open_nursery() as nursery:
|
|
||||||
nursery.start_soon(
|
|
||||||
fetch_price,
|
|
||||||
0,
|
|
||||||
"https://api.kraken.com/0/public/Ticker?pair=XXBTZUSD",
|
|
||||||
lambda d: d["result"]["XXBTCZUSD"]["c"][0],
|
|
||||||
)
|
|
||||||
nursery.start_soon(
|
|
||||||
fetch_price,
|
|
||||||
1,
|
|
||||||
"https://www.bitstamp.net/api/v2/ticker/btcusd",
|
|
||||||
lambda d: d["last"],
|
|
||||||
)
|
|
||||||
nursery.start_soon(
|
|
||||||
fetch_price,
|
|
||||||
2,
|
|
||||||
"https://api.coincap.io/v2/rates/bitcoin",
|
|
||||||
lambda d: d["data"]["rateUsd"],
|
|
||||||
)
|
|
||||||
|
|
||||||
satoshi_prices = [x for x in satoshi_prices if x]
|
|
||||||
return sum(satoshi_prices) / len(satoshi_prices)
|
|
@ -5,10 +5,10 @@ from quart import jsonify, url_for, request
|
|||||||
from lnurl import LnurlPayResponse, LnurlPayActionResponse, LnurlErrorResponse # type: ignore
|
from lnurl import LnurlPayResponse, LnurlPayActionResponse, LnurlErrorResponse # type: ignore
|
||||||
|
|
||||||
from lnbits.core.services import create_invoice
|
from lnbits.core.services import create_invoice
|
||||||
|
from lnbits.utils.exchange_rates import get_fiat_rate_satoshis
|
||||||
|
|
||||||
from . import lnurlp_ext
|
from . import lnurlp_ext
|
||||||
from .crud import increment_pay_link
|
from .crud import increment_pay_link
|
||||||
from .helpers import get_fiat_rate
|
|
||||||
|
|
||||||
|
|
||||||
@lnurlp_ext.route("/api/v1/lnurl/<link_id>", methods=["GET"])
|
@lnurlp_ext.route("/api/v1/lnurl/<link_id>", methods=["GET"])
|
||||||
@ -17,7 +17,7 @@ async def api_lnurl_response(link_id):
|
|||||||
if not link:
|
if not link:
|
||||||
return jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}), HTTPStatus.OK
|
return jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}), HTTPStatus.OK
|
||||||
|
|
||||||
rate = await get_fiat_rate(link.currency) if link.currency else 1
|
rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1
|
||||||
resp = LnurlPayResponse(
|
resp = LnurlPayResponse(
|
||||||
callback=url_for("lnurlp.api_lnurl_callback", link_id=link.id, _external=True),
|
callback=url_for("lnurlp.api_lnurl_callback", link_id=link.id, _external=True),
|
||||||
min_sendable=math.ceil(link.min * rate) * 1000,
|
min_sendable=math.ceil(link.min * rate) * 1000,
|
||||||
@ -39,7 +39,7 @@ async def api_lnurl_callback(link_id):
|
|||||||
return jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}), HTTPStatus.OK
|
return jsonify({"status": "ERROR", "reason": "LNURL-pay not found."}), HTTPStatus.OK
|
||||||
|
|
||||||
min, max = link.min, link.max
|
min, max = link.min, link.max
|
||||||
rate = await get_fiat_rate(link.currency) if link.currency else 1
|
rate = await get_fiat_rate_satoshis(link.currency) if link.currency else 1
|
||||||
if link.currency:
|
if link.currency:
|
||||||
# allow some fluctuation (as the fiat price may have changed between the calls)
|
# allow some fluctuation (as the fiat price may have changed between the calls)
|
||||||
min = rate * 995 * link.min
|
min = rate * 995 * link.min
|
||||||
|
@ -4,6 +4,7 @@ from lnurl.exceptions import InvalidUrl as LnurlInvalidUrl # type: ignore
|
|||||||
|
|
||||||
from lnbits.core.crud import get_user
|
from lnbits.core.crud import get_user
|
||||||
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
|
from lnbits.decorators import api_check_wallet_key, api_validate_post_request
|
||||||
|
from lnbits.utils.exchange_rates import get_fiat_rate_satoshis
|
||||||
|
|
||||||
from . import lnurlp_ext
|
from . import lnurlp_ext
|
||||||
from .crud import (
|
from .crud import (
|
||||||
@ -13,7 +14,6 @@ from .crud import (
|
|||||||
update_pay_link,
|
update_pay_link,
|
||||||
delete_pay_link,
|
delete_pay_link,
|
||||||
)
|
)
|
||||||
from .helpers import get_fiat_rate
|
|
||||||
|
|
||||||
|
|
||||||
@lnurlp_ext.route("/api/v1/links", methods=["GET"])
|
@lnurlp_ext.route("/api/v1/links", methods=["GET"])
|
||||||
@ -109,7 +109,7 @@ async def api_link_delete(link_id):
|
|||||||
@lnurlp_ext.route("/api/v1/rate/<currency>", methods=["GET"])
|
@lnurlp_ext.route("/api/v1/rate/<currency>", methods=["GET"])
|
||||||
async def api_check_fiat_rate(currency):
|
async def api_check_fiat_rate(currency):
|
||||||
try:
|
try:
|
||||||
rate = await get_fiat_rate(currency)
|
rate = await get_fiat_rate_satoshis(currency)
|
||||||
except AssertionError:
|
except AssertionError:
|
||||||
rate = None
|
rate = None
|
||||||
|
|
||||||
|
@ -1,57 +1,9 @@
|
|||||||
import trio # type: ignore
|
|
||||||
import httpx
|
|
||||||
import base64
|
import base64
|
||||||
import struct
|
import struct
|
||||||
import hmac
|
import hmac
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
async def get_fiat_rate(currency: str):
|
|
||||||
assert currency == "USD", "Only USD is supported as fiat currency."
|
|
||||||
return await get_usd_rate()
|
|
||||||
|
|
||||||
|
|
||||||
async def get_usd_rate():
|
|
||||||
"""
|
|
||||||
Returns an average satoshi price from multiple sources.
|
|
||||||
"""
|
|
||||||
|
|
||||||
satoshi_prices = [None, None, None]
|
|
||||||
|
|
||||||
async def fetch_price(index, url, getter):
|
|
||||||
try:
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
r = await client.get(url)
|
|
||||||
r.raise_for_status()
|
|
||||||
satoshi_price = int(100_000_000 / float(getter(r.json())))
|
|
||||||
satoshi_prices[index] = satoshi_price
|
|
||||||
except Exception:
|
|
||||||
pass
|
|
||||||
|
|
||||||
async with trio.open_nursery() as nursery:
|
|
||||||
nursery.start_soon(
|
|
||||||
fetch_price,
|
|
||||||
0,
|
|
||||||
"https://api.kraken.com/0/public/Ticker?pair=XXBTZUSD",
|
|
||||||
lambda d: d["result"]["XXBTCZUSD"]["c"][0],
|
|
||||||
)
|
|
||||||
nursery.start_soon(
|
|
||||||
fetch_price,
|
|
||||||
1,
|
|
||||||
"https://www.bitstamp.net/api/v2/ticker/btcusd",
|
|
||||||
lambda d: d["last"],
|
|
||||||
)
|
|
||||||
nursery.start_soon(
|
|
||||||
fetch_price,
|
|
||||||
2,
|
|
||||||
"https://api.coincap.io/v2/rates/bitcoin",
|
|
||||||
lambda d: d["data"]["rateUsd"],
|
|
||||||
)
|
|
||||||
|
|
||||||
satoshi_prices = [x for x in satoshi_prices if x]
|
|
||||||
return sum(satoshi_prices) / len(satoshi_prices)
|
|
||||||
|
|
||||||
|
|
||||||
def hotp(key, counter, digits=6, digest="sha1"):
|
def hotp(key, counter, digits=6, digest="sha1"):
|
||||||
key = base64.b32decode(key.upper() + "=" * ((8 - len(key)) % 8))
|
key = base64.b32decode(key.upper() + "=" * ((8 - len(key)) % 8))
|
||||||
counter = struct.pack(">Q", counter)
|
counter = struct.pack(">Q", counter)
|
||||||
|
@ -3,10 +3,10 @@ from quart import jsonify, url_for, request
|
|||||||
from lnurl import LnurlPayResponse, LnurlPayActionResponse, LnurlErrorResponse # type: ignore
|
from lnurl import LnurlPayResponse, LnurlPayActionResponse, LnurlErrorResponse # type: ignore
|
||||||
|
|
||||||
from lnbits.core.services import create_invoice
|
from lnbits.core.services import create_invoice
|
||||||
|
from lnbits.utils.exchange_rates import fiat_amount_as_satoshis
|
||||||
|
|
||||||
from . import offlineshop_ext
|
from . import offlineshop_ext
|
||||||
from .crud import get_shop, get_item
|
from .crud import get_shop, get_item
|
||||||
from .helpers import get_fiat_rate
|
|
||||||
|
|
||||||
|
|
||||||
@offlineshop_ext.route("/lnurl/<item_id>", methods=["GET"])
|
@offlineshop_ext.route("/lnurl/<item_id>", methods=["GET"])
|
||||||
@ -18,8 +18,7 @@ async def lnurl_response(item_id):
|
|||||||
if not item.enabled:
|
if not item.enabled:
|
||||||
return jsonify({"status": "ERROR", "reason": "Item disabled."})
|
return jsonify({"status": "ERROR", "reason": "Item disabled."})
|
||||||
|
|
||||||
rate = await get_fiat_rate(item.unit) if item.unit != "sat" else 1
|
price_msat = (await fiat_amount_as_satoshis(item.price, item.unit) if item.unit != "sat" else item.price) * 1000
|
||||||
price_msat = int(item.price * rate) * 1000
|
|
||||||
|
|
||||||
resp = LnurlPayResponse(
|
resp = LnurlPayResponse(
|
||||||
callback=url_for("offlineshop.lnurl_callback", item_id=item.id, _external=True),
|
callback=url_for("offlineshop.lnurl_callback", item_id=item.id, _external=True),
|
||||||
@ -41,10 +40,10 @@ async def lnurl_callback(item_id):
|
|||||||
min = item.price * 1000
|
min = item.price * 1000
|
||||||
max = item.price * 1000
|
max = item.price * 1000
|
||||||
else:
|
else:
|
||||||
rate = await get_fiat_rate(item.unit)
|
price = await fiat_amount_as_satoshis(item.price, item.unit)
|
||||||
# allow some fluctuation (the fiat price may have changed between the calls)
|
# allow some fluctuation (the fiat price may have changed between the calls)
|
||||||
min = int(rate * item.price) * 995
|
min = price * 995
|
||||||
max = int(rate * item.price) * 1010
|
max = price * 1010
|
||||||
|
|
||||||
amount_received = int(request.args.get("amount"))
|
amount_received = int(request.args.get("amount"))
|
||||||
if amount_received < min:
|
if amount_received < min:
|
||||||
|
0
lnbits/utils/__init__.py
Normal file
0
lnbits/utils/__init__.py
Normal file
262
lnbits/utils/exchange_rates.py
Normal file
262
lnbits/utils/exchange_rates.py
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
import trio # type: ignore
|
||||||
|
import httpx
|
||||||
|
from typing import Callable, NamedTuple
|
||||||
|
|
||||||
|
currencies = {
|
||||||
|
"AED": "United Arab Emirates Dirham",
|
||||||
|
"AFN": "Afghan Afghani",
|
||||||
|
"ALL": "Albanian Lek",
|
||||||
|
"AMD": "Armenian Dram",
|
||||||
|
"ANG": "Netherlands Antillean Gulden",
|
||||||
|
"AOA": "Angolan Kwanza",
|
||||||
|
"ARS": "Argentine Peso",
|
||||||
|
"AUD": "Australian Dollar",
|
||||||
|
"AWG": "Aruban Florin",
|
||||||
|
"AZN": "Azerbaijani Manat",
|
||||||
|
"BAM": "Bosnia and Herzegovina Convertible Mark",
|
||||||
|
"BBD": "Barbadian Dollar",
|
||||||
|
"BDT": "Bangladeshi Taka",
|
||||||
|
"BGN": "Bulgarian Lev",
|
||||||
|
"BHD": "Bahraini Dinar",
|
||||||
|
"BIF": "Burundian Franc",
|
||||||
|
"BMD": "Bermudian Dollar",
|
||||||
|
"BND": "Brunei Dollar",
|
||||||
|
"BOB": "Bolivian Boliviano",
|
||||||
|
"BRL": "Brazilian Real",
|
||||||
|
"BSD": "Bahamian Dollar",
|
||||||
|
"BTN": "Bhutanese Ngultrum",
|
||||||
|
"BWP": "Botswana Pula",
|
||||||
|
"BYN": "Belarusian Ruble",
|
||||||
|
"BYR": "Belarusian Ruble",
|
||||||
|
"BZD": "Belize Dollar",
|
||||||
|
"CAD": "Canadian Dollar",
|
||||||
|
"CDF": "Congolese Franc",
|
||||||
|
"CHF": "Swiss Franc",
|
||||||
|
"CLF": "Unidad de Fomento",
|
||||||
|
"CLP": "Chilean Peso",
|
||||||
|
"CNH": "Chinese Renminbi Yuan Offshore",
|
||||||
|
"CNY": "Chinese Renminbi Yuan",
|
||||||
|
"COP": "Colombian Peso",
|
||||||
|
"CRC": "Costa Rican Colón",
|
||||||
|
"CUC": "Cuban Convertible Peso",
|
||||||
|
"CVE": "Cape Verdean Escudo",
|
||||||
|
"CZK": "Czech Koruna",
|
||||||
|
"DJF": "Djiboutian Franc",
|
||||||
|
"DKK": "Danish Krone",
|
||||||
|
"DOP": "Dominican Peso",
|
||||||
|
"DZD": "Algerian Dinar",
|
||||||
|
"EGP": "Egyptian Pound",
|
||||||
|
"ERN": "Eritrean Nakfa",
|
||||||
|
"ETB": "Ethiopian Birr",
|
||||||
|
"EUR": "Euro",
|
||||||
|
"FJD": "Fijian Dollar",
|
||||||
|
"FKP": "Falkland Pound",
|
||||||
|
"GBP": "British Pound",
|
||||||
|
"GEL": "Georgian Lari",
|
||||||
|
"GGP": "Guernsey Pound",
|
||||||
|
"GHS": "Ghanaian Cedi",
|
||||||
|
"GIP": "Gibraltar Pound",
|
||||||
|
"GMD": "Gambian Dalasi",
|
||||||
|
"GNF": "Guinean Franc",
|
||||||
|
"GTQ": "Guatemalan Quetzal",
|
||||||
|
"GYD": "Guyanese Dollar",
|
||||||
|
"HKD": "Hong Kong Dollar",
|
||||||
|
"HNL": "Honduran Lempira",
|
||||||
|
"HRK": "Croatian Kuna",
|
||||||
|
"HTG": "Haitian Gourde",
|
||||||
|
"HUF": "Hungarian Forint",
|
||||||
|
"IDR": "Indonesian Rupiah",
|
||||||
|
"ILS": "Israeli New Sheqel",
|
||||||
|
"IMP": "Isle of Man Pound",
|
||||||
|
"INR": "Indian Rupee",
|
||||||
|
"IQD": "Iraqi Dinar",
|
||||||
|
"ISK": "Icelandic Króna",
|
||||||
|
"JEP": "Jersey Pound",
|
||||||
|
"JMD": "Jamaican Dollar",
|
||||||
|
"JOD": "Jordanian Dinar",
|
||||||
|
"JPY": "Japanese Yen",
|
||||||
|
"KES": "Kenyan Shilling",
|
||||||
|
"KGS": "Kyrgyzstani Som",
|
||||||
|
"KHR": "Cambodian Riel",
|
||||||
|
"KMF": "Comorian Franc",
|
||||||
|
"KRW": "South Korean Won",
|
||||||
|
"KWD": "Kuwaiti Dinar",
|
||||||
|
"KYD": "Cayman Islands Dollar",
|
||||||
|
"KZT": "Kazakhstani Tenge",
|
||||||
|
"LAK": "Lao Kip",
|
||||||
|
"LBP": "Lebanese Pound",
|
||||||
|
"LKR": "Sri Lankan Rupee",
|
||||||
|
"LRD": "Liberian Dollar",
|
||||||
|
"LSL": "Lesotho Loti",
|
||||||
|
"LYD": "Libyan Dinar",
|
||||||
|
"MAD": "Moroccan Dirham",
|
||||||
|
"MDL": "Moldovan Leu",
|
||||||
|
"MGA": "Malagasy Ariary",
|
||||||
|
"MKD": "Macedonian Denar",
|
||||||
|
"MMK": "Myanmar Kyat",
|
||||||
|
"MNT": "Mongolian Tögrög",
|
||||||
|
"MOP": "Macanese Pataca",
|
||||||
|
"MRO": "Mauritanian Ouguiya",
|
||||||
|
"MUR": "Mauritian Rupee",
|
||||||
|
"MVR": "Maldivian Rufiyaa",
|
||||||
|
"MWK": "Malawian Kwacha",
|
||||||
|
"MXN": "Mexican Peso",
|
||||||
|
"MYR": "Malaysian Ringgit",
|
||||||
|
"MZN": "Mozambican Metical",
|
||||||
|
"NAD": "Namibian Dollar",
|
||||||
|
"NGN": "Nigerian Naira",
|
||||||
|
"NIO": "Nicaraguan Córdoba",
|
||||||
|
"NOK": "Norwegian Krone",
|
||||||
|
"NPR": "Nepalese Rupee",
|
||||||
|
"NZD": "New Zealand Dollar",
|
||||||
|
"OMR": "Omani Rial",
|
||||||
|
"PAB": "Panamanian Balboa",
|
||||||
|
"PEN": "Peruvian Sol",
|
||||||
|
"PGK": "Papua New Guinean Kina",
|
||||||
|
"PHP": "Philippine Peso",
|
||||||
|
"PKR": "Pakistani Rupee",
|
||||||
|
"PLN": "Polish Złoty",
|
||||||
|
"PYG": "Paraguayan Guaraní",
|
||||||
|
"QAR": "Qatari Riyal",
|
||||||
|
"RON": "Romanian Leu",
|
||||||
|
"RSD": "Serbian Dinar",
|
||||||
|
"RUB": "Russian Ruble",
|
||||||
|
"RWF": "Rwandan Franc",
|
||||||
|
"SAR": "Saudi Riyal",
|
||||||
|
"SBD": "Solomon Islands Dollar",
|
||||||
|
"SCR": "Seychellois Rupee",
|
||||||
|
"SEK": "Swedish Krona",
|
||||||
|
"SGD": "Singapore Dollar",
|
||||||
|
"SHP": "Saint Helenian Pound",
|
||||||
|
"SLL": "Sierra Leonean Leone",
|
||||||
|
"SOS": "Somali Shilling",
|
||||||
|
"SRD": "Surinamese Dollar",
|
||||||
|
"SSP": "South Sudanese Pound",
|
||||||
|
"STD": "São Tomé and Príncipe Dobra",
|
||||||
|
"SVC": "Salvadoran Colón",
|
||||||
|
"SZL": "Swazi Lilangeni",
|
||||||
|
"THB": "Thai Baht",
|
||||||
|
"TJS": "Tajikistani Somoni",
|
||||||
|
"TMT": "Turkmenistani Manat",
|
||||||
|
"TND": "Tunisian Dinar",
|
||||||
|
"TOP": "Tongan Paʻanga",
|
||||||
|
"TRY": "Turkish Lira",
|
||||||
|
"TTD": "Trinidad and Tobago Dollar",
|
||||||
|
"TWD": "New Taiwan Dollar",
|
||||||
|
"TZS": "Tanzanian Shilling",
|
||||||
|
"UAH": "Ukrainian Hryvnia",
|
||||||
|
"UGX": "Ugandan Shilling",
|
||||||
|
"USD": "US Dollar",
|
||||||
|
"UYU": "Uruguayan Peso",
|
||||||
|
"UZS": "Uzbekistan Som",
|
||||||
|
"VEF": "Venezuelan Bolívar",
|
||||||
|
"VES": "Venezuelan Bolívar Soberano",
|
||||||
|
"VND": "Vietnamese Đồng",
|
||||||
|
"VUV": "Vanuatu Vatu",
|
||||||
|
"WST": "Samoan Tala",
|
||||||
|
"XAF": "Central African Cfa Franc",
|
||||||
|
"XAG": "Silver (Troy Ounce)",
|
||||||
|
"XAU": "Gold (Troy Ounce)",
|
||||||
|
"XCD": "East Caribbean Dollar",
|
||||||
|
"XDR": "Special Drawing Rights",
|
||||||
|
"XOF": "West African Cfa Franc",
|
||||||
|
"XPD": "Palladium",
|
||||||
|
"XPF": "Cfp Franc",
|
||||||
|
"XPT": "Platinum",
|
||||||
|
"YER": "Yemeni Rial",
|
||||||
|
"ZAR": "South African Rand",
|
||||||
|
"ZMW": "Zambian Kwacha",
|
||||||
|
"ZWL": "Zimbabwean Dollar",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class Provider(NamedTuple):
|
||||||
|
name: str
|
||||||
|
domain: str
|
||||||
|
api_url: str
|
||||||
|
getter: Callable
|
||||||
|
|
||||||
|
|
||||||
|
exchange_rate_providers = {
|
||||||
|
"bitfinex": Provider(
|
||||||
|
"Bitfinex",
|
||||||
|
"bitfinex.com",
|
||||||
|
"https://api.bitfinex.com/v1/pubticker/{from}{to}",
|
||||||
|
lambda data, replacements: data["last_price"],
|
||||||
|
),
|
||||||
|
"bitstamp": Provider(
|
||||||
|
"Bitstamp",
|
||||||
|
"bitstamp.net",
|
||||||
|
"https://www.bitstamp.net/api/v2/ticker/{from}{to}/",
|
||||||
|
lambda data, replacements: data["last"],
|
||||||
|
),
|
||||||
|
"coinbase": Provider(
|
||||||
|
"Coinbase",
|
||||||
|
"coinbase.com",
|
||||||
|
"https://api.coinbase.com/v2/exchange-rates?currency={FROM}",
|
||||||
|
lambda data, replacements: data["data"]["rates"][replacements["TO"]],
|
||||||
|
),
|
||||||
|
"coinmate": Provider(
|
||||||
|
"CoinMate",
|
||||||
|
"coinmate.io",
|
||||||
|
"https://coinmate.io/api/ticker?currencyPair={FROM}_{TO}",
|
||||||
|
lambda data, replacements: data["data"]["last"],
|
||||||
|
),
|
||||||
|
"kraken": Provider(
|
||||||
|
"Kraken",
|
||||||
|
"kraken.com",
|
||||||
|
"https://api.kraken.com/0/public/Ticker?pair=XBT{TO}",
|
||||||
|
lambda data, replacements: data["result"]["XXBTZ" + replacements["TO"]]["c"][0],
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def btc_price(currency: str) -> float:
|
||||||
|
replacements = {"FROM": "BTC", "from": "btc", "TO": currency.upper(), "to": currency.lower()}
|
||||||
|
rates = []
|
||||||
|
send_channel, receive_channel = trio.open_memory_channel(0)
|
||||||
|
|
||||||
|
async def controller(nursery):
|
||||||
|
failures = 0
|
||||||
|
while True:
|
||||||
|
rate = await receive_channel.receive()
|
||||||
|
if rate:
|
||||||
|
rates.append(rate)
|
||||||
|
else:
|
||||||
|
failures += 1
|
||||||
|
if len(rates) >= 2 or len(rates) == 1 and failures >= 2:
|
||||||
|
nursery.cancel_scope.cancel()
|
||||||
|
break
|
||||||
|
if failures == len(exchange_rate_providers):
|
||||||
|
nursery.cancel_scope.cancel()
|
||||||
|
break
|
||||||
|
|
||||||
|
async def fetch_price(key: str, provider: Provider):
|
||||||
|
try:
|
||||||
|
url = provider.api_url.format(**replacements)
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
r = await client.get(url, timeout=0.5)
|
||||||
|
r.raise_for_status()
|
||||||
|
data = r.json()
|
||||||
|
rate = float(provider.getter(data, replacements))
|
||||||
|
await send_channel.send(rate)
|
||||||
|
except Exception:
|
||||||
|
await send_channel.send(None)
|
||||||
|
|
||||||
|
async with trio.open_nursery() as nursery:
|
||||||
|
nursery.start_soon(controller, nursery)
|
||||||
|
for key, provider in exchange_rate_providers.items():
|
||||||
|
nursery.start_soon(fetch_price, key, provider)
|
||||||
|
|
||||||
|
if not rates:
|
||||||
|
return 9999999999
|
||||||
|
|
||||||
|
return sum([rate for rate in rates]) / len(rates)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_fiat_rate_satoshis(currency: str) -> float:
|
||||||
|
return int(100_000_000 / (await btc_price(currency)))
|
||||||
|
|
||||||
|
|
||||||
|
async def fiat_amount_as_satoshis(amount: float, currency: str) -> int:
|
||||||
|
return int(amount * (await get_fiat_rate_satoshis(currency)))
|
Loading…
Reference in New Issue
Block a user