Add multi-mint swap to CLI and API (#212)

* Add multi-mint swap to CLI and API

* Add confirmation prompt to CLI

* Replace several assert statements

* Define response for API endpoint swap

* Adapt to latest changes on main

* Fix: missing argument after latest changes on main

* API: use local wallet for swap

* Improve swap command

* clean up cli swap

* fix comment

* clean up router

* fix up

* remove outgoing

---------

Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
This commit is contained in:
sihamon
2023-06-23 20:04:28 +02:00
committed by GitHub
parent 25385f1f16
commit a0df47f13a
5 changed files with 103 additions and 3 deletions

View File

@@ -17,6 +17,13 @@ class InvoiceResponse(BaseModel):
hash: Optional[str] = None
class SwapResponse(BaseModel):
outgoing_mint: str
incoming_mint: str
invoice: Invoice
balances: Dict
class BalanceResponse(BaseModel):
balance: int
keysets: Optional[Dict] = None

View File

@@ -30,6 +30,7 @@ from .responses import (
PendingResponse,
ReceiveResponse,
SendResponse,
SwapResponse,
WalletsResponse,
)
@@ -130,6 +131,53 @@ async def invoice(
return
@router.post(
"/swap",
name="Multi-mint swaps",
summary="Swap funds between mints",
response_model=SwapResponse,
)
async def swap(
amount: int = Query(default=..., description="Amount to swap between mints"),
outgoing_mint: str = Query(default=..., description="URL of outgoing mint"),
incoming_mint: str = Query(default=..., description="URL of incoming mint"),
):
if not settings.lightning:
raise Exception("lightning not supported")
incoming_wallet = await load_mint(wallet, mint=incoming_mint)
outgoing_wallet = await load_mint(wallet, mint=outgoing_mint)
if incoming_wallet.url == outgoing_wallet.url:
raise Exception("mints for swap have to be different")
# request invoice from incoming mint
invoice = await incoming_wallet.request_mint(amount)
# pay invoice from outgoing mint
await outgoing_wallet.load_proofs()
total_amount, fee_reserve_sat = await outgoing_wallet.get_pay_amount_with_fees(
invoice.pr
)
assert total_amount > 0, "amount must be positive"
if outgoing_wallet.available_balance < total_amount:
raise Exception("balance too low")
_, send_proofs = await outgoing_wallet.split_to_send(
outgoing_wallet.proofs, total_amount, set_reserved=True
)
await outgoing_wallet.pay_lightning(send_proofs, invoice.pr, fee_reserve_sat)
# mint token in incoming mint
await incoming_wallet.mint(amount, hash=invoice.hash)
await incoming_wallet.load_proofs()
mint_balances = await incoming_wallet.balance_per_minturl()
return SwapResponse(
outgoing_mint=outgoing_mint,
incoming_mint=incoming_mint,
invoice=invoice,
balances=mint_balances,
)
@router.get(
"/balance",
name="Balance",

View File

@@ -209,6 +209,48 @@ async def invoice(ctx: Context, amount: int, hash: str, split: int):
return
@cli.command("swap", help="Swap funds between mints.")
@click.pass_context
@coro
async def swap(ctx: Context):
if not settings.lightning:
raise Exception("lightning not supported.")
print("Select the mint to swap from:")
outgoing_wallet = await get_mint_wallet(ctx, force_select=True)
print("Select the mint to swap to:")
incoming_wallet = await get_mint_wallet(ctx, force_select=True)
await incoming_wallet.load_mint()
await outgoing_wallet.load_mint()
if incoming_wallet.url == outgoing_wallet.url:
raise Exception("mints for swap have to be different")
amount = int(input("Enter amount to swap in sats: "))
assert amount > 0, "amount is not positive"
# request invoice from incoming mint
invoice = await incoming_wallet.request_mint(amount)
# pay invoice from outgoing mint
total_amount, fee_reserve_sat = await outgoing_wallet.get_pay_amount_with_fees(
invoice.pr
)
if outgoing_wallet.available_balance < total_amount:
raise Exception("balance too low")
_, send_proofs = await outgoing_wallet.split_to_send(
outgoing_wallet.proofs, total_amount, set_reserved=True
)
await outgoing_wallet.pay_lightning(send_proofs, invoice.pr, fee_reserve_sat)
# mint token in incoming mint
await incoming_wallet.mint(amount, hash=invoice.hash)
await incoming_wallet.load_proofs(reload=True)
await print_mint_balances(incoming_wallet, show_mints=True)
@cli.command("balance", help="Balance.")
@click.option(
"--verbose",

View File

@@ -21,16 +21,17 @@ from ...wallet.crud import get_keyset
from ...wallet.wallet import Wallet as Wallet
async def get_mint_wallet(ctx: Context):
async def get_mint_wallet(ctx: Context, force_select: bool = False):
"""
Helper function that asks the user for an input to select which mint they want to load.
Useful for selecting the mint that the user wants to send tokens from.
"""
# we load a dummy wallet so we can check the balance per mint
wallet: Wallet = ctx.obj["WALLET"]
await wallet.load_proofs(reload=True)
mint_balances = await wallet.balance_per_minturl()
if ctx.obj["HOST"] not in mint_balances:
if ctx.obj["HOST"] not in mint_balances and not force_select:
mint_url = wallet.url
elif len(mint_balances) > 1:
# if we have balances on more than one mint, we ask the user to select one

View File

@@ -614,6 +614,8 @@ class Wallet(LedgerAPI):
time_paid=time.time(),
hash="",
)
# we have a unique constraint on the hash, so we generate a random one if it doesn't exist
invoice_obj.hash = invoice_obj.hash or self._generate_secret()
await store_lightning_invoice(db=self.db, invoice=invoice_obj)
# handle change and produce proofs
@@ -824,7 +826,7 @@ class Wallet(LedgerAPI):
)
async def invalidate(self, proofs: List[Proof], check_spendable=True):
"""Invalidates all spendable tokens supplied in proofs.
"""Invalidates all unspendable tokens supplied in proofs.
Args:
proofs (List[Proof]): Which proofs to delete