Skip to content

Commit

Permalink
Add fees (#503)
Browse files Browse the repository at this point in the history
* wip

* wip

* model

* refactor wallet transactions

* refactor wallet

* sending with fees works and outputs fill up the wallet

* wip work

* ok

* comments

* receive with amount=0

* correctly import postmeltrequest

* fix melt amount

* tests working

* remove mint_loaded decorator in deprecated wallet api

* wallet works with units

* refactor: melt_quote

* fix fees

* add file

* fees for melt inputs

* set default input fee for internal quotes to 0

* fix coinselect

* coin selection working

* yo

* fix all tests

* clean up

* last commit added fees for inputs for melt transactions - this commit adds a blanace too low exception

* fix fee return and melt quote max allowed amount check during creation of melt quote

* clean up code

* add tests for fees

* add melt tests

* update wallet fee information
  • Loading branch information
callebtc authored Jun 15, 2024
1 parent d80280e commit d30b1a2
Show file tree
Hide file tree
Showing 47 changed files with 2,444 additions and 1,552 deletions.
321 changes: 32 additions & 289 deletions cashu/core/base.py

Large diffs are not rendered by default.

19 changes: 16 additions & 3 deletions cashu/core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@ def __init__(self):
super().__init__(self.detail, code=self.code)


class TransactionNotBalancedError(TransactionError):
code = 11002

def __init__(self, detail):
super().__init__(detail, code=self.code)


class SecretTooLongError(TransactionError):
detail = "secret too long"
code = 11003

def __init__(self):
super().__init__(self.detail, code=self.code)
def __init__(self, detail="secret too long"):
super().__init__(detail, code=self.code)


class NoSecretInProofsError(TransactionError):
Expand All @@ -51,6 +57,13 @@ def __init__(self):
super().__init__(self.detail, code=self.code)


class TransactionUnitError(TransactionError):
code = 11005

def __init__(self, detail):
super().__init__(detail, code=self.code)


class KeysetError(CashuError):
detail = "keyset error"
code = 12000
Expand Down
13 changes: 12 additions & 1 deletion cashu/core/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,21 @@
from functools import partial, wraps
from typing import List

from ..core.base import BlindedSignature, Proof
from ..core.base import Amount, BlindedSignature, Proof, Unit
from ..core.settings import settings


def amount_summary(proofs: List[Proof], unit: Unit) -> str:
amounts_we_have = [
(amount, len([p for p in proofs if p.amount == amount]))
for amount in set([p.amount for p in proofs])
]
amounts_we_have.sort(key=lambda x: x[0])
return (
f"{', '.join([f'{Amount(unit, a).str()} ({c}x)' for a, c in amounts_we_have])}"
)


def sum_proofs(proofs: List[Proof]):
return sum([p.amount for p in proofs])

Expand Down
265 changes: 265 additions & 0 deletions cashu/core/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
from typing import Any, Dict, List, Optional, Union

from pydantic import BaseModel, Field

from .base import (
BlindedMessage,
BlindedMessage_Deprecated,
BlindedSignature,
Proof,
ProofState,
)
from .settings import settings

# ------- API -------

# ------- API: INFO -------


class MintMeltMethodSetting(BaseModel):
method: str
unit: str
min_amount: Optional[int] = None
max_amount: Optional[int] = None


class GetInfoResponse(BaseModel):
name: Optional[str] = None
pubkey: Optional[str] = None
version: Optional[str] = None
description: Optional[str] = None
description_long: Optional[str] = None
contact: Optional[List[List[str]]] = None
motd: Optional[str] = None
nuts: Optional[Dict[int, Any]] = None


class Nut15MppSupport(BaseModel):
method: str
unit: str
mpp: bool


class GetInfoResponse_deprecated(BaseModel):
name: Optional[str] = None
pubkey: Optional[str] = None
version: Optional[str] = None
description: Optional[str] = None
description_long: Optional[str] = None
contact: Optional[List[List[str]]] = None
nuts: Optional[List[str]] = None
motd: Optional[str] = None
parameter: Optional[dict] = None


# ------- API: KEYS -------


class KeysResponseKeyset(BaseModel):
id: str
unit: str
keys: Dict[int, str]


class KeysResponse(BaseModel):
keysets: List[KeysResponseKeyset]


class KeysetsResponseKeyset(BaseModel):
id: str
unit: str
active: bool
input_fee_ppk: Optional[int] = None


class KeysetsResponse(BaseModel):
keysets: list[KeysetsResponseKeyset]


class KeysResponse_deprecated(BaseModel):
__root__: Dict[str, str]


class KeysetsResponse_deprecated(BaseModel):
keysets: list[str]


# ------- API: MINT QUOTE -------


class PostMintQuoteRequest(BaseModel):
unit: str = Field(..., max_length=settings.mint_max_request_length) # output unit
amount: int = Field(..., gt=0) # output amount


class PostMintQuoteResponse(BaseModel):
quote: str # quote id
request: str # input payment request
paid: bool # whether the request has been paid
expiry: Optional[int] # expiry of the quote


# ------- API: MINT -------


class PostMintRequest(BaseModel):
quote: str = Field(..., max_length=settings.mint_max_request_length) # quote id
outputs: List[BlindedMessage] = Field(
..., max_items=settings.mint_max_request_length
)


class PostMintResponse(BaseModel):
signatures: List[BlindedSignature] = []


class GetMintResponse_deprecated(BaseModel):
pr: str
hash: str


class PostMintRequest_deprecated(BaseModel):
outputs: List[BlindedMessage_Deprecated] = Field(
..., max_items=settings.mint_max_request_length
)


class PostMintResponse_deprecated(BaseModel):
promises: List[BlindedSignature] = []


# ------- API: MELT QUOTE -------


class PostMeltQuoteRequest(BaseModel):
unit: str = Field(..., max_length=settings.mint_max_request_length) # input unit
request: str = Field(
..., max_length=settings.mint_max_request_length
) # output payment request
amount: Optional[int] = Field(default=None, gt=0) # input amount


class PostMeltQuoteResponse(BaseModel):
quote: str # quote id
amount: int # input amount
fee_reserve: int # input fee reserve
paid: bool # whether the request has been paid
expiry: Optional[int] # expiry of the quote


# ------- API: MELT -------


class PostMeltRequest(BaseModel):
quote: str = Field(..., max_length=settings.mint_max_request_length) # quote id
inputs: List[Proof] = Field(..., max_items=settings.mint_max_request_length)
outputs: Union[List[BlindedMessage], None] = Field(
None, max_items=settings.mint_max_request_length
)


class PostMeltResponse(BaseModel):
paid: Union[bool, None]
payment_preimage: Union[str, None]
change: Union[List[BlindedSignature], None] = None


class PostMeltRequest_deprecated(BaseModel):
proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length)
pr: str = Field(..., max_length=settings.mint_max_request_length)
outputs: Union[List[BlindedMessage_Deprecated], None] = Field(
None, max_items=settings.mint_max_request_length
)


