Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into dlc
Browse files Browse the repository at this point in the history
  • Loading branch information
lollerfirst committed Sep 13, 2024
2 parents a5f7590 + 637e4ba commit 3ed21e4
Show file tree
Hide file tree
Showing 27 changed files with 345 additions and 135 deletions.
7 changes: 6 additions & 1 deletion cashu/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ class GetInfoResponse(BaseModel):
description_long: Optional[str] = None
contact: Optional[List[MintInfoContact]] = None
motd: Optional[str] = None
icon_url: Optional[str] = None
time: Optional[int] = None
nuts: Optional[Dict[int, Any]] = None

def supports(self, nut: int) -> Optional[bool]:
Expand Down Expand Up @@ -123,6 +125,9 @@ class KeysetsResponse_deprecated(BaseModel):
class PostMintQuoteRequest(BaseModel):
unit: str = Field(..., max_length=settings.mint_max_request_length) # output unit
amount: int = Field(..., gt=0) # output amount
description: Optional[str] = Field(
default=None, max_length=settings.mint_max_request_length
) # invoice description


class PostMintQuoteResponse(BaseModel):
Expand Down Expand Up @@ -212,7 +217,7 @@ class PostMeltQuoteResponse(BaseModel):
state: Optional[str] # state of the quote
expiry: Optional[int] # expiry of the quote
payment_preimage: Optional[str] = None # payment preimage
change: Union[List[BlindedSignature], None] = None
change: Union[List[BlindedSignature], None] = None # NUT-08 change

@classmethod
def from_melt_quote(self, melt_quote: MeltQuote) -> "PostMeltQuoteResponse":
Expand Down
3 changes: 3 additions & 0 deletions cashu/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ class MintInformation(CashuSettings):
mint_info_description_long: str = Field(default=None)
mint_info_contact: List[List[str]] = Field(default=[])
mint_info_motd: str = Field(default=None)
mint_info_icon_url: str = Field(default=None)


class WalletSettings(CashuSettings):
Expand Down Expand Up @@ -201,11 +202,13 @@ class LndRestFundingSource(MintSettings):
mint_lnd_rest_invoice_macaroon: Optional[str] = Field(default=None)
mint_lnd_enable_mpp: bool = Field(default=False)


class LndRPCFundingSource(MintSettings):
mint_lnd_rpc_endpoint: Optional[str] = Field(default=None)
mint_lnd_rpc_cert: Optional[str] = Field(default=None)
mint_lnd_rpc_macaroon: Optional[str] = Field(default=None)


class CLNRestFundingSource(MintSettings):
mint_clnrest_url: Optional[str] = Field(default=None)
mint_clnrest_cert: Optional[str] = Field(default=None)
Expand Down
1 change: 1 addition & 0 deletions cashu/lightning/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class LightningBackend(ABC):
supports_mpp: bool = False
supports_incoming_payment_stream: bool = False
supported_units: set[Unit]
supports_description: bool = False
unit: Unit

def assert_unit_supported(self, unit: Unit):
Expand Down
1 change: 1 addition & 0 deletions cashu/lightning/blink.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class BlinkWallet(LightningBackend):
payment_statuses = {"SUCCESS": True, "PENDING": None, "FAILURE": False}

supported_units = set([Unit.sat, Unit.msat])
supports_description: bool = True
unit = Unit.sat

def __init__(self, unit: Unit = Unit.sat, **kwargs):
Expand Down
1 change: 1 addition & 0 deletions cashu/lightning/clnrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class CLNRestWallet(LightningBackend):
unit = Unit.sat
supports_mpp = settings.mint_clnrest_enable_mpp
supports_incoming_payment_stream: bool = True
supports_description: bool = True

def __init__(self, unit: Unit = Unit.sat, **kwargs):
self.assert_unit_supported(unit)
Expand Down
1 change: 1 addition & 0 deletions cashu/lightning/corelightningrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class CoreLightningRestWallet(LightningBackend):
supported_units = set([Unit.sat, Unit.msat])
unit = Unit.sat
supports_incoming_payment_stream: bool = True
supports_description: bool = True

def __init__(self, unit: Unit = Unit.sat, **kwargs):
self.assert_unit_supported(unit)
Expand Down
1 change: 1 addition & 0 deletions cashu/lightning/fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class FakeWallet(LightningBackend):
unit = Unit.sat

supports_incoming_payment_stream: bool = True
supports_description: bool = True

def __init__(self, unit: Unit = Unit.sat, **kwargs):
self.assert_unit_supported(unit)
Expand Down
1 change: 1 addition & 0 deletions cashu/lightning/lnbits.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class LNbitsWallet(LightningBackend):
supported_units = set([Unit.sat])
unit = Unit.sat
supports_incoming_payment_stream: bool = True
supports_description: bool = True

def __init__(self, unit: Unit = Unit.sat, **kwargs):
self.assert_unit_supported(unit)
Expand Down
94 changes: 54 additions & 40 deletions cashu/lightning/lnd_grpc/lnd_grpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,39 +45,43 @@
lnrpc.Invoice.InvoiceState.ACCEPTED: None,
}

