Files
nutshell/cashu/wallet/lightning/lightning.py
callebtc a518274f7e Nutshell cleanup wishlist (#332)
* fix keys

* fix tests

* backwards compatible api upgrade

* upgrade seems to work

* fix tests

* add deprecated api functions

* add more tests of backwards compat

* add test serialization for nut00

* remove a redundant test

* move mint and melt to new api

* mypy works

* CI: mypy --check-untyped-defs

* add deprecated router

* add hints and remove logs

* fix tests

* cleanup

* use new mint and melt endpoints

* tests passing?

* fix mypy

* make format

* make format

* make format

* commit

* errors gone

* save

* adjust the API

* store quotes in db

* make mypy happy

* add fakewallet settings

* remove LIGHTNING=True and pass quote id for melt

* format

* tests passing

* add CoreLightningRestWallet

* add macaroon loader

* add correct config

* preimage -> proof

* move wallet.status() to cli.helpers.print_status()

* remove statuses from tests

* remove

* make format

* Use httpx in deprecated wallet

* fix cln interface

* create invoice before quote

* internal transactions and deprecated api testing

* fix tests

* add deprecated API tests

* fastapi type hints break things

* fix duplicate wallet error

* make format

* update poetry in CI to 1.7.1

* precommit restore

* remove bolt11

* oops

* default poetry

* store fee reserve for melt quotes and refactor melt()

* works?

* make format

* test

* finally

* fix deprecated models

* rename v1 endpoints to bolt11

* raise restore and check to v1, bump version to 0.15.0

* add version byte to keyset id

* remove redundant fields in json

* checks

* generate bip32 keyset wip

* migrate old keysets

* load duplicate keys

* duplicate old keysets

* revert router changes

* add deprecated /check and /restore endpoints

* try except invalidate

* parse unit from derivation path, adjust keyset id calculation with bytes

* remove keyest id from functions again and rely on self.keyset_id

* mosts tests work

* mint loads multiple derivation paths

* make format

* properly print units

* fix tests

* wallet works with multiple units

* add strike wallet and choose backend dynamically

* fix mypy

* add get_payment_quote to lightning backends

* make format

* fix startup

* fix lnbitswallet

* fix tests

* LightningWallet -> LightningBackend

* remove comments

* make format

* remove msat conversion

* add Amount type

* fix regtest

* use melt_quote as argument for pay_invoice

* test old api

* fees in sats

* fix deprecated fees

* fixes

* print balance correctly

* internally index keyset response by int

* add pydantic validation to input models

* add timestamps to mint db

* store timestamps for invoices, promises, proofs_used

* fix wallet migration

* rotate keys correctly for testing

* remove print

* update latest keyset

* fix tests

* fix test

* make format

* make format with correct black version

* remove nsat and cheese

* test against deprecated mint

* fix tests?

* actually use env var

* mint run with env vars

* moar test

* cleanup

* simplify tests, load all keys

* try out testing with internal invoices

* fix internal melt test

* fix test

* deprecated checkfees expects appropriate fees

* adjust comment

* drop lightning table

* split migration for testing for now, remove it later

* remove unused lightning table

* skip_private_key -> skip_db_read

* throw error on migration error

* reorder

* fix migrations

* fix lnbits fee return value negative

* fix typo

* comments

* add type

* make format

* split must use correct amount

* fix tests

* test deprecated api with internal/external melts

* do not split if not necessary

* refactor

* fix test

* make format with new black

* cleanup and add comments

* add quote state check endpoints

* fix deprecated wallet response

* split -> swap endpoint

* make format

* add expiry to quotes, get quote endpoints, and adjust to nut review comments

* allow overpayment of melt

* add lightning wallet tests

* commiting to save

* fix tests a bit

* make format

* remove comments

* get mint info

* check_spendable default False, and return payment quote checking id

* make format

* bump version in pyproject

* update to /v1/checkstate

* make format

* fix mint api checks

* return witness on /v1/checkstate

* no failfast

* try fail-fast: false in ci.yaml

* fix db lookup

* clean up literals
2024-01-08 00:57:15 +01:00

152 lines
5.1 KiB
Python

import bolt11
from ...core.base import Amount, SpentState, Unit
from ...core.helpers import sum_promises
from ...core.settings import settings
from ...lightning.base import (
InvoiceResponse,
PaymentResponse,
PaymentStatus,
StatusResponse,
)
from ...wallet.crud import get_lightning_invoice, get_proofs
from ..wallet import Wallet
class LightningWallet(Wallet):
"""
Lightning wallet interface for Cashu
"""
async def async_init(self, raise_connection_error: bool = True):
"""Async init for lightning wallet"""
settings.tor = False
await self.load_proofs()
try:
await self.load_mint()
except Exception as e:
if raise_connection_error:
raise e
def __init__(self, *args, **kwargs):
if not args and not kwargs:
pass
super().__init__(*args, **kwargs)
async def create_invoice(self, amount: int) -> InvoiceResponse:
"""Create lightning invoice
Args:
amount (int): amount in satoshis
Returns:
str: invoice
"""
invoice = await self.request_mint(amount)
return InvoiceResponse(
ok=True, payment_request=invoice.bolt11, checking_id=invoice.payment_hash
)
async def pay_invoice(self, pr: str) -> PaymentResponse:
"""Pay lightning invoice
Args:
pr (str): bolt11 payment request
Returns:
bool: True if successful
"""
quote = await self.get_pay_amount_with_fees(pr)
total_amount = quote.amount + quote.fee_reserve
assert total_amount > 0, "amount is not positive"
if self.available_balance < total_amount:
print("Error: Balance too low.")
return PaymentResponse(ok=False)
_, send_proofs = await self.split_to_send(self.proofs, total_amount)
try:
resp = await self.pay_lightning(
send_proofs, pr, quote.fee_reserve, quote.quote
)
if resp.change:
fees_paid_sat = quote.fee_reserve - sum_promises(resp.change)
else:
fees_paid_sat = quote.fee_reserve
invoice_obj = bolt11.decode(pr)
return PaymentResponse(
ok=True,
checking_id=invoice_obj.payment_hash,
preimage=resp.payment_preimage,
fee=Amount(Unit.msat, fees_paid_sat),
)
except Exception as e:
print("Exception:", e)
return PaymentResponse(ok=False, error_message=str(e))
async def get_invoice_status(self, payment_hash: str) -> PaymentStatus:
"""Get lightning invoice status (incoming)
Args:
invoice (str): lightning invoice
Returns:
str: status
"""
invoice = await get_lightning_invoice(
db=self.db, payment_hash=payment_hash, out=False
)
if not invoice:
return PaymentStatus(paid=None)
if invoice.paid:
return PaymentStatus(paid=True)
try:
# to check the invoice state, we try minting tokens
await self.mint(invoice.amount, id=invoice.id)
return PaymentStatus(paid=True)
except Exception as e:
print(e)
return PaymentStatus(paid=False)
async def get_payment_status(self, payment_hash: str) -> PaymentStatus:
"""Get lightning payment status (outgoing)
Args:
payment_hash (str): lightning invoice payment_hash
Returns:
str: status
"""
# NOTE: consider adding this in wallet.py and update invoice state to paid in DB
invoice = await get_lightning_invoice(
db=self.db, payment_hash=payment_hash, out=True
)
if not invoice:
return PaymentStatus(paid=False) # "invoice not found (in db)"
if invoice.paid:
return PaymentStatus(paid=True, preimage=invoice.preimage) # "paid (in db)"
proofs = await get_proofs(db=self.db, melt_id=invoice.id)
if not proofs:
return PaymentStatus(paid=False) # "proofs not fount (in db)"
proofs_states = await self.check_proof_state(proofs)
if not proofs_states:
return PaymentStatus(paid=False) # "states not fount"
if all([p.state == SpentState.pending for p in proofs_states.states]):
return PaymentStatus(paid=None) # "pending (with check)"
if any([p.state == SpentState.spent for p in proofs_states.states]):
# NOTE: consider adding this check in wallet.py and mark the invoice as paid if all proofs are spent
return PaymentStatus(paid=True) # "paid (with check)"
if all([p.state == SpentState.unspent for p in proofs_states.states]):
return PaymentStatus(paid=False) # "failed (with check)"
return PaymentStatus(paid=None) # "undefined state"
async def get_balance(self) -> StatusResponse:
"""Get lightning balance
Returns:
int: balance in satoshis
"""
return StatusResponse(error_message=None, balance=self.available_balance * 1000)