mm-btc 0.4.2__tar.gz → 0.5.0__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.
Files changed (30) hide show
  1. mm_btc-0.5.0/PKG-INFO +11 -0
  2. mm_btc-0.5.0/README.md +154 -0
  3. {mm_btc-0.4.2 → mm_btc-0.5.0}/pyproject.toml +34 -27
  4. {mm_btc-0.4.2 → mm_btc-0.5.0}/src/mm_btc/blockstream.py +4 -2
  5. {mm_btc-0.4.2 → mm_btc-0.5.0}/src/mm_btc/cli/cli.py +2 -2
  6. {mm_btc-0.4.2 → mm_btc-0.5.0}/src/mm_btc/cli/cmd/address_cmd.py +2 -2
  7. {mm_btc-0.4.2 → mm_btc-0.5.0}/src/mm_btc/cli/cmd/create_tx_cmd.py +3 -2
  8. {mm_btc-0.4.2 → mm_btc-0.5.0}/src/mm_btc/cli/cmd/decode_tx_cmd.py +2 -2
  9. {mm_btc-0.4.2 → mm_btc-0.5.0}/src/mm_btc/cli/cmd/mnemonic_cmd.py +4 -4
  10. {mm_btc-0.4.2 → mm_btc-0.5.0}/src/mm_btc/cli/cmd/utxo_cmd.py +2 -2
  11. {mm_btc-0.4.2 → mm_btc-0.5.0}/tests/conftest.py +9 -3
  12. {mm_btc-0.4.2 → mm_btc-0.5.0}/tests/test_blockstream.py +2 -2
  13. mm_btc-0.5.0/uv.lock +1362 -0
  14. mm_btc-0.4.2/PKG-INFO +0 -10
  15. mm_btc-0.4.2/uv.lock +0 -1594
  16. {mm_btc-0.4.2 → mm_btc-0.5.0}/.env.example +0 -0
  17. {mm_btc-0.4.2 → mm_btc-0.5.0}/.gitignore +0 -0
  18. {mm_btc-0.4.2 → mm_btc-0.5.0}/README.txt +0 -0
  19. {mm_btc-0.4.2 → mm_btc-0.5.0}/dict.dic +0 -0
  20. {mm_btc-0.4.2 → mm_btc-0.5.0}/justfile +0 -0
  21. {mm_btc-0.4.2 → mm_btc-0.5.0}/src/mm_btc/__init__.py +0 -0
  22. {mm_btc-0.4.2 → mm_btc-0.5.0}/src/mm_btc/cli/__init__.py +0 -0
  23. {mm_btc-0.4.2 → mm_btc-0.5.0}/src/mm_btc/cli/cmd/__init__.py +0 -0
  24. {mm_btc-0.4.2 → mm_btc-0.5.0}/src/mm_btc/py.typed +0 -0
  25. {mm_btc-0.4.2 → mm_btc-0.5.0}/src/mm_btc/tx.py +0 -0
  26. {mm_btc-0.4.2 → mm_btc-0.5.0}/src/mm_btc/wallet.py +0 -0
  27. {mm_btc-0.4.2 → mm_btc-0.5.0}/tests/__init__.py +0 -0
  28. {mm_btc-0.4.2 → mm_btc-0.5.0}/tests/cmd/__init__.py +0 -0
  29. {mm_btc-0.4.2 → mm_btc-0.5.0}/tests/cmd/test_mnemonic_cmd.py +0 -0
  30. {mm_btc-0.4.2 → mm_btc-0.5.0}/tests/test_wallet.py +0 -0
