boltz_client 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of boltz_client might be problematic. Click here for more details.
- {boltz_client-0.2.0 → boltz_client-0.2.1}/PKG-INFO +1 -1
- {boltz_client-0.2.0 → boltz_client-0.2.1}/boltz_client/boltz.py +2 -2
- {boltz_client-0.2.0 → boltz_client-0.2.1}/boltz_client/cli.py +11 -0
- {boltz_client-0.2.0 → boltz_client-0.2.1}/boltz_client/mempool.py +11 -6
- {boltz_client-0.2.0 → boltz_client-0.2.1}/boltz_client/onchain_wally.py +135 -31
- {boltz_client-0.2.0 → boltz_client-0.2.1}/pyproject.toml +2 -1
- {boltz_client-0.2.0 → boltz_client-0.2.1}/LICENSE +0 -0
- {boltz_client-0.2.0 → boltz_client-0.2.1}/README.md +0 -0
- {boltz_client-0.2.0 → boltz_client-0.2.1}/boltz_client/__init__.py +0 -0
- {boltz_client-0.2.0 → boltz_client-0.2.1}/boltz_client/helpers.py +0 -0
- {boltz_client-0.2.0 → boltz_client-0.2.1}/boltz_client/onchain.py +0 -0
- {boltz_client-0.2.0 → boltz_client-0.2.1}/boltz_client/py.typed +0 -0
|
@@ -100,8 +100,8 @@ class BoltzConfig:
|
|
|
100
100
|
network_liquid: str = "liquidv1"
|
|
101
101
|
pairs: list = field(default_factory=lambda: ["BTC/BTC", "L-BTC/BTC"])
|
|
102
102
|
api_url: str = "https://boltz.exchange/api"
|
|
103
|
-
mempool_url: str = "https://mempool.space/api
|
|
104
|
-
mempool_liquid_url: str = "https://liquid.network/api
|
|
103
|
+
mempool_url: str = "https://mempool.space/api"
|
|
104
|
+
mempool_liquid_url: str = "https://liquid.network/api"
|
|
105
105
|
referral_id: str = "dni"
|
|
106
106
|
|
|
107
107
|
|
|
@@ -294,10 +294,21 @@ def show_pairs():
|
|
|
294
294
|
click.echo(json.dumps(data))
|
|
295
295
|
|
|
296
296
|
|
|
297
|
+
@click.command()
|
|
298
|
+
def get_fees():
|
|
299
|
+
"""
|
|
300
|
+
show mempool recommended fees
|
|
301
|
+
"""
|
|
302
|
+
client = BoltzClient(config)
|
|
303
|
+
fees = client.mempool.get_fees()
|
|
304
|
+
click.echo(fees)
|
|
305
|
+
|
|
306
|
+
|
|
297
307
|
def main():
|
|
298
308
|
"""main function"""
|
|
299
309
|
command_group.add_command(swap_status)
|
|
300
310
|
command_group.add_command(show_pairs)
|
|
311
|
+
command_group.add_command(get_fees)
|
|
301
312
|
command_group.add_command(create_swap)
|
|
302
313
|
command_group.add_command(refund_swap)
|
|
303
314
|
command_group.add_command(create_reverse_swap)
|
|
@@ -108,7 +108,7 @@ class MempoolClient:
|
|
|
108
108
|
txid=tx["txid"],
|
|
109
109
|
script_pub_key=vout["scriptpubkey_address"],
|
|
110
110
|
vout_cnt=i,
|
|
111
|
-
vout_amount=vout
|
|
111
|
+
vout_amount=vout.get("value") or 0,
|
|
112
112
|
status=status,
|
|
113
113
|
)
|
|
114
114
|
return None
|
|
@@ -136,10 +136,13 @@ class MempoolClient:
|
|
|
136
136
|
|
|
137
137
|
async def get_tx_from_txid(self, txid: str, address: str) -> LockupData:
|
|
138
138
|
while True:
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
try:
|
|
140
|
+
tx = self.get_tx(txid)
|
|
141
|
+
output = self.find_output(tx, address)
|
|
142
|
+
if output:
|
|
143
|
+
return output
|
|
144
|
+
except MempoolApiException:
|
|
145
|
+
pass
|
|
143
146
|
await asyncio.sleep(3)
|
|
144
147
|
|
|
145
148
|
async def get_tx_from_address(self, address: str) -> LockupData:
|
|
@@ -152,9 +155,11 @@ class MempoolClient:
|
|
|
152
155
|
return lockup_tx
|
|
153
156
|
|
|
154
157
|
def get_fees(self) -> int:
|
|
158
|
+
# mempool.space quirk, needed for regtest
|
|
159
|
+
api_url = self._api_url.replace("/v1", "")
|
|
155
160
|
data = self.request(
|
|
156
161
|
"get",
|
|
157
|
-
f"{
|
|
162
|
+
f"{api_url}/v1/fees/recommended",
|
|
158
163
|
headers={"Content-Type": "application/json"},
|
|
159
164
|
)
|
|
160
165
|
return int(data["halfHourFee"])
|
|
@@ -7,12 +7,73 @@ special thanks to @jgriffiths for helping debugging this!
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
9
|
import secrets
|
|
10
|
-
from
|
|
10
|
+
from dataclasses import dataclass
|
|
11
|
+
from typing import Any, Optional
|
|
11
12
|
|
|
12
13
|
from .mempool import LockupData
|
|
13
14
|
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
@dataclass
|
|
17
|
+
class Network:
|
|
18
|
+
name: str
|
|
19
|
+
lbtc_asset: bytes
|
|
20
|
+
blech32_prefix: str
|
|
21
|
+
bech32_prefix: str
|
|
22
|
+
|
|
23
|
+
def wif_net(self, wally) -> Any:
|
|
24
|
+
if self.name == "mainnet":
|
|
25
|
+
return wally.WALLY_ADDRESS_VERSION_WIF_MAINNET
|
|
26
|
+
return wally.WALLY_ADDRESS_VERSION_WIF_TESTNET
|
|
27
|
+
|
|
28
|
+
def blinded_prefix(self, wally) -> Any:
|
|
29
|
+
if self.name == "mainnet":
|
|
30
|
+
return wally.WALLY_CA_PREFIX_LIQUID
|
|
31
|
+
if self.name == "testnet":
|
|
32
|
+
return wally.WALLY_CA_PREFIX_LIQUID_TESTNET
|
|
33
|
+
return wally.WALLY_CA_PREFIX_LIQUID_REGTEST
|
|
34
|
+
|
|
35
|
+
def wally_network(self, wally) -> Any:
|
|
36
|
+
if self.name == "mainnet":
|
|
37
|
+
return wally.WALLY_NETWORK_LIQUID
|
|
38
|
+
if self.name == "testnet":
|
|
39
|
+
return wally.WALLY_NETWORK_LIQUID_TESTNET
|
|
40
|
+
return wally.WALLY_NETWORK_LIQUID_REGTEST
|
|
41
|
+
|
|
42
|
+
@staticmethod
|
|
43
|
+
def parse_asset(asset: str) -> bytes:
|
|
44
|
+
return bytes.fromhex(asset)[::-1]
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# TODO: is this type hint compatible with all support Python versions of lnbits
|
|
48
|
+
NETWORKS: list[Network] = [
|
|
49
|
+
Network(
|
|
50
|
+
name="mainnet",
|
|
51
|
+
lbtc_asset=Network.parse_asset(
|
|
52
|
+
"6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d"
|
|
53
|
+
),
|
|
54
|
+
blech32_prefix="lq",
|
|
55
|
+
bech32_prefix="ex",
|
|
56
|
+
),
|
|
57
|
+
Network(
|
|
58
|
+
name="testnet",
|
|
59
|
+
lbtc_asset=Network.parse_asset(
|
|
60
|
+
"144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49"
|
|
61
|
+
),
|
|
62
|
+
blech32_prefix="tlq",
|
|
63
|
+
bech32_prefix="tex",
|
|
64
|
+
),
|
|
65
|
+
Network(
|
|
66
|
+
name="regtest",
|
|
67
|
+
lbtc_asset=Network.parse_asset(
|
|
68
|
+
"5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225"
|
|
69
|
+
),
|
|
70
|
+
blech32_prefix="el",
|
|
71
|
+
bech32_prefix="ert",
|
|
72
|
+
),
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_entropy(num_outputs_to_blind: int) -> bytes:
|
|
16
77
|
# For each output to blind, we need 32 bytes of entropy for each of:
|
|
17
78
|
# - Output assetblinder
|
|
18
79
|
# - Output amountblinder
|
|
@@ -22,6 +83,69 @@ def get_entropy(num_outputs_to_blind):
|
|
|
22
83
|
return secrets.token_bytes(num_outputs_to_blind * 5 * 32)
|
|
23
84
|
|
|
24
85
|
|
|
86
|
+
def get_address_network(wally, address: str) -> Network:
|
|
87
|
+
def address_has_network_prefix(n: Network) -> bool:
|
|
88
|
+
# If address decoding doesn't fail -> correct network
|
|
89
|
+
try:
|
|
90
|
+
decode_address(wally, n, address)
|
|
91
|
+
return True
|
|
92
|
+
except Exception:
|
|
93
|
+
return False
|
|
94
|
+
|
|
95
|
+
network = next(
|
|
96
|
+
(network for network in NETWORKS if address_has_network_prefix(network)),
|
|
97
|
+
None,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if network is None:
|
|
101
|
+
raise ValueError("Unknown network of address")
|
|
102
|
+
|
|
103
|
+
return network
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def is_possible_confidential_address(wally, address) -> bool:
|
|
107
|
+
expected_len = (
|
|
108
|
+
2 + wally.EC_PUBLIC_KEY_LEN + wally.HASH160_LEN + wally.BASE58_CHECKSUM_LEN
|
|
109
|
+
)
|
|
110
|
+
try:
|
|
111
|
+
return wally.base58_n_get_length(address, len(address)) == expected_len
|
|
112
|
+
except ValueError:
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
# TODO: is this type hint compatible with all support Python versions of lnbits
|
|
117
|
+
def decode_address(
|
|
118
|
+
wally, network: Network, address: str
|
|
119
|
+
) -> tuple[bytearray, bytearray]:
|
|
120
|
+
if address.lower().startswith(network.blech32_prefix):
|
|
121
|
+
blinding_key = wally.confidential_addr_segwit_to_ec_public_key(
|
|
122
|
+
address, network.blech32_prefix
|
|
123
|
+
)
|
|
124
|
+
unconfidential_address = wally.confidential_addr_to_addr_segwit(
|
|
125
|
+
address, network.blech32_prefix, network.bech32_prefix
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return blinding_key, wally.addr_segwit_to_bytes(
|
|
129
|
+
unconfidential_address, network.bech32_prefix, 0
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if is_possible_confidential_address(wally, address):
|
|
133
|
+
unconfidential_address = wally.confidential_addr_to_addr(
|
|
134
|
+
address, network.blinded_prefix(wally)
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
blinding_key = wally.confidential_addr_to_ec_public_key(
|
|
138
|
+
address,
|
|
139
|
+
network.blinded_prefix(wally),
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
return blinding_key, wally.address_to_scriptpubkey(
|
|
143
|
+
unconfidential_address, network.wally_network(wally)
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
raise ValueError("only confidential addresses are supported")
|
|
147
|
+
|
|
148
|
+
|
|
25
149
|
def create_liquid_tx(
|
|
26
150
|
lockup_tx: LockupData,
|
|
27
151
|
receive_address: str,
|
|
@@ -33,7 +157,6 @@ def create_liquid_tx(
|
|
|
33
157
|
preimage_hex: str = "",
|
|
34
158
|
blinding_key: Optional[str] = None,
|
|
35
159
|
) -> str:
|
|
36
|
-
|
|
37
160
|
try:
|
|
38
161
|
import wallycore as wally
|
|
39
162
|
except ImportError as exc:
|
|
@@ -41,28 +164,13 @@ def create_liquid_tx(
|
|
|
41
164
|
"`wallycore` is not installed, but required for liquid support."
|
|
42
165
|
) from exc
|
|
43
166
|
|
|
44
|
-
|
|
45
|
-
lasset_hex = "5ac9f65c0efcc4775e0baec4ec03abdde22473cd3cf33c0419ca290e0751b225"
|
|
46
|
-
confidential_addr_prefix = "ert"
|
|
47
|
-
confidential_addr_family = "el"
|
|
48
|
-
elif receive_address.startswith("tex") or receive_address.startswith("tlq"):
|
|
49
|
-
lasset_hex = "144c654344aa716d6f3abcc1ca90e5641e4e2a7f633bc09fe3baf64585819a49"
|
|
50
|
-
confidential_addr_prefix = "tex"
|
|
51
|
-
confidential_addr_family = "tlq"
|
|
52
|
-
elif receive_address.startswith("ex") or receive_address.startswith("lq"):
|
|
53
|
-
lasset_hex = "6f0279e9ed041c3d710a9f57d0c02928416460c4b722ae3457a11eec381c526d"
|
|
54
|
-
confidential_addr_prefix = "ex"
|
|
55
|
-
confidential_addr_family = "lq"
|
|
56
|
-
else:
|
|
57
|
-
raise ValueError(f"Unknown prefix: {receive_address[:3]}")
|
|
58
|
-
|
|
59
|
-
LASSET = bytes.fromhex(lasset_hex)[::-1]
|
|
167
|
+
network = get_address_network(wally, receive_address)
|
|
60
168
|
|
|
61
169
|
redeem_script = bytes.fromhex(redeem_script_hex)
|
|
62
170
|
preimage = bytes.fromhex(preimage_hex)
|
|
63
171
|
private_key = wally.wif_to_bytes(
|
|
64
172
|
privkey_wif,
|
|
65
|
-
wally
|
|
173
|
+
network.wif_net(wally),
|
|
66
174
|
wally.WALLY_WIF_FLAG_COMPRESSED,
|
|
67
175
|
) # type: ignore
|
|
68
176
|
|
|
@@ -72,15 +180,9 @@ def create_liquid_tx(
|
|
|
72
180
|
except ValueError as exc:
|
|
73
181
|
raise ValueError("blinding_key must be hex encoded") from exc
|
|
74
182
|
|
|
75
|
-
receive_blinding_pubkey =
|
|
76
|
-
|
|
77
|
-
)
|
|
78
|
-
receive_unconfidential_address = wally.confidential_addr_to_addr_segwit(
|
|
79
|
-
receive_address, confidential_addr_family, confidential_addr_prefix
|
|
80
|
-
) # type: ignore
|
|
81
|
-
receive_script_pubkey = wally.addr_segwit_to_bytes(
|
|
82
|
-
receive_unconfidential_address, confidential_addr_prefix, 0
|
|
83
|
-
) # type: ignore
|
|
183
|
+
receive_blinding_pubkey, receive_script_pubkey = decode_address(
|
|
184
|
+
wally, network, receive_address
|
|
185
|
+
)
|
|
84
186
|
|
|
85
187
|
# parse lockup tx
|
|
86
188
|
lockup_transaction = wally.tx_from_hex(
|
|
@@ -89,7 +191,9 @@ def create_liquid_tx(
|
|
|
89
191
|
vout_n: Optional[int] = None
|
|
90
192
|
for vout in range(wally.tx_get_num_outputs(lockup_transaction)):
|
|
91
193
|
script_out = wally.tx_get_output_script(lockup_transaction, vout) # type: ignore
|
|
92
|
-
|
|
194
|
+
|
|
195
|
+
# Lockup addresses on liquid are always bech32
|
|
196
|
+
pub_key = wally.addr_segwit_from_bytes(script_out, network.bech32_prefix, 0)
|
|
93
197
|
if pub_key == lockup_tx.script_pub_key:
|
|
94
198
|
vout_n = vout
|
|
95
199
|
break
|
|
@@ -113,7 +217,7 @@ def create_liquid_tx(
|
|
|
113
217
|
lockup_asset_commitment,
|
|
114
218
|
) # type: ignore
|
|
115
219
|
|
|
116
|
-
assert unblinded_asset ==
|
|
220
|
+
assert unblinded_asset == network.lbtc_asset, "Wrong asset"
|
|
117
221
|
|
|
118
222
|
# INITIALIZE PSBT (PSET)
|
|
119
223
|
num_vin = 1
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[tool.poetry]
|
|
2
2
|
name = "boltz_client"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.1"
|
|
4
4
|
description = "python boltz client"
|
|
5
5
|
license = "MIT"
|
|
6
6
|
authors = ["dni <office@dnilabs.com>"]
|
|
@@ -63,4 +63,5 @@ disable = [
|
|
|
63
63
|
"too-many-instance-attributes",
|
|
64
64
|
"too-many-locals",
|
|
65
65
|
"too-many-statements",
|
|
66
|
+
"broad-exception-caught",
|
|
66
67
|
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|