mm-btc 0.0.2__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.
- mm_btc-0.0.2/PKG-INFO +20 -0
- mm_btc-0.0.2/README.txt +2 -0
- mm_btc-0.0.2/pyproject.toml +71 -0
- mm_btc-0.0.2/setup.cfg +4 -0
- mm_btc-0.0.2/src/mm_btc/__init__.py +0 -0
- mm_btc-0.0.2/src/mm_btc/blockstream.py +63 -0
- mm_btc-0.0.2/src/mm_btc/py.typed +0 -0
- mm_btc-0.0.2/src/mm_btc/wallet.py +45 -0
- mm_btc-0.0.2/src/mm_btc.egg-info/PKG-INFO +20 -0
- mm_btc-0.0.2/src/mm_btc.egg-info/SOURCES.txt +13 -0
- mm_btc-0.0.2/src/mm_btc.egg-info/dependency_links.txt +1 -0
- mm_btc-0.0.2/src/mm_btc.egg-info/requires.txt +17 -0
- mm_btc-0.0.2/src/mm_btc.egg-info/top_level.txt +1 -0
- mm_btc-0.0.2/tests/test_blockstream.py +24 -0
- mm_btc-0.0.2/tests/test_wallet.py +44 -0
mm_btc-0.0.2/PKG-INFO
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: mm-btc
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Requires-Python: >=3.12
|
|
5
|
+
Requires-Dist: mm-std~=0.1.0
|
|
6
|
+
Requires-Dist: hdwallet~=2.2.1
|
|
7
|
+
Provides-Extra: dev
|
|
8
|
+
Requires-Dist: build~=1.2.1; extra == "dev"
|
|
9
|
+
Requires-Dist: twine~=5.1.0; extra == "dev"
|
|
10
|
+
Requires-Dist: pytest~=8.3.2; extra == "dev"
|
|
11
|
+
Requires-Dist: pytest-xdist~=3.6.1; extra == "dev"
|
|
12
|
+
Requires-Dist: pytest-httpserver~=1.0.8; extra == "dev"
|
|
13
|
+
Requires-Dist: coverage~=7.6.0; extra == "dev"
|
|
14
|
+
Requires-Dist: ruff~=0.5.2; extra == "dev"
|
|
15
|
+
Requires-Dist: pip-audit~=2.7.0; extra == "dev"
|
|
16
|
+
Requires-Dist: bandit~=1.7.7; extra == "dev"
|
|
17
|
+
Requires-Dist: mypy~=1.11.0; extra == "dev"
|
|
18
|
+
Requires-Dist: types-python-dateutil~=2.9.0; extra == "dev"
|
|
19
|
+
Requires-Dist: types-requests~=2.32.0.20240523; extra == "dev"
|
|
20
|
+
Requires-Dist: types-PyYAML~=6.0.12.12; extra == "dev"
|
mm_btc-0.0.2/README.txt
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mm-btc"
|
|
3
|
+
version = "0.0.2"
|
|
4
|
+
description = ""
|
|
5
|
+
dependencies = [
|
|
6
|
+
"mm-std~=0.1.0",
|
|
7
|
+
"hdwallet~=2.2.1"
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
[project.optional-dependencies]
|
|
11
|
+
dev = [
|
|
12
|
+
"build~=1.2.1",
|
|
13
|
+
"twine~=5.1.0",
|
|
14
|
+
"pytest~=8.3.2",
|
|
15
|
+
"pytest-xdist~=3.6.1",
|
|
16
|
+
"pytest-httpserver~=1.0.8",
|
|
17
|
+
"coverage~=7.6.0",
|
|
18
|
+
"ruff~=0.5.2",
|
|
19
|
+
"pip-audit~=2.7.0",
|
|
20
|
+
"bandit~=1.7.7",
|
|
21
|
+
"mypy~=1.11.0",
|
|
22
|
+
"types-python-dateutil~=2.9.0",
|
|
23
|
+
"types-requests~=2.32.0.20240523",
|
|
24
|
+
"types-PyYAML~=6.0.12.12",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["setuptools"]
|
|
30
|
+
build-backend = "setuptools.build_meta"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
[tool.mypy]
|
|
34
|
+
python_version = "3.12"
|
|
35
|
+
warn_no_return = false
|
|
36
|
+
strict = true
|
|
37
|
+
exclude = ["^tests/", "^tmp/"]
|
|
38
|
+
[[tool.mypy.overrides]]
|
|
39
|
+
module = ["hdwallet.*", "hdwallet.symbols.*", "telebot.util.*"]
|
|
40
|
+
ignore_missing_imports = true
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
[tool.ruff]
|
|
44
|
+
line-length = 130
|
|
45
|
+
target-version = "py312"
|
|
46
|
+
lint.select = [
|
|
47
|
+
"F", # Pyflakes
|
|
48
|
+
"E", "W", # pycodestyle
|
|
49
|
+
"UP", # pyupgrade
|
|
50
|
+
"B", # flake8-bugbear
|
|
51
|
+
"A", # flake8-builtins
|
|
52
|
+
"COM", # flake8-commas
|
|
53
|
+
"C40", # flake8-comprehensions
|
|
54
|
+
"G", # flake8-logging-format
|
|
55
|
+
"PIE", # flake8-pie
|
|
56
|
+
"T20", # flake8-print
|
|
57
|
+
"RUF", # Ruff-specific rules
|
|
58
|
+
]
|
|
59
|
+
lint.ignore = [
|
|
60
|
+
"A003", #builtin-attribute-shadowing
|
|
61
|
+
"UP040", # non-pep695-type-alias
|
|
62
|
+
"COM812",
|
|
63
|
+
]
|
|
64
|
+
[tool.ruff.format]
|
|
65
|
+
quote-style = "double"
|
|
66
|
+
indent-style = "space"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
[tool.bandit]
|
|
70
|
+
exclude_dirs = ["tests"]
|
|
71
|
+
skips = ["B311"]
|
mm_btc-0.0.2/setup.cfg
ADDED
|
File without changes
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from collections.abc import Sequence
|
|
2
|
+
from typing import TypeAlias
|
|
3
|
+
|
|
4
|
+
from mm_std import Err, Ok, Result, hr
|
|
5
|
+
from mm_std.random_ import random_str_choice
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
MAINNET_BASE_URL = "https://blockstream.info/api"
|
|
9
|
+
TESTNET_BASE_URL = "https://blockstream.info/testnet/api"
|
|
10
|
+
|
|
11
|
+
ERROR_INVALID_ADDRESS = "INVALID_ADDRESS"
|
|
12
|
+
ERROR_INVALID_NETWORK = "INVALID_NETWORK"
|
|
13
|
+
|
|
14
|
+
Proxy: TypeAlias = str | Sequence[str] | None
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Address(BaseModel):
|
|
18
|
+
class ChainStats(BaseModel):
|
|
19
|
+
funded_txo_count: int
|
|
20
|
+
funded_txo_sum: int
|
|
21
|
+
spent_txo_count: int
|
|
22
|
+
spent_txo_sum: int
|
|
23
|
+
tx_count: int
|
|
24
|
+
|
|
25
|
+
class MempoolStats(BaseModel):
|
|
26
|
+
funded_txo_count: int
|
|
27
|
+
funded_txo_sum: int
|
|
28
|
+
spent_txo_count: int
|
|
29
|
+
spent_txo_sum: int
|
|
30
|
+
tx_count: int
|
|
31
|
+
|
|
32
|
+
chain_stats: ChainStats
|
|
33
|
+
mempool_stats: MempoolStats
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_address(
|
|
37
|
+
address: str, testnet: bool = False, timeout: int = 10, proxies: Proxy = None, attempts: int = 1
|
|
38
|
+
) -> Result[Address]:
|
|
39
|
+
result: Result[Address] = Err("not started yet")
|
|
40
|
+
data = None
|
|
41
|
+
base_url = TESTNET_BASE_URL if testnet else MAINNET_BASE_URL
|
|
42
|
+
for _ in range(attempts):
|
|
43
|
+
try:
|
|
44
|
+
res = hr(f"{base_url}/address/{address}", timeout=timeout, proxy=random_str_choice(proxies))
|
|
45
|
+
data = res.to_dict()
|
|
46
|
+
if res.code == 400 and (
|
|
47
|
+
"invalid bitcoin address" in res.body.lower() or "bech32 segwit decoding error" in res.body.lower()
|
|
48
|
+
):
|
|
49
|
+
return Err(ERROR_INVALID_ADDRESS, data=data)
|
|
50
|
+
elif res.code == 400 and "invalid network" in res.body.lower():
|
|
51
|
+
return Err(ERROR_INVALID_NETWORK, data=data)
|
|
52
|
+
return Ok(Address(**res.json))
|
|
53
|
+
except Exception as err:
|
|
54
|
+
result = Err(err, data=data)
|
|
55
|
+
return result
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def get_confirmed_balance(
|
|
59
|
+
address: str, testnet: bool = False, timeout: int = 10, proxies: Proxy = None, attempts: int = 1
|
|
60
|
+
) -> Result[int]:
|
|
61
|
+
return get_address(address, testnet=testnet, timeout=timeout, proxies=proxies, attempts=attempts).and_then(
|
|
62
|
+
lambda a: Ok(a.chain_stats.funded_txo_sum - a.chain_stats.spent_txo_sum),
|
|
63
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
|
|
3
|
+
from hdwallet import BIP44HDWallet, BIP84HDWallet
|
|
4
|
+
from hdwallet.symbols import BTC, BTCTEST
|
|
5
|
+
from hdwallet.utils import generate_mnemonic as new_mnemonic
|
|
6
|
+
|
|
7
|
+
BIP44_MAINNET_PATH = "m/44'/0'/0'/0"
|
|
8
|
+
BIP44_TESTNET_PATH = "m/44'/1'/0'/0"
|
|
9
|
+
BIP84_MAINNET_PATH = "m/84'/0'/0'/0"
|
|
10
|
+
BIP84_TESTNET_PATH = "m/84'/1'/0'/0"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class Account:
|
|
15
|
+
address: str
|
|
16
|
+
private: str
|
|
17
|
+
path: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def generate_mnemonic(language: str = "english", strength: int = 128) -> str:
|
|
21
|
+
return new_mnemonic(language=language, strength=strength) # type: ignore[no-any-return]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def derive_accounts(mnemonic: str, passphrase: str, path: str, limit: int) -> list[Account]:
|
|
25
|
+
if path.startswith("m/84'/1'"):
|
|
26
|
+
w = BIP84HDWallet(symbol=BTCTEST)
|
|
27
|
+
elif path.startswith("m/44'/1'"):
|
|
28
|
+
w = BIP44HDWallet(symbol=BTCTEST)
|
|
29
|
+
elif path.startswith("m/84'/0'"):
|
|
30
|
+
w = BIP84HDWallet(symbol=BTC)
|
|
31
|
+
elif path.startswith("m/44'/0'"):
|
|
32
|
+
w = BIP44HDWallet(symbol=BTC)
|
|
33
|
+
else:
|
|
34
|
+
raise ValueError("Invalid path")
|
|
35
|
+
|
|
36
|
+
w.from_mnemonic(mnemonic, passphrase=passphrase)
|
|
37
|
+
w.clean_derivation()
|
|
38
|
+
|
|
39
|
+
accounts = []
|
|
40
|
+
for index_path in range(limit):
|
|
41
|
+
w.from_path(path=f"{path}/{index_path}")
|
|
42
|
+
accounts.append(Account(address=w.address(), private=w.wif(), path=f"{path}/{index_path}"))
|
|
43
|
+
w.clean_derivation()
|
|
44
|
+
|
|
45
|
+
return accounts
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: mm-btc
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Requires-Python: >=3.12
|
|
5
|
+
Requires-Dist: mm-std~=0.1.0
|
|
6
|
+
Requires-Dist: hdwallet~=2.2.1
|
|
7
|
+
Provides-Extra: dev
|
|
8
|
+
Requires-Dist: build~=1.2.1; extra == "dev"
|
|
9
|
+
Requires-Dist: twine~=5.1.0; extra == "dev"
|
|
10
|
+
Requires-Dist: pytest~=8.3.2; extra == "dev"
|
|
11
|
+
Requires-Dist: pytest-xdist~=3.6.1; extra == "dev"
|
|
12
|
+
Requires-Dist: pytest-httpserver~=1.0.8; extra == "dev"
|
|
13
|
+
Requires-Dist: coverage~=7.6.0; extra == "dev"
|
|
14
|
+
Requires-Dist: ruff~=0.5.2; extra == "dev"
|
|
15
|
+
Requires-Dist: pip-audit~=2.7.0; extra == "dev"
|
|
16
|
+
Requires-Dist: bandit~=1.7.7; extra == "dev"
|
|
17
|
+
Requires-Dist: mypy~=1.11.0; extra == "dev"
|
|
18
|
+
Requires-Dist: types-python-dateutil~=2.9.0; extra == "dev"
|
|
19
|
+
Requires-Dist: types-requests~=2.32.0.20240523; extra == "dev"
|
|
20
|
+
Requires-Dist: types-PyYAML~=6.0.12.12; extra == "dev"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
README.txt
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/mm_btc/__init__.py
|
|
4
|
+
src/mm_btc/blockstream.py
|
|
5
|
+
src/mm_btc/py.typed
|
|
6
|
+
src/mm_btc/wallet.py
|
|
7
|
+
src/mm_btc.egg-info/PKG-INFO
|
|
8
|
+
src/mm_btc.egg-info/SOURCES.txt
|
|
9
|
+
src/mm_btc.egg-info/dependency_links.txt
|
|
10
|
+
src/mm_btc.egg-info/requires.txt
|
|
11
|
+
src/mm_btc.egg-info/top_level.txt
|
|
12
|
+
tests/test_blockstream.py
|
|
13
|
+
tests/test_wallet.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
mm-std~=0.1.0
|
|
2
|
+
hdwallet~=2.2.1
|
|
3
|
+
|
|
4
|
+
[dev]
|
|
5
|
+
build~=1.2.1
|
|
6
|
+
twine~=5.1.0
|
|
7
|
+
pytest~=8.3.2
|
|
8
|
+
pytest-xdist~=3.6.1
|
|
9
|
+
pytest-httpserver~=1.0.8
|
|
10
|
+
coverage~=7.6.0
|
|
11
|
+
ruff~=0.5.2
|
|
12
|
+
pip-audit~=2.7.0
|
|
13
|
+
bandit~=1.7.7
|
|
14
|
+
mypy~=1.11.0
|
|
15
|
+
types-python-dateutil~=2.9.0
|
|
16
|
+
types-requests~=2.32.0.20240523
|
|
17
|
+
types-PyYAML~=6.0.12.12
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
mm_btc
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from mm_btc import blockstream
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_get_address(binance_address):
|
|
5
|
+
# non-empty address
|
|
6
|
+
res = blockstream.get_address(binance_address)
|
|
7
|
+
assert res.unwrap().chain_stats.tx_count > 800
|
|
8
|
+
|
|
9
|
+
# empty address
|
|
10
|
+
res = blockstream.get_address("bc1pa48c294qk7yd7sc8y0wxydc3a2frv5j83e65rvm48v3ej098s5zs8kvh6d")
|
|
11
|
+
assert res.unwrap().chain_stats.tx_count == 0
|
|
12
|
+
|
|
13
|
+
# invalid address
|
|
14
|
+
res = blockstream.get_address("bc1pa48c294qk7yd7sc8y0wxydc3a2frv5j83e65rvm48v3ej098s5zs8kvh5d")
|
|
15
|
+
assert res.unwrap_err() == blockstream.ERROR_INVALID_ADDRESS
|
|
16
|
+
|
|
17
|
+
# invalid network
|
|
18
|
+
res = blockstream.get_address(binance_address, testnet=True)
|
|
19
|
+
assert res.unwrap_err() == blockstream.ERROR_INVALID_NETWORK
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_get_confirmed_balance(binance_address):
|
|
23
|
+
res = blockstream.get_confirmed_balance(binance_address)
|
|
24
|
+
assert res.unwrap() > 0
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from mm_btc.wallet import (
|
|
2
|
+
BIP44_MAINNET_PATH,
|
|
3
|
+
BIP44_TESTNET_PATH,
|
|
4
|
+
BIP84_MAINNET_PATH,
|
|
5
|
+
BIP84_TESTNET_PATH,
|
|
6
|
+
derive_accounts,
|
|
7
|
+
generate_mnemonic,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_generate_mnemonic():
|
|
12
|
+
mnemonic = generate_mnemonic(strength=256)
|
|
13
|
+
assert len(mnemonic.split()) == 24
|
|
14
|
+
|
|
15
|
+
assert generate_mnemonic() != generate_mnemonic()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_derive_accounts():
|
|
19
|
+
mnemonic = "arrange mutual earth tackle smart kangaroo rigid census exotic acoustic stock dream eager area laptop"
|
|
20
|
+
passphrase = "my-secret"
|
|
21
|
+
|
|
22
|
+
# mainnet bip44
|
|
23
|
+
accs = derive_accounts(mnemonic, passphrase, BIP44_MAINNET_PATH, 2)
|
|
24
|
+
assert accs[1].path == "m/44'/0'/0'/0/1"
|
|
25
|
+
assert accs[1].address == "1L4Ghh4kuJuRCXrFrczwCV2TaggfCgf6B3"
|
|
26
|
+
assert accs[1].private == "Kz4zoYPJ5astywoTbxHQr5SgFvMCekMqnWDFnXjY9CfvFtyNnGiE"
|
|
27
|
+
|
|
28
|
+
# mainnet bip84
|
|
29
|
+
accs = derive_accounts(mnemonic, passphrase, BIP84_MAINNET_PATH, 2)
|
|
30
|
+
assert accs[1].path == "m/84'/0'/0'/0/1"
|
|
31
|
+
assert accs[1].address == "bc1qrq2mwuqz3utyqz97xjq5npl0dyq5gmddpxdf8k"
|
|
32
|
+
assert accs[1].private == "L447sW2JenqmEMMi7GoiUZDxTpY3Ni5ZfRrRxAbufiYwZV1AHytj"
|
|
33
|
+
|
|
34
|
+
# testnet bip44
|
|
35
|
+
accs = derive_accounts(mnemonic, passphrase, BIP44_TESTNET_PATH, 2)
|
|
36
|
+
assert accs[1].path == "m/44'/1'/0'/0/1"
|
|
37
|
+
assert accs[1].address == "n3DsvbopmKJaxHNgdZ6VVK4mB9fcuhXy5w"
|
|
38
|
+
assert accs[1].private == "cVsAhYpSm2x8V2fQJdjmfBGm1aW7xKH1U8eZTpG9giz8usxNv4da"
|
|
39
|
+
|
|
40
|
+
# testnet bip84
|
|
41
|
+
accs = derive_accounts(mnemonic, passphrase, BIP84_TESTNET_PATH, 2)
|
|
42
|
+
assert accs[1].path == "m/84'/1'/0'/0/1"
|
|
43
|
+
assert accs[1].address == "tb1q4cu7pgs0w5s4ctjz4m6gx6m4rv8eqjwtnt0a4j"
|
|
44
|
+
assert accs[1].private == "cPASMrozgCxXD6GQsJhge7hPY81j3huF8RJBY1PYwKmUVSYgeSRw"
|