mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-03-10 09:19:42 +01:00
remove boltz
This commit is contained in:
parent
891227b279
commit
7baa248204
26 changed files with 0 additions and 2584 deletions
|
@ -1,42 +0,0 @@
|
||||||
# Swap on [Boltz](https://boltz.exchange)
|
|
||||||
providing **trustless** and **account-free** swap services since **2018.**
|
|
||||||
move **IN** and **OUT** of the **lightning network** and remain in control of your bitcoin, at all times.
|
|
||||||
* [Lightning Node](https://amboss.space/node/026165850492521f4ac8abd9bd8088123446d126f648ca35e60f88177dc149ceb2)
|
|
||||||
* [Documentation](https://docs.boltz.exchange/en/latest/)
|
|
||||||
* [Discord](https://discord.gg/d6EK85KK)
|
|
||||||
* [Twitter](https://twitter.com/Boltzhq)
|
|
||||||
* [FAQ](https://www.notion.so/Frequently-Asked-Questions-585328ae43944e2eba351050790d5eec) very cool!
|
|
||||||
|
|
||||||
# usage
|
|
||||||
This extension lets you create swaps, reverse swaps and in the case of failure refund your onchain funds.
|
|
||||||
|
|
||||||
## create normal swap (Onchain -> Lightning)
|
|
||||||
1. click on "Swap (IN)" button to open following dialog, select a wallet, choose a proper amount in the min-max range and choose a onchain address to do your refund to if the swap fails after you already commited onchain funds.
|
|
||||||
---
|
|
||||||

|
|
||||||
---
|
|
||||||
2. after you confirm your inputs, following dialog with the QR code for the onchain transaction, onchain- address and amount, will pop up.
|
|
||||||
---
|
|
||||||

|
|
||||||
---
|
|
||||||
3. after you pay this onchain address with the correct amount, boltz will see it and will pay your invoice and the sats will appear on your wallet.
|
|
||||||
|
|
||||||
if anything goes wrong when boltz is trying to pay your invoice, the swap will fail and you will need to refund your onchain funds after the timeout block height hit. (if boltz can pay the invoice, it wont be able to redeem your onchain funds either).
|
|
||||||
|
|
||||||
## create reverse swap (Lightning -> Onchain)
|
|
||||||
1. click on "Swap (OUT)" button to open following dialog, select a wallet, choose a proper amount in the min-max range and choose a onchain address to receive your funds to. Instant settlement: means that LNbits will create the onchain claim transaction if it sees the boltz lockup transaction in the mempool, but it is not confirmed yet. it is advised to leave this checked because it is faster and the longer is takes to settle, the higher the chances are that the lightning invoice expires and the swap fails.
|
|
||||||
---
|
|
||||||

|
|
||||||
---
|
|
||||||
if this swap fails, boltz is doing the onchain refunding, because they have to commit onchain funds.
|
|
||||||
|
|
||||||
# refund locked onchain funds from a normal swap (Onchain -> Lightning)
|
|
||||||
if for some reason the normal swap fails and you already paid onchain, you can easily refund your btc.
|
|
||||||
this can happen if boltz is not able to pay your lightning invoice after you locked up your funds.
|
|
||||||
in case that happens, there is a info icon in the Swap (In) List which opens following dialog.
|
|
||||||
---
|
|
||||||

|
|
||||||
----
|
|
||||||
if the timeout block height is exceeded you can either press refund and lnbits will do the refunding to the address you specified when creating the swap. Or download the refundfile so you can manually refund your onchain directly on the boltz.exchange website.
|
|
||||||
if you think there is something wrong and/or you are unsure, you can ask for help either in LNbits telegram or in Boltz [Discord](https://discord.gg/d6EK85KK).
|
|
||||||
In a recent update we made *automated check*, every 15 minutes, to check if LNbits can refund your failed swap.
|
|
|
@ -1,35 +0,0 @@
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from fastapi import APIRouter
|
|
||||||
from fastapi.staticfiles import StaticFiles
|
|
||||||
|
|
||||||
from lnbits.db import Database
|
|
||||||
from lnbits.helpers import template_renderer
|
|
||||||
from lnbits.tasks import catch_everything_and_restart
|
|
||||||
|
|
||||||
db = Database("ext_boltz")
|
|
||||||
|
|
||||||
boltz_ext: APIRouter = APIRouter(prefix="/boltz", tags=["boltz"])
|
|
||||||
|
|
||||||
|
|
||||||
def boltz_renderer():
|
|
||||||
return template_renderer(["lnbits/extensions/boltz/templates"])
|
|
||||||
|
|
||||||
|
|
||||||
boltz_static_files = [
|
|
||||||
{
|
|
||||||
"path": "/boltz/static",
|
|
||||||
"app": StaticFiles(directory="lnbits/extensions/boltz/static"),
|
|
||||||
"name": "boltz_static",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
from .tasks import check_for_pending_swaps, wait_for_paid_invoices
|
|
||||||
from .views import * # noqa: F401,F403
|
|
||||||
from .views_api import * # noqa: F401,F403
|
|
||||||
|
|
||||||
|
|
||||||
def boltz_start():
|
|
||||||
loop = asyncio.get_event_loop()
|
|
||||||
loop.create_task(check_for_pending_swaps())
|
|
||||||
loop.create_task(catch_everything_and_restart(wait_for_paid_invoices))
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"name": "Boltz",
|
|
||||||
"short_description": "Perform onchain/offchain swaps",
|
|
||||||
"tile": "/boltz/static/image/boltz.png",
|
|
||||||
"contributors": ["dni"]
|
|
||||||
}
|
|
|
@ -1,284 +0,0 @@
|
||||||
import time
|
|
||||||
from typing import List, Optional, Union
|
|
||||||
|
|
||||||
from boltz_client.boltz import BoltzReverseSwapResponse, BoltzSwapResponse
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
|
||||||
|
|
||||||
from . import db
|
|
||||||
from .models import (
|
|
||||||
AutoReverseSubmarineSwap,
|
|
||||||
CreateAutoReverseSubmarineSwap,
|
|
||||||
CreateReverseSubmarineSwap,
|
|
||||||
CreateSubmarineSwap,
|
|
||||||
ReverseSubmarineSwap,
|
|
||||||
SubmarineSwap,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_submarine_swaps(wallet_ids: Union[str, List[str]]) -> List[SubmarineSwap]:
|
|
||||||
if isinstance(wallet_ids, str):
|
|
||||||
wallet_ids = [wallet_ids]
|
|
||||||
|
|
||||||
q = ",".join(["?"] * len(wallet_ids))
|
|
||||||
rows = await db.fetchall(
|
|
||||||
f"SELECT * FROM boltz.submarineswap WHERE wallet IN ({q}) order by time DESC",
|
|
||||||
(*wallet_ids,),
|
|
||||||
)
|
|
||||||
|
|
||||||
return [SubmarineSwap(**row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
async def get_all_pending_submarine_swaps() -> List[SubmarineSwap]:
|
|
||||||
rows = await db.fetchall(
|
|
||||||
"SELECT * FROM boltz.submarineswap WHERE status='pending' order by time DESC",
|
|
||||||
)
|
|
||||||
return [SubmarineSwap(**row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
async def get_submarine_swap(swap_id) -> Optional[SubmarineSwap]:
|
|
||||||
row = await db.fetchone(
|
|
||||||
"SELECT * FROM boltz.submarineswap WHERE id = ?", (swap_id,)
|
|
||||||
)
|
|
||||||
return SubmarineSwap(**row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
async def create_submarine_swap(
|
|
||||||
data: CreateSubmarineSwap,
|
|
||||||
swap: BoltzSwapResponse,
|
|
||||||
swap_id: str,
|
|
||||||
refund_privkey_wif: str,
|
|
||||||
payment_hash: str,
|
|
||||||
) -> Optional[SubmarineSwap]:
|
|
||||||
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO boltz.submarineswap (
|
|
||||||
id,
|
|
||||||
wallet,
|
|
||||||
payment_hash,
|
|
||||||
status,
|
|
||||||
boltz_id,
|
|
||||||
refund_privkey,
|
|
||||||
refund_address,
|
|
||||||
expected_amount,
|
|
||||||
timeout_block_height,
|
|
||||||
address,
|
|
||||||
bip21,
|
|
||||||
redeem_script,
|
|
||||||
amount
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
swap_id,
|
|
||||||
data.wallet,
|
|
||||||
payment_hash,
|
|
||||||
"pending",
|
|
||||||
swap.id,
|
|
||||||
refund_privkey_wif,
|
|
||||||
data.refund_address,
|
|
||||||
swap.expectedAmount,
|
|
||||||
swap.timeoutBlockHeight,
|
|
||||||
swap.address,
|
|
||||||
swap.bip21,
|
|
||||||
swap.redeemScript,
|
|
||||||
data.amount,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return await get_submarine_swap(swap_id)
|
|
||||||
|
|
||||||
|
|
||||||
async def get_reverse_submarine_swaps(
|
|
||||||
wallet_ids: Union[str, List[str]]
|
|
||||||
) -> List[ReverseSubmarineSwap]:
|
|
||||||
if isinstance(wallet_ids, str):
|
|
||||||
wallet_ids = [wallet_ids]
|
|
||||||
|
|
||||||
q = ",".join(["?"] * len(wallet_ids))
|
|
||||||
rows = await db.fetchall(
|
|
||||||
f"SELECT * FROM boltz.reverse_submarineswap WHERE wallet IN ({q}) order by time DESC",
|
|
||||||
(*wallet_ids,),
|
|
||||||
)
|
|
||||||
|
|
||||||
return [ReverseSubmarineSwap(**row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
async def get_all_pending_reverse_submarine_swaps() -> List[ReverseSubmarineSwap]:
|
|
||||||
rows = await db.fetchall(
|
|
||||||
"SELECT * FROM boltz.reverse_submarineswap WHERE status='pending' order by time DESC"
|
|
||||||
)
|
|
||||||
|
|
||||||
return [ReverseSubmarineSwap(**row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
async def get_reverse_submarine_swap(swap_id) -> Optional[ReverseSubmarineSwap]:
|
|
||||||
row = await db.fetchone(
|
|
||||||
"SELECT * FROM boltz.reverse_submarineswap WHERE id = ?", (swap_id,)
|
|
||||||
)
|
|
||||||
return ReverseSubmarineSwap(**row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
async def create_reverse_submarine_swap(
|
|
||||||
data: CreateReverseSubmarineSwap,
|
|
||||||
claim_privkey_wif: str,
|
|
||||||
preimage_hex: str,
|
|
||||||
swap: BoltzReverseSwapResponse,
|
|
||||||
) -> ReverseSubmarineSwap:
|
|
||||||
|
|
||||||
swap_id = urlsafe_short_hash()
|
|
||||||
|
|
||||||
reverse_swap = ReverseSubmarineSwap(
|
|
||||||
id=swap_id,
|
|
||||||
wallet=data.wallet,
|
|
||||||
status="pending",
|
|
||||||
boltz_id=swap.id,
|
|
||||||
instant_settlement=data.instant_settlement,
|
|
||||||
preimage=preimage_hex,
|
|
||||||
claim_privkey=claim_privkey_wif,
|
|
||||||
lockup_address=swap.lockupAddress,
|
|
||||||
invoice=swap.invoice,
|
|
||||||
onchain_amount=swap.onchainAmount,
|
|
||||||
onchain_address=data.onchain_address,
|
|
||||||
timeout_block_height=swap.timeoutBlockHeight,
|
|
||||||
redeem_script=swap.redeemScript,
|
|
||||||
amount=data.amount,
|
|
||||||
time=int(time.time()),
|
|
||||||
)
|
|
||||||
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO boltz.reverse_submarineswap (
|
|
||||||
id,
|
|
||||||
wallet,
|
|
||||||
status,
|
|
||||||
boltz_id,
|
|
||||||
instant_settlement,
|
|
||||||
preimage,
|
|
||||||
claim_privkey,
|
|
||||||
lockup_address,
|
|
||||||
invoice,
|
|
||||||
onchain_amount,
|
|
||||||
onchain_address,
|
|
||||||
timeout_block_height,
|
|
||||||
redeem_script,
|
|
||||||
amount
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
reverse_swap.id,
|
|
||||||
reverse_swap.wallet,
|
|
||||||
reverse_swap.status,
|
|
||||||
reverse_swap.boltz_id,
|
|
||||||
reverse_swap.instant_settlement,
|
|
||||||
reverse_swap.preimage,
|
|
||||||
reverse_swap.claim_privkey,
|
|
||||||
reverse_swap.lockup_address,
|
|
||||||
reverse_swap.invoice,
|
|
||||||
reverse_swap.onchain_amount,
|
|
||||||
reverse_swap.onchain_address,
|
|
||||||
reverse_swap.timeout_block_height,
|
|
||||||
reverse_swap.redeem_script,
|
|
||||||
reverse_swap.amount,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return reverse_swap
|
|
||||||
|
|
||||||
|
|
||||||
async def get_auto_reverse_submarine_swaps(
|
|
||||||
wallet_ids: List[str],
|
|
||||||
) -> List[AutoReverseSubmarineSwap]:
|
|
||||||
q = ",".join(["?"] * len(wallet_ids))
|
|
||||||
rows = await db.fetchall(
|
|
||||||
f"SELECT * FROM boltz.auto_reverse_submarineswap WHERE wallet IN ({q}) order by time DESC",
|
|
||||||
(*wallet_ids,),
|
|
||||||
)
|
|
||||||
return [AutoReverseSubmarineSwap(**row) for row in rows]
|
|
||||||
|
|
||||||
|
|
||||||
async def get_auto_reverse_submarine_swap(
|
|
||||||
swap_id,
|
|
||||||
) -> Optional[AutoReverseSubmarineSwap]:
|
|
||||||
row = await db.fetchone(
|
|
||||||
"SELECT * FROM boltz.auto_reverse_submarineswap WHERE id = ?", (swap_id,)
|
|
||||||
)
|
|
||||||
return AutoReverseSubmarineSwap(**row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
async def get_auto_reverse_submarine_swap_by_wallet(
|
|
||||||
wallet_id,
|
|
||||||
) -> Optional[AutoReverseSubmarineSwap]:
|
|
||||||
row = await db.fetchone(
|
|
||||||
"SELECT * FROM boltz.auto_reverse_submarineswap WHERE wallet = ?", (wallet_id,)
|
|
||||||
)
|
|
||||||
return AutoReverseSubmarineSwap(**row) if row else None
|
|
||||||
|
|
||||||
|
|
||||||
async def create_auto_reverse_submarine_swap(
|
|
||||||
swap: CreateAutoReverseSubmarineSwap,
|
|
||||||
) -> Optional[AutoReverseSubmarineSwap]:
|
|
||||||
|
|
||||||
swap_id = urlsafe_short_hash()
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
INSERT INTO boltz.auto_reverse_submarineswap (
|
|
||||||
id,
|
|
||||||
wallet,
|
|
||||||
onchain_address,
|
|
||||||
instant_settlement,
|
|
||||||
balance,
|
|
||||||
amount
|
|
||||||
)
|
|
||||||
VALUES (?, ?, ?, ?, ?, ?)
|
|
||||||
""",
|
|
||||||
(
|
|
||||||
swap_id,
|
|
||||||
swap.wallet,
|
|
||||||
swap.onchain_address,
|
|
||||||
swap.instant_settlement,
|
|
||||||
swap.balance,
|
|
||||||
swap.amount,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
return await get_auto_reverse_submarine_swap(swap_id)
|
|
||||||
|
|
||||||
|
|
||||||
async def delete_auto_reverse_submarine_swap(swap_id):
|
|
||||||
await db.execute(
|
|
||||||
"DELETE FROM boltz.auto_reverse_submarineswap WHERE id = ?", (swap_id,)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def update_swap_status(swap_id: str, status: str):
|
|
||||||
|
|
||||||
swap = await get_submarine_swap(swap_id)
|
|
||||||
if swap:
|
|
||||||
await db.execute(
|
|
||||||
"UPDATE boltz.submarineswap SET status='"
|
|
||||||
+ status
|
|
||||||
+ "' WHERE id='"
|
|
||||||
+ swap.id
|
|
||||||
+ "'"
|
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
f"Boltz - swap status change: {status}. boltz_id: {swap.boltz_id}, wallet: {swap.wallet}"
|
|
||||||
)
|
|
||||||
return swap
|
|
||||||
|
|
||||||
reverse_swap = await get_reverse_submarine_swap(swap_id)
|
|
||||||
if reverse_swap:
|
|
||||||
await db.execute(
|
|
||||||
"UPDATE boltz.reverse_submarineswap SET status='"
|
|
||||||
+ status
|
|
||||||
+ "' WHERE id='"
|
|
||||||
+ reverse_swap.id
|
|
||||||
+ "'"
|
|
||||||
)
|
|
||||||
logger.info(
|
|
||||||
f"Boltz - reverse swap status change: {status}. boltz_id: {reverse_swap.boltz_id}, wallet: {reverse_swap.wallet}"
|
|
||||||
)
|
|
||||||
return reverse_swap
|
|
||||||
|
|
||||||
return None
|
|
|
@ -1,64 +0,0 @@
|
||||||
async def m001_initial(db):
|
|
||||||
await db.execute(
|
|
||||||
f"""
|
|
||||||
CREATE TABLE boltz.submarineswap (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
wallet TEXT NOT NULL,
|
|
||||||
payment_hash TEXT NOT NULL,
|
|
||||||
amount {db.big_int} NOT NULL,
|
|
||||||
status TEXT NOT NULL,
|
|
||||||
boltz_id TEXT NOT NULL,
|
|
||||||
refund_address TEXT NOT NULL,
|
|
||||||
refund_privkey TEXT NOT NULL,
|
|
||||||
expected_amount {db.big_int} NOT NULL,
|
|
||||||
timeout_block_height INT NOT NULL,
|
|
||||||
address TEXT NOT NULL,
|
|
||||||
bip21 TEXT NOT NULL,
|
|
||||||
redeem_script TEXT NOT NULL,
|
|
||||||
time TIMESTAMP NOT NULL DEFAULT """
|
|
||||||
+ db.timestamp_now
|
|
||||||
+ """
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
await db.execute(
|
|
||||||
f"""
|
|
||||||
CREATE TABLE boltz.reverse_submarineswap (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
wallet TEXT NOT NULL,
|
|
||||||
onchain_address TEXT NOT NULL,
|
|
||||||
amount {db.big_int} NOT NULL,
|
|
||||||
instant_settlement BOOLEAN NOT NULL,
|
|
||||||
status TEXT NOT NULL,
|
|
||||||
boltz_id TEXT NOT NULL,
|
|
||||||
timeout_block_height INT NOT NULL,
|
|
||||||
redeem_script TEXT NOT NULL,
|
|
||||||
preimage TEXT NOT NULL,
|
|
||||||
claim_privkey TEXT NOT NULL,
|
|
||||||
lockup_address TEXT NOT NULL,
|
|
||||||
invoice TEXT NOT NULL,
|
|
||||||
onchain_amount {db.big_int} NOT NULL,
|
|
||||||
time TIMESTAMP NOT NULL DEFAULT """
|
|
||||||
+ db.timestamp_now
|
|
||||||
+ """
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def m002_auto_swaps(db):
|
|
||||||
await db.execute(
|
|
||||||
"""
|
|
||||||
CREATE TABLE boltz.auto_reverse_submarineswap (
|
|
||||||
id TEXT PRIMARY KEY,
|
|
||||||
wallet TEXT NOT NULL,
|
|
||||||
onchain_address TEXT NOT NULL,
|
|
||||||
amount INT NOT NULL,
|
|
||||||
balance INT NOT NULL,
|
|
||||||
instant_settlement BOOLEAN NOT NULL,
|
|
||||||
time TIMESTAMP NOT NULL DEFAULT """
|
|
||||||
+ db.timestamp_now
|
|
||||||
+ """
|
|
||||||
);
|
|
||||||
"""
|
|
||||||
)
|
|
|
@ -1,68 +0,0 @@
|
||||||
from fastapi import Query
|
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
|
|
||||||
class SubmarineSwap(BaseModel):
|
|
||||||
id: str
|
|
||||||
wallet: str
|
|
||||||
amount: int
|
|
||||||
payment_hash: str
|
|
||||||
time: int
|
|
||||||
status: str
|
|
||||||
refund_privkey: str
|
|
||||||
refund_address: str
|
|
||||||
boltz_id: str
|
|
||||||
expected_amount: int
|
|
||||||
timeout_block_height: int
|
|
||||||
address: str
|
|
||||||
bip21: str
|
|
||||||
redeem_script: str
|
|
||||||
|
|
||||||
|
|
||||||
class CreateSubmarineSwap(BaseModel):
|
|
||||||
wallet: str = Query(...)
|
|
||||||
refund_address: str = Query(...)
|
|
||||||
amount: int = Query(...)
|
|
||||||
|
|
||||||
|
|
||||||
class ReverseSubmarineSwap(BaseModel):
|
|
||||||
id: str
|
|
||||||
wallet: str
|
|
||||||
amount: int
|
|
||||||
onchain_address: str
|
|
||||||
instant_settlement: bool
|
|
||||||
time: int
|
|
||||||
status: str
|
|
||||||
boltz_id: str
|
|
||||||
preimage: str
|
|
||||||
claim_privkey: str
|
|
||||||
lockup_address: str
|
|
||||||
invoice: str
|
|
||||||
onchain_amount: int
|
|
||||||
timeout_block_height: int
|
|
||||||
redeem_script: str
|
|
||||||
|
|
||||||
|
|
||||||
class CreateReverseSubmarineSwap(BaseModel):
|
|
||||||
wallet: str = Query(...)
|
|
||||||
amount: int = Query(...)
|
|
||||||
instant_settlement: bool = Query(...)
|
|
||||||
onchain_address: str = Query(...)
|
|
||||||
|
|
||||||
|
|
||||||
class AutoReverseSubmarineSwap(BaseModel):
|
|
||||||
id: str
|
|
||||||
wallet: str
|
|
||||||
amount: int
|
|
||||||
balance: int
|
|
||||||
onchain_address: str
|
|
||||||
instant_settlement: bool
|
|
||||||
time: int
|
|
||||||
|
|
||||||
|
|
||||||
class CreateAutoReverseSubmarineSwap(BaseModel):
|
|
||||||
wallet: str = Query(...)
|
|
||||||
amount: int = Query(...)
|
|
||||||
balance: int = Query(0)
|
|
||||||
instant_settlement: bool = Query(...)
|
|
||||||
onchain_address: str = Query(...)
|
|
Binary file not shown.
Before Width: | Height: | Size: 37 KiB |
|
@ -1,180 +0,0 @@
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from boltz_client.boltz import BoltzNotFoundException, BoltzSwapStatusException
|
|
||||||
from boltz_client.mempool import MempoolBlockHeightException
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from lnbits.core.crud import get_wallet
|
|
||||||
from lnbits.core.models import Payment
|
|
||||||
from lnbits.core.services import check_transaction_status, fee_reserve
|
|
||||||
from lnbits.helpers import get_current_extension_name
|
|
||||||
from lnbits.tasks import register_invoice_listener
|
|
||||||
|
|
||||||
from .crud import (
|
|
||||||
create_reverse_submarine_swap,
|
|
||||||
get_all_pending_reverse_submarine_swaps,
|
|
||||||
get_all_pending_submarine_swaps,
|
|
||||||
get_auto_reverse_submarine_swap_by_wallet,
|
|
||||||
get_submarine_swap,
|
|
||||||
update_swap_status,
|
|
||||||
)
|
|
||||||
from .models import CreateReverseSubmarineSwap, ReverseSubmarineSwap, SubmarineSwap
|
|
||||||
from .utils import create_boltz_client, execute_reverse_swap
|
|
||||||
|
|
||||||
|
|
||||||
async def wait_for_paid_invoices():
|
|
||||||
invoice_queue = asyncio.Queue()
|
|
||||||
register_invoice_listener(invoice_queue, get_current_extension_name())
|
|
||||||
|
|
||||||
while True:
|
|
||||||
payment = await invoice_queue.get()
|
|
||||||
await on_invoice_paid(payment)
|
|
||||||
|
|
||||||
|
|
||||||
async def on_invoice_paid(payment: Payment) -> None:
|
|
||||||
|
|
||||||
await check_for_auto_swap(payment)
|
|
||||||
|
|
||||||
if payment.extra.get("tag") != "boltz":
|
|
||||||
# not a boltz invoice
|
|
||||||
return
|
|
||||||
|
|
||||||
await payment.set_pending(False)
|
|
||||||
|
|
||||||
if payment.extra:
|
|
||||||
swap_id = payment.extra.get("swap_id")
|
|
||||||
if swap_id:
|
|
||||||
swap = await get_submarine_swap(swap_id)
|
|
||||||
if swap:
|
|
||||||
await update_swap_status(swap_id, "complete")
|
|
||||||
|
|
||||||
|
|
||||||
async def check_for_auto_swap(payment: Payment) -> None:
|
|
||||||
auto_swap = await get_auto_reverse_submarine_swap_by_wallet(payment.wallet_id)
|
|
||||||
if auto_swap:
|
|
||||||
wallet = await get_wallet(payment.wallet_id)
|
|
||||||
if wallet:
|
|
||||||
reserve = fee_reserve(wallet.balance_msat) / 1000
|
|
||||||
balance = wallet.balance_msat / 1000
|
|
||||||
amount = balance - auto_swap.balance - reserve
|
|
||||||
if amount >= auto_swap.amount:
|
|
||||||
|
|
||||||
client = create_boltz_client()
|
|
||||||
claim_privkey_wif, preimage_hex, swap = client.create_reverse_swap(
|
|
||||||
amount=int(amount)
|
|
||||||
)
|
|
||||||
new_swap = await create_reverse_submarine_swap(
|
|
||||||
CreateReverseSubmarineSwap(
|
|
||||||
wallet=auto_swap.wallet,
|
|
||||||
amount=int(amount),
|
|
||||||
instant_settlement=auto_swap.instant_settlement,
|
|
||||||
onchain_address=auto_swap.onchain_address,
|
|
||||||
),
|
|
||||||
claim_privkey_wif,
|
|
||||||
preimage_hex,
|
|
||||||
swap,
|
|
||||||
)
|
|
||||||
await execute_reverse_swap(client, new_swap)
|
|
||||||
|
|
||||||
logger.info(
|
|
||||||
f"Boltz: auto reverse swap created with amount: {amount}, boltz_id: {new_swap.boltz_id}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
"""
|
|
||||||
testcases for boltz startup
|
|
||||||
A. normal swaps
|
|
||||||
1. test: create -> kill -> start -> startup invoice listeners -> pay onchain funds -> should complete
|
|
||||||
2. test: create -> kill -> pay onchain funds -> mine block -> start -> startup check -> should complete
|
|
||||||
3. test: create -> kill -> mine blocks and hit timeout -> start -> should go timeout/failed
|
|
||||||
4. test: create -> kill -> pay to less onchain funds -> mine blocks hit timeout -> start lnbits -> should be refunded
|
|
||||||
|
|
||||||
B. reverse swaps
|
|
||||||
1. test: create instant -> kill -> boltz does lockup -> not confirmed -> start lnbits -> should claim/complete
|
|
||||||
2. test: create -> kill -> boltz does lockup -> not confirmed -> start lnbits -> mine blocks -> should claim/complete
|
|
||||||
3. test: create -> kill -> boltz does lockup -> confirmed -> start lnbits -> should claim/complete
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
async def check_for_pending_swaps():
|
|
||||||
try:
|
|
||||||
swaps = await get_all_pending_submarine_swaps()
|
|
||||||
reverse_swaps = await get_all_pending_reverse_submarine_swaps()
|
|
||||||
if len(swaps) > 0 or len(reverse_swaps) > 0:
|
|
||||||
logger.debug("Boltz - startup swap check")
|
|
||||||
except:
|
|
||||||
logger.error(
|
|
||||||
"Boltz - startup swap check, database is not created yet, do nothing"
|
|
||||||
)
|
|
||||||
return
|
|
||||||
|
|
||||||
client = create_boltz_client()
|
|
||||||
|
|
||||||
if len(swaps) > 0:
|
|
||||||
logger.debug(f"Boltz - {len(swaps)} pending swaps")
|
|
||||||
for swap in swaps:
|
|
||||||
await check_swap(swap, client)
|
|
||||||
|
|
||||||
if len(reverse_swaps) > 0:
|
|
||||||
logger.debug(f"Boltz - {len(reverse_swaps)} pending reverse swaps")
|
|
||||||
for reverse_swap in reverse_swaps:
|
|
||||||
await check_reverse_swap(reverse_swap, client)
|
|
||||||
|
|
||||||
|
|
||||||
async def check_swap(swap: SubmarineSwap, client):
|
|
||||||
try:
|
|
||||||
payment_status = await check_transaction_status(swap.wallet, swap.payment_hash)
|
|
||||||
if payment_status.paid:
|
|
||||||
logger.debug(f"Boltz - swap: {swap.boltz_id} got paid while offline.")
|
|
||||||
await update_swap_status(swap.id, "complete")
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
_ = client.swap_status(swap.id)
|
|
||||||
except:
|
|
||||||
txs = client.mempool.get_txs_from_address(swap.address)
|
|
||||||
if len(txs) == 0:
|
|
||||||
await update_swap_status(swap.id, "timeout")
|
|
||||||
else:
|
|
||||||
await client.refund_swap(
|
|
||||||
privkey_wif=swap.refund_privkey,
|
|
||||||
lockup_address=swap.address,
|
|
||||||
receive_address=swap.refund_address,
|
|
||||||
redeem_script_hex=swap.redeem_script,
|
|
||||||
timeout_block_height=swap.timeout_block_height,
|
|
||||||
)
|
|
||||||
await update_swap_status(swap.id, "refunded")
|
|
||||||
except BoltzNotFoundException:
|
|
||||||
logger.debug(f"Boltz - swap: {swap.boltz_id} does not exist.")
|
|
||||||
await update_swap_status(swap.id, "failed")
|
|
||||||
except MempoolBlockHeightException:
|
|
||||||
logger.debug(
|
|
||||||
f"Boltz - tried to refund swap: {swap.id}, but has not reached the timeout."
|
|
||||||
)
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error(f"Boltz - unhandled exception, swap: {swap.id} - {str(exc)}")
|
|
||||||
|
|
||||||
|
|
||||||
async def check_reverse_swap(reverse_swap: ReverseSubmarineSwap, client):
|
|
||||||
try:
|
|
||||||
_ = client.swap_status(reverse_swap.boltz_id)
|
|
||||||
await client.claim_reverse_swap(
|
|
||||||
lockup_address=reverse_swap.lockup_address,
|
|
||||||
receive_address=reverse_swap.onchain_address,
|
|
||||||
privkey_wif=reverse_swap.claim_privkey,
|
|
||||||
preimage_hex=reverse_swap.preimage,
|
|
||||||
redeem_script_hex=reverse_swap.redeem_script,
|
|
||||||
zeroconf=reverse_swap.instant_settlement,
|
|
||||||
)
|
|
||||||
await update_swap_status(reverse_swap.id, "complete")
|
|
||||||
|
|
||||||
except BoltzSwapStatusException as exc:
|
|
||||||
logger.debug(f"Boltz - swap_status: {str(exc)}")
|
|
||||||
await update_swap_status(reverse_swap.id, "failed")
|
|
||||||
# should only happen while development when regtest is reset
|
|
||||||
except BoltzNotFoundException:
|
|
||||||
logger.debug(f"Boltz - reverse swap: {reverse_swap.boltz_id} does not exist.")
|
|
||||||
await update_swap_status(reverse_swap.id, "failed")
|
|
||||||
except Exception as exc:
|
|
||||||
logger.error(
|
|
||||||
f"Boltz - unhandled exception, reverse swap: {reverse_swap.id} - {str(exc)}"
|
|
||||||
)
|
|
|
@ -1,109 +0,0 @@
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<img src="https://boltz.exchange/static/media/Shape.6c1a92b3.svg" alt="" />
|
|
||||||
<img
|
|
||||||
src="https://boltz.exchange/static/media/Boltz.02fb7acb.svg"
|
|
||||||
style="padding: 5px 9px"
|
|
||||||
alt=""
|
|
||||||
/>
|
|
||||||
<p><b>NON CUSTODIAL atomic swap service</b></p>
|
|
||||||
<h5 class="text-subtitle1 q-my-none">
|
|
||||||
Providing trustless and account-free swap services since 2018. Move IN and
|
|
||||||
OUT of the lightning network and remain in control of your bitcoin, at all
|
|
||||||
time.
|
|
||||||
</h5>
|
|
||||||
<p>
|
|
||||||
Link:
|
|
||||||
<a target="_blank" href="https://boltz.exchange"
|
|
||||||
>https://boltz.exchange
|
|
||||||
</a>
|
|
||||||
<br />
|
|
||||||
README:
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
href="https://github.com/lnbits/lnbits-legend/tree/main/lnbits/extensions/boltz"
|
|
||||||
>read more</a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<small
|
|
||||||
>Extension created by,
|
|
||||||
<a target="_blank" href="https://github.com/dni">dni</a></small
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<h3 class="text-subtitle1 q-my-none">
|
|
||||||
<b>Fee Information</b>
|
|
||||||
</h3>
|
|
||||||
<span>
|
|
||||||
{% raw %} Every swap consists of 2 onchain transactions, lockup and claim
|
|
||||||
/ refund, routing fees and a Boltz fee of
|
|
||||||
<b>{{ boltzConfig.fee_percentage }}%</b>. {% endraw %}
|
|
||||||
</span>
|
|
||||||
</q-card-section>
|
|
||||||
<q-expansion-item
|
|
||||||
group="extras"
|
|
||||||
icon="swap_vertical_circle"
|
|
||||||
label="Fee example: Lightning -> Onchain"
|
|
||||||
:content-inset-level="0.5"
|
|
||||||
>
|
|
||||||
<q-card-section>
|
|
||||||
{% raw %} You want to swap out {{ boltzExample.amount }} sats, Lightning
|
|
||||||
to Onchain:
|
|
||||||
<ul style="padding-left: 12px">
|
|
||||||
<li>Onchain lockup tx fee: ~{{ boltzExample.onchain_boltz }} sats</li>
|
|
||||||
<li>
|
|
||||||
Onchain claim tx fee: {{ boltzExample.onchain_lnbits }} sats
|
|
||||||
(hardcoded)
|
|
||||||
</li>
|
|
||||||
<li>Routing fees (paid by you): unknown</li>
|
|
||||||
<li>
|
|
||||||
Boltz fees: {{ boltzExample.boltz_fee }} sats ({{
|
|
||||||
boltzConfig.fee_percentage }}%)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Fees total: {{ boltzExample.reverse_fee_total }} sats + routing fees
|
|
||||||
</li>
|
|
||||||
<li>You receive: {{ boltzExample.reverse_receive }} sats</li>
|
|
||||||
</ul>
|
|
||||||
<p>
|
|
||||||
onchain_amount_received = amount - (amount * boltz_fee / 100) -
|
|
||||||
lockup_fee - claim_fee
|
|
||||||
</p>
|
|
||||||
{% endraw %}
|
|
||||||
</q-card-section>
|
|
||||||
</q-expansion-item>
|
|
||||||
<q-expansion-item
|
|
||||||
group="extras"
|
|
||||||
icon="swap_vertical_circle"
|
|
||||||
label="Fee example: Onchain -> Lightning"
|
|
||||||
:content-inset-level="0.5"
|
|
||||||
>
|
|
||||||
<q-card-section>
|
|
||||||
{% raw %} You want to swap in {{ boltzExample.amount }} sats, Onchain to
|
|
||||||
Lightning:
|
|
||||||
<ul style="padding-left: 12px">
|
|
||||||
<li>Onchain lockup tx fee: whatever you choose when paying</li>
|
|
||||||
<li>Onchain claim tx fee: ~{{ boltzExample.onchain_boltz }} sats</li>
|
|
||||||
<li>Routing fees (paid by boltz): unknown</li>
|
|
||||||
<li>
|
|
||||||
Boltz fees: {{ boltzExample.boltz_fee }} sats ({{
|
|
||||||
boltzConfig.fee_percentage }}%)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Fees total: {{ boltzExample.normal_fee_total }} sats + lockup_fee
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
You pay onchain: {{ boltzExample.normal_expected_amount }} sats +
|
|
||||||
lockup_fee
|
|
||||||
</li>
|
|
||||||
<li>You receive lightning: {{ boltzExample.amount }} sats</li>
|
|
||||||
</ul>
|
|
||||||
<p>onchain_payment = amount + (amount * boltz_fee / 100) + claim_fee</p>
|
|
||||||
{% endraw %}
|
|
||||||
</q-card-section>
|
|
||||||
</q-expansion-item>
|
|
||||||
</q-card>
|
|
|
@ -1,83 +0,0 @@
|
||||||
<q-dialog v-model="autoReverseSubmarineSwapDialog.show" position="top">
|
|
||||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
|
||||||
<q-form @submit="sendAutoReverseSubmarineSwapFormData" class="q-gutter-md">
|
|
||||||
<q-select
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model="autoReverseSubmarineSwapDialog.data.wallet"
|
|
||||||
:options="g.user.walletOptions"
|
|
||||||
label="Wallet *"
|
|
||||||
:disable="autoReverseSubmarineSwapDialog.data.id ? true : false"
|
|
||||||
>
|
|
||||||
</q-select>
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
label="Balance to kept + fee_reserve"
|
|
||||||
v-model="autoReverseSubmarineSwapDialog.data.balance"
|
|
||||||
type="number"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
|
|
||||||
mininum balance kept in wallet after a swap + the fee_reserve
|
|
||||||
</q-tooltip>
|
|
||||||
</q-input>
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
:label="amountLabel()"
|
|
||||||
v-model.trim="autoReverseSubmarineSwapDialog.data.amount"
|
|
||||||
type="number"
|
|
||||||
></q-input>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<q-checkbox
|
|
||||||
v-model="autoReverseSubmarineSwapDialog.data.instant_settlement"
|
|
||||||
value="false"
|
|
||||||
label="Instant settlement"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
|
|
||||||
Create Onchain TX when transaction is in mempool, but not
|
|
||||||
confirmed yet.
|
|
||||||
</q-tooltip>
|
|
||||||
</q-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model.trim="autoReverseSubmarineSwapDialog.data.onchain_address"
|
|
||||||
type="string"
|
|
||||||
label="Onchain address to receive funds"
|
|
||||||
></q-input>
|
|
||||||
<div class="row q-mt-lg">
|
|
||||||
<q-btn
|
|
||||||
v-if="autoReverseSubmarineSwapDialog.data.id"
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
type="submit"
|
|
||||||
label="Update Swap"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-else
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
:disable="disableAutoReverseSubmarineSwapDialog()"
|
|
||||||
type="submit"
|
|
||||||
label="Create Auto Reverse Swap (Out)"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
@click="resetAutoReverseSubmarineSwapDialog"
|
|
||||||
>Cancel</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</q-form>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
|
@ -1,54 +0,0 @@
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<div class="row items-center no-wrap q-mb-md">
|
|
||||||
<div class="col">
|
|
||||||
<h5 class="text-subtitle1 q-my-none">Auto Lightning -> Onchain</h5>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<q-btn flat color="grey" @click="exportAutoReverseSubmarineSwapCSV"
|
|
||||||
>Export to CSV</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<q-table
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
:data="autoReverseSubmarineSwaps"
|
|
||||||
row-key="id"
|
|
||||||
:columns="autoReverseSubmarineSwapTable.columns"
|
|
||||||
:pagination.sync="autoReverseSubmarineSwapTable.pagination"
|
|
||||||
>
|
|
||||||
{% raw %}
|
|
||||||
<template v-slot:header="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-th auto-width></q-th>
|
|
||||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
||||||
{{ col.label }}
|
|
||||||
</q-th>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
<template v-slot:body="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-td>
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="delete"
|
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
||||||
@click="deleteAutoReverseSwap(props.row.id)"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
|
|
||||||
>delete the automatic reverse swap</q-tooltip
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
</q-td>
|
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
||||||
{{ col.value }}
|
|
||||||
</q-td>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
{% endraw %}
|
|
||||||
</q-table>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
|
@ -1,35 +0,0 @@
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<q-btn
|
|
||||||
label="Onchain -> Lightning"
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
@click="submarineSwapDialog.show = true"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
|
|
||||||
Send onchain funds offchain (BTC -> LN)
|
|
||||||
</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
label="Lightning -> Onchain"
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
@click="reverseSubmarineSwapDialog.show = true"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
|
|
||||||
Send offchain funds to onchain address (LN -> BTC)
|
|
||||||
</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
label="Auto (Lightning -> Onchain)"
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
@click="autoReverseSubmarineSwapDialog.show = true"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
|
|
||||||
Automatically send offchain funds to onchain address (LN -> BTC) with a
|
|
||||||
predefined threshold
|
|
||||||
</q-tooltip>
|
|
||||||
</q-btn>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
|
@ -1,113 +0,0 @@
|
||||||
<q-dialog v-model="checkSwapDialog.show" maximized position="top">
|
|
||||||
<q-card v-if="checkSwapDialog.data" class="q-pa-lg lnbits__dialog-card">
|
|
||||||
<h5>pending swaps</h5>
|
|
||||||
<q-table
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
:data="checkSwapDialog.data.swaps"
|
|
||||||
row-key="id"
|
|
||||||
:columns="allStatusTable.columns"
|
|
||||||
:rows-per-page-options="[0]"
|
|
||||||
>
|
|
||||||
{% raw %}
|
|
||||||
<template v-slot:header="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-th auto-width></q-th>
|
|
||||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
||||||
{{ col.label }}
|
|
||||||
</q-th>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
<template v-slot:body="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-td style="width: 10%">
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="cached"
|
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
||||||
@click="refundSwap(props.row.swap_id)"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
|
|
||||||
>refund swap</q-tooltip
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="download"
|
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
||||||
@click="downloadRefundFile(props.row.swap_id)"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
|
|
||||||
>dowload refund file</q-tooltip
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="flip_to_front"
|
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
||||||
@click="openMempool(props.row.swap_id)"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
|
|
||||||
>open tx on mempool.space</q-tooltip
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
</q-td>
|
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
||||||
{{ col.value }}
|
|
||||||
</q-td>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
{% endraw %}
|
|
||||||
</q-table>
|
|
||||||
<h5>pending reverse swaps</h5>
|
|
||||||
<q-table
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
:data="checkSwapDialog.data.reverse_swaps"
|
|
||||||
row-key="id"
|
|
||||||
:columns="allStatusTable.columns"
|
|
||||||
:rows-per-page-options="[0]"
|
|
||||||
>
|
|
||||||
{% raw %}
|
|
||||||
<template v-slot:header="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-th auto-width></q-th>
|
|
||||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
||||||
{{ col.label }}
|
|
||||||
</q-th>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
<template v-slot:body="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-td style="width: 10%">
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="flip_to_front"
|
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
||||||
@click="openMempool(props.row.swap_id)"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
|
|
||||||
>open tx on mempool.space</q-tooltip
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
</q-td>
|
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
||||||
{{ col.value }}
|
|
||||||
</q-td>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
{% endraw %}
|
|
||||||
</q-table>
|
|
||||||
<div class="row q-mt-lg q-gutter-sm">
|
|
||||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
|
@ -1,31 +0,0 @@
|
||||||
<q-dialog v-model="qrCodeDialog.show" position="top">
|
|
||||||
<q-card v-if="qrCodeDialog.data" class="q-pa-lg lnbits__dialog-card">
|
|
||||||
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
|
|
||||||
<qrcode
|
|
||||||
:value="qrCodeDialog.data.bip21"
|
|
||||||
:options="{width: 800}"
|
|
||||||
class="rounded-borders"
|
|
||||||
></qrcode>
|
|
||||||
</q-responsive>
|
|
||||||
<div>
|
|
||||||
{% raw %}
|
|
||||||
<b>Bitcoin On-Chain TX</b><br />
|
|
||||||
<b>Expected amount (sats): </b> {{ qrCodeDialog.data.expected_amount }}
|
|
||||||
<br />
|
|
||||||
<b>Expected amount (btc): </b> {{ qrCodeDialog.data.expected_amount_btc }}
|
|
||||||
<br />
|
|
||||||
<b>Onchain Address: </b> {{ qrCodeDialog.data.address }} <br />
|
|
||||||
{% endraw %}
|
|
||||||
</div>
|
|
||||||
<div class="row q-mt-lg q-gutter-sm">
|
|
||||||
<q-btn
|
|
||||||
outline
|
|
||||||
color="grey"
|
|
||||||
@click="copyText(qrCodeDialog.data.address, 'Onchain address copied to clipboard!')"
|
|
||||||
class="q-ml-sm"
|
|
||||||
>Copy On-Chain Address</q-btn
|
|
||||||
>
|
|
||||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
|
@ -1,72 +0,0 @@
|
||||||
<q-dialog v-model="reverseSubmarineSwapDialog.show" position="top">
|
|
||||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
|
||||||
<q-form @submit="sendReverseSubmarineSwapFormData" class="q-gutter-md">
|
|
||||||
<q-select
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model="reverseSubmarineSwapDialog.data.wallet"
|
|
||||||
:options="g.user.walletOptions"
|
|
||||||
label="Wallet *"
|
|
||||||
:disable="reverseSubmarineSwapDialog.data.id ? true : false"
|
|
||||||
>
|
|
||||||
</q-select>
|
|
||||||
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
:label="amountLabel()"
|
|
||||||
v-model.trim="reverseSubmarineSwapDialog.data.amount"
|
|
||||||
type="number"
|
|
||||||
></q-input>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col">
|
|
||||||
<q-checkbox
|
|
||||||
v-model="reverseSubmarineSwapDialog.data.instant_settlement"
|
|
||||||
value="false"
|
|
||||||
label="Instant settlement"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left">
|
|
||||||
Create Onchain TX when transaction is in mempool, but not
|
|
||||||
confirmed yet.
|
|
||||||
</q-tooltip>
|
|
||||||
</q-checkbox>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model.trim="reverseSubmarineSwapDialog.data.onchain_address"
|
|
||||||
type="string"
|
|
||||||
label="Onchain address to receive funds"
|
|
||||||
></q-input>
|
|
||||||
<div class="row q-mt-lg">
|
|
||||||
<q-btn
|
|
||||||
v-if="reverseSubmarineSwapDialog.data.id"
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
type="submit"
|
|
||||||
label="Update Swap"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-else
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
:disable="disableReverseSubmarineSwapDialog()"
|
|
||||||
type="submit"
|
|
||||||
label="Create Reverse Swap (OUT)"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
@click="resetReverseSubmarineSwapDialog"
|
|
||||||
>Cancel</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</q-form>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
|
@ -1,66 +0,0 @@
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<div class="row items-center no-wrap q-mb-md">
|
|
||||||
<div class="col">
|
|
||||||
<h5 class="text-subtitle1 q-my-none">Lightning -> Onchain</h5>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<q-btn flat color="grey" @click="exportReverseSubmarineSwapCSV"
|
|
||||||
>Export to CSV</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<q-table
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
:data="reverseSubmarineSwaps"
|
|
||||||
row-key="id"
|
|
||||||
:columns="reverseSubmarineSwapTable.columns"
|
|
||||||
:pagination.sync="reverseSubmarineSwapTable.pagination"
|
|
||||||
>
|
|
||||||
{% raw %}
|
|
||||||
<template v-slot:header="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-th auto-width></q-th>
|
|
||||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
||||||
{{ col.label }}
|
|
||||||
</q-th>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
<template v-slot:body="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-td style="width: 10%">
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="info"
|
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
||||||
@click="openStatusDialog(props.row.id, true)"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
|
|
||||||
>open swap status info</q-tooltip
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="flip_to_front"
|
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
||||||
@click="openMempool(props.row.id)"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
|
|
||||||
>open tx on mempool.space</q-tooltip
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
</q-td>
|
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
||||||
{{ col.value }}
|
|
||||||
</q-td>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
{% endraw %}
|
|
||||||
</q-table>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
|
@ -1,29 +0,0 @@
|
||||||
<q-dialog v-model="statusDialog.show" position="top">
|
|
||||||
<q-card v-if="statusDialog.data" class="q-pa-lg lnbits__dialog-card">
|
|
||||||
<div>
|
|
||||||
{% raw %}
|
|
||||||
<b>Status: </b> {{ statusDialog.data.status }} <br />
|
|
||||||
<br />
|
|
||||||
{% endraw %}
|
|
||||||
</div>
|
|
||||||
<div class="row q-mt-lg q-gutter-sm">
|
|
||||||
<q-btn
|
|
||||||
outline
|
|
||||||
color="grey"
|
|
||||||
@click="refundSwap(statusDialog.data.swap_id)"
|
|
||||||
v-if="!statusDialog.data.reverse"
|
|
||||||
class="q-ml-sm"
|
|
||||||
>Refund
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
outline
|
|
||||||
color="grey"
|
|
||||||
@click="downloadRefundFile(statusDialog.data.swap_id)"
|
|
||||||
v-if="!statusDialog.data.reverse"
|
|
||||||
class="q-ml-sm"
|
|
||||||
>Download refundfile</q-btn
|
|
||||||
>
|
|
||||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
|
||||||
</div>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
|
@ -1,58 +0,0 @@
|
||||||
<q-dialog v-model="submarineSwapDialog.show" position="top">
|
|
||||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
|
||||||
<q-form @submit="sendSubmarineSwapFormData" class="q-gutter-md">
|
|
||||||
<q-select
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model="submarineSwapDialog.data.wallet"
|
|
||||||
:options="g.user.walletOptions"
|
|
||||||
label="Wallet *"
|
|
||||||
:disable="submarineSwapDialog.data.id ? true : false"
|
|
||||||
>
|
|
||||||
</q-select>
|
|
||||||
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model.trim="submarineSwapDialog.data.amount"
|
|
||||||
:label="amountLabel()"
|
|
||||||
type="number"
|
|
||||||
></q-input>
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
dense
|
|
||||||
emit-value
|
|
||||||
v-model.trim="submarineSwapDialog.data.refund_address"
|
|
||||||
type="string"
|
|
||||||
label="Onchain address to receive funds if swap fails"
|
|
||||||
></q-input>
|
|
||||||
<div class="row q-mt-lg">
|
|
||||||
<q-btn
|
|
||||||
v-if="submarineSwapDialog.data.id"
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
type="submit"
|
|
||||||
label="Update Swap"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-else
|
|
||||||
unelevated
|
|
||||||
color="primary"
|
|
||||||
:disable="disableSubmarineSwapDialog()"
|
|
||||||
type="submit"
|
|
||||||
label="Create Swap (IN)"
|
|
||||||
></q-btn>
|
|
||||||
<q-btn
|
|
||||||
v-close-popup
|
|
||||||
flat
|
|
||||||
color="grey"
|
|
||||||
class="q-ml-auto"
|
|
||||||
@click="resetSubmarineSwapDialog"
|
|
||||||
>Cancel</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</q-form>
|
|
||||||
</q-card>
|
|
||||||
</q-dialog>
|
|
|
@ -1,78 +0,0 @@
|
||||||
<q-card>
|
|
||||||
<q-card-section>
|
|
||||||
<div class="row items-center no-wrap q-mb-md">
|
|
||||||
<div class="col">
|
|
||||||
<h5 class="text-subtitle1 q-my-none">Onchain -> Lightning</h5>
|
|
||||||
</div>
|
|
||||||
<div class="col-auto">
|
|
||||||
<q-btn flat color="grey" @click="exportSubmarineSwapCSV"
|
|
||||||
>Export to CSV</q-btn
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<q-table
|
|
||||||
dense
|
|
||||||
flat
|
|
||||||
:data="submarineSwaps"
|
|
||||||
row-key="id"
|
|
||||||
:columns="submarineSwapTable.columns"
|
|
||||||
:pagination.sync="submarineSwapTable.pagination"
|
|
||||||
>
|
|
||||||
{% raw %}
|
|
||||||
<template v-slot:header="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-th auto-width></q-th>
|
|
||||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
|
||||||
{{ col.label }}
|
|
||||||
</q-th>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
<template v-slot:body="props">
|
|
||||||
<q-tr :props="props">
|
|
||||||
<q-td style="width: 10%">
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="visibility"
|
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
||||||
@click="openQrCodeDialog(props.row.id)"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
|
|
||||||
>open swap onchain details</q-tooltip
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="info"
|
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
||||||
@click="openStatusDialog(props.row.id)"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
|
|
||||||
>open swap status info</q-tooltip
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
<q-btn
|
|
||||||
unelevated
|
|
||||||
dense
|
|
||||||
size="xs"
|
|
||||||
icon="flip_to_front"
|
|
||||||
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
|
|
||||||
@click="openMempool(props.row.id)"
|
|
||||||
>
|
|
||||||
<q-tooltip class="bg-grey-8" anchor="bottom left" self="top left"
|
|
||||||
>open tx on mempool.space</q-tooltip
|
|
||||||
>
|
|
||||||
</q-btn>
|
|
||||||
</q-td>
|
|
||||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
|
||||||
{{ col.value }}
|
|
||||||
</q-td>
|
|
||||||
</q-tr>
|
|
||||||
</template>
|
|
||||||
{% endraw %}
|
|
||||||
</q-table>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
|
@ -1,621 +0,0 @@
|
||||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
|
||||||
%} {% block page %}
|
|
||||||
<div class="row q-col-gutter-md">
|
|
||||||
<div class="col-12 col-md-8 q-gutter-y-md">
|
|
||||||
{% include "boltz/_buttons.html" %} {% include
|
|
||||||
"boltz/_submarineSwapList.html" %} {% include
|
|
||||||
"boltz/_reverseSubmarineSwapList.html" %} {% include
|
|
||||||
"boltz/_autoReverseSwapList.html" %}
|
|
||||||
</div>
|
|
||||||
<div class="col-12 col-md-4 q-gutter-y-md">
|
|
||||||
{% include "boltz/_api_docs.html" %}
|
|
||||||
</div>
|
|
||||||
{% include "boltz/_submarineSwapDialog.html" %} {% include
|
|
||||||
"boltz/_reverseSubmarineSwapDialog.html" %} {% include
|
|
||||||
"boltz/_autoReverseSwapDialog.html" %} {% include "boltz/_qrDialog.html" %} {%
|
|
||||||
include "boltz/_statusDialog.html" %}
|
|
||||||
</div>
|
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
|
||||||
<script>
|
|
||||||
Vue.component(VueQrcode.name, VueQrcode)
|
|
||||||
new Vue({
|
|
||||||
el: '#vue',
|
|
||||||
mixins: [windowMixin],
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
mempool: '',
|
|
||||||
boltzConfig: {},
|
|
||||||
submarineSwaps: [],
|
|
||||||
reverseSubmarineSwaps: [],
|
|
||||||
autoReverseSubmarineSwaps: [],
|
|
||||||
statuses: [],
|
|
||||||
submarineSwapDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
},
|
|
||||||
reverseSubmarineSwapDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {
|
|
||||||
instant_settlement: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
autoReverseSubmarineSwapDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {
|
|
||||||
balance: 100,
|
|
||||||
instant_settlement: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
qrCodeDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
},
|
|
||||||
statusDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
},
|
|
||||||
allStatusTable: {
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'swap_id',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Swap ID',
|
|
||||||
field: 'swap_id'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Status',
|
|
||||||
field: 'message'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'boltz',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Boltz',
|
|
||||||
field: 'boltz'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'mempool',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Mempool',
|
|
||||||
field: 'mempool'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'timeout_block_height',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Timeout block height',
|
|
||||||
field: 'timeout_block_height'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
pagination: {
|
|
||||||
rowsPerPage: 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
autoReverseSubmarineSwapTable: {
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'time',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Time',
|
|
||||||
field: 'time',
|
|
||||||
sortable: true,
|
|
||||||
format: function (val, row) {
|
|
||||||
return new Date(val * 1000).toUTCString()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'wallet',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Wallet',
|
|
||||||
field: data => {
|
|
||||||
let wallet = _.findWhere(this.g.user.wallets, {
|
|
||||||
id: data.wallet
|
|
||||||
})
|
|
||||||
if (wallet) {
|
|
||||||
return wallet.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'balance',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Balance',
|
|
||||||
field: 'balance'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'amount',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Amount',
|
|
||||||
field: 'amount'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'onchain_address',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Onchain address',
|
|
||||||
field: 'onchain_address'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
pagination: {
|
|
||||||
rowsPerPage: 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
reverseSubmarineSwapTable: {
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'time',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Time',
|
|
||||||
field: 'time',
|
|
||||||
sortable: true,
|
|
||||||
format: function (val, row) {
|
|
||||||
return new Date(val * 1000).toUTCString()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'wallet',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Wallet',
|
|
||||||
field: data => {
|
|
||||||
let wallet = _.findWhere(this.g.user.wallets, {
|
|
||||||
id: data.wallet
|
|
||||||
})
|
|
||||||
if (wallet) {
|
|
||||||
return wallet.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Status',
|
|
||||||
field: 'status'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'boltz_id',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Boltz ID',
|
|
||||||
field: 'boltz_id'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'onchain_amount',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Onchain amount',
|
|
||||||
field: 'onchain_amount'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'timeout_block_height',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Timeout block height',
|
|
||||||
field: 'timeout_block_height'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
pagination: {
|
|
||||||
rowsPerPage: 10
|
|
||||||
}
|
|
||||||
},
|
|
||||||
submarineSwapTable: {
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'time',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Time',
|
|
||||||
field: 'time',
|
|
||||||
sortable: true,
|
|
||||||
format: function (val, row) {
|
|
||||||
return new Date(val * 1000).toUTCString()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'wallet',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Wallet',
|
|
||||||
field: data => {
|
|
||||||
let wallet = _.findWhere(this.g.user.wallets, {
|
|
||||||
id: data.wallet
|
|
||||||
})
|
|
||||||
if (wallet) {
|
|
||||||
return wallet.name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'status',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Status',
|
|
||||||
field: 'status'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'boltz_id',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Boltz ID',
|
|
||||||
field: 'boltz_id'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'expected_amount',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Expected amount',
|
|
||||||
field: 'expected_amount'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'timeout_block_height',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Timeout block height',
|
|
||||||
field: 'timeout_block_height'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
pagination: {
|
|
||||||
rowsPerPage: 10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
boltzExample() {
|
|
||||||
let amount = 100000
|
|
||||||
let onchain_lnbits = 1000
|
|
||||||
let onchain_boltz = 500
|
|
||||||
let boltz_fee = (amount * this.boltzConfig.fee_percentage) / 100
|
|
||||||
let normal_fee_total = onchain_boltz + boltz_fee
|
|
||||||
let reverse_fee_total = onchain_boltz + boltz_fee + onchain_lnbits
|
|
||||||
return {
|
|
||||||
amount: amount,
|
|
||||||
boltz_fee: boltz_fee,
|
|
||||||
reverse_fee_total: reverse_fee_total,
|
|
||||||
reverse_receive: amount - reverse_fee_total,
|
|
||||||
onchain_lnbits: onchain_lnbits,
|
|
||||||
onchain_boltz: onchain_boltz,
|
|
||||||
normal_fee_total: normal_fee_total,
|
|
||||||
normal_expected_amount: amount + normal_fee_total
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
getLimits() {
|
|
||||||
if (this.boltzConfig) {
|
|
||||||
return {
|
|
||||||
min: this.boltzConfig.minimal,
|
|
||||||
max: this.boltzConfig.maximal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
min: 0,
|
|
||||||
max: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
amountLabel() {
|
|
||||||
let limits = this.getLimits()
|
|
||||||
return 'min: (' + limits.min + '), max: (' + limits.max + ')'
|
|
||||||
},
|
|
||||||
disableSubmarineSwapDialog() {
|
|
||||||
const data = this.submarineSwapDialog.data
|
|
||||||
let limits = this.getLimits()
|
|
||||||
return (
|
|
||||||
data.wallet == null ||
|
|
||||||
data.refund_address == null ||
|
|
||||||
data.refund_address.search(
|
|
||||||
/^(bcrt1|bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$/
|
|
||||||
) !== 0 ||
|
|
||||||
data.amount < limits.min ||
|
|
||||||
data.amount > limits.max
|
|
||||||
)
|
|
||||||
},
|
|
||||||
disableReverseSubmarineSwapDialog() {
|
|
||||||
const data = this.reverseSubmarineSwapDialog.data
|
|
||||||
let limits = this.getLimits()
|
|
||||||
return (
|
|
||||||
data.onchain_address == null ||
|
|
||||||
data.onchain_address.search(
|
|
||||||
/^(bcrt1|bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$/
|
|
||||||
) !== 0 ||
|
|
||||||
data.wallet == null ||
|
|
||||||
data.amount < limits.min ||
|
|
||||||
data.amount > limits.max
|
|
||||||
)
|
|
||||||
},
|
|
||||||
disableAutoReverseSubmarineSwapDialog() {
|
|
||||||
const data = this.autoReverseSubmarineSwapDialog.data
|
|
||||||
let limits = this.getLimits()
|
|
||||||
return (
|
|
||||||
data.onchain_address == null ||
|
|
||||||
data.onchain_address.search(
|
|
||||||
/^(bcrt1|bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$/
|
|
||||||
) !== 0 ||
|
|
||||||
data.wallet == null ||
|
|
||||||
data.amount < limits.min ||
|
|
||||||
data.amount > limits.max
|
|
||||||
)
|
|
||||||
},
|
|
||||||
downloadRefundFile(swapId) {
|
|
||||||
let swap = _.findWhere(this.submarineSwaps, {id: swapId})
|
|
||||||
let json = {
|
|
||||||
id: swap.boltz_id,
|
|
||||||
currency: 'BTC',
|
|
||||||
redeemScript: swap.redeem_script,
|
|
||||||
privateKey: swap.refund_privkey,
|
|
||||||
timeoutBlockHeight: swap.timeout_block_height
|
|
||||||
}
|
|
||||||
let hiddenElement = document.createElement('a')
|
|
||||||
hiddenElement.href =
|
|
||||||
'data:application/json;charset=utf-8,' +
|
|
||||||
encodeURI(JSON.stringify(json))
|
|
||||||
hiddenElement.target = '_blank'
|
|
||||||
hiddenElement.download = 'boltz-refund-' + swap.boltz_id + '.json'
|
|
||||||
hiddenElement.click()
|
|
||||||
},
|
|
||||||
refundSwap(swapId) {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'POST',
|
|
||||||
'/boltz/api/v1/swap/refund?swap_id=' + swapId,
|
|
||||||
this.g.user.wallets[0].adminkey
|
|
||||||
)
|
|
||||||
.then(res => {
|
|
||||||
this.resetStatusDialog()
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log('error', error)
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
openMempool(swap_id) {
|
|
||||||
var swap = _.findWhere(this.submarineSwaps, {id: swap_id})
|
|
||||||
if (swap === undefined) {
|
|
||||||
var swap = _.findWhere(this.reverseSubmarineSwaps, {id: swap_id})
|
|
||||||
var address = swap.lockup_address
|
|
||||||
} else {
|
|
||||||
var address = swap.address
|
|
||||||
}
|
|
||||||
var mempool_address = this.mempool
|
|
||||||
|
|
||||||
// used for development, replace docker hosts with localhost
|
|
||||||
if (mempool_address.search('mempool-web') !== -1) {
|
|
||||||
mempool_address = mempool_address.replace('mempool-web', 'localhost')
|
|
||||||
}
|
|
||||||
|
|
||||||
window.open(mempool_address + '/address/' + address, '_blank')
|
|
||||||
},
|
|
||||||
openStatusDialog(swap_id, reverse) {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'POST',
|
|
||||||
'/boltz/api/v1/swap/status?swap_id=' + swap_id,
|
|
||||||
this.g.user.wallets[0].adminkey
|
|
||||||
)
|
|
||||||
.then(res => {
|
|
||||||
this.resetStatusDialog()
|
|
||||||
this.statusDialog.data = {
|
|
||||||
reverse: reverse,
|
|
||||||
swap_id: swap_id,
|
|
||||||
wallet: res.data.wallet,
|
|
||||||
boltz: res.data.boltz,
|
|
||||||
status: res.data.status,
|
|
||||||
mempool: res.data.mempool,
|
|
||||||
timeout_block_height: res.data.timeout_block_height,
|
|
||||||
date: new Date().toUTCString()
|
|
||||||
}
|
|
||||||
this.statusDialog.show = true
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log('error', error)
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
openQrCodeDialog(submarineSwapId) {
|
|
||||||
var swap = _.findWhere(this.submarineSwaps, {id: submarineSwapId})
|
|
||||||
if (swap === undefined) {
|
|
||||||
return console.assert('swap is undefined, this should not happen')
|
|
||||||
}
|
|
||||||
this.qrCodeDialog.data = {
|
|
||||||
id: swap.id,
|
|
||||||
expected_amount: swap.expected_amount,
|
|
||||||
expected_amount_btc: swap.expected_amount / 100000000,
|
|
||||||
bip21: swap.bip21,
|
|
||||||
address: swap.address
|
|
||||||
}
|
|
||||||
this.qrCodeDialog.show = true
|
|
||||||
},
|
|
||||||
resetStatusDialog() {
|
|
||||||
this.statusDialog = {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resetSubmarineSwapDialog() {
|
|
||||||
this.submarineSwapDialog = {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resetReverseSubmarineSwapDialog() {
|
|
||||||
this.reverseSubmarineSwapDialog = {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
resetAutoReverseSubmarineSwapDialog() {
|
|
||||||
this.autoReverseSubmarineSwapDialog = {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sendReverseSubmarineSwapFormData() {
|
|
||||||
let wallet = _.findWhere(this.g.user.wallets, {
|
|
||||||
id: this.reverseSubmarineSwapDialog.data.wallet
|
|
||||||
})
|
|
||||||
let data = this.reverseSubmarineSwapDialog.data
|
|
||||||
this.createReverseSubmarineSwap(wallet, data)
|
|
||||||
},
|
|
||||||
sendAutoReverseSubmarineSwapFormData() {
|
|
||||||
let wallet = _.findWhere(this.g.user.wallets, {
|
|
||||||
id: this.autoReverseSubmarineSwapDialog.data.wallet
|
|
||||||
})
|
|
||||||
let data = this.autoReverseSubmarineSwapDialog.data
|
|
||||||
this.createAutoReverseSubmarineSwap(wallet, data)
|
|
||||||
},
|
|
||||||
sendSubmarineSwapFormData() {
|
|
||||||
let wallet = _.findWhere(this.g.user.wallets, {
|
|
||||||
id: this.submarineSwapDialog.data.wallet
|
|
||||||
})
|
|
||||||
let data = this.submarineSwapDialog.data
|
|
||||||
this.createSubmarineSwap(wallet, data)
|
|
||||||
},
|
|
||||||
exportSubmarineSwapCSV() {
|
|
||||||
LNbits.utils.exportCSV(
|
|
||||||
this.submarineSwapTable.columns,
|
|
||||||
this.submarineSwaps
|
|
||||||
)
|
|
||||||
},
|
|
||||||
exportReverseSubmarineSwapCSV() {
|
|
||||||
LNbits.utils.exportCSV(
|
|
||||||
this.reverseSubmarineSwapTable.columns,
|
|
||||||
this.reverseSubmarineSwaps
|
|
||||||
)
|
|
||||||
},
|
|
||||||
exportAutoReverseSubmarineSwapCSV() {
|
|
||||||
LNbits.utils.exportCSV(
|
|
||||||
this.autoReverseSubmarineSwapTable.columns,
|
|
||||||
this.autoReverseSubmarineSwaps
|
|
||||||
)
|
|
||||||
},
|
|
||||||
createSubmarineSwap(wallet, data) {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'POST',
|
|
||||||
'/boltz/api/v1/swap',
|
|
||||||
this.g.user.wallets[0].adminkey,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
.then(res => {
|
|
||||||
this.submarineSwaps.unshift(res.data)
|
|
||||||
this.resetSubmarineSwapDialog()
|
|
||||||
this.openQrCodeDialog(res.data.id)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
createReverseSubmarineSwap(wallet, data) {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'POST',
|
|
||||||
'/boltz/api/v1/swap/reverse',
|
|
||||||
this.g.user.wallets[0].adminkey,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
.then(res => {
|
|
||||||
this.reverseSubmarineSwaps.unshift(res.data)
|
|
||||||
this.resetReverseSubmarineSwapDialog()
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
createAutoReverseSubmarineSwap(wallet, data) {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'POST',
|
|
||||||
'/boltz/api/v1/swap/reverse/auto',
|
|
||||||
this.g.user.wallets[0].adminkey,
|
|
||||||
data
|
|
||||||
)
|
|
||||||
.then(res => {
|
|
||||||
this.autoReverseSubmarineSwaps.unshift(res.data)
|
|
||||||
this.resetAutoReverseSubmarineSwapDialog()
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
deleteAutoReverseSwap(swap_id) {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'DELETE',
|
|
||||||
'/boltz/api/v1/swap/reverse/auto/' + swap_id,
|
|
||||||
this.g.user.wallets[0].adminkey
|
|
||||||
)
|
|
||||||
.then(res => {
|
|
||||||
let i = this.autoReverseSubmarineSwaps.findIndex(
|
|
||||||
swap => swap.id === swap_id
|
|
||||||
)
|
|
||||||
this.autoReverseSubmarineSwaps.splice(i, 1)
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log(error)
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getSubmarineSwap() {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/boltz/api/v1/swap?all_wallets=true',
|
|
||||||
this.g.user.wallets[0].inkey
|
|
||||||
)
|
|
||||||
.then(response => {
|
|
||||||
this.submarineSwaps = response.data
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getReverseSubmarineSwap() {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/boltz/api/v1/swap/reverse?all_wallets=true',
|
|
||||||
this.g.user.wallets[0].inkey
|
|
||||||
)
|
|
||||||
.then(response => {
|
|
||||||
this.reverseSubmarineSwaps = response.data
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getAutoReverseSubmarineSwap() {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'GET',
|
|
||||||
'/boltz/api/v1/swap/reverse/auto?all_wallets=true',
|
|
||||||
this.g.user.wallets[0].inkey
|
|
||||||
)
|
|
||||||
.then(response => {
|
|
||||||
this.autoReverseSubmarineSwaps = response.data
|
|
||||||
})
|
|
||||||
.catch(function (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getMempool() {
|
|
||||||
LNbits.api
|
|
||||||
.request('GET', '/boltz/api/v1/swap/mempool')
|
|
||||||
.then(res => {
|
|
||||||
this.mempool = res.data
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log('error', error)
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getBoltzConfig() {
|
|
||||||
LNbits.api
|
|
||||||
.request('GET', '/boltz/api/v1/swap/boltz')
|
|
||||||
.then(res => {
|
|
||||||
this.boltzConfig = res.data
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log('error', error)
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created: function () {
|
|
||||||
this.getMempool()
|
|
||||||
this.getBoltzConfig()
|
|
||||||
this.getSubmarineSwap()
|
|
||||||
this.getReverseSubmarineSwap()
|
|
||||||
this.getAutoReverseSubmarineSwap()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
|
@ -1,87 +0,0 @@
|
||||||
import asyncio
|
|
||||||
import calendar
|
|
||||||
import datetime
|
|
||||||
from typing import Awaitable
|
|
||||||
|
|
||||||
from boltz_client.boltz import BoltzClient, BoltzConfig
|
|
||||||
|
|
||||||
from lnbits.core.services import fee_reserve, get_wallet, pay_invoice
|
|
||||||
from lnbits.settings import settings
|
|
||||||
|
|
||||||
from .models import ReverseSubmarineSwap
|
|
||||||
|
|
||||||
|
|
||||||
def create_boltz_client() -> BoltzClient:
|
|
||||||
config = BoltzConfig(
|
|
||||||
network=settings.boltz_network,
|
|
||||||
api_url=settings.boltz_url,
|
|
||||||
mempool_url=f"{settings.boltz_mempool_space_url}/api",
|
|
||||||
mempool_ws_url=f"{settings.boltz_mempool_space_url_ws}/api/v1/ws",
|
|
||||||
referral_id="lnbits",
|
|
||||||
)
|
|
||||||
return BoltzClient(config)
|
|
||||||
|
|
||||||
|
|
||||||
async def check_balance(data) -> bool:
|
|
||||||
# check if we can pay the invoice before we create the actual swap on boltz
|
|
||||||
amount_msat = data.amount * 1000
|
|
||||||
fee_reserve_msat = fee_reserve(amount_msat)
|
|
||||||
wallet = await get_wallet(data.wallet)
|
|
||||||
assert wallet
|
|
||||||
if wallet.balance_msat - fee_reserve_msat < amount_msat:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def get_timestamp():
|
|
||||||
date = datetime.datetime.utcnow()
|
|
||||||
return calendar.timegm(date.utctimetuple())
|
|
||||||
|
|
||||||
|
|
||||||
async def execute_reverse_swap(client, swap: ReverseSubmarineSwap):
|
|
||||||
# claim_task is watching onchain address for the lockup transaction to arrive / confirm
|
|
||||||
# and if the lockup is there, claim the onchain revealing preimage for hold invoice
|
|
||||||
claim_task = asyncio.create_task(
|
|
||||||
client.claim_reverse_swap(
|
|
||||||
privkey_wif=swap.claim_privkey,
|
|
||||||
preimage_hex=swap.preimage,
|
|
||||||
lockup_address=swap.lockup_address,
|
|
||||||
receive_address=swap.onchain_address,
|
|
||||||
redeem_script_hex=swap.redeem_script,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# pay_task is paying the hold invoice which gets held until you reveal your preimage when claiming your onchain funds
|
|
||||||
pay_task = pay_invoice_and_update_status(
|
|
||||||
swap.id,
|
|
||||||
claim_task,
|
|
||||||
pay_invoice(
|
|
||||||
wallet_id=swap.wallet,
|
|
||||||
payment_request=swap.invoice,
|
|
||||||
description=f"reverse swap for {swap.onchain_amount} sats on boltz.exchange",
|
|
||||||
extra={"tag": "boltz", "swap_id": swap.id, "reverse": True},
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
# they need to run be concurrently, because else pay_task will lock the eventloop and claim_task will not be executed.
|
|
||||||
# the lockup transaction can only happen after you pay the invoice, which cannot be redeemed immediatly -> hold invoice
|
|
||||||
# after getting the lockup transaction, you can claim the onchain funds revealing the preimage for boltz to redeem the hold invoice
|
|
||||||
asyncio.gather(claim_task, pay_task)
|
|
||||||
|
|
||||||
|
|
||||||
def pay_invoice_and_update_status(
|
|
||||||
swap_id: str, wstask: asyncio.Task, awaitable: Awaitable
|
|
||||||
) -> asyncio.Task:
|
|
||||||
async def _pay_invoice(awaitable):
|
|
||||||
from .crud import update_swap_status
|
|
||||||
|
|
||||||
try:
|
|
||||||
awaited = await awaitable
|
|
||||||
await update_swap_status(swap_id, "complete")
|
|
||||||
return awaited
|
|
||||||
except asyncio.exceptions.CancelledError:
|
|
||||||
"""lnbits process was exited, do nothing and handle it in startup script"""
|
|
||||||
except:
|
|
||||||
wstask.cancel()
|
|
||||||
await update_swap_status(swap_id, "failed")
|
|
||||||
|
|
||||||
return asyncio.create_task(_pay_invoice(awaitable))
|
|
|
@ -1,21 +0,0 @@
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
from fastapi import Depends, Request
|
|
||||||
from fastapi.templating import Jinja2Templates
|
|
||||||
from starlette.responses import HTMLResponse
|
|
||||||
|
|
||||||
from lnbits.core.models import User
|
|
||||||
from lnbits.decorators import check_user_exists
|
|
||||||
|
|
||||||
from . import boltz_ext, boltz_renderer
|
|
||||||
|
|
||||||
templates = Jinja2Templates(directory="templates")
|
|
||||||
|
|
||||||
|
|
||||||
@boltz_ext.get("/", response_class=HTMLResponse)
|
|
||||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
|
||||||
root_url = urlparse(str(request.url)).netloc
|
|
||||||
return boltz_renderer().TemplateResponse(
|
|
||||||
"boltz/index.html",
|
|
||||||
{"request": request, "user": user.dict(), "root_url": root_url},
|
|
||||||
)
|
|
|
@ -1,332 +0,0 @@
|
||||||
from http import HTTPStatus
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
from fastapi import Depends, Query, status
|
|
||||||
from starlette.exceptions import HTTPException
|
|
||||||
|
|
||||||
from lnbits.core.crud import get_user
|
|
||||||
from lnbits.core.services import create_invoice
|
|
||||||
from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key
|
|
||||||
from lnbits.helpers import urlsafe_short_hash
|
|
||||||
from lnbits.settings import settings
|
|
||||||
|
|
||||||
from . import boltz_ext
|
|
||||||
from .crud import (
|
|
||||||
create_auto_reverse_submarine_swap,
|
|
||||||
create_reverse_submarine_swap,
|
|
||||||
create_submarine_swap,
|
|
||||||
delete_auto_reverse_submarine_swap,
|
|
||||||
get_auto_reverse_submarine_swap_by_wallet,
|
|
||||||
get_auto_reverse_submarine_swaps,
|
|
||||||
get_reverse_submarine_swap,
|
|
||||||
get_reverse_submarine_swaps,
|
|
||||||
get_submarine_swap,
|
|
||||||
get_submarine_swaps,
|
|
||||||
update_swap_status,
|
|
||||||
)
|
|
||||||
from .models import (
|
|
||||||
AutoReverseSubmarineSwap,
|
|
||||||
CreateAutoReverseSubmarineSwap,
|
|
||||||
CreateReverseSubmarineSwap,
|
|
||||||
CreateSubmarineSwap,
|
|
||||||
ReverseSubmarineSwap,
|
|
||||||
SubmarineSwap,
|
|
||||||
)
|
|
||||||
from .utils import check_balance, create_boltz_client, execute_reverse_swap
|
|
||||||
|
|
||||||
|
|
||||||
@boltz_ext.get(
|
|
||||||
"/api/v1/swap/mempool",
|
|
||||||
name="boltz.get /swap/mempool",
|
|
||||||
summary="get a the mempool url",
|
|
||||||
description="""
|
|
||||||
This endpoint gets the URL from mempool.space
|
|
||||||
""",
|
|
||||||
response_description="mempool.space url",
|
|
||||||
response_model=str,
|
|
||||||
)
|
|
||||||
async def api_mempool_url():
|
|
||||||
return settings.boltz_mempool_space_url
|
|
||||||
|
|
||||||
|
|
||||||
# NORMAL SWAP
|
|
||||||
@boltz_ext.get(
|
|
||||||
"/api/v1/swap",
|
|
||||||
name="boltz.get /swap",
|
|
||||||
summary="get a list of swaps a swap",
|
|
||||||
description="""
|
|
||||||
This endpoint gets a list of normal swaps.
|
|
||||||
""",
|
|
||||||
response_description="list of normal swaps",
|
|
||||||
dependencies=[Depends(get_key_type)],
|
|
||||||
response_model=List[SubmarineSwap],
|
|
||||||
)
|
|
||||||
async def api_submarineswap(
|
|
||||||
g: WalletTypeInfo = Depends(get_key_type),
|
|
||||||
all_wallets: bool = Query(False),
|
|
||||||
):
|
|
||||||
wallet_ids = [g.wallet.id]
|
|
||||||
if all_wallets:
|
|
||||||
user = await get_user(g.wallet.user)
|
|
||||||
wallet_ids = user.wallet_ids if user else []
|
|
||||||
return [swap.dict() for swap in await get_submarine_swaps(wallet_ids)]
|
|
||||||
|
|
||||||
|
|
||||||
@boltz_ext.post(
|
|
||||||
"/api/v1/swap/refund",
|
|
||||||
name="boltz.swap_refund",
|
|
||||||
summary="refund of a swap",
|
|
||||||
description="""
|
|
||||||
This endpoint attempts to refund a normal swaps, creates onchain tx and sets swap status ro refunded.
|
|
||||||
""",
|
|
||||||
response_description="refunded swap with status set to refunded",
|
|
||||||
dependencies=[Depends(require_admin_key)],
|
|
||||||
response_model=SubmarineSwap,
|
|
||||||
responses={
|
|
||||||
400: {"description": "when swap_id is missing"},
|
|
||||||
404: {"description": "when swap is not found"},
|
|
||||||
405: {"description": "when swap is not pending"},
|
|
||||||
500: {
|
|
||||||
"description": "when something goes wrong creating the refund onchain tx"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
async def api_submarineswap_refund(swap_id: str):
|
|
||||||
if not swap_id:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.BAD_REQUEST, detail="swap_id missing"
|
|
||||||
)
|
|
||||||
swap = await get_submarine_swap(swap_id)
|
|
||||||
if not swap:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="swap does not exist."
|
|
||||||
)
|
|
||||||
if swap.status != "pending":
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.METHOD_NOT_ALLOWED, detail="swap is not pending."
|
|
||||||
)
|
|
||||||
|
|
||||||
client = create_boltz_client()
|
|
||||||
await client.refund_swap(
|
|
||||||
privkey_wif=swap.refund_privkey,
|
|
||||||
lockup_address=swap.address,
|
|
||||||
receive_address=swap.refund_address,
|
|
||||||
redeem_script_hex=swap.redeem_script,
|
|
||||||
timeout_block_height=swap.timeout_block_height,
|
|
||||||
)
|
|
||||||
|
|
||||||
await update_swap_status(swap.id, "refunded")
|
|
||||||
return swap
|
|
||||||
|
|
||||||
|
|
||||||
@boltz_ext.post(
|
|
||||||
"/api/v1/swap",
|
|
||||||
status_code=status.HTTP_201_CREATED,
|
|
||||||
name="boltz.post /swap",
|
|
||||||
summary="create a submarine swap",
|
|
||||||
description="""
|
|
||||||
This endpoint creates a submarine swap
|
|
||||||
""",
|
|
||||||
response_description="create swap",
|
|
||||||
response_model=SubmarineSwap,
|
|
||||||
dependencies=[Depends(require_admin_key)],
|
|
||||||
responses={
|
|
||||||
405: {
|
|
||||||
"description": "auto reverse swap is active, a swap would immediatly be swapped out again."
|
|
||||||
},
|
|
||||||
500: {"description": "boltz error"},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
async def api_submarineswap_create(data: CreateSubmarineSwap):
|
|
||||||
|
|
||||||
auto_swap = await get_auto_reverse_submarine_swap_by_wallet(data.wallet)
|
|
||||||
if auto_swap:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.METHOD_NOT_ALLOWED,
|
|
||||||
detail="auto reverse swap is active, a swap would immediatly be swapped out again.",
|
|
||||||
)
|
|
||||||
|
|
||||||
client = create_boltz_client()
|
|
||||||
swap_id = urlsafe_short_hash()
|
|
||||||
payment_hash, payment_request = await create_invoice(
|
|
||||||
wallet_id=data.wallet,
|
|
||||||
amount=data.amount,
|
|
||||||
memo=f"swap of {data.amount} sats on boltz.exchange",
|
|
||||||
extra={"tag": "boltz", "swap_id": swap_id},
|
|
||||||
)
|
|
||||||
refund_privkey_wif, swap = client.create_swap(payment_request)
|
|
||||||
new_swap = await create_submarine_swap(
|
|
||||||
data, swap, swap_id, refund_privkey_wif, payment_hash
|
|
||||||
)
|
|
||||||
return new_swap.dict() if new_swap else None
|
|
||||||
|
|
||||||
|
|
||||||
# REVERSE SWAP
|
|
||||||
@boltz_ext.get(
|
|
||||||
"/api/v1/swap/reverse",
|
|
||||||
name="boltz.get /swap/reverse",
|
|
||||||
summary="get a list of reverse swaps",
|
|
||||||
description="""
|
|
||||||
This endpoint gets a list of reverse swaps.
|
|
||||||
""",
|
|
||||||
response_description="list of reverse swaps",
|
|
||||||
dependencies=[Depends(get_key_type)],
|
|
||||||
response_model=List[ReverseSubmarineSwap],
|
|
||||||
)
|
|
||||||
async def api_reverse_submarineswap(
|
|
||||||
g: WalletTypeInfo = Depends(get_key_type),
|
|
||||||
all_wallets: bool = Query(False),
|
|
||||||
):
|
|
||||||
wallet_ids = [g.wallet.id]
|
|
||||||
if all_wallets:
|
|
||||||
user = await get_user(g.wallet.user)
|
|
||||||
wallet_ids = user.wallet_ids if user else []
|
|
||||||
return [swap for swap in await get_reverse_submarine_swaps(wallet_ids)]
|
|
||||||
|
|
||||||
|
|
||||||
@boltz_ext.post(
|
|
||||||
"/api/v1/swap/reverse",
|
|
||||||
status_code=status.HTTP_201_CREATED,
|
|
||||||
name="boltz.post /swap/reverse",
|
|
||||||
summary="create a reverse submarine swap",
|
|
||||||
description="""
|
|
||||||
This endpoint creates a reverse submarine swap
|
|
||||||
""",
|
|
||||||
response_description="create reverse swap",
|
|
||||||
response_model=ReverseSubmarineSwap,
|
|
||||||
dependencies=[Depends(require_admin_key)],
|
|
||||||
responses={
|
|
||||||
405: {"description": "not allowed method, insufficient balance"},
|
|
||||||
500: {"description": "boltz error"},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
async def api_reverse_submarineswap_create(
|
|
||||||
data: CreateReverseSubmarineSwap,
|
|
||||||
) -> ReverseSubmarineSwap:
|
|
||||||
|
|
||||||
if not await check_balance(data):
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.METHOD_NOT_ALLOWED, detail="Insufficient balance."
|
|
||||||
)
|
|
||||||
client = create_boltz_client()
|
|
||||||
claim_privkey_wif, preimage_hex, swap = client.create_reverse_swap(
|
|
||||||
amount=data.amount
|
|
||||||
)
|
|
||||||
new_swap = await create_reverse_submarine_swap(
|
|
||||||
data, claim_privkey_wif, preimage_hex, swap
|
|
||||||
)
|
|
||||||
await execute_reverse_swap(client, new_swap)
|
|
||||||
return new_swap
|
|
||||||
|
|
||||||
|
|
||||||
@boltz_ext.get(
|
|
||||||
"/api/v1/swap/reverse/auto",
|
|
||||||
name="boltz.get /swap/reverse/auto",
|
|
||||||
summary="get a list of auto reverse swaps",
|
|
||||||
description="""
|
|
||||||
This endpoint gets a list of auto reverse swaps.
|
|
||||||
""",
|
|
||||||
response_description="list of auto reverse swaps",
|
|
||||||
dependencies=[Depends(get_key_type)],
|
|
||||||
response_model=List[AutoReverseSubmarineSwap],
|
|
||||||
)
|
|
||||||
async def api_auto_reverse_submarineswap(
|
|
||||||
g: WalletTypeInfo = Depends(get_key_type),
|
|
||||||
all_wallets: bool = Query(False),
|
|
||||||
):
|
|
||||||
wallet_ids = [g.wallet.id]
|
|
||||||
if all_wallets:
|
|
||||||
user = await get_user(g.wallet.user)
|
|
||||||
wallet_ids = user.wallet_ids if user else []
|
|
||||||
return [swap.dict() for swap in await get_auto_reverse_submarine_swaps(wallet_ids)]
|
|
||||||
|
|
||||||
|
|
||||||
@boltz_ext.post(
|
|
||||||
"/api/v1/swap/reverse/auto",
|
|
||||||
status_code=status.HTTP_201_CREATED,
|
|
||||||
name="boltz.post /swap/reverse/auto",
|
|
||||||
summary="create a auto reverse submarine swap",
|
|
||||||
description="""
|
|
||||||
This endpoint creates a auto reverse submarine swap
|
|
||||||
""",
|
|
||||||
response_description="create auto reverse swap",
|
|
||||||
response_model=AutoReverseSubmarineSwap,
|
|
||||||
dependencies=[Depends(require_admin_key)],
|
|
||||||
responses={
|
|
||||||
405: {
|
|
||||||
"description": "auto reverse swap is active, only 1 swap per wallet possible."
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
async def api_auto_reverse_submarineswap_create(data: CreateAutoReverseSubmarineSwap):
|
|
||||||
|
|
||||||
auto_swap = await get_auto_reverse_submarine_swap_by_wallet(data.wallet)
|
|
||||||
if auto_swap:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.METHOD_NOT_ALLOWED,
|
|
||||||
detail="auto reverse swap is active, only 1 swap per wallet possible.",
|
|
||||||
)
|
|
||||||
|
|
||||||
swap = await create_auto_reverse_submarine_swap(data)
|
|
||||||
return swap.dict() if swap else None
|
|
||||||
|
|
||||||
|
|
||||||
@boltz_ext.delete(
|
|
||||||
"/api/v1/swap/reverse/auto/{swap_id}",
|
|
||||||
name="boltz.delete /swap/reverse/auto",
|
|
||||||
summary="delete a auto reverse submarine swap",
|
|
||||||
description="""
|
|
||||||
This endpoint deletes a auto reverse submarine swap
|
|
||||||
""",
|
|
||||||
response_description="delete auto reverse swap",
|
|
||||||
dependencies=[Depends(require_admin_key)],
|
|
||||||
)
|
|
||||||
async def api_auto_reverse_submarineswap_delete(swap_id: str):
|
|
||||||
await delete_auto_reverse_submarine_swap(swap_id)
|
|
||||||
return "OK"
|
|
||||||
|
|
||||||
|
|
||||||
@boltz_ext.post(
|
|
||||||
"/api/v1/swap/status",
|
|
||||||
name="boltz.swap_status",
|
|
||||||
summary="shows the status of a swap",
|
|
||||||
description="""
|
|
||||||
This endpoint attempts to get the status of the swap.
|
|
||||||
""",
|
|
||||||
response_description="status of swap json",
|
|
||||||
dependencies=[Depends(require_admin_key)],
|
|
||||||
responses={
|
|
||||||
404: {"description": "when swap_id is not found"},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
async def api_swap_status(swap_id: str):
|
|
||||||
swap = await get_submarine_swap(swap_id) or await get_reverse_submarine_swap(
|
|
||||||
swap_id
|
|
||||||
)
|
|
||||||
if not swap:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=HTTPStatus.NOT_FOUND, detail="swap does not exist."
|
|
||||||
)
|
|
||||||
|
|
||||||
client = create_boltz_client()
|
|
||||||
status = client.swap_status(swap.boltz_id)
|
|
||||||
return status
|
|
||||||
|
|
||||||
|
|
||||||
@boltz_ext.get(
|
|
||||||
"/api/v1/swap/boltz",
|
|
||||||
name="boltz.get /swap/boltz",
|
|
||||||
summary="get a boltz configuration",
|
|
||||||
description="""
|
|
||||||
This endpoint gets configuration for boltz. (limits, fees...)
|
|
||||||
""",
|
|
||||||
response_description="dict of boltz config",
|
|
||||||
response_model=dict,
|
|
||||||
)
|
|
||||||
async def api_boltz_config():
|
|
||||||
client = create_boltz_client()
|
|
||||||
return {
|
|
||||||
"minimal": client.limit_minimal,
|
|
||||||
"maximal": client.limit_maximal,
|
|
||||||
"fee_percentage": client.fee_percentage,
|
|
||||||
}
|
|
|
@ -1,14 +0,0 @@
|
||||||
import pytest_asyncio
|
|
||||||
|
|
||||||
from lnbits.extensions.boltz.models import CreateReverseSubmarineSwap
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture(scope="session")
|
|
||||||
async def reverse_swap(from_wallet):
|
|
||||||
data = CreateReverseSubmarineSwap(
|
|
||||||
wallet=from_wallet.id,
|
|
||||||
instant_settlement=True,
|
|
||||||
onchain_address="bcrt1q4vfyszl4p8cuvqh07fyhtxve5fxq8e2ux5gx43",
|
|
||||||
amount=20_000,
|
|
||||||
)
|
|
||||||
return data
|
|
|
@ -1,102 +0,0 @@
|
||||||
import pytest
|
|
||||||
|
|
||||||
from tests.helpers import is_fake
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
|
||||||
async def test_mempool_url(client):
|
|
||||||
response = await client.get("/boltz/api/v1/swap/mempool")
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
|
||||||
async def test_boltz_config(client):
|
|
||||||
response = await client.get("/boltz/api/v1/swap/boltz")
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
|
||||||
async def test_endpoints_unauthenticated(client):
|
|
||||||
response = await client.get("/boltz/api/v1/swap?all_wallets=true")
|
|
||||||
assert response.status_code == 401
|
|
||||||
response = await client.get("/boltz/api/v1/swap/reverse?all_wallets=true")
|
|
||||||
assert response.status_code == 401
|
|
||||||
response = await client.post("/boltz/api/v1/swap")
|
|
||||||
assert response.status_code == 401
|
|
||||||
response = await client.post("/boltz/api/v1/swap/reverse")
|
|
||||||
assert response.status_code == 401
|
|
||||||
response = await client.post("/boltz/api/v1/swap/status")
|
|
||||||
assert response.status_code == 401
|
|
||||||
response = await client.post("/boltz/api/v1/swap/check")
|
|
||||||
assert response.status_code == 401
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
|
||||||
async def test_endpoints_inkey(client, inkey_headers_to):
|
|
||||||
response = await client.get(
|
|
||||||
"/boltz/api/v1/swap?all_wallets=true", headers=inkey_headers_to
|
|
||||||
)
|
|
||||||
assert response.status_code == 200
|
|
||||||
response = await client.get(
|
|
||||||
"/boltz/api/v1/swap/reverse?all_wallets=true", headers=inkey_headers_to
|
|
||||||
)
|
|
||||||
assert response.status_code == 200
|
|
||||||
|
|
||||||
response = await client.post("/boltz/api/v1/swap", headers=inkey_headers_to)
|
|
||||||
assert response.status_code == 401
|
|
||||||
response = await client.post("/boltz/api/v1/swap/reverse", headers=inkey_headers_to)
|
|
||||||
assert response.status_code == 401
|
|
||||||
response = await client.post("/boltz/api/v1/swap/refund", headers=inkey_headers_to)
|
|
||||||
assert response.status_code == 401
|
|
||||||
response = await client.post("/boltz/api/v1/swap/status", headers=inkey_headers_to)
|
|
||||||
assert response.status_code == 401
|
|
||||||
response = await client.post("/boltz/api/v1/swap/check", headers=inkey_headers_to)
|
|
||||||
assert response.status_code == 401
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
|
||||||
async def test_endpoints_adminkey_badrequest(client, adminkey_headers_to):
|
|
||||||
response = await client.post("/boltz/api/v1/swap", headers=adminkey_headers_to)
|
|
||||||
assert response.status_code == 400
|
|
||||||
response = await client.post(
|
|
||||||
"/boltz/api/v1/swap/reverse", headers=adminkey_headers_to
|
|
||||||
)
|
|
||||||
assert response.status_code == 400
|
|
||||||
response = await client.post(
|
|
||||||
"/boltz/api/v1/swap/refund", headers=adminkey_headers_to
|
|
||||||
)
|
|
||||||
assert response.status_code == 400
|
|
||||||
response = await client.post(
|
|
||||||
"/boltz/api/v1/swap/status", headers=adminkey_headers_to
|
|
||||||
)
|
|
||||||
assert response.status_code == 400
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
|
||||||
@pytest.mark.skipif(is_fake, reason="this test is only passes with regtest")
|
|
||||||
async def test_endpoints_adminkey_regtest(client, from_wallet, adminkey_headers_to):
|
|
||||||
swap = {
|
|
||||||
"wallet": from_wallet.id,
|
|
||||||
"refund_address": "bcrt1q3cwq33y435h52gq3qqsdtczh38ltlnf69zvypm",
|
|
||||||
"amount": 50_000,
|
|
||||||
}
|
|
||||||
response = await client.post(
|
|
||||||
"/boltz/api/v1/swap", json=swap, headers=adminkey_headers_to
|
|
||||||
)
|
|
||||||
assert response.status_code == 201
|
|
||||||
|
|
||||||
reverse_swap = {
|
|
||||||
"wallet": from_wallet.id,
|
|
||||||
"instant_settlement": True,
|
|
||||||
"onchain_address": "bcrt1q4vfyszl4p8cuvqh07fyhtxve5fxq8e2ux5gx43",
|
|
||||||
"amount": 50_000,
|
|
||||||
}
|
|
||||||
response = await client.post(
|
|
||||||
"/boltz/api/v1/swap/reverse", json=reverse_swap, headers=adminkey_headers_to
|
|
||||||
)
|
|
||||||
assert response.status_code == 201
|
|
Loading…
Add table
Reference in a new issue