mm_btc-0.5.0/PKG-INFO ADDED
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.4
2
+ Name: mm-btc
3
+ Version: 0.5.0
4
+ Requires-Python: >=3.13
5
+ Requires-Dist: bitcoinlib~=0.7.4
6
+ Requires-Dist: bit~=0.8.0
7
+ Requires-Dist: hdwallet~=3.4.0
8
+ Requires-Dist: mm-cryptocurrency~=0.4.1
9
+ Requires-Dist: mm-print>=0.0.1
10
+ Requires-Dist: mnemonic~=0.21
11
+ Requires-Dist: typer~=0.16.0
mm_btc-0.5.0/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # mm-btc
2
+
3
+ A Python library and CLI tool for Bitcoin operations, designed for developers who need to work with Bitcoin wallets, transactions, and blockchain data.
4
+
5
+ ⚠️ **This project is under active development. New features and breaking changes may be introduced.**
6
+
7
+
8
+
9
+
10
+ ## Library Usage
11
+
12
+ ### Working with Mnemonics and Wallets
13
+
14
+ ```python
15
+ from mm_btc.wallet import generate_mnemonic, derive_accounts, AddressType
16
+
17
+ # Generate a new mnemonic
18
+ mnemonic = generate_mnemonic(words=12)
19
+ print(f"Mnemonic: {mnemonic}")
20
+
21
+ # Derive accounts from mnemonic
22
+ accounts = derive_accounts(
23
+ mnemonic=mnemonic,
24
+ passphrase="",
25
+ path_prefix="m/84'/0'/0'/0", # BIP84 mainnet
26
+ address_type=AddressType.P2WPKH,
27
+ limit=5
28
+ )
29
+
30
+ for account in accounts:
31
+ print(f"Address: {account.address}")
32
+ print(f"Private Key: {account.private}")
33
+ print(f"WIF: {account.wif}")
34
+ print(f"Path: {account.path}")
35
+ ```
36
+
37
+ ### Blockchain API Integration
38
+
39
+ ```python
40
+ import asyncio
41
+ from mm_btc.blockstream import BlockstreamClient
42
+
43
+ async def get_address_info():
44
+ client = BlockstreamClient(testnet=False)
45
+
46
+ # Get address information
47
+ address_info = await client.get_address("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh")
48
+ if address_info.is_ok():
49
+ print(f"Balance: {address_info.value.chain_stats.funded_txo_sum}")
50
+
51
+ # Get UTXOs
52
+ utxos = await client.get_utxo("bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh")
53
+ if utxos.is_ok():
54
+ for utxo in utxos.value:
55
+ print(f"TXID: {utxo.txid}, Value: {utxo.value}")
56
+
57
+ asyncio.run(get_address_info())
58
+ ```
59
+
60
+
61
+ ## CLI Usage
62
+
63
+ The `mm-btc` command provides several subcommands for Bitcoin operations.
64
+
65
+ ### Mnemonic Operations
66
+
67
+ ```bash
68
+ # Generate a new 12-word mnemonic and derive addresses
69
+ mm-btc mnemonic
70
+
71
+ # Use existing mnemonic with custom parameters
72
+ mm-btc mnemonic -m "your twelve word mnemonic phrase here..." -l 5 --address-type P2WPKH
73
+
74
+ # Generate testnet addresses
75
+ mm-btc mnemonic --testnet --path bip84
76
+
77
+ # Use custom derivation path
78
+ mm-btc mnemonic --path "m/44'/0'/0'/0" --hex
79
+ ```
80
+
81
+ ### Address Information
82
+
83
+ ```bash
84
+ # Get address information from Blockstream API
85
+ mm-btc address bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh
86
+
87
+ # Get UTXOs for an address
88
+ mm-btc utxo bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh
89
+ ```
90
+
91
+ ### Transaction Operations
92
+
93
+ ```bash
94
+ # Create a transaction from config file
95
+ mm-btc create-tx config.toml
96
+
97
+ # Decode a transaction
98
+ mm-btc decode-tx 02000000000101... --testnet
99
+ ```
100
+
101
+ ### Configuration Example
102
+
103
+ Create a `config.toml` file for transaction creation:
104
+
105
+ ```toml
106
+ from_address = "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh"
107
+ private = "your_private_key_wif_format"
108
+
109
+ [[outputs]]
110
+ address = "bc1qrecipient_address_here"
111
+ amount = 50000 # Amount in satoshis
112
+
113
+ [[outputs]]
114
+ address = "bc1qanother_recipient"
115
+ amount = 25000
116
+ ```
117
+
118
+ ## Address Types
119
+
120
+ Supported Bitcoin address types:
121
+
122
+ - `P2PKH` - Pay to Public Key Hash (Legacy)
123
+ - `P2SH` - Pay to Script Hash
124
+ - `P2WPKH` - Native SegWit (Bech32)
125
+ - `P2WPKH_IN_P2SH` - SegWit wrapped in P2SH
126
+ - `P2WSH` - Native SegWit Script
127
+ - `P2WSH_IN_P2SH` - SegWit Script wrapped in P2SH
128
+ - `P2TR` - Taproot
129
+
130
+ ## Derivation Paths
131
+
132
+ - **BIP44**: `m/44'/0'/0'/0` (mainnet) / `m/44'/1'/0'/0` (testnet)
133
+ - **BIP84**: `m/84'/0'/0'/0` (mainnet) / `m/84'/1'/0'/0` (testnet)
134
+ - **Custom**: Specify your own path like `m/44'/0'/0'/0`
135
+
136
+
137
+ ## Dependencies
138
+
139
+ - `hdwallet` - HD wallet operations
140
+ - `bit` - Bitcoin transaction creation
141
+ - `bitcoinlib` - Bitcoin utilities
142
+ - `mnemonic` - BIP39 mnemonic generation
143
+ - `typer` - CLI framework
144
+ - `mm-print` - Pretty printing utilities
145
+ - `mm-cryptocurrency` - Cryptocurrency utilities
146
+
147
+
148
+ ## References
149
+
150
+ - [Bitcoin Transactions](https://en.bitcoin.it/wiki/Transaction)
151
+ - [Address Prefixes](https://en.bitcoin.it/wiki/List_of_address_prefixes)
152
+ - [Bitcoin BIPs](https://github.com/bitcoin/bips/blob/master/README.mediawiki)
153
+ - [Blockstream API](https://github.com/Blockstream/esplora/blob/master/API.md)
154
+
@@ -1,15 +1,16 @@
1
1
  [project]
2
2
  name = "mm-btc"
3
- version = "0.4.2"
3
+ version = "0.5.0"
4
4
  description = ""
5
- requires-python = ">=3.12"
5
+ requires-python = ">=3.13"
6
6
  dependencies = [
7
- "mm-crypto-utils>=0.3.6",
8
7
  "hdwallet~=3.4.0",
9
8
  "bit~=0.8.0",
10
- "bitcoinlib~=0.7.3",
9
+ "bitcoinlib~=0.7.4",
11
10
  "mnemonic~=0.21",
12
- "typer~=0.15.2",
11
+ "typer~=0.16.0",
12
+ "mm-print>=0.0.1",
13
+ "mm-cryptocurrency~=0.4.1",
13
14
  ]
14
15
  [project.scripts]
15
16
  mm-btc = "mm_btc.cli.cli:app"
@@ -21,14 +22,15 @@ build-backend = "hatchling.build"
21
22
 
22
23
  [tool.uv]
23
24
  dev-dependencies = [
24
- "pytest~=8.3.5",
25
- "pytest-xdist~=3.6.1",
25
+ "pytest~=8.4.0",
26
+ "pytest-asyncio~=1.0.0",
27
+ "pytest-xdist~=3.7.0",
26
28
  "pytest-httpserver~=1.1.3",
27
- "ruff~=0.11.6",
29
+ "ruff~=0.11.12",
28
30
  "pip-audit~=2.9.0",
29
31
  "bandit~=1.8.3",
30
- "mypy~=1.15.0",
31
- "pytest-asyncio>=0.26.0",
32
+ "mypy~=1.16.0",
33
+ "python-dotenv>=1.1.0",
32
34
  ]
33
35
 
34
36
 
@@ -38,7 +40,12 @@ warn_no_return = false
38
40
  strict = true
39
41
  exclude = ["^tests/", "^tmp/"]
40
42
  [[tool.mypy.overrides]]
41
- module = ["hdwallet.*", "hdwallet.symbols.*", "bit.*", "bitcoinlib.transactions.*"]
43
+ module = [
44
+ "hdwallet.*",
45
+ "hdwallet.symbols.*",
46
+ "bit.*",
47
+ "bitcoinlib.transactions.*",
48
+ ]
42
49
  ignore_missing_imports = true
43
50
 
44
51
  [tool.ruff]
@@ -47,27 +54,27 @@ target-version = "py313"
47
54
  [tool.ruff.lint]
48
55
  select = ["ALL"]
49
56
  ignore = [
50
- "TC", # flake8-type-checking, TYPE_CHECKING is dangerous, for example it doesn't work with pydantic
51
- "A005", # flake8-builtins: stdlib-module-shadowing
52
- "ERA001", # eradicate: commented-out-code
53
- "PT", # flake8-pytest-style
54
- "D", # pydocstyle
55
- "FIX", # flake8-fixme
57
+ "TC", # flake8-type-checking, TYPE_CHECKING is dangerous, for example it doesn't work with pydantic
58
+ "A005", # flake8-builtins: stdlib-module-shadowing
59
+ "ERA001", # eradicate: commented-out-code
60
+ "PT", # flake8-pytest-style
61
+ "D", # pydocstyle
62
+ "FIX", # flake8-fixme
56
63
  "PLR0911", # pylint: too-many-return-statements
57
64
  "PLR0912", # pylint: too-many-branches
58
65
  "PLR0913", # pylint: too-many-arguments
59
66
  "PLR2004", # pylint: magic-value-comparison
60
67
  "PLC0414", # pylint: useless-import-alias
61
- "FBT", # flake8-boolean-trap
62
- "EM", # flake8-errmsg
63
- "TRY003", # tryceratops: raise-vanilla-args
64
- "C901", # mccabe: complex-structure,
65
- "BLE001", # flake8-blind-except
66
- "S311", # bandit: suspicious-non-cryptographic-random-usage
67
- "TD002", # flake8-todos: missing-todo-author
68
- "TD003", # flake8-todos: missing-todo-link
69
- "RET503", # flake8-return: implicit-return
70
- "COM812", # it's used in ruff formatter
68
+ "FBT", # flake8-boolean-trap
69
+ "EM", # flake8-errmsg
70
+ "TRY003", # tryceratops: raise-vanilla-args
71
+ "C901", # mccabe: complex-structure,
72
+ "BLE001", # flake8-blind-except
73
+ "S311", # bandit: suspicious-non-cryptographic-random-usage
74
+ "TD002", # flake8-todos: missing-todo-author
75
+ "TD003", # flake8-todos: missing-todo-link
76
+ "RET503", # flake8-return: implicit-return
77
+ "COM812", # it's used in ruff formatter
71
78
  ]
72
79
  [tool.ruff.lint.pep8-naming]
73
80
  classmethod-decorators = ["field_validator"]
@@ -1,6 +1,8 @@
1
1
  from collections.abc import Sequence
2
2
 
3
- from mm_std import HttpResponse, Result, http_request, random_str_choice
3
+ from mm_cryptocurrency import random_proxy
4
+ from mm_http import HttpResponse, http_request
5
+ from mm_result import Result
4
6
  from pydantic import BaseModel
5
7
 
6
8
  MAINNET_BASE_URL = "https://blockstream.info/api"
@@ -99,4 +101,4 @@ class BlockstreamClient:
99
101
  return result
100
102
 
101
103
  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))
104
+ return await http_request(f"{self.base_url}{url}", timeout=self.timeout, proxy=random_proxy(self.proxies))
@@ -3,9 +3,9 @@ import importlib.metadata
3
3
  from pathlib import Path
4
4
  from typing import Annotated
5
5
 
6
+ import mm_print
6
7
  import typer
7
8
  import typer.core
8
- from mm_std import print_plain
9
9
 
10
10
  from mm_btc.wallet import AddressType
11
11
 
@@ -68,7 +68,7 @@ def utxo_command(address: str) -> None:
68
68
 
69
69
  def version_callback(value: bool) -> None:
70
70
  if value:
71
- print_plain(f"mm-btc: v{importlib.metadata.version('mm-btc')}")
71
+ mm_print.print_plain(f"mm-btc: v{importlib.metadata.version('mm-btc')}")
72
72
  raise typer.Exit
73
73
 
74
74
 
@@ -1,4 +1,4 @@
1
- from mm_std import print_json
1
+ import mm_print
2
2
 
3
3
  from mm_btc.blockstream import BlockstreamClient
4
4
  from mm_btc.wallet import is_testnet_address
@@ -7,4 +7,4 @@ from mm_btc.wallet import is_testnet_address
7
7
  async def run(address: str) -> None:
8
8
  client = BlockstreamClient(testnet=is_testnet_address(address))
9
9
  res = await client.get_address(address)
10
- print_json(res.value_or_error())
10
+ mm_print.print_json(res.value_or_error())
@@ -1,7 +1,8 @@
1
1
  from pathlib import Path
2
2
 
3
+ import mm_print
3
4
  from bit import PrivateKey, PrivateKeyTestnet
4
- from mm_std import BaseConfig, print_console
5
+ from mm_cryptocurrency import BaseConfig
5
6
 
6
7
  from mm_btc.wallet import is_testnet_address
7
8
 
@@ -24,4 +25,4 @@ def run(config_path: Path) -> None:
24
25
  outputs = [(o.address, o.amount, "satoshi") for o in config.outputs]
25
26
 
26
27
  tx = key.create_transaction(outputs)
27
- print_console(tx)
28
+ mm_print.print_json(tx)
@@ -1,8 +1,8 @@
1
- from mm_std import print_json
1
+ import mm_print
2
2
 
3
3
  from mm_btc.tx import decode_tx
4
4
 
5
5
 
6
6
  def run(tx_hex: str, testnet: bool = False) -> None:
7
7
  res = decode_tx(tx_hex, testnet)
8
- print_json(res)
8
+ mm_print.print_json(res)
@@ -1,7 +1,7 @@
1
1
  from dataclasses import dataclass
2
2
  from enum import Enum
3
3
 
4
- from mm_std import print_plain
4
+ import mm_print
5
5
 
6
6
  from mm_btc.wallet import AddressType, derive_accounts, generate_mnemonic
7
7
 
@@ -29,12 +29,12 @@ def run(args: Args) -> None:
29
29
  path = get_derivation_path_prefix(args.path, args.testnet)
30
30
  accounts = derive_accounts(mnemonic, passphrase, path, args.address_type, args.limit)
31
31
 
32
- print_plain(f"{mnemonic}")
32
+ mm_print.print_plain(f"{mnemonic}")
33
33
  if passphrase:
34
- print_plain(f"{passphrase}")
34
+ mm_print.print_plain(f"{passphrase}")
35
35
  for acc in accounts:
36
36
  private = acc.private if args.hex else acc.wif
37
- print_plain(f"{acc.path} {acc.address} {private}")
37
+ mm_print.print_plain(f"{acc.path} {acc.address} {private}")
38
38
 
39
39
 
40
40
  def get_derivation_path_prefix(path: str, testnet: bool) -> str:
@@ -1,4 +1,4 @@
1
- from mm_std import print_json
1
+ import mm_print
2
2
 
3
3
  from mm_btc.blockstream import BlockstreamClient
4
4
  from mm_btc.wallet import is_testnet_address
@@ -7,4 +7,4 @@ from mm_btc.wallet import is_testnet_address
7
7
  async def run(address: str) -> None:
8
8
  client = BlockstreamClient(testnet=is_testnet_address(address))
9
9
  res = await client.get_utxo(address)
10
- print_json(res.value_or_error())
10
+ mm_print.print_json(res.value_or_error())
@@ -1,8 +1,14 @@
1
+ import os
2
+
1
3
  import pytest
2
- from mm_crypto_utils.proxy import fetch_proxies_or_fatal_sync
3
- from mm_std import get_dotenv
4
+ from dotenv import load_dotenv
5
+ from mm_cryptocurrency import fetch_proxies_sync
4
6
  from typer.testing import CliRunner
5
7
 
8
+ load_dotenv()
9
+
10
+ PROXIES_URL = os.getenv("PROXIES_URL")
11
+
6
12
 
7
13
  @pytest.fixture()
8
14
  def mnemonic() -> str:
@@ -26,7 +32,7 @@ def binance_address() -> str:
26
32
 
27
33
  @pytest.fixture
28
34
  def proxies() -> list[str]:
29
- return fetch_proxies_or_fatal_sync(get_dotenv("PROXIES_URL"))
35
+ return fetch_proxies_sync(PROXIES_URL).unwrap("cannot fetch proxies")
30
36
 
31
37
 
32
38
  @pytest.fixture
@@ -19,11 +19,11 @@ async def test_get_address(binance_address, proxies):
19
19
 
20
20
  # invalid address
21
21
  res = await client.get_address("bc1pa48c294qk7yd7sc8y0wxydc3a2frv5j83e65rvm48v3ej098s5zs8kvh5d")
22
- assert res.unwrap_error() == blockstream.ERROR_400_BAD_REQUEST
22
+ assert res.expect_error() == blockstream.ERROR_400_BAD_REQUEST
23
23
 
24
24
  # invalid network
25
25
  res = await client.get_address("mqkwWDWdgXhYunfoKvQfYyydwB5vdma3cK")
26
- assert res.unwrap_error() == blockstream.ERROR_400_BAD_REQUEST
26
+ assert res.expect_error() == blockstream.ERROR_400_BAD_REQUEST
27
27
 
28
28
 
29
29
  async def test_get_confirmed_balance(binance_address, proxies):