mm-btc 0.2.1__py3-none-any.whl → 0.4.0__py3-none-any.whl

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.
mm_btc/blockstream.py CHANGED
@@ -1,14 +1,15 @@
1
1
  from collections.abc import Sequence
2
2
 
3
- from mm_std import Err, HResponse, Ok, Result, hr
4
- from mm_std.random_ import random_str_choice
3
+ from mm_std import HttpResponse, Result, http_request, random_str_choice
5
4
  from pydantic import BaseModel
6
5
 
7
6
  MAINNET_BASE_URL = "https://blockstream.info/api"
8
7
  TESTNET_BASE_URL = "https://blockstream.info/testnet/api"
9
8
 
10
- ERROR_INVALID_ADDRESS = "INVALID_ADDRESS"
11
- ERROR_INVALID_NETWORK = "INVALID_NETWORK"
9
+ # ERROR_INVALID_ADDRESS = "INVALID_ADDRESS"
10
+ # ERROR_INVALID_NETWORK = "INVALID_NETWORK"
11
+
12
+ ERROR_400_BAD_REQUEST = "400 Bad Request"
12
13
 
13
14
  type Proxy = str | Sequence[str] | None
14
15
 
@@ -53,57 +54,49 @@ class Utxo(BaseModel):
53
54
 
54
55
 
55
56
  class BlockstreamClient:
56
- def __init__(self, testnet: bool = False, timeout: int = 10, proxies: Proxy = None, attempts: int = 1) -> None:
57
+ def __init__(self, testnet: bool = False, timeout: float = 5, proxies: Proxy = None, attempts: int = 1) -> None:
57
58
  self.testnet = testnet
58
59
  self.timeout = timeout
59
60
  self.proxies = proxies
60
61
  self.attempts = attempts
61
62
  self.base_url = TESTNET_BASE_URL if testnet else MAINNET_BASE_URL
62
63
 
63
- def get_address(self, address: str) -> Result[Address]:
64
- result: Result[Address] = Err("not started yet")
65
- data = None
64
+ async def get_address(self, address: str) -> Result[Address]:
65
+ result: Result[Address] = Result.failure("not started yet")
66
66
  for _ in range(self.attempts):
67
+ res = await self._request(f"/address/{address}")
67
68
  try:
68
- res = self._request(f"/address/{address}")
69
- data = res.to_dict()
70
- if res.code == 400 and (
71
- "invalid bitcoin address" in res.body.lower() or "bech32 segwit decoding error" in res.body.lower()
72
- ):
73
- return Err(ERROR_INVALID_ADDRESS, data=data)
74
- if res.code == 400 and "invalid network" in res.body.lower():
75
- return Err(ERROR_INVALID_NETWORK, data=data)
76
- return Ok(Address(**res.json), data=data)
77
- except Exception as err:
78
- result = Err(err, data=data)
69
+ if res.status_code == 400:
70
+ return res.to_result_failure("400 Bad Request")
71
+ return res.to_result_success(Address(**res.parse_json_body()))
72
+ except Exception as e:
73
+ result = res.to_result_failure(e)
79
74
  return result
80
75
 
81
- def get_confirmed_balance(self, address: str) -> Result[int]:
82
- return self.get_address(address).and_then(lambda a: Ok(a.chain_stats.funded_txo_sum - a.chain_stats.spent_txo_sum))
76
+ async def get_confirmed_balance(self, address: str) -> Result[int]:
77
+ return (await self.get_address(address)).and_then(
78
+ lambda a: Result.success(a.chain_stats.funded_txo_sum - a.chain_stats.spent_txo_sum)
79
+ )
83
80
 
84
- def get_utxo(self, address: str) -> Result[list[Utxo]]:
85
- result: Result[list[Utxo]] = Err("not started yet")
86
- data = None
81
+ async def get_utxo(self, address: str) -> Result[list[Utxo]]:
82
+ result: Result[list[Utxo]] = Result.failure("not started yet")
87
83
  for _ in range(self.attempts):
