Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add withdraw execute to core and cli #31

Merged
merged 1 commit into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading