diff --git a/boltz_client/boltz.py b/boltz_client/boltz.py index bd19410..e1bc1ab 100644 --- a/boltz_client/boltz.py +++ b/boltz_client/boltz.py @@ -114,36 +114,44 @@ def __init__(self, config: BoltzConfig, pair: str = "BTC/BTC"): f"invalid pair {pair}, possible pairs: {', '.join(self._cfg.pairs)}" ) self.pair = pair - self.pairs = self.get_pairs() - self.fees = self.pairs[self.pair]["fees"] - self.limits = self.pairs[self.pair]["limits"] if self.pair == "L-BTC/BTC": self.network = self._cfg.network_liquid else: self.network = self._cfg.network + return None - def request(self, funcname, *args, **kwargs) -> dict: + async def init_pairs(self): + self.pairs = await self.get_pairs() + self.fees = self.pairs[self.pair]["fees"] + self.limits = self.pairs[self.pair]["limits"] + + async def request(self, funcname, *args, **kwargs) -> dict: try: - return req_wrap(funcname, *args, **kwargs) + return await req_wrap(funcname, *args, **kwargs) except httpx.RequestError as exc: msg = f"unreachable: {exc.request.url!r}." raise BoltzApiException(f"boltz api connection error: {msg}") from exc except httpx.HTTPStatusError as exc: - if exc.response.status_code == 404: - raise BoltzNotFoundException(exc.response.json()["error"]) from exc - msg = f"{exc.response.status_code} while requesting {exc.request.url!r}. message: {exc.response.json()['error']}" + try: + err_msg = exc.response.json()["error"] + except Exception: + err_msg = str(exc) + code = exc.response.status_code + if code == 404: + raise BoltzNotFoundException(err_msg) from exc + msg = f"{code} while requesting {exc.request.url!r}. message: {err_msg}" raise BoltzApiException(f"boltz api status error: {msg}") from exc - def check_version(self): - return self.request( + async def check_version(self): + return await self.request( "get", f"{self._cfg.api_url}/version", headers={"Content-Type": "application/json"}, ) - def send_onchain_tx(self, rawtw: str) -> str: - data = self.request( + async def send_onchain_tx(self, rawtw: str) -> str: + data = await self.request( "post", f"{self._cfg.api_url}/broadcasttransaction", headers={"Content-Type": "application/json"}, @@ -168,8 +176,8 @@ def get_fee_estimation_claim(self) -> int: def get_fee_estimation_refund(self) -> int: return self.fees["minerFees"]["baseAsset"]["normal"] - def get_pairs(self) -> dict: - data = self.request( + async def get_pairs(self) -> dict: + data = await self.request( "get", f"{self._cfg.api_url}/getpairs", headers={"Content-Type": "application/json"}, @@ -185,8 +193,8 @@ def check_limits(self, amount: int) -> None: f"min: {limits['minimal']}, max: {limits['maximal']}" ) - def swap_status(self, boltz_id: str) -> BoltzSwapStatusResponse: - data = self.request( + async def swap_status(self, boltz_id: str) -> BoltzSwapStatusResponse: + data = await self.request( "post", f"{self._cfg.api_url}/swapstatus", json={"id": boltz_id}, @@ -199,8 +207,8 @@ def swap_status(self, boltz_id: str) -> BoltzSwapStatusResponse: return status - def swap_transaction(self, boltz_id: str) -> BoltzSwapTransactionResponse: - data = self.request( + async def swap_transaction(self, boltz_id: str) -> BoltzSwapTransactionResponse: + data = await self.request( "post", f"{self._cfg.api_url}/getswaptransaction", json={"id": boltz_id}, @@ -216,7 +224,7 @@ def swap_transaction(self, boltz_id: str) -> BoltzSwapTransactionResponse: async def wait_for_tx(self, boltz_id: str) -> str: while True: try: - swap_transaction = self.swap_transaction(boltz_id) + swap_transaction = await self.swap_transaction(boltz_id) assert swap_transaction.transactionHex return swap_transaction.transactionHex except (ValueError, BoltzApiException, BoltzSwapTransactionException): @@ -225,7 +233,7 @@ async def wait_for_tx(self, boltz_id: str) -> str: async def wait_for_tx_on_status(self, boltz_id: str, zeroconf: bool = True) -> str: while True: try: - status = self.swap_status(boltz_id) + status = await self.swap_status(boltz_id) assert status.transaction tx_hex = status.transaction.get("hex") assert tx_hex @@ -295,12 +303,12 @@ async def refund_swap( blinding_key=blinding_key, fees=self.get_fee_estimation_refund(), ) - return self.send_onchain_tx(transaction) + return await self.send_onchain_tx(transaction) - def create_swap(self, payment_request: str) -> tuple[str, BoltzSwapResponse]: + async 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.network, self.pair) - data = self.request( + data = await self.request( "post", f"{self._cfg.api_url}/createswap", json={ @@ -315,14 +323,14 @@ def create_swap(self, payment_request: str) -> tuple[str, BoltzSwapResponse]: ) return refund_privkey_wif, BoltzSwapResponse(**data) - def create_reverse_swap( + async def create_reverse_swap( self, amount: int = 0 ) -> 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.network, self.pair) preimage_hex, preimage_hash = create_preimage() - data = self.request( + data = await self.request( "post", f"{self._cfg.api_url}/createswap", json={ diff --git a/boltz_client/cli.py b/boltz_client/cli.py index cfea750..9e85e44 100644 --- a/boltz_client/cli.py +++ b/boltz_client/cli.py @@ -34,7 +34,7 @@ def command_group(): @click.command() @click.argument("payment_request", type=str) @click.argument("pair", type=str, default="BTC/BTC") -def create_swap(payment_request: str, pair: str = "BTC/BTC"): +async def create_swap(payment_request: str, pair: str = "BTC/BTC"): """ create a swap boltz will pay your invoice after you paid the onchain address @@ -43,7 +43,7 @@ def create_swap(payment_request: str, pair: str = "BTC/BTC"): PAYMENT_REQUEST with the same amount as specified in SATS """ client = BoltzClient(config, pair) - refund_privkey_wif, swap = client.create_swap(payment_request) + refund_privkey_wif, swap = await client.create_swap(payment_request) click.echo() click.echo(f"boltz_id: {swap.id}") @@ -109,7 +109,9 @@ def refund_swap( @click.argument("sats", type=int) @click.argument("pair", type=str, default="BTC/BTC") @click.argument("direction", type=str, default="send") -def create_reverse_swap(sats: int, pair: str = "BTC/BTC", direction: str = "send"): +async def create_reverse_swap( + sats: int, pair: str = "BTC/BTC", direction: str = "send" +): """ create a reverse swap """ @@ -123,7 +125,7 @@ def create_reverse_swap(sats: int, pair: str = "BTC/BTC", direction: str = "send raise ValueError( f"direction must be '{SwapDirection.send}' or '{SwapDirection.receive}'" ) - claim_privkey_wif, preimage_hex, swap = client.create_reverse_swap(sats) + claim_privkey_wif, preimage_hex, swap = await client.create_reverse_swap(sats) click.echo("reverse swap created!") click.echo() @@ -155,7 +157,7 @@ def create_reverse_swap(sats: int, pair: str = "BTC/BTC", direction: str = "send @click.argument("pair", type=str, default="BTC/BTC") @click.argument("zeroconf", type=bool, default=True) @click.argument("direction", type=str, default="send") -def create_reverse_swap_and_claim( +async def create_reverse_swap_and_claim( receive_address: str, sats: int, pair: str = "BTC/BTC", @@ -166,6 +168,7 @@ def create_reverse_swap_and_claim( create a reverse swap and claim """ client = BoltzClient(config, pair) + await client.init_pairs() if direction == SwapDirection.receive: sats = client.add_reverse_swap_fees(sats) elif direction == SwapDirection.send: @@ -176,7 +179,7 @@ def create_reverse_swap_and_claim( f"direction must be '{SwapDirection.send}' or '{SwapDirection.receive}'" ) - claim_privkey_wif, preimage_hex, swap = client.create_reverse_swap(sats) + claim_privkey_wif, preimage_hex, swap = await client.create_reverse_swap(sats) click.echo("reverse swap created!") click.echo() @@ -224,7 +227,7 @@ def create_reverse_swap_and_claim( @click.argument("pair", type=str, default="BTC/BTC") @click.argument("zeroconf", type=bool, default=True) @click.argument("blinding_key", type=str, default=None) -def claim_reverse_swap( +async def claim_reverse_swap( boltz_id: str, lockup_address: str, receive_address: str, @@ -239,6 +242,7 @@ def claim_reverse_swap( claims a reverse swap """ client = BoltzClient(config, pair) + await client.init_pairs() txid = asyncio.run( client.claim_reverse_swap( @@ -259,7 +263,7 @@ def claim_reverse_swap( @click.command() @click.argument("swap_id", type=str) -def swap_status(swap_id): +async def swap_status(swap_id): """ get swap status retrieves the status of your boltz swap from the api @@ -267,27 +271,30 @@ def swap_status(swap_id): ID is the id of your boltz swap """ client = BoltzClient(config) + await client.init_pairs() data = client.swap_status(swap_id) click.echo(data) @click.command() @click.argument("amount", type=int) -def calculate_swap_send_amount(amount): +async def calculate_swap_send_amount(amount): """ calculate the amount of the invoice you have to send to boltz to send the specified amount onchain """ client = BoltzClient(config) + await client.init_pairs() click.echo(client.substract_swap_fees(amount)) @click.command() -def show_pairs(): +async def show_pairs(): """ show pairs of possible assets to swap """ client = BoltzClient(config) + await client.init_pairs() data = client.get_pairs() click.echo(json.dumps(data)) diff --git a/boltz_client/helpers.py b/boltz_client/helpers.py index 9a82cd4..d487714 100644 --- a/boltz_client/helpers.py +++ b/boltz_client/helpers.py @@ -1,15 +1,16 @@ """boltz_client helpers""" -import httpx +from httpx import AsyncClient -def req_wrap(funcname, *args, **kwargs) -> dict: +async def req_wrap(funcname, *args, **kwargs) -> dict: """request wrapper for httpx""" - func = getattr(httpx, funcname) - res = func(*args, timeout=30, **kwargs) - res.raise_for_status() - return ( - res.json() - if kwargs["headers"]["Content-Type"] == "application/json" - else {"text": res.text} - ) + async with AsyncClient(follow_redirects=True) as client: + func = getattr(client, funcname) + res = await func(*args, timeout=30, **kwargs) + res.raise_for_status() + return ( + res.json() + if kwargs["headers"]["Content-Type"] == "application/json" + else {"text": res.text} + ) diff --git a/pyproject.toml b/pyproject.toml index 6e8bf8a..a1257f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,8 +8,8 @@ urls = { Homepage = "https://lnbits.com", Repository = "https://github.com/lnbit readme = "README.md" dependencies = [ "lnbits>1" ] -[tool.uv] -dev-dependencies = [ +[dependency-groups] +dev = [ "black", "pytest-asyncio", "pytest", diff --git a/tasks.py b/tasks.py index cd25969..3d95313 100644 --- a/tasks.py +++ b/tasks.py @@ -65,8 +65,8 @@ async def check_for_auto_swap(payment: Payment) -> None: f"{auto_swap.feerate_limit}, actual fees: {fees}" ) return - claim_privkey_wif, preimage_hex, swap = client.create_reverse_swap( - amount=int(amount) + claim_privkey_wif, preimage_hex, swap = ( + await client.create_reverse_swap(amount=int(amount)) ) new_swap = await create_reverse_submarine_swap( CreateReverseSubmarineSwap( diff --git a/utils.py b/utils.py index f834910..8de6305 100644 --- a/utils.py +++ b/utils.py @@ -20,7 +20,9 @@ async def create_boltz_client(pair: str = "BTC/BTC") -> BoltzClient: network=settings.boltz_network, network_liquid=settings.boltz_network_liquid, ) - return BoltzClient(config, pair) + client = BoltzClient(config, pair) + await client.init_pairs() + return client async def check_balance(data) -> bool: diff --git a/views_api.py b/views_api.py index 11bddbf..60632e3 100644 --- a/views_api.py +++ b/views_api.py @@ -203,7 +203,7 @@ async def api_submarineswap_create(data: CreateSubmarineSwap) -> SubmarineSwap: expiry=60 * 60 * 24, # 1 day ) try: - refund_privkey_wif, swap = client.create_swap(payment.bolt11) + refund_privkey_wif, swap = await client.create_swap(payment.bolt11) except Exception as exc: logger.error(exc) raise HTTPException( @@ -281,7 +281,7 @@ async def api_reverse_submarineswap_create( ) try: - claim_privkey_wif, preimage_hex, swap = client.create_reverse_swap( + claim_privkey_wif, preimage_hex, swap = await client.create_reverse_swap( amount=amount, ) except Exception as exc: