Skip to content

Commit

Permalink
feat: add withdraw execute to core and cli
Browse files Browse the repository at this point in the history
also adds bolt11 library for decoding for checking amount the amount of
the invoice
  • Loading branch information
dni committed Apr 24, 2024
1 parent 10cb8a2 commit b334db0
Show file tree
Hide file tree
Showing 5 changed files with 325 additions and 38 deletions.
34 changes: 30 additions & 4 deletions lnurl/core.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from typing import Any, Optional, Union

import requests
from bolt11 import Bolt11Exception, MilliSatoshi
from bolt11 import decode as bolt11_decode
from pydantic import ValidationError

from .exceptions import InvalidLnurl, InvalidUrl, LnurlResponseException
from .helpers import lnurlauth_signature, url_encode
from .models import LnurlAuthResponse, LnurlPayResponse, LnurlResponse, LnurlResponseModel
from .types import ClearnetUrl, DebugUrl, LnAddress, Lnurl, MilliSatoshi, OnionUrl
from .models import LnurlAuthResponse, LnurlPayResponse, LnurlResponse, LnurlResponseModel, LnurlWithdrawResponse
from .types import ClearnetUrl, DebugUrl, LnAddress, Lnurl, OnionUrl


def decode(bech32_lnurl: str) -> Union[OnionUrl, ClearnetUrl, DebugUrl]:
Expand Down Expand Up @@ -66,13 +68,14 @@ def execute(bech32_or_address: str, value: str) -> LnurlResponseModel:
return execute_pay_request(res, value)
elif isinstance(res, LnurlAuthResponse) and res.tag == "login":
return execute_login(res, value)
elif isinstance(res, LnurlWithdrawResponse) and res.tag == "withdrawRequest":
return execute_withdraw(res, value)

raise LnurlResponseException(f"{res.tag} not implemented") # type: ignore


def execute_pay_request(res: LnurlPayResponse, msat: str) -> LnurlResponseModel:
print(res)
if res.min_sendable > MilliSatoshi(msat) > res.max_sendable:
if not res.min_sendable <= MilliSatoshi(msat) <= res.max_sendable:
raise LnurlResponseException(f"Amount {msat} not in range {res.min_sendable} - {res.max_sendable}")
try:
req = requests.get(
Expand Down Expand Up @@ -102,3 +105,26 @@ def execute_login(res: LnurlAuthResponse, secret: str) -> LnurlResponseModel:
return LnurlResponse.from_dict(req.json())
except Exception as e:
raise LnurlResponseException(str(e))


def execute_withdraw(res: LnurlWithdrawResponse, pr: str) -> LnurlResponseModel:
try:
invoice = bolt11_decode(pr)
except Bolt11Exception as exc:
raise LnurlResponseException(str(exc))
# if invoice does not have amount use the min withdrawable amount
amount = invoice.amount_msat or res.min_withdrawable
if not res.min_withdrawable <= MilliSatoshi(amount) <= res.max_withdrawable:
raise LnurlResponseException(f"Amount {amount} not in range {res.min_withdrawable} - {res.max_withdrawable}")
try:
req = requests.get(
res.callback,
params={
"k1": res.k1,
"pr": pr,
},
)
req.raise_for_status()
return LnurlResponse.from_dict(req.json())
except Exception as exc:
raise LnurlResponseException(str(exc))
10 changes: 5 additions & 5 deletions lnurl/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import math
from typing import List, Literal, Optional, Union

from bolt11 import MilliSatoshi
from pydantic import BaseModel, Field, validator

from .exceptions import LnurlResponseException
Expand All @@ -12,7 +13,6 @@
LightningNodeUri,
LnurlPayMetadata,
Max144Str,
MilliSatoshi,
OnionUrl,
)

Expand Down Expand Up @@ -102,8 +102,8 @@ class LnurlHostedChannelResponse(LnurlResponseModel):
class LnurlPayResponse(LnurlResponseModel):
tag: Literal["payRequest"] = "payRequest"
callback: Union[ClearnetUrl, OnionUrl, DebugUrl]
min_sendable: MilliSatoshi = Field(..., alias="minSendable")
max_sendable: MilliSatoshi = Field(..., alias="maxSendable")
min_sendable: MilliSatoshi = Field(..., alias="minSendable", gt=0)
max_sendable: MilliSatoshi = Field(..., alias="maxSendable", gt=0)
metadata: LnurlPayMetadata

@validator("max_sendable")
Expand Down Expand Up @@ -144,8 +144,8 @@ class LnurlWithdrawResponse(LnurlResponseModel):
tag: Literal["withdrawRequest"] = "withdrawRequest"
callback: Union[ClearnetUrl, OnionUrl, DebugUrl]
k1: str
min_withdrawable: MilliSatoshi = Field(..., alias="minWithdrawable")
max_withdrawable: MilliSatoshi = Field(..., alias="maxWithdrawable")
min_withdrawable: MilliSatoshi = Field(..., alias="minWithdrawable", gt=0)
max_withdrawable: MilliSatoshi = Field(..., alias="maxWithdrawable", gt=0)
default_description: str = Field("", alias="defaultDescription")

@validator("max_withdrawable")
Expand Down
17 changes: 0 additions & 17 deletions lnurl/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
ConstrainedStr,
HttpUrl,
Json,
PositiveInt,
ValidationError,
parse_obj_as,
validator,
Expand Down Expand Up @@ -131,18 +130,6 @@ def validate_host(cls, parts: Parts) -> Tuple[str, Optional[str], str, bool]:
class LightningInvoice(Bech32):
"""Bech32 Lightning invoice."""

@property
def amount(self) -> int:
raise NotImplementedError

@property
def prefix(self) -> str:
raise NotImplementedError

@property
def h(self):
raise NotImplementedError


class LightningNodeUri(ReprMixin, str):
"""Remote node address of form `node_key@ip_address:port_number`."""
Expand Down Expand Up @@ -296,7 +283,3 @@ class InitializationVector(ConstrainedStr):

class Max144Str(ConstrainedStr):
max_length = 144


class MilliSatoshi(PositiveInt):
"""A thousandth of a satoshi."""
Loading

0 comments on commit b334db0

Please sign in to comment.