class PostMeltResponse_deprecated(BaseModel):
paid: Union[bool, None]
preimage: Union[str, None]
change: Union[List[BlindedSignature], None] = None


# ------- API: SPLIT -------


class PostSplitRequest(BaseModel):
inputs: List[Proof] = Field(..., max_items=settings.mint_max_request_length)
outputs: List[BlindedMessage] = Field(
..., max_items=settings.mint_max_request_length
)


class PostSplitResponse(BaseModel):
signatures: List[BlindedSignature]


# deprecated since 0.13.0
class PostSplitRequest_Deprecated(BaseModel):
proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length)
amount: Optional[int] = None
outputs: List[BlindedMessage_Deprecated] = Field(
..., max_items=settings.mint_max_request_length
)


class PostSplitResponse_Deprecated(BaseModel):
promises: List[BlindedSignature] = []


class PostSplitResponse_Very_Deprecated(BaseModel):
fst: List[BlindedSignature] = []
snd: List[BlindedSignature] = []
deprecated: str = "The amount field is deprecated since 0.13.0"


# ------- API: CHECK -------


class PostCheckStateRequest(BaseModel):
Ys: List[str] = Field(..., max_items=settings.mint_max_request_length)


class PostCheckStateResponse(BaseModel):
states: List[ProofState] = []