class LndRPCWallet(LightningBackend):

class LndRPCWallet(LightningBackend):
supports_mpp = settings.mint_lnd_enable_mpp
supports_incoming_payment_stream = True
supported_units = set([Unit.sat, Unit.msat])
supports_description: bool = True

unit = Unit.sat

def __init__(self, unit: Unit = Unit.sat, **kwargs):
self.assert_unit_supported(unit)
self.unit = unit
self.endpoint = settings.mint_lnd_rpc_endpoint
cert_path = settings.mint_lnd_rpc_cert

macaroon_path = settings.mint_lnd_rpc_macaroon

if not self.endpoint:
if not settings.mint_lnd_rpc_endpoint:
raise Exception("cannot initialize LndRPCWallet: no endpoint")

self.endpoint = settings.mint_lnd_rpc_endpoint

if not macaroon_path:
raise Exception("cannot initialize LndRPCWallet: no macaroon")

if not cert_path:
raise Exception("no certificate for LndRPCWallet provided")

self.macaroon = codecs.encode(open(macaroon_path, 'rb').read(), 'hex')
self.macaroon = codecs.encode(open(macaroon_path, "rb").read(), "hex")

def metadata_callback(context, callback):
callback([('macaroon', self.macaroon)], None)
callback([("macaroon", self.macaroon)], None)

auth_creds = grpc.metadata_call_credentials(metadata_callback)

# create SSL credentials
os.environ['GRPC_SSL_CIPHER_SUITES'] = 'HIGH+ECDSA'
cert = open(cert_path, 'rb').read()
os.environ["GRPC_SSL_CIPHER_SUITES"] = "HIGH+ECDSA"
cert = open(cert_path, "rb").read()
ssl_creds = grpc.ssl_channel_credentials(cert)

# combine macaroon and SSL credentials
Expand All @@ -86,21 +90,21 @@ def metadata_callback(context, callback):
if self.supports_mpp:
logger.info("LndRPCWallet enabling MPP feature")


async def status(self) -> StatusResponse:
r = None
try:
async with grpc.aio.secure_channel(self.endpoint, self.combined_creds) as channel:
async with grpc.aio.secure_channel(
self.endpoint, self.combined_creds
) as channel:
lnstub = lightningstub.LightningStub(channel)
r = await lnstub.ChannelBalance(lnrpc.ChannelBalanceRequest())
except AioRpcError as e:
return StatusResponse(
error_message=f"Error calling Lnd gRPC: {e}", balance=0
)
# NOTE: `balance` field is deprecated. Change this.
return StatusResponse(error_message=None, balance=r.balance*1000)
return StatusResponse(error_message=None, balance=r.balance * 1000)


