[Mint] Add support for BTC and EUR in StrikeWallet backend, add EUR to FakeWallet (#561)

* strike for btc and eur

* strike works with eur

* backend check
This commit is contained in:
callebtc
2024-06-26 14:50:39 +02:00
committed by GitHub
parent 6b38ef6c29
commit 1766b6e92e
9 changed files with 86 additions and 25 deletions

View File

@@ -50,13 +50,14 @@ MINT_DERIVATION_PATH="m/0'/0'/0'"
# m/0'/0'/0' is "sat" (default)
# m/0'/1'/0' is "msat"
# m/0'/2'/0' is "usd"
# m/0'/3'/0' is "eur"
# In this example, we have 2 keysets for sat, 1 for msat and 1 for usd
# MINT_DERIVATION_PATH_LIST=["m/0'/0'/0'", "m/0'/0'/1'", "m/0'/1'/0'", "m/0'/2'/0'"]
MINT_DATABASE=data/mint
# Funding source backends
# Supported: FakeWallet, LndRestWallet, CoreLightningRestWallet, BlinkWallet, LNbitsWallet, StrikeUSDWallet
# Supported: FakeWallet, LndRestWallet, CoreLightningRestWallet, BlinkWallet, LNbitsWallet, StrikeWallet
MINT_BACKEND_BOLT11_SAT=FakeWallet
# Only works if a usd derivation path is set
# MINT_BACKEND_BOLT11_SAT=FakeWallet
@@ -78,7 +79,7 @@ MINT_CORELIGHTNING_REST_CERT="./clightning-2-rest/certificate.pem"
# Use with BlinkWallet
MINT_BLINK_KEY=blink_abcdefgh
# Use with StrikeUSDWallet (usd only, does not currently support sats/msats)
# Use with StrikeWallet for BTC, USD, and EUR
MINT_STRIKE_KEY=ABC123
# fee to reserve in percent of the amount

View File

@@ -436,6 +436,8 @@ class Unit(Enum):
sat = 0
msat = 1
usd = 2
eur = 3
btc = 4
def str(self, amount: int) -> str:
if self == Unit.sat:
@@ -444,6 +446,10 @@ class Unit(Enum):
return f"{amount} msat"
elif self == Unit.usd:
return f"${amount/100:.2f} USD"
elif self == Unit.eur:
return f"{amount/100:.2f} EUR"
elif self == Unit.btc:
return f"{amount/1e8:.8f} BTC"
else:
raise Exception("Invalid unit")
@@ -478,6 +484,33 @@ class Amount:
else:
return self
def to_float_string(self) -> str:
if self.unit == Unit.usd or self.unit == Unit.eur:
return self.cents_to_usd()
elif self.unit == Unit.sat:
return self.sat_to_btc()
else:
raise Exception("Amount must be in satoshis or cents")
@classmethod
def from_float(cls, amount: float, unit: Unit) -> "Amount":
if unit == Unit.usd or unit == Unit.eur:
return cls(unit, int(amount * 100))
elif unit == Unit.sat:
return cls(unit, int(amount * 1e8))
else:
raise Exception("Amount must be in satoshis or cents")
def sat_to_btc(self) -> str:
if self.unit != Unit.sat:
raise Exception("Amount must be in satoshis")
return f"{self.amount/1e8:.8f}"
def cents_to_usd(self) -> str:
if self.unit != Unit.usd and self.unit != Unit.eur:
raise Exception("Amount must be in cents")
return f"{self.amount/100:.2f}"
def str(self) -> str:
return self.unit.str(self.amount)

View File

@@ -67,6 +67,7 @@ class MintBackends(MintSettings):
mint_lightning_backend: str = Field(default="") # deprecated
mint_backend_bolt11_sat: str = Field(default="")
mint_backend_bolt11_usd: str = Field(default="")
mint_backend_bolt11_eur: str = Field(default="")
mint_lnbits_endpoint: str = Field(default=None)
mint_lnbits_key: str = Field(default=None)

View File

@@ -5,7 +5,14 @@ from .corelightningrest import CoreLightningRestWallet # noqa: F401
from .fake import FakeWallet # noqa: F401
from .lnbits import LNbitsWallet # noqa: F401
from .lndrest import LndRestWallet # noqa: F401
from .strike import StrikeUSDWallet # noqa: F401
from .strike import StrikeWallet # noqa: F401
if settings.mint_backend_bolt11_sat is None or settings.mint_backend_bolt11_usd is None:
raise Exception("MINT_BACKEND_BOLT11_SAT or MINT_BACKEND_BOLT11_USD not set")
backend_settings = [
settings.mint_backend_bolt11_sat,
settings.mint_backend_bolt11_usd,
settings.mint_backend_bolt11_eur,
]
if all([s is None for s in backend_settings]):
raise Exception(
"MINT_BACKEND_BOLT11_SAT or MINT_BACKEND_BOLT11_USD or MINT_BACKEND_BOLT11_EUR not set"
)

View File

@@ -45,7 +45,7 @@ class FakeWallet(LightningBackend):
32,
).hex()
supported_units = set([Unit.sat, Unit.msat, Unit.usd])
supported_units = set([Unit.sat, Unit.msat, Unit.usd, Unit.eur])
unit = Unit.sat
supports_incoming_payment_stream: bool = True
@@ -113,7 +113,7 @@ class FakeWallet(LightningBackend):
amount_msat = 0
if self.unit == Unit.sat:
amount_msat = MilliSatoshi(amount.to(Unit.msat, round="up").amount)
elif self.unit == Unit.usd:
elif self.unit == Unit.usd or self.unit == Unit.eur:
amount_msat = MilliSatoshi(
math.ceil(amount.amount / self.fake_btc_price * 1e9)
)
@@ -194,10 +194,10 @@ class FakeWallet(LightningBackend):
fees_msat = fee_reserve(amount_msat)
fees = Amount(unit=Unit.msat, amount=fees_msat)
amount = Amount(unit=Unit.msat, amount=amount_msat)
elif self.unit == Unit.usd:
elif self.unit == Unit.usd or self.unit == Unit.eur:
amount_usd = math.ceil(invoice_obj.amount_msat / 1e9 * self.fake_btc_price)
amount = Amount(unit=Unit.usd, amount=amount_usd)
fees = Amount(unit=Unit.usd, amount=2)
amount = Amount(unit=self.unit, amount=amount_usd)
fees = Amount(unit=self.unit, amount=2)
else:
raise NotImplementedError()

