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

REFACTOR: paid flag -> PaymentResult object #619

Closed
wants to merge 20 commits into from
Closed
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
68 changes: 56 additions & 12 deletions cashu/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@ def identifier(self) -> str:
def kind(self) -> JSONRPCSubscriptionKinds:
return JSONRPCSubscriptionKinds.PROOF_STATE

@property
def unspent(self) -> bool:
return self.state == ProofSpentState.unspent

@property
def spent(self) -> bool:
return self.state == ProofSpentState.spent

@property
def pending(self) -> bool:
return self.state == ProofSpentState.pending


class HTLCWitness(BaseModel):
preimage: Optional[str] = None
Expand Down Expand Up @@ -290,7 +302,6 @@ class MeltQuote(LedgerEvent):
unit: str
amount: int
fee_reserve: int
paid: bool
state: MeltQuoteState
created_time: Union[int, None] = None
paid_time: Union[int, None] = None
Expand Down Expand Up @@ -325,7 +336,6 @@ def from_row(cls, row: Row):
unit=row["unit"],
amount=row["amount"],
fee_reserve=row["fee_reserve"],
paid=row["paid"],
state=MeltQuoteState[row["state"]],
created_time=created_time,
paid_time=paid_time,
Expand All @@ -344,17 +354,34 @@ def identifier(self) -> str:
def kind(self) -> JSONRPCSubscriptionKinds:
return JSONRPCSubscriptionKinds.BOLT11_MELT_QUOTE

@property
def unpaid(self) -> bool:
return self.state == MeltQuoteState.unpaid

@property
def pending(self) -> bool:
return self.state == MeltQuoteState.pending

@property
def paid(self) -> bool:
return self.state == MeltQuoteState.paid

# method that is invoked when the `state` attribute is changed. to protect the state from being set to anything else if the current state is paid
def __setattr__(self, name, value):
# an unpaid quote can only be set to pending or paid
if name == "state" and self.state == MeltQuoteState.unpaid:
if name == "state" and self.unpaid:
if value not in [MeltQuoteState.pending, MeltQuoteState.paid]:
raise Exception(
f"Cannot change state of an unpaid melt quote to {value}."
)
# a paid quote can not be changed
if name == "state" and self.state == MeltQuoteState.paid:
if name == "state" and self.paid:
raise Exception("Cannot change state of a paid melt quote.")

if name == "paid":
raise Exception(
"MeltQuote does not support `paid` anymore! Use `state` instead."
)
super().__setattr__(name, value)


Expand All @@ -375,8 +402,6 @@ class MintQuote(LedgerEvent):
checking_id: str
unit: str
amount: int
paid: bool
issued: bool
state: MintQuoteState
created_time: Union[int, None] = None
paid_time: Union[int, None] = None
Expand All @@ -401,8 +426,6 @@ def from_row(cls, row: Row):
checking_id=row["checking_id"],
unit=row["unit"],
amount=row["amount"],
paid=row["paid"],
issued=row["issued"],
state=MintQuoteState[row["state"]],
created_time=created_time,
paid_time=paid_time,
Expand All @@ -417,24 +440,45 @@ def identifier(self) -> str:
def kind(self) -> JSONRPCSubscriptionKinds:
return JSONRPCSubscriptionKinds.BOLT11_MINT_QUOTE

@property
def unpaid(self) -> bool:
return self.state == MintQuoteState.unpaid

@property
def paid(self) -> bool:
return self.state == MintQuoteState.paid

@property
def pending(self) -> bool:
return self.state == MintQuoteState.pending

@property
def issued(self) -> bool:
return self.state == MintQuoteState.issued

def __setattr__(self, name, value):
# un unpaid quote can only be set to paid
if name == "state" and self.state == MintQuoteState.unpaid:
if name == "state" and self.unpaid:
if value != MintQuoteState.paid:
raise Exception(
f"Cannot change state of an unpaid mint quote to {value}."
)
# a paid quote can only be set to pending or issued
if name == "state" and self.state == MintQuoteState.paid:
if name == "state" and self.paid:
if value != MintQuoteState.pending and value != MintQuoteState.issued:
raise Exception(f"Cannot change state of a paid mint quote to {value}.")
# a pending quote can only be set to paid or issued
if name == "state" and self.state == MintQuoteState.pending:
if name == "state" and self.pending:
if value not in [MintQuoteState.paid, MintQuoteState.issued]:
raise Exception("Cannot change state of a pending mint quote.")
# an issued quote cannot be changed
if name == "state" and self.state == MintQuoteState.issued:
if name == "state" and self.issued:
raise Exception("Cannot change state of an issued mint quote.")