async def create_invoice(
self,
amount: Amount,
Expand All @@ -124,7 +128,9 @@ async def create_invoice(

r = None
try:
async with grpc.aio.secure_channel(self.endpoint, self.combined_creds) as channel:
async with grpc.aio.secure_channel(
self.endpoint, self.combined_creds
) as channel:
lnstub = lightningstub.LightningStub(channel)
r = await lnstub.AddInvoice(data)
except AioRpcError as e:
Expand All @@ -144,7 +150,7 @@ async def create_invoice(
payment_request=payment_request,
error_message=None,
)

async def pay_invoice(
self, quote: MeltQuote, fee_limit_msat: int
) -> PaymentResponse:
Expand All @@ -159,12 +165,12 @@ async def pay_invoice(
)

# set the fee limit for the payment
feelimit = lnrpc.FeeLimit(
fixed_msat=fee_limit_msat
)
feelimit = lnrpc.FeeLimit(fixed_msat=fee_limit_msat)
r = None
try:
async with grpc.aio.secure_channel(self.endpoint, self.combined_creds) as channel:
async with grpc.aio.secure_channel(
self.endpoint, self.combined_creds
) as channel:
lnstub = lightningstub.LightningStub(channel)
r = await lnstub.SendPaymentSync(
lnrpc.SendRequest(
Expand Down Expand Up @@ -195,14 +201,12 @@ async def pay_invoice(
preimage=preimage,
error_message=None,
)

async def pay_partial_invoice(
self, quote: MeltQuote, amount: Amount, fee_limit_msat: int
) -> PaymentResponse:
# set the fee limit for the payment
feelimit = lnrpc.FeeLimit(
fixed_msat=fee_limit_msat
)
feelimit = lnrpc.FeeLimit(fixed_msat=fee_limit_msat)
invoice = bolt11.decode(quote.request)

invoice_amount = invoice.amount_msat
Expand All @@ -220,7 +224,9 @@ async def pay_partial_invoice(
# get the route
r = None
try:
async with grpc.aio.secure_channel(self.endpoint, self.combined_creds) as channel:
async with grpc.aio.secure_channel(
self.endpoint, self.combined_creds
) as channel:
lnstub = lightningstub.LightningStub(channel)
router_stub = routerstub.RouterStub(channel)
r = await lnstub.QueryRoutes(
Expand All @@ -230,23 +236,27 @@ async def pay_partial_invoice(
fee_limit=feelimit,
)
)
'''
"""
# We need to set the mpp_record for a partial payment
mpp_record = lnrpc.MPPRecord(
payment_addr=bytes.fromhex(payer_addr),
total_amt_msat=total_amount_msat,
)
'''
"""
# modify the mpp_record in the last hop
route_nr = 0
r.routes[route_nr].hops[-1].mpp_record.payment_addr = bytes.fromhex(payer_addr)
r.routes[route_nr].hops[-1].mpp_record.total_amt_msat = total_amount_msat
r.routes[route_nr].hops[-1].mpp_record.payment_addr = bytes.fromhex( # type: ignore
payer_addr
)
r.routes[route_nr].hops[ # type: ignore
-1
].mpp_record.total_amt_msat = total_amount_msat

# Send to route request
r = await router_stub.SendToRouteV2(
routerrpc.SendToRouteRequest(
payment_hash=bytes.fromhex(invoice.payment_hash),
route=r.routes[route_nr],
route=r.routes[route_nr], # type: ignore
)
)
except AioRpcError as e:
Expand Down Expand Up @@ -275,24 +285,24 @@ async def pay_partial_invoice(
preimage=preimage,
error_message=None,
)

async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
r = None
try:
async with grpc.aio.secure_channel(self.endpoint, self.combined_creds) as channel:
async with grpc.aio.secure_channel(
self.endpoint, self.combined_creds
) as channel:
lnstub = lightningstub.LightningStub(channel)
r = await lnstub.LookupInvoice(
lnrpc.PaymentHash(
r_hash=bytes.fromhex(checking_id)
)
lnrpc.PaymentHash(r_hash=bytes.fromhex(checking_id))
)
except AioRpcError as e:
error_message = f"LookupInvoice failed: {e}"
logger.error(error_message)
return PaymentStatus(paid=None)

return PaymentStatus(paid=INVOICE_STATUSES[r.state])

async def get_payment_status(self, checking_id: str) -> PaymentStatus:
"""
This routine checks the payment status using routerpc.TrackPaymentV2.
Expand All @@ -307,7 +317,9 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus:
request = routerrpc.TrackPaymentRequest(payment_hash=checking_id_bytes)

try:
async with grpc.aio.secure_channel(self.endpoint, self.combined_creds) as channel:
async with grpc.aio.secure_channel(
self.endpoint, self.combined_creds
) as channel:
router_stub = routerstub.RouterStub(channel)
async for payment in router_stub.TrackPaymentV2(request):
if payment is not None and payment.status:
Expand All @@ -325,21 +337,23 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus:
logger.error(error_message)

return PaymentStatus(paid=None)

async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
while True:
try:
async with grpc.aio.secure_channel(self.endpoint, self.combined_creds) as channel:
async with grpc.aio.secure_channel(
self.endpoint, self.combined_creds
) as channel:
lnstub = lightningstub.LightningStub(channel)
async for invoice in lnstub.SubscribeInvoices(lnrpc.InvoiceSubscription()):
async for invoice in lnstub.SubscribeInvoices(
lnrpc.InvoiceSubscription()
):
if invoice.state != lnrpc.Invoice.InvoiceState.SETTLED:
continue
payment_hash = invoice.r_hash.hex()
yield payment_hash
except AioRpcError as exc:
logger.error(
f"SubscribeInvoices failed: {exc}. Retrying in 1 sec..."
)
logger.error(f"SubscribeInvoices failed: {exc}. Retrying in 1 sec...")
await asyncio.sleep(1)

async def get_payment_quote(
Expand Down
1 change: 1 addition & 0 deletions cashu/lightning/lndrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ class LndRestWallet(LightningBackend):
supports_mpp = settings.mint_lnd_enable_mpp
supports_incoming_payment_stream = True
supported_units = set([Unit.sat, Unit.msat])
supports_description: bool = True
unit = Unit.sat

def __init__(self, unit: Unit = Unit.sat, **kwargs):
Expand Down
10 changes: 2 additions & 8 deletions cashu/lightning/strike.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# type: ignore
import secrets
from typing import AsyncGenerator, Dict, Optional
from typing import AsyncGenerator, Optional

import httpx

Expand All @@ -23,6 +23,7 @@ class StrikeWallet(LightningBackend):
"""https://docs.strike.me/api/"""

supported_units = [Unit.sat, Unit.usd, Unit.eur]
supports_description: bool = False
currency_map = {Unit.sat: "BTC", Unit.usd: "USD", Unit.eur: "EUR"}

def __init__(self, unit: Unit, **kwargs):
Expand Down Expand Up @@ -95,13 +96,6 @@ async def create_invoice(
) -> InvoiceResponse:
self.assert_unit_supported(amount.unit)

data: Dict = {"out": False, "amount": amount}
if description_hash:
data["description_hash"] = description_hash.hex()
if unhashed_description:
data["unhashed_description"] = unhashed_description.hex()

data["memo"] = memo or ""
payload = {
"correlationId": secrets.token_hex(16),
"description": "Invoice for order 123",
Expand Down
Loading

0 comments on commit 3ed21e4

Please sign in to comment.