View File

@@ -17,15 +17,17 @@ from .base import (
)
class StrikeUSDWallet(LightningBackend):
"""https://github.com/lnbits/lnbits"""
class StrikeWallet(LightningBackend):
"""https://docs.strike.me/api/"""
supported_units = [Unit.usd]
supported_units = [Unit.sat, Unit.usd, Unit.eur]
currency_map = {Unit.sat: "BTC", Unit.usd: "USD", Unit.eur: "EUR"}
def __init__(self, unit: Unit = Unit.usd, **kwargs):
def __init__(self, unit: Unit, **kwargs):
self.assert_unit_supported(unit)
self.unit = unit
self.endpoint = "https://api.strike.me"
self.currency = self.currency_map[self.unit]
# bearer auth with settings.mint_strike_key
bearer_auth = {
@@ -57,8 +59,13 @@ class StrikeUSDWallet(LightningBackend):
)
for balance in data:
if balance["currency"] == "USD":
return StatusResponse(error_message=None, balance=balance["total"])
if balance["currency"] == self.currency:
return StatusResponse(
error_message=None,
balance=Amount.from_float(
float(balance["total"]), self.unit
).amount,
)
async def create_invoice(
self,
@@ -79,7 +86,7 @@ class StrikeUSDWallet(LightningBackend):
payload = {
"correlationId": secrets.token_hex(16),
"description": "Invoice for order 123",
"amount": {"amount": str(amount.amount / 100), "currency": "USD"},
"amount": {"amount": amount.to_float_string(), "currency": self.currency},
}
try:
r = await self.client.post(url=f"{self.endpoint}/v1/invoices", json=payload)
@@ -126,7 +133,7 @@ class StrikeUSDWallet(LightningBackend):
try:
r = await self.client.post(
url=f"{self.endpoint}/v1/payment-quotes/lightning",
json={"sourceCurrency": "USD", "lnInvoice": bolt11},
json={"sourceCurrency": self.currency, "lnInvoice": bolt11},
timeout=None,
)
r.raise_for_status()
@@ -135,11 +142,11 @@ class StrikeUSDWallet(LightningBackend):
raise Exception(error_message)
data = r.json()
amount_cent = int(float(data.get("amount").get("amount")) * 100)
amount = Amount.from_float(float(data.get("amount").get("amount")), self.unit)
quote = PaymentQuoteResponse(
amount=Amount(Unit.usd, amount=amount_cent),
amount=amount,
checking_id=data.get("paymentQuoteId"),
fee=Amount(Unit.usd, 0),
fee=Amount(self.unit, 0),
)
return quote

View File

@@ -55,6 +55,11 @@ if settings.mint_backend_bolt11_usd:
unit=Unit.usd
)
backends.setdefault(Method.bolt11, {})[Unit.usd] = backend_bolt11_usd
if settings.mint_backend_bolt11_eur:
backend_bolt11_eur = getattr(wallets_module, settings.mint_backend_bolt11_eur)(
unit=Unit.eur
)
backends.setdefault(Method.bolt11, {})[Unit.eur] = backend_bolt11_eur
if not backends:
raise Exception("No backends are set.")

View File

@@ -26,8 +26,15 @@ class LedgerTasks(SupportsDb, SupportsBackends, SupportsEvents):
asyncio.create_task(self.invoice_listener(backend))
async def invoice_listener(self, backend: LightningBackend) -> None:
async for checking_id in backend.paid_invoices_stream():
await self.invoice_callback_dispatcher(checking_id)
if backend.supports_incoming_payment_stream:
while True:
try:
async for checking_id in backend.paid_invoices_stream():
await self.invoice_callback_dispatcher(checking_id)
except Exception as e:
logger.error(f"Error in invoice listener: {e}")
logger.info("Restarting invoice listener...")
await asyncio.sleep(1)
async def invoice_callback_dispatcher(self, checking_id: str) -> None:
logger.debug(f"Invoice callback dispatcher: {checking_id}")

View File

@@ -262,7 +262,7 @@ async def invoice(ctx: Context, amount: float, id: str, split: int, no_check: bo
wallet: Wallet = ctx.obj["WALLET"]
await wallet.load_mint()
await print_balance(ctx)
amount = int(amount * 100) if wallet.unit == Unit.usd else int(amount)
amount = int(amount * 100) if wallet.unit in [Unit.usd, Unit.eur] else int(amount)
print(f"Requesting invoice for {wallet.unit.str(amount)}.")
# in case the user wants a specific split, we create a list of amounts
optional_split = None
@@ -546,7 +546,7 @@ async def send_command(
include_fees: bool,
):
wallet: Wallet = ctx.obj["WALLET"]
amount = int(amount * 100) if wallet.unit == Unit.usd else int(amount)
amount = int(amount * 100) if wallet.unit in [Unit.usd, Unit.eur] else int(amount)
if not nostr and not nopt:
await send(
wallet,