Skip to content

Commit

Permalink
feat: add liquid support
Browse files Browse the repository at this point in the history
introducing pair to BoltzClient constructor, add show-pairs cli command
better tests, some refactors
blindingkey
  • Loading branch information
dni committed Nov 6, 2023
1 parent 83be105 commit 2f83b02
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 73 deletions.
82 changes: 57 additions & 25 deletions boltz_client/boltz.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
""" boltz_client main module """

import asyncio
from dataclasses import dataclass
from typing import Optional
from dataclasses import dataclass, field
from typing import Optional, List

from embit.liquid.addresses import to_unconfidential

import httpx

Expand Down Expand Up @@ -34,6 +36,10 @@ class BoltzNotFoundException(Exception):
pass


class BoltzPairException(Exception):
pass


class BoltzSwapStatusException(Exception):
def __init__(self, message: str, status: str):
self.message = message
Expand Down Expand Up @@ -69,6 +75,7 @@ class BoltzSwapResponse:
acceptZeroConf: bool
expectedAmount: int
timeoutBlockHeight: int
blindingKey: Optional[str] = None


@dataclass
Expand All @@ -79,25 +86,38 @@ class BoltzReverseSwapResponse:
lockupAddress: str
timeoutBlockHeight: int
onchainAmount: int
blindingKey: Optional[str] = None


@dataclass
class BoltzConfig:
network: str = "main"
pairs: list = ["BTC/BTC", "L-BTC/BTC"]
api_url: str = "https://boltz.exchange/api"
mempool_url: str = "https://mempool.space/api/v1"
mempool_ws_url: str = "wss://mempool.space/api/v1/ws"
mempool_liquid_url: str = "https://liquid.network/api/v1"
mempool_liquid_ws_url: str = "wss://liquid.network/api/v1/ws"
referral_id: str = "dni"


class BoltzClient:
def __init__(self, config: BoltzConfig):
def __init__(self, config: BoltzConfig, pair: str = "BTC/BTC"):
self._cfg = config
self.limit_minimal = 0
self.limit_maximal = 0
self.fee_percentage = 0
self.set_limits()
self.mempool = MempoolClient(self._cfg.mempool_url, self._cfg.mempool_ws_url)
if pair not in self._cfg.pairs:
raise BoltzPairException(f"invalid pair {pair}, possible pairs: {','.join(self._cfg.pairs)}")
self.pair = pair
self.pairs = self.get_pairs()
self.init_mempool()

def init_mempool(self):
if self.pair == "L-BTC/BTC":
mempool_url = self._cfg.mempool_liquid_url
mempool_ws_url = self._cfg.mempool_liquid_ws_url
else:
mempool_url = self._cfg.mempool_url
mempool_ws_url = self._cfg.mempool_ws_url
self.mempool = MempoolClient(mempool_url, mempool_ws_url)

def request(self, funcname, *args, **kwargs) -> dict:
try:
Expand Down Expand Up @@ -125,25 +145,21 @@ def get_fee_estimation(self, feerate: Optional[int]) -> int:
mempool_fees = feerate if feerate else self.mempool.get_fees()
return mempool_fees * tx_size_vbyte

def set_limits(self) -> None:
def get_pairs(self) -> dict:
data = self.request(
"get",
f"{self._cfg.api_url}/getpairs",
headers={"Content-Type": "application/json"},
)
pair = data["pairs"]["BTC/BTC"]
limits = pair["limits"]
fees = pair["fees"]
self.limit_maximal = limits["maximal"]
self.limit_minimal = limits["minimal"]
self.fee_percentage = fees["percentage"]
return data["pairs"]

def check_limits(self, amount: int) -> None:
valid = self.limit_minimal <= amount <= self.limit_maximal
limits = self.pairs[self.pair]["limits"]
valid = limits["minimal"] <= amount <= limits["maximal"]
if not valid:
raise BoltzLimitException(
f"Boltz - swap not in boltz limits, amount: {amount}, "
f"min: {self.limit_minimal}, max: {self.limit_maximal}"
f"min: {limits['minimal']}, max: {limits['maximal']}"
)

