[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'/0'/0' is "sat" (default)
# m/0'/1'/0' is "msat" # m/0'/1'/0' is "msat"
# m/0'/2'/0' is "usd" # 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 # 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_DERIVATION_PATH_LIST=["m/0'/0'/0'", "m/0'/0'/1'", "m/0'/1'/0'", "m/0'/2'/0'"]
MINT_DATABASE=data/mint MINT_DATABASE=data/mint
# Funding source backends # Funding source backends
# Supported: FakeWallet, LndRestWallet, CoreLightningRestWallet, BlinkWallet, LNbitsWallet, StrikeUSDWallet # Supported: FakeWallet, LndRestWallet, CoreLightningRestWallet, BlinkWallet, LNbitsWallet, StrikeWallet
MINT_BACKEND_BOLT11_SAT=FakeWallet MINT_BACKEND_BOLT11_SAT=FakeWallet
# Only works if a usd derivation path is set # Only works if a usd derivation path is set
# MINT_BACKEND_BOLT11_SAT=FakeWallet # MINT_BACKEND_BOLT11_SAT=FakeWallet
@@ -78,7 +79,7 @@ MINT_CORELIGHTNING_REST_CERT="./clightning-2-rest/certificate.pem"
# Use with BlinkWallet # Use with BlinkWallet
MINT_BLINK_KEY=blink_abcdefgh 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 MINT_STRIKE_KEY=ABC123
# fee to reserve in percent of the amount # fee to reserve in percent of the amount

View File

@@ -436,6 +436,8 @@ class Unit(Enum):
sat = 0 sat = 0
msat = 1 msat = 1
usd = 2 usd = 2
eur = 3
btc = 4
def str(self, amount: int) -> str: def str(self, amount: int) -> str:
if self == Unit.sat: if self == Unit.sat:
@@ -444,6 +446,10 @@ class Unit(Enum):
return f"{amount} msat" return f"{amount} msat"
elif self == Unit.usd: elif self == Unit.usd:
return f"${amount/100:.2f} 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: else:
raise Exception("Invalid unit") raise Exception("Invalid unit")
@@ -478,6 +484,33 @@ class Amount:
else: else:
return self 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: def str(self) -> str:
return self.unit.str(self.amount) return self.unit.str(self.amount)

View File

@@ -67,6 +67,7 @@ class MintBackends(MintSettings):
mint_lightning_backend: str = Field(default="") # deprecated mint_lightning_backend: str = Field(default="") # deprecated
mint_backend_bolt11_sat: str = Field(default="") mint_backend_bolt11_sat: str = Field(default="")
mint_backend_bolt11_usd: 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_endpoint: str = Field(default=None)
mint_lnbits_key: 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 .fake import FakeWallet # noqa: F401
from .lnbits import LNbitsWallet # noqa: F401 from .lnbits import LNbitsWallet # noqa: F401
from .lndrest import LndRestWallet # 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: backend_settings = [
raise Exception("MINT_BACKEND_BOLT11_SAT or MINT_BACKEND_BOLT11_USD not set") 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, 32,
).hex() ).hex()
supported_units = set([Unit.sat, Unit.msat, Unit.usd]) supported_units = set([Unit.sat, Unit.msat, Unit.usd, Unit.eur])
unit = Unit.sat unit = Unit.sat
supports_incoming_payment_stream: bool = True supports_incoming_payment_stream: bool = True
@@ -113,7 +113,7 @@ class FakeWallet(LightningBackend):
amount_msat = 0 amount_msat = 0
if self.unit == Unit.sat: if self.unit == Unit.sat:
amount_msat = MilliSatoshi(amount.to(Unit.msat, round="up").amount) 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( amount_msat = MilliSatoshi(
math.ceil(amount.amount / self.fake_btc_price * 1e9) math.ceil(amount.amount / self.fake_btc_price * 1e9)
) )
@@ -194,10 +194,10 @@ class FakeWallet(LightningBackend):
fees_msat = fee_reserve(amount_msat) fees_msat = fee_reserve(amount_msat)
fees = Amount(unit=Unit.msat, amount=fees_msat) fees = Amount(unit=Unit.msat, amount=fees_msat)
amount = Amount(unit=Unit.msat, amount=amount_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_usd = math.ceil(invoice_obj.amount_msat / 1e9 * self.fake_btc_price)
amount = Amount(unit=Unit.usd, amount=amount_usd) amount = Amount(unit=self.unit, amount=amount_usd)
fees = Amount(unit=Unit.usd, amount=2) fees = Amount(unit=self.unit, amount=2)
else: else:
raise NotImplementedError() raise NotImplementedError()

View File

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

View File

@@ -55,6 +55,11 @@ if settings.mint_backend_bolt11_usd:
unit=Unit.usd unit=Unit.usd
) )
backends.setdefault(Method.bolt11, {})[Unit.usd] = backend_bolt11_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: if not backends:
raise Exception("No backends are set.") raise Exception("No backends are set.")

View File

@@ -26,8 +26,15 @@ class LedgerTasks(SupportsDb, SupportsBackends, SupportsEvents):
asyncio.create_task(self.invoice_listener(backend)) asyncio.create_task(self.invoice_listener(backend))
async def invoice_listener(self, backend: LightningBackend) -> None: async def invoice_listener(self, backend: LightningBackend) -> None:
async for checking_id in backend.paid_invoices_stream(): if backend.supports_incoming_payment_stream:
await self.invoice_callback_dispatcher(checking_id) 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: async def invoice_callback_dispatcher(self, checking_id: str) -> None:
logger.debug(f"Invoice callback dispatcher: {checking_id}") 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"] wallet: Wallet = ctx.obj["WALLET"]
await wallet.load_mint() await wallet.load_mint()
await print_balance(ctx) 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)}.") print(f"Requesting invoice for {wallet.unit.str(amount)}.")
# in case the user wants a specific split, we create a list of amounts # in case the user wants a specific split, we create a list of amounts
optional_split = None optional_split = None
@@ -546,7 +546,7 @@ async def send_command(
include_fees: bool, include_fees: bool,
): ):
wallet: Wallet = ctx.obj["WALLET"] 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: if not nostr and not nopt:
await send( await send(
wallet, wallet,