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

Mint: settle mint-melt on same mint with different units externally #651

Merged
merged 4 commits into from
Oct 30, 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
2 changes: 1 addition & 1 deletion cashu/lightning/fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@


class FakeWallet(LightningBackend):
unit: Unit
fake_btc_price = 1e8 / 1337
paid_invoices_queue: asyncio.Queue[Bolt11] = asyncio.Queue(0)
payment_secrets: Dict[str, str] = dict()
Expand All @@ -46,7 +47,6 @@ class FakeWallet(LightningBackend):
).hex()

supported_units = {Unit.sat, Unit.msat, Unit.usd, Unit.eur}
unit = Unit.sat

supports_incoming_payment_stream: bool = True
supports_description: bool = True
Expand Down
8 changes: 5 additions & 3 deletions cashu/mint/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ async def melt_quote(
# so that we would be able to handle the transaction internally
# and therefore respond with internal transaction fees (0 for now)
mint_quote = await self.crud.get_mint_quote(request=request, db=self.db)
if mint_quote:
if mint_quote and mint_quote.unit == melt_quote.unit:
payment_quote = self.create_internal_melt_quote(mint_quote, melt_quote)
else:
# not internal
Expand Down Expand Up @@ -811,6 +811,10 @@ async def melt_mint_settle_internally(
if not mint_quote:
return melt_quote

# settle externally if units are different
if mint_quote.unit != melt_quote.unit:
return melt_quote

# we settle the transaction internally
if melt_quote.paid:
raise TransactionError("melt quote already paid")
Expand All @@ -825,8 +829,6 @@ async def melt_mint_settle_internally(
raise TransactionError("amounts do not match")
if not bolt11_request == mint_quote.request:
raise TransactionError("bolt11 requests do not match")
if not mint_quote.unit == melt_quote.unit:
raise TransactionError("units do not match")
if not mint_quote.method == melt_quote.method:
raise TransactionError("methods do not match")

Expand Down
5 changes: 3 additions & 2 deletions cashu/mint/router_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from fastapi import APIRouter, Request
from loguru import logger

from ..core.base import BlindedMessage, BlindedSignature
from ..core.base import BlindedMessage, BlindedSignature, Unit
from ..core.errors import CashuError
from ..core.models import (
CheckFeesRequest_deprecated,
Expand Down Expand Up @@ -114,7 +114,8 @@ async def keyset_deprecated(idBase64Urlsafe: str) -> Dict[str, str]:
async def keysets_deprecated() -> KeysetsResponse_deprecated:
"""This endpoint returns a list of keysets that the mint currently supports and will accept tokens from."""
logger.trace("> GET /keysets")
keysets = KeysetsResponse_deprecated(keysets=list(ledger.keysets.keys()))
sat_keysets = {k: v for k, v in ledger.keysets.items() if v.unit == Unit.sat}
keysets = KeysetsResponse_deprecated(keysets=list(sat_keysets.keys()))
return keysets


Expand Down
22 changes: 12 additions & 10 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
settings.tor = False
settings.wallet_unit = "sat"
settings.mint_backend_bolt11_sat = settings.mint_backend_bolt11_sat or "FakeWallet"
settings.mint_backend_bolt11_usd = settings.mint_backend_bolt11_usd or "FakeWallet"
settings.fakewallet_brr = True
settings.fakewallet_delay_outgoing_payment = 0
settings.fakewallet_delay_incoming_payment = 1
Expand All @@ -42,7 +43,7 @@
), "Test database is the same as the main database"
settings.mint_database = settings.mint_test_database
settings.mint_derivation_path = "m/0'/0'/0'"
settings.mint_derivation_path_list = []
settings.mint_derivation_path_list = ["m/0'/2'/0'"] # USD
settings.mint_private_key = "TEST_PRIVATE_KEY"
settings.mint_seed_decryption_key = ""
settings.mint_max_balance = 0
Expand Down Expand Up @@ -85,13 +86,6 @@ def run(self, *args, **kwargs):
async def ledger():
async def start_mint_init(ledger: Ledger) -> Ledger:
await migrate_databases(ledger.db, migrations_mint)
ledger = Ledger(
db=Database("mint", settings.mint_database),
seed=settings.mint_private_key,
derivation_path=settings.mint_derivation_path,
backends=backends,
crud=LedgerCrudSqlite(),
)
await ledger.startup_ledger()
return ledger

Expand All @@ -110,9 +104,17 @@ async def start_mint_init(ledger: Ledger) -> Ledger:
await db.engine.dispose()

wallets_module = importlib.import_module("cashu.lightning")
lightning_backend = getattr(wallets_module, settings.mint_backend_bolt11_sat)()
lightning_backend_sat = getattr(wallets_module, settings.mint_backend_bolt11_sat)(
unit=Unit.sat
)
lightning_backend_usd = getattr(wallets_module, settings.mint_backend_bolt11_usd)(
unit=Unit.usd
)
backends = {
Method.bolt11: {Unit.sat: lightning_backend},
Method.bolt11: {
Unit.sat: lightning_backend_sat,
Unit.usd: lightning_backend_usd,
},
}
ledger = Ledger(
db=Database("mint", settings.mint_database),
Expand Down
6 changes: 6 additions & 0 deletions tests/test_mint_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,12 @@ async def test_api_keysets(ledger: Ledger):
"active": True,
"input_fee_ppk": 0,
},
{
"id": "00c074b96c7e2b0e",
"unit": "usd",
"active": True,
"input_fee_ppk": 0,
},
]
}
assert response.json() == expected
Expand Down
5 changes: 3 additions & 2 deletions tests/test_mint_api_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import pytest
import pytest_asyncio

from cashu.core.base import Proof
from cashu.core.base import Proof, Unit
from cashu.core.models import (
CheckSpendableRequest_deprecated,
CheckSpendableResponse_deprecated,
Expand Down Expand Up @@ -51,7 +51,8 @@ async def test_api_keysets(ledger: Ledger):
response = httpx.get(f"{BASE_URL}/keysets")
assert response.status_code == 200, f"{response.url} {response.status_code}"
assert ledger.keyset.public_keys
assert response.json()["keysets"] == list(ledger.keysets.keys())
sat_keysets = {k: v for k, v in ledger.keysets.items() if v.unit == Unit.sat}
assert response.json()["keysets"] == list(sat_keysets.keys())


@pytest.mark.asyncio
Expand Down
2 changes: 1 addition & 1 deletion tests/test_mint_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ async def wallet(ledger: Ledger):
async def test_init_keysets(ledger: Ledger):
ledger.keysets = {}
await ledger.init_keysets()
assert len(ledger.keysets) == 1
assert len(ledger.keysets) == 2


@pytest.mark.asyncio
Expand Down
39 changes: 38 additions & 1 deletion tests/test_mint_melt.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from cashu.core.base import MeltQuote, MeltQuoteState, Proof
from cashu.core.errors import LightningError
from cashu.core.models import PostMeltQuoteRequest
from cashu.core.models import PostMeltQuoteRequest, PostMintQuoteRequest
from cashu.core.settings import settings
from cashu.lightning.base import PaymentResult
from cashu.mint.ledger import Ledger
Expand Down Expand Up @@ -331,3 +331,40 @@ async def test_melt_lightning_pay_invoice_exception_exception(
ledger.melt(proofs=wallet.proofs, quote=quote_id),
"Melt is disabled. Please contact the operator.",
)


@pytest.mark.asyncio
@pytest.mark.skipif(is_regtest, reason="only fake wallet")
async def test_mint_melt_different_units(ledger: Ledger, wallet: Wallet):
"""Mint and melt different units."""
# load the wallet
invoice = await wallet.request_mint(64)
await wallet.mint(64, id=invoice.id)

amount = 32

# mint quote in sat
sat_mint_quote = await ledger.mint_quote(
quote_request=PostMintQuoteRequest(amount=amount, unit="sat")
)
sat_invoice = sat_mint_quote.request
assert sat_mint_quote.paid is False

# melt quote in usd
usd_melt_quote = await ledger.melt_quote(
PostMeltQuoteRequest(unit="usd", request=sat_invoice)
)
assert usd_melt_quote.paid is False

# pay melt quote with usd
await ledger.melt(proofs=wallet.proofs, quote=usd_melt_quote.quote)

output_amounts = [32]

secrets, rs, derivation_paths = await wallet.generate_n_secrets(len(output_amounts))
outputs, rs = wallet._construct_outputs(output_amounts, secrets, rs)

# mint in sat
mint_resp = await ledger.mint(outputs=outputs, quote_id=sat_mint_quote.quote)

assert len(mint_resp) == len(outputs)
Loading