mirror of
https://github.com/aljazceru/nutshell.git
synced 2026-02-23 09:34:22 +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
|
hash: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
class SwapResponse(BaseModel):
|
||||||
|
outgoing_mint: str
|
||||||
|
incoming_mint: str
|
||||||
|
invoice: Invoice
|
||||||
|
balances: Dict
|
||||||
|
|
||||||
|
|
||||||
class BalanceResponse(BaseModel):
|
class BalanceResponse(BaseModel):
|
||||||
balance: int
|
balance: int
|
||||||
keysets: Optional[Dict] = None
|
keysets: Optional[Dict] = None
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ from .responses import (
|
|||||||
PendingResponse,
|
PendingResponse,
|
||||||
ReceiveResponse,
|
ReceiveResponse,
|
||||||
SendResponse,
|
SendResponse,
|
||||||
|
SwapResponse,
|
||||||
WalletsResponse,
|
WalletsResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -130,6 +131,53 @@ async def invoice(
|
|||||||
return
|
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(
|
@router.get(
|
||||||
"/balance",
|
"/balance",
|
||||||
name="Balance",
|
name="Balance",
|
||||||
|
|||||||
@@ -209,6 +209,48 @@ async def invoice(ctx: Context, amount: int, hash: str, split: int):
|
|||||||
return
|
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.")
|
@cli.command("balance", help="Balance.")
|
||||||
@click.option(
|
@click.option(
|
||||||
"--verbose",
|
"--verbose",
|
||||||
|
|||||||
@@ -21,16 +21,17 @@ from ...wallet.crud import get_keyset
|
|||||||
from ...wallet.wallet import Wallet as Wallet
|
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.
|
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.
|
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
|
# we load a dummy wallet so we can check the balance per mint
|
||||||
wallet: Wallet = ctx.obj["WALLET"]
|
wallet: Wallet = ctx.obj["WALLET"]
|
||||||
|
await wallet.load_proofs(reload=True)
|
||||||
mint_balances = await wallet.balance_per_minturl()
|
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
|
mint_url = wallet.url
|
||||||
elif len(mint_balances) > 1:
|
elif len(mint_balances) > 1:
|
||||||
# if we have balances on more than one mint, we ask the user to select one
|
# 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(),
|
time_paid=time.time(),
|
||||||
hash="",
|
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)
|
await store_lightning_invoice(db=self.db, invoice=invoice_obj)
|
||||||
|
|
||||||
# handle change and produce proofs
|
# handle change and produce proofs
|
||||||
@@ -824,7 +826,7 @@ class Wallet(LedgerAPI):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def invalidate(self, proofs: List[Proof], check_spendable=True):
|
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:
|
Args:
|
||||||
proofs (List[Proof]): Which proofs to delete
|
proofs (List[Proof]): Which proofs to delete
|
||||||
|
|||||||
Reference in New Issue
Block a user