if name == "paid":
raise Exception(
"MintQuote does not support `paid` anymore! Use `state` instead."
)
super().__setattr__(name, value)


Expand Down
4 changes: 3 additions & 1 deletion cashu/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ class PostMeltQuoteResponse(BaseModel):
fee_reserve: int # input fee reserve
paid: Optional[
bool
] # whether the request has been paid # DEPRECATED as per NUT PR #136
] = None # whether the request has been paid # DEPRECATED as per NUT PR #136
state: Optional[str] # state of the quote
expiry: Optional[int] # expiry of the quote
payment_preimage: Optional[str] = None # payment preimage
Expand All @@ -216,6 +216,8 @@ def from_melt_quote(self, melt_quote: MeltQuote) -> "PostMeltQuoteResponse":
to_dict = melt_quote.dict()
# turn state into string
to_dict["state"] = melt_quote.state.value
# add deprecated "paid" field
to_dict["paid"] = melt_quote.paid
return PostMeltQuoteResponse.parse_obj(to_dict)


Expand Down
2 changes: 1 addition & 1 deletion cashu/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class FakeWalletSettings(MintSettings):
fakewallet_delay_outgoing_payment: Optional[float] = Field(default=3.0)
fakewallet_delay_incoming_payment: Optional[float] = Field(default=3.0)
fakewallet_stochastic_invoice: bool = Field(default=False)
fakewallet_payment_state: Optional[bool] = Field(default=None)
fakewallet_payment_state: Optional[str] = Field(default="SETTLED")


class MintInformation(CashuSettings):
Expand Down
64 changes: 53 additions & 11 deletions cashu/lightning/base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from enum import Enum, auto
from typing import AsyncGenerator, Coroutine, Optional, Union

from pydantic import BaseModel
Expand All @@ -12,8 +13,8 @@


class StatusResponse(BaseModel):
error_message: Optional[str]
balance: Union[int, float]
error_message: Optional[str] = None


class InvoiceQuoteResponse(BaseModel):
Expand All @@ -34,36 +35,77 @@ class InvoiceResponse(BaseModel):
error_message: Optional[str] = None


class PaymentResult(Enum):
SETTLED = auto()
FAILED = auto()
PENDING = auto()
UNKNOWN = auto()

def __str__(self):
return self.name


class PaymentResponse(BaseModel):
ok: Optional[bool] = None # True: paid, False: failed, None: pending or unknown
result: PaymentResult
checking_id: Optional[str] = None
fee: Optional[Amount] = None
preimage: Optional[str] = None
error_message: Optional[str] = None

@property
def pending(self) -> bool:
return self.result == PaymentResult.PENDING

@property
def settled(self) -> bool:
return self.result == PaymentResult.SETTLED

@property
def failed(self) -> bool:
return self.result == PaymentResult.FAILED

@property
def unknown(self) -> bool:
return self.result == PaymentResult.UNKNOWN


class PaymentStatus(BaseModel):
paid: Optional[bool] = None
result: PaymentResult
fee: Optional[Amount] = None
preimage: Optional[str] = None
error_message: Optional[str] = None

@property
def pending(self) -> bool:
return self.paid is not True
return self.result == PaymentResult.PENDING

@property
def settled(self) -> bool:
return self.result == PaymentResult.SETTLED

@property
def failed(self) -> bool:
return self.paid is False
return self.result == PaymentResult.FAILED

@property
def unknown(self) -> bool:
return self.result == PaymentResult.UNKNOWN

def __str__(self) -> str:
if self.paid is True:
return "settled"
elif self.paid is False:
if self.result == PaymentResult.SETTLED:
return (
"settled"
+ (f" (preimage: {self.preimage})" if self.preimage else "")
+ (f" (fee: {self.fee})" if self.fee else "")
)
elif self.result == PaymentResult.FAILED:
return "failed"
elif self.paid is None:
elif self.result == PaymentResult.PENDING:
return "still pending"
else:
return "unknown (should never happen)"
else: # self.result == PaymentResult.UNKNOWN:
return "unknown" + (
f" (Error: {self.error_message})" if self.error_message else ""
)


class LightningBackend(ABC):
Expand Down
Loading
Loading