class CheckSpendableRequest_deprecated(BaseModel):
proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length)


class CheckSpendableResponse_deprecated(BaseModel):
spendable: List[bool]
pending: List[bool]


class CheckFeesRequest_deprecated(BaseModel):
pr: str = Field(..., max_length=settings.mint_max_request_length)


class CheckFeesResponse_deprecated(BaseModel):
fee: Union[int, None]


# ------- API: RESTORE -------


class PostRestoreRequest(BaseModel):
outputs: List[BlindedMessage] = Field(
..., max_items=settings.mint_max_request_length
)


class PostRestoreRequest_Deprecated(BaseModel):
outputs: List[BlindedMessage_Deprecated] = Field(
..., max_items=settings.mint_max_request_length
)


class PostRestoreResponse(BaseModel):
outputs: List[BlindedMessage] = []
signatures: List[BlindedSignature] = []
promises: Optional[List[BlindedSignature]] = [] # deprecated since 0.15.1

# duplicate value of "signatures" for backwards compatibility with old clients < 0.15.1
def __init__(self, **data):
super().__init__(**data)
self.promises = self.signatures
5 changes: 5 additions & 0 deletions cashu/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ class MintSettings(CashuSettings):

mint_database: str = Field(default="data/mint")
mint_test_database: str = Field(default="test_data/test_mint")
mint_max_secret_length: int = Field(default=512)

mint_input_fee_ppk: int = Field(default=0)


class MintBackends(MintSettings):
Expand Down Expand Up @@ -170,6 +173,8 @@ class WalletSettings(CashuSettings):
locktime_delta_seconds: int = Field(default=86400) # 1 day
proofs_batch_size: int = Field(default=1000)

wallet_target_amount_count: int = Field(default=3)


class LndRestFundingSource(MintSettings):
mint_lnd_rest_endpoint: Optional[str] = Field(default=None)
Expand Down
2 changes: 1 addition & 1 deletion cashu/lightning/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
from ..core.base import (
Amount,
MeltQuote,
PostMeltQuoteRequest,
Unit,
)
from ..core.models import PostMeltQuoteRequest


class StatusResponse(BaseModel):
Expand Down
3 changes: 2 additions & 1 deletion cashu/lightning/blink.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
)
from loguru import logger

from ..core.base import Amount, MeltQuote, PostMeltQuoteRequest, Unit
from ..core.base import Amount, MeltQuote, Unit
from ..core.models import PostMeltQuoteRequest
from ..core.settings import settings
from .base import (
InvoiceResponse,
Expand Down
3 changes: 2 additions & 1 deletion cashu/lightning/corelightningrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
)
from loguru import logger

from ..core.base import Amount, MeltQuote, PostMeltQuoteRequest, Unit
from ..core.base import Amount, MeltQuote, Unit
from ..core.helpers import fee_reserve
from ..core.models import PostMeltQuoteRequest
from ..core.settings import settings
from .base import (
InvoiceResponse,
Expand Down
3 changes: 2 additions & 1 deletion cashu/lightning/fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@
encode,
)

from ..core.base import Amount, MeltQuote, PostMeltQuoteRequest, Unit
from ..core.base import Amount, MeltQuote, Unit
from ..core.helpers import fee_reserve
from ..core.models import PostMeltQuoteRequest
from ..core.settings import settings
from .base import (
InvoiceResponse,
Expand Down
3 changes: 2 additions & 1 deletion cashu/lightning/lnbits.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
decode,
)

from ..core.base import Amount, MeltQuote, PostMeltQuoteRequest, Unit
from ..core.base import Amount, MeltQuote, Unit
from ..core.helpers import fee_reserve
from ..core.models import PostMeltQuoteRequest
from ..core.settings import settings
from .base import (
InvoiceResponse,
Expand Down
Loading

0 comments on commit d30b1a2

Please sign in to comment.