mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 02:24:20 +01:00
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:
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user