def swap_status(self, boltz_id: str) -> BoltzSwapStatusResponse:
Expand Down Expand Up @@ -209,21 +225,36 @@ async def claim_reverse_swap(
privkey_wif: str,
preimage_hex: str,
redeem_script_hex: str,
zeroconf: bool = False,
zeroconf: bool = True,
feerate: Optional[int] = None,
blinding_key: Optional[str] = None,
):

self.validate_address(receive_address)

if (self.pair == "L-BTC/BTC"):
unconfidential_lockup_address = to_unconfidential(lockup_address)
if not unconfidential_lockup_address:
raise BoltzApiException("can not unconfidentialize lockup address")
lockup_address = to_unconfidential(lockup_address) or ""
print(lockup_address)

lockup_txid = await self.wait_for_txid_on_status(boltz_id)
lockup_tx = await self.mempool.get_tx_from_txid(lockup_txid, lockup_address)
# lockup_tx = await self.mempool.get_tx_from_address(lockup_address)

if not zeroconf and lockup_tx.status != "confirmed":
await self.mempool.wait_for_tx_confirmed(lockup_tx.txid)
txid, transaction = create_claim_tx(

txid, transaction, _ = create_claim_tx(
lockup_tx=lockup_tx,
receive_address=receive_address,
privkey_wif=privkey_wif,
redeem_script_hex=redeem_script_hex,
preimage_hex=preimage_hex,
fees=self.get_fee_estimation(feerate),
pair=self.pair,
blinding_key=blinding_key,
)
self.mempool.send_onchain_tx(transaction)
return txid
Expand All @@ -242,27 +273,28 @@ async def refund_swap(
self.mempool.check_block_height(timeout_block_height)
lockup_txid = await self.wait_for_txid(boltz_id)
lockup_tx = await self.mempool.get_tx_from_txid(lockup_txid, lockup_address)
txid, transaction = create_refund_tx(
# lockup_tx = await self.mempool.get_tx_from_address(lockup_address)
txid, transaction, _ = create_refund_tx(
lockup_tx=lockup_tx,
privkey_wif=privkey_wif,
receive_address=receive_address,
redeem_script_hex=redeem_script_hex,
timeout_block_height=timeout_block_height,
fees=self.get_fee_estimation(feerate),
pair=self.pair,
)

self.mempool.send_onchain_tx(transaction)
return txid

def create_swap(self, payment_request: str) -> tuple[str, BoltzSwapResponse]:
"""create swap and return private key and boltz response"""
refund_privkey_wif, refund_pubkey_hex = create_key_pair(self._cfg.network)
refund_privkey_wif, refund_pubkey_hex = create_key_pair(self._cfg.network, self.pair)
data = self.request(
"post",
f"{self._cfg.api_url}/createswap",
json={
"type": "submarine",
"pairId": "BTC/BTC",
"pairId": self.pair,
"orderSide": "sell",
"refundPublicKey": refund_pubkey_hex,
"invoice": payment_request,
Expand All @@ -277,14 +309,14 @@ def create_reverse_swap(
) -> tuple[str, str, BoltzReverseSwapResponse]:
"""create reverse swap and return privkey, preimage and boltz response"""
self.check_limits(amount)
claim_privkey_wif, claim_pubkey_hex = create_key_pair(self._cfg.network)
claim_privkey_wif, claim_pubkey_hex = create_key_pair(self._cfg.network, self.pair)
preimage_hex, preimage_hash = create_preimage()
data = self.request(
"post",
f"{self._cfg.api_url}/createswap",
json={
"type": "reversesubmarine",
"pairId": "BTC/BTC",
"pairId": self.pair,
"orderSide": "buy",
"invoiceAmount": amount,
"preimageHash": preimage_hash,
Expand Down
Loading

0 comments on commit 2f83b02

Please sign in to comment.