84
+ res = await self._request(f"/address/{address}/utxo")
88
85
  try:
89
- res = self._request(f"/address/{address}/utxo")
90
- data = res.to_dict()
91
- return Ok([Utxo(**out) for out in res.json], data=data)
92
- except Exception as err:
93
- result = Err(err, data=data)
86
+ return res.to_result_success([Utxo(**out) for out in res.parse_json_body()])
87
+ except Exception as e:
88
+ result = res.to_result_failure(e)
94
89
  return result
95
90
 
96
- def get_mempool(self) -> Result[Mempool]:
97
- result: Result[Mempool] = Err("not started yet")
98
- data = None
91
+ async def get_mempool(self) -> Result[Mempool]:
92
+ result: Result[Mempool] = Result.failure("not started yet")
99
93
  for _ in range(self.attempts):
94
+ res = await self._request("/mempool")
100
95
  try:
101
- res = self._request("/mempool")
102
- data = res.to_dict()
103
- return Ok(Mempool(**res.json), data=data)
104
- except Exception as err:
105
- result = Err(err, data=data)
96
+ return res.to_result_success(Mempool(**res.parse_json_body()))
97
+ except Exception as e:
98
+ result = res.to_result_failure(e)
106
99
  return result
107
100
 
108
- def _request(self, url: str) -> HResponse:
109
- return hr(f"{self.base_url}{url}", timeout=self.timeout, proxy=random_str_choice(self.proxies))
101
+ async def _request(self, url: str) -> HttpResponse:
102
+ return await http_request(f"{self.base_url}{url}", timeout=self.timeout, proxy=random_str_choice(self.proxies))
mm_btc/cli/cli.py CHANGED
@@ -1,3 +1,5 @@
1
+ import asyncio
2
+ import importlib.metadata
1
3
  from pathlib import Path
2
4
  from typing import Annotated
3
5
 
@@ -7,7 +9,6 @@ from mm_std import print_plain
7
9
 
8
10
  from mm_btc.wallet import AddressType
9
11
 
10
- from . import cli_utils
11
12
  from .cmd import address_cmd, create_tx_cmd, decode_tx_cmd, mnemonic_cmd, utxo_cmd
12
13
 
13
14
  app = typer.Typer(no_args_is_help=True, pretty_exceptions_enable=False, add_completion=False)
@@ -44,7 +45,7 @@ def mnemonic_command( # nosec B107:hardcoded_password_default
44
45
  @app.command(name="a", hidden=True)
45
46
  def address_command(address: str) -> None:
46
47
  """Get address info from Blockstream API"""
47
- address_cmd.run(address)
48
+ asyncio.run(address_cmd.run(address))
48
49
 
49
50
 
50
51
  @app.command("create-tx")
@@ -62,12 +63,12 @@ def decode_tx_command(tx_hex: str, testnet: Annotated[bool, typer.Option("--test
62
63
  @app.command("utxo")
63
64
  def utxo_command(address: str) -> None:
64
65
  """Get UTXOs from Blockstream API"""
65
- utxo_cmd.run(address)
66
+ asyncio.run(utxo_cmd.run(address))
66
67
 
67
68
 
68
69
  def version_callback(value: bool) -> None:
69
70
  if value:
70
- print_plain(f"mm-btc: v{cli_utils.get_version()}")
71
+ print_plain(f"mm-btc: v{importlib.metadata.version('mm-btc')}")
71
72
  raise typer.Exit
72
73
 
73
74
 
@@ -4,7 +4,7 @@ from mm_btc.blockstream import BlockstreamClient
4
4
  from mm_btc.wallet import is_testnet_address
5
5
 
6
6
 
7
- def run(address: str) -> None:
7
+ async def run(address: str) -> None:
8
8
  client = BlockstreamClient(testnet=is_testnet_address(address))
9
- res = client.get_address(address)
10
- print_json(res)
9
+ res = await client.get_address(address)
10
+ print_json(res.ok_or_error())
@@ -3,7 +3,6 @@ from pathlib import Path
3
3
  from bit import PrivateKey, PrivateKeyTestnet
4
4
  from mm_std import BaseConfig, print_console
5
5
 
6
- from mm_btc.cli.cli_utils import read_config
7
6
  from mm_btc.wallet import is_testnet_address
8
7
 
9
8
 
@@ -18,7 +17,7 @@ class Config(BaseConfig):
18
17
 
19
18
 
20
19
  def run(config_path: Path) -> None:
21
- config = read_config(Config, config_path)
20
+ config = Config.read_toml_config_or_exit(config_path)
22
21
  testnet = is_testnet_address(config.from_address)
23
22
  key = PrivateKeyTestnet(config.private) if testnet else PrivateKey(config.private)
24
23
 
@@ -4,8 +4,7 @@ from mm_btc.blockstream import BlockstreamClient
4
4
  from mm_btc.wallet import is_testnet_address
5
5
 
6
6
 
7
- def run(address: str) -> None:
7
+ async def run(address: str) -> None:
8
8
  client = BlockstreamClient(testnet=is_testnet_address(address))
9
- res = client.get_utxo(address)
10
-
11
- print_json(res.ok_or_err())
9
+ res = await client.get_utxo(address)
10
+ print_json(res.ok_or_error())
@@ -0,0 +1,10 @@
1
+ Metadata-Version: 2.4
2
+ Name: mm-btc
3
+ Version: 0.4.0
4
+ Requires-Python: >=3.12
5
+ Requires-Dist: bitcoinlib~=0.7.3
6
+ Requires-Dist: bit~=0.8.0
7
+ Requires-Dist: hdwallet~=3.4.0
8
+ Requires-Dist: mm-crypto-utils>=0.3.1
9
+ Requires-Dist: mnemonic~=0.21
10
+ Requires-Dist: typer~=0.15.2
@@ -0,0 +1,17 @@
1
+ mm_btc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ mm_btc/blockstream.py,sha256=cJyECppf4qjnDHDrwdZUzQTJ0RIHTR5ykhqoedAzQsk,3371
3
+ mm_btc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ mm_btc/tx.py,sha256=7KTMNuL1n8rRj_245BV1bH9M2_PctNvlvPsLEsRTYx4,245
5
+ mm_btc/wallet.py,sha256=_pGTuAf6Y9KzaWTTSg_tn6WEoRsGqhxJuPof0_xyrmM,2333
6
+ mm_btc/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
+ mm_btc/cli/cli.py,sha256=peUio3WAa2oXomZvq-87QAbuTP37CPNoq5RAG7ax3ns,2656
8
+ mm_btc/cli/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ mm_btc/cli/cmd/address_cmd.py,sha256=PCjOEpZbMqBKpaAJnDHzYSqRnQEqHKqxN-w5c1Xq6jQ,310
10
+ mm_btc/cli/cmd/create_tx_cmd.py,sha256=QHhfA91FGWf5r6DkWTaXInZJi6HYLfxueA1GtcBD2-s,703
11
+ mm_btc/cli/cmd/decode_tx_cmd.py,sha256=0jGlUjnA1X2Q2aYwSBeAjqVNZIBsW5X2lEwIpSfWJsU,175
12
+ mm_btc/cli/cmd/mnemonic_cmd.py,sha256=CY6tPsqOTNfM-4WhKDhqitCi2OeqSIUMEUTFIRGHwIg,1254
13
+ mm_btc/cli/cmd/utxo_cmd.py,sha256=B3vI2BPWMSPVvMLGBctJw-C9k9vVLUg4nKFEIYNtmgY,307
14
+ mm_btc-0.4.0.dist-info/METADATA,sha256=nDKkAIMrgMP6GntwePrLKr8_LDN6AEPOLluK2Gfo_7U,261
15
+ mm_btc-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
16
+ mm_btc-0.4.0.dist-info/entry_points.txt,sha256=KphzLNE9eb9H1DO-L0gvBQaQT5ImedyVCN4a6svfiRg,46
17
+ mm_btc-0.4.0.dist-info/RECORD,,
mm_btc/cli/cli_utils.py DELETED
@@ -1,19 +0,0 @@
1
- import importlib.metadata
2
- import sys
3
- from pathlib import Path
4
-
5
- from mm_std import BaseConfig
6
- from rich import print_json
7
-
8
-
9
- def get_version() -> str:
10
- return importlib.metadata.version("mm-btc")
11
-
12
-
13
- def read_config[T: BaseConfig](config_type: type[T], config_path: Path) -> T:
14
- res = config_type.read_config(config_path)
15
- if res.is_ok():
16
- return res.unwrap()
17
-
18
- print_json(res.err)
19
- sys.exit(1)
@@ -1,10 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: mm-btc
3
- Version: 0.2.1
4
- Requires-Python: >=3.12
5
- Requires-Dist: bitcoinlib~=0.7.1
6
- Requires-Dist: bit~=0.8.0
7
- Requires-Dist: hdwallet~=3.2.3
8
- Requires-Dist: mm-std~=0.1.11
9
- Requires-Dist: mnemonic~=0.21
10
- Requires-Dist: typer~=0.15.1
@@ -1,18 +0,0 @@
1
- mm_btc/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- mm_btc/blockstream.py,sha256=as3Rv_OsbdvclUEotxrXC8K5mwddxvznY3SuFscKkY4,3591
3
- mm_btc/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- mm_btc/tx.py,sha256=7KTMNuL1n8rRj_245BV1bH9M2_PctNvlvPsLEsRTYx4,245
5
- mm_btc/wallet.py,sha256=_pGTuAf6Y9KzaWTTSg_tn6WEoRsGqhxJuPof0_xyrmM,2333
6
- mm_btc/cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
7
- mm_btc/cli/cli.py,sha256=RleK9_widQ6OQEWU10qTRKvi7ooHKGE5JPYNOM9DfnM,2600
8
- mm_btc/cli/cli_utils.py,sha256=vNXTicMAyUWbPmdnAcBn5cCGON75ayZ6rVK8T5McgtQ,413
9
- mm_btc/cli/cmd/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- mm_btc/cli/cmd/address_cmd.py,sha256=fipX9FiQ2EgCEq8nVN6ELnDMoR5KTIX7bpAaRK3t_yg,284
11
- mm_btc/cli/cmd/create_tx_cmd.py,sha256=kD3lTc7sSDconCfB-PFHiH415ABeZ86kfkDwEw2jx50,736
12
- mm_btc/cli/cmd/decode_tx_cmd.py,sha256=0jGlUjnA1X2Q2aYwSBeAjqVNZIBsW5X2lEwIpSfWJsU,175
13
- mm_btc/cli/cmd/mnemonic_cmd.py,sha256=CY6tPsqOTNfM-4WhKDhqitCi2OeqSIUMEUTFIRGHwIg,1254
14
- mm_btc/cli/cmd/utxo_cmd.py,sha256=mp-lVLURpXFqO3IeBIEwnHusEvT5tFjUKxnRYC-rFqQ,294
15
- mm_btc-0.2.1.dist-info/METADATA,sha256=uEDpGrymap4CQcZEBzNK9devojVPZ6aW8iLFrS9fczM,253
16
- mm_btc-0.2.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- mm_btc-0.2.1.dist-info/entry_points.txt,sha256=KphzLNE9eb9H1DO-L0gvBQaQT5ImedyVCN4a6svfiRg,46
18
- mm_btc-0.2.1.dist-info/RECORD,,
File without changes