btcaaron 0.1.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.
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.2
2
+ Name: btcaaron
3
+ Version: 0.1.1
4
+ Summary: A Bitcoin Testnet transaction toolkit supporting Legacy, SegWit, and Taproot
5
+ Home-page: https://x.com/aaron_recompile
6
+ Author: Aaron Zhang
7
+ Author-email: aaron.recompile@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries
14
+ Classifier: Topic :: Internet :: WWW/HTTP
15
+ Requires-Python: >=3.7
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: requests>=2.25.0
18
+ Requires-Dist: bitcoin-utils>=0.7.1
19
+ Dynamic: author
20
+ Dynamic: author-email
21
+ Dynamic: classifier
22
+ Dynamic: description
23
+ Dynamic: description-content-type
24
+ Dynamic: home-page
25
+ Dynamic: requires-dist
26
+ Dynamic: requires-python
27
+ Dynamic: summary
28
+
29
+
30
+
31
+ # btcaaron
32
+
33
+ A simple Bitcoin Testnet toolkit for developers.
34
+ Easily generate addresses, scan UTXOs, build and broadcast transactions β€” with full support for Legacy, SegWit, and Taproot.
35
+
36
+ ---
37
+
38
+ ## πŸ”§ Features
39
+
40
+ - βœ… Generate Legacy / SegWit / Taproot addresses from WIF
41
+ - πŸ” Scan UTXOs and check balance via public APIs
42
+ - 🧠 Build & sign transactions (manual or quick mode)
43
+ - πŸš€ Broadcast to Blockstream or Mempool endpoints
44
+ - πŸ§ͺ Simple test suite for local debugging
45
+
46
+ ---
47
+
48
+ ## πŸ“¦ Installation
49
+
50
+ ```bash
51
+ pip install btcaaron
52
+
53
+ Or install from source:
54
+
55
+ git clone https://github.com/aaron-recompile/btcaaron.git
56
+ cd btcaaron
57
+ pip install .
58
+
59
+
60
+ βΈ»
61
+
62
+ πŸš€ Quick Start
63
+
64
+ from btcaaron import WIFKey, quick_transfer
65
+
66
+ # Your testnet WIF private key
67
+ wif = ""
68
+
69
+ # Generate addresses
70
+ key = WIFKey(wif)
71
+ print("Taproot:", key.get_taproot().address)
72
+
73
+ # Check balance
74
+ balance = key.get_taproot().get_balance()
75
+ print("Balance:", balance, "sats")
76
+
77
+ # Quick transfer
78
+ if balance > 1000:
79
+ txid = quick_transfer(wif, "taproot", "tb1q...", amount=500, fee=300)
80
+ print("Broadcasted:", txid)
81
+
82
+
83
+ βΈ»
84
+
85
+ πŸ“ Project Structure
86
+
87
+ btcaaron/
88
+ β”œβ”€β”€ btcaaron.py # Main library
89
+ β”œβ”€β”€ test.py # Example-based test runner
90
+ β”œβ”€β”€ README.md # This file
91
+ β”œβ”€β”€ setup.py # Install and packaging
92
+ β”œβ”€β”€ LICENSE # MIT License
93
+
94
+
95
+ βΈ»
96
+
97
+ πŸ‘¨β€πŸ’» Author
98
+
99
+ Aaron Zhang
100
+ https://x.com/aaron_recompile
101
+
102
+ βΈ»
103
+
104
+ πŸ“„ License
105
+
106
+ MIT License - Free for commercial and personal use.
107
+
@@ -0,0 +1,79 @@
1
+
2
+
3
+ # btcaaron
4
+
5
+ A simple Bitcoin Testnet toolkit for developers.
6
+ Easily generate addresses, scan UTXOs, build and broadcast transactions β€” with full support for Legacy, SegWit, and Taproot.
7
+
8
+ ---
9
+
10
+ ## πŸ”§ Features
11
+
12
+ - βœ… Generate Legacy / SegWit / Taproot addresses from WIF
13
+ - πŸ” Scan UTXOs and check balance via public APIs
14
+ - 🧠 Build & sign transactions (manual or quick mode)
15
+ - πŸš€ Broadcast to Blockstream or Mempool endpoints
16
+ - πŸ§ͺ Simple test suite for local debugging
17
+
18
+ ---
19
+
20
+ ## πŸ“¦ Installation
21
+
22
+ ```bash
23
+ pip install btcaaron
24
+
25
+ Or install from source:
26
+
27
+ git clone https://github.com/aaron-recompile/btcaaron.git
28
+ cd btcaaron
29
+ pip install .
30
+
31
+
32
+ βΈ»
33
+
34
+ πŸš€ Quick Start
35
+
36
+ from btcaaron import WIFKey, quick_transfer
37
+
38
+ # Your testnet WIF private key
39
+ wif = ""
40
+
41
+ # Generate addresses
42
+ key = WIFKey(wif)
43
+ print("Taproot:", key.get_taproot().address)
44
+
45
+ # Check balance
46
+ balance = key.get_taproot().get_balance()
47
+ print("Balance:", balance, "sats")
48
+
49
+ # Quick transfer
50
+ if balance > 1000:
51
+ txid = quick_transfer(wif, "taproot", "tb1q...", amount=500, fee=300)
52
+ print("Broadcasted:", txid)
53
+
54
+
55
+ βΈ»
56
+
57
+ πŸ“ Project Structure
58
+
59
+ btcaaron/
60
+ β”œβ”€β”€ btcaaron.py # Main library
61
+ β”œβ”€β”€ test.py # Example-based test runner
62
+ β”œβ”€β”€ README.md # This file
63
+ β”œβ”€β”€ setup.py # Install and packaging
64
+ β”œβ”€β”€ LICENSE # MIT License
65
+
66
+
67
+ βΈ»
68
+
69
+ πŸ‘¨β€πŸ’» Author
70
+
71
+ Aaron Zhang
72
+ https://x.com/aaron_recompile
73
+
74
+ βΈ»
75
+
76
+ πŸ“„ License
77
+
78
+ MIT License - Free for commercial and personal use.
79
+
@@ -0,0 +1,9 @@
1
+ from .btcaaron import WIFKey, wif_to_addresses, quick_transfer
2
+
3
+ __all__ = [
4
+ "WIFKey",
5
+ "wif_to_addresses",
6
+ "quick_transfer"
7
+ ]
8
+
9
+ __version__ = "0.1.1"
@@ -0,0 +1,484 @@
1
+ """
2
+ btcaaron - Lightweight Bitcoin Testnet Toolkit
3
+
4
+ A simple, clean toolkit for Bitcoin testnet operations including address generation,
5
+ UTXO querying, transaction creation, signing, and broadcasting.
6
+
7
+ Author: Aaron Zhang (https://x.com/aaron_recompile)
8
+ Version: 0.1.1
9
+ License: MIT
10
+ """
11
+
12
+ from typing import List, Dict, Optional
13
+ import requests
14
+ import concurrent.futures
15
+ from bitcoinutils.setup import setup
16
+ from bitcoinutils.keys import PrivateKey, P2pkhAddress, P2wpkhAddress, P2trAddress
17
+ from bitcoinutils.transactions import Transaction, TxInput, TxOutput, TxWitnessInput
18
+ from bitcoinutils.script import Script
19
+
20
+ # Initialize testnet environment
21
+ setup('testnet')
22
+
23
+ # Network configuration
24
+ TIMEOUT = 5
25
+ MAX_WORKERS = 2
26
+ DEFAULT_FEE = 300
27
+ DUST_LIMIT = 546
28
+
29
+
30
+ class WIFKey:
31
+ """
32
+ WIF Private Key Manager
33
+
34
+ Generate different types of Bitcoin addresses from a WIF private key
35
+ """
36
+
37
+ def __init__(self, wif_str: str):
38
+ """
39
+ Initialize WIF private key
40
+
41
+ Args:
42
+ wif_str: WIF format private key string
43
+
44
+ Raises:
45
+ ValueError: When WIF format is invalid
46
+ """
47
+ try:
48
+ self.wif = wif_str
49
+ self.private_key = PrivateKey(wif_str)
50
+ self.public_key = self.private_key.get_public_key()
51
+ except Exception as e:
52
+ raise ValueError(f"Invalid WIF private key: {str(e)}")
53
+
54
+ def get_legacy(self) -> 'BTCAddress':
55
+ """Get Legacy address object (P2PKH)"""
56
+ address = self.public_key.get_address().to_string()
57
+ return BTCAddress(self.wif, address, "legacy", self.private_key, self.public_key)
58
+
59
+ def get_segwit(self) -> 'BTCAddress':
60
+ """Get SegWit address object (P2WPKH)"""
61
+ address = self.public_key.get_segwit_address().to_string()
62
+ return BTCAddress(self.wif, address, "segwit", self.private_key, self.public_key)
63
+
64
+ def get_taproot(self) -> 'BTCAddress':
65
+ """Get Taproot address object (P2TR)"""
66
+ address = self.public_key.get_taproot_address().to_string()
67
+ return BTCAddress(self.wif, address, "taproot", self.private_key, self.public_key)
68
+
69
+
70
+ class BTCAddress:
71
+ """
72
+ Bitcoin Address Handler
73
+
74
+ Handles specific address type operations (UTXO queries, transfers, etc.)
75
+ """
76
+
77
+ def __init__(self, wif: str, address: str, addr_type: str, private_key, public_key):
78
+ self.wif = wif
79
+ self.address = address
80
+ self.type = addr_type
81
+ self.private_key = private_key
82
+ self.public_key = public_key
83
+
84
+ # Create address object
85
+ if addr_type == "legacy":
86
+ self.addr_obj = P2pkhAddress(address)
87
+ elif addr_type == "segwit":
88
+ self.addr_obj = P2wpkhAddress(address)
89
+ elif addr_type == "taproot":
90
+ self.addr_obj = P2trAddress(address)
91
+ else:
92
+ raise ValueError(f"Unsupported address type: {addr_type}")
93
+
94
+ def __str__(self) -> str:
95
+ return f"{self.type.upper()} Address: {self.address}"
96
+
97
+ def scan_utxos(self, debug: bool = False) -> List[Dict]:
98
+ """
99
+ Scan all available UTXOs for this address
100
+
101
+ Args:
102
+ debug: Whether to output debug information
103
+
104
+ Returns:
105
+ List[Dict]: List of UTXOs
106
+ """
107
+ if debug:
108
+ print(f"Scanning UTXOs for {self.type} address: {self.address}")
109
+
110
+ # API endpoints
111
+ apis = [
112
+ ("Blockstream testnet", f"https://blockstream.info/testnet/api/address/{self.address}/utxo"),
113
+ ("Mempool testnet", f"https://mempool.space/testnet/api/address/{self.address}/utxo"),
114
+ ]
115
+
116
+ def fetch_utxos(api_info) -> Optional[List[Dict]]:
117
+ """Fetch UTXOs from a single API"""
118
+ api_name, api_url = api_info
119
+ try:
120
+ response = requests.get(api_url, timeout=TIMEOUT)
121
+ if response.status_code == 200:
122
+ raw_utxos = response.json()
123
+ if debug:
124
+ print(f" {api_name}: Found {len(raw_utxos)} UTXOs")
125
+
126
+ utxos = []
127
+ for utxo in raw_utxos:
128
+ processed_utxo = {
129
+ 'txid': utxo['txid'],
130
+ 'vout': utxo['vout'],
131
+ 'amount': utxo['value'],
132
+ 'scriptPubKey': self.addr_obj.to_script_pub_key().to_hex(),
133
+ 'type': self.type.upper()
134
+ }
135
+ utxos.append(processed_utxo)
136
+
137
+ if debug:
138
+ print(f" - {utxo['txid'][:16]}...:{utxo['vout']} = {utxo['value']} sats")
139
+
140
+ return utxos
141
+ else:
142
+ if debug:
143
+ print(f" {api_name}: HTTP {response.status_code}")
144
+ except Exception as e:
145
+ if debug:
146
+ print(f" {api_name}: Error - {str(e)}")
147
+ return None
148
+
149
+ # Try APIs sequentially (simpler than concurrent)
150
+ for api_name, api_url in apis:
151
+ result = fetch_utxos((api_name, api_url))
152
+ if result is not None:
153
+ if debug:
154
+ total = sum(utxo['amount'] for utxo in result)
155
+ print(f" Success: {len(result)} UTXOs, Total: {total} sats")
156
+ return result
157
+
158
+ if debug:
159
+ print(" All APIs failed")
160
+ return []
161
+
162
+ def get_balance(self, debug: bool = False) -> int:
163
+ """
164
+ Get address balance
165
+
166
+ Args:
167
+ debug: Whether to output debug information
168
+
169
+ Returns:
170
+ int: Address balance in satoshis
171
+ """
172
+ if debug:
173
+ print(f"Checking balance for {self.type} address: {self.address}")
174
+
175
+ utxos = self.scan_utxos(debug=debug)
176
+ balance = sum(utxo['amount'] for utxo in utxos)
177
+
178
+ if debug:
179
+ print(f" Balance: {balance:,} sats")
180
+
181
+ return balance
182
+
183
+ def send(self, to_addr: str, amount: int, fee: int = DEFAULT_FEE, debug: bool = False) -> 'BTCTransaction':
184
+ """
185
+ Create and sign a transfer transaction
186
+
187
+ Args:
188
+ to_addr: Recipient address
189
+ amount: Transfer amount in satoshis
190
+ fee: Transaction fee in satoshis
191
+ debug: Whether to output debug information
192
+
193
+ Returns:
194
+ BTCTransaction: Signed transaction object
195
+
196
+ Raises:
197
+ ValueError: When insufficient balance or invalid parameters
198
+ """
199
+ if debug:
200
+ print(f"Creating transaction from {self.type} address:")
201
+ print(f" From: {self.address}")
202
+ print(f" To: {to_addr}")
203
+ print(f" Amount: {amount:,} sats")
204
+ print(f" Fee: {fee:,} sats")
205
+
206
+ # Get UTXOs
207
+ utxos = self.scan_utxos(debug=debug)
208
+ if not utxos:
209
+ raise ValueError(f"No UTXOs available for address {self.address}")
210
+
211
+ # Check balance
212
+ total_needed = amount + fee
213
+ total_available = sum(utxo['amount'] for utxo in utxos)
214
+
215
+ if total_available < total_needed:
216
+ raise ValueError(f"Insufficient balance. Need: {total_needed:,}, Available: {total_available:,}")
217
+
218
+ # Select UTXOs (largest first)
219
+ utxos.sort(key=lambda x: x['amount'], reverse=True)
220
+ selected_utxos = []
221
+ total_input = 0
222
+
223
+ for utxo in utxos:
224
+ selected_utxos.append(utxo)
225
+ total_input += utxo['amount']
226
+ if total_input >= total_needed:
227
+ break
228
+
229
+ if debug:
230
+ print(f" Selected UTXOs:")
231
+ for i, utxo in enumerate(selected_utxos, 1):
232
+ print(f" {i}. {utxo['txid'][:16]}...:{utxo['vout']} = {utxo['amount']:,} sats")
233
+ print(f" Total input: {total_input:,} sats")
234
+
235
+ # Create transaction inputs
236
+ tx_inputs = []
237
+ for utxo in selected_utxos:
238
+ tx_input = TxInput(utxo['txid'], utxo['vout'])
239
+ tx_inputs.append(tx_input)
240
+
241
+ # Create transaction outputs
242
+ tx_outputs = []
243
+
244
+ # 1. Output to recipient
245
+ to_addr_obj = self._create_address_object(to_addr)
246
+ tx_out = TxOutput(amount, to_addr_obj.to_script_pub_key())
247
+ tx_outputs.append(tx_out)
248
+
249
+ if debug:
250
+ print(f" Output 1: {amount:,} sats -> {to_addr}")
251
+
252
+ # 2. Change output (check dust limit)
253
+ change_amount = total_input - amount - fee
254
+ if change_amount > 0:
255
+ if change_amount < DUST_LIMIT:
256
+ # Change too small, add to fee
257
+ if debug:
258
+ print(f" Warning: Change amount {change_amount} sats < {DUST_LIMIT} sats (dust limit)")
259
+ print(f" Adding change to fee: {fee} + {change_amount} = {fee + change_amount} sats")
260
+ else:
261
+ change_out = TxOutput(change_amount, self.addr_obj.to_script_pub_key())
262
+ tx_outputs.append(change_out)
263
+ if debug:
264
+ print(f" Change: {change_amount:,} sats -> {self.address}")
265
+ elif debug:
266
+ print(f" No change (exact amount match)")
267
+
268
+ # Create transaction
269
+ has_segwit = self.type in ['segwit', 'taproot']
270
+ tx = Transaction(tx_inputs, tx_outputs, has_segwit=has_segwit)
271
+
272
+ if debug:
273
+ print(f" Created {'SegWit' if has_segwit else 'Legacy'} transaction")
274
+
275
+ # Sign transaction
276
+ self._sign_transaction(tx, selected_utxos, debug=debug)
277
+
278
+ signed_hex = tx.serialize()
279
+
280
+ if debug:
281
+ print(f" Signing complete!")
282
+ print(f" Transaction size: {len(signed_hex)//2} bytes")
283
+
284
+ return BTCTransaction(signed_hex, debug=debug)
285
+
286
+ def _create_address_object(self, address: str):
287
+ """Create address object from address string"""
288
+ if address.startswith(('1', 'm', 'n')): # Legacy
289
+ return P2pkhAddress(address)
290
+ elif address.startswith(('bc1q', 'tb1q')): # SegWit v0
291
+ return P2wpkhAddress(address)
292
+ elif address.startswith(('bc1p', 'tb1p')): # Taproot
293
+ return P2trAddress(address)
294
+ else:
295
+ raise ValueError(f"Unsupported address format: {address}")
296
+
297
+ def _sign_transaction(self, tx: Transaction, selected_utxos: List[Dict], debug: bool = False):
298
+ """Sign transaction based on address type"""
299
+ if debug:
300
+ print(f" Signing with {self.type} method...")
301
+
302
+ if self.type == 'legacy':
303
+ # P2PKH signing
304
+ for i, tx_input in enumerate(tx.inputs):
305
+ previous_locking_script = self.addr_obj.to_script_pub_key()
306
+ sig = self.private_key.sign_input(tx, i, previous_locking_script)
307
+ pk = self.private_key.get_public_key().to_hex()
308
+ unlocking_script = Script([sig, pk])
309
+ tx_input.script_sig = unlocking_script
310
+
311
+ elif self.type == 'segwit':
312
+ # P2WPKH signing
313
+ for i, tx_input in enumerate(tx.inputs):
314
+ script_code = self.public_key.get_address().to_script_pub_key()
315
+ input_amount = selected_utxos[i]['amount']
316
+ sig = self.private_key.sign_segwit_input(tx, i, script_code, input_amount)
317
+ public_key_hex = self.private_key.get_public_key().to_hex()
318
+ tx_input.script_sig = Script([])
319
+ tx.witnesses.append(TxWitnessInput([sig, public_key_hex]))
320
+
321
+ elif self.type == 'taproot':
322
+ # P2TR signing
323
+ input_amounts = [utxo['amount'] for utxo in selected_utxos]
324
+ input_scripts = [self.addr_obj.to_script_pub_key() for _ in selected_utxos]
325
+
326
+ for i, tx_input in enumerate(tx.inputs):
327
+ sig = self.private_key.sign_taproot_input(tx, i, input_scripts, input_amounts)
328
+ tx_input.script_sig = Script([])
329
+ tx.witnesses.append(TxWitnessInput([sig]))
330
+
331
+
332
+ class BTCTransaction:
333
+ """
334
+ Bitcoin Transaction Handler
335
+
336
+ Handles signed transaction broadcasting
337
+ """
338
+
339
+ def __init__(self, tx_hex: str, debug: bool = False):
340
+ self.tx_hex = tx_hex
341
+ self.debug = debug
342
+ self.txid = None
343
+
344
+ def __str__(self) -> str:
345
+ status = f"(TxID: {self.txid})" if self.txid else "(Not broadcasted)"
346
+ return f"BTCTransaction {status}"
347
+
348
+ def broadcast(self) -> Optional[str]:
349
+ """
350
+ Broadcast transaction to network
351
+
352
+ Returns:
353
+ Optional[str]: Transaction ID if successful, None if failed
354
+ """
355
+ if self.debug:
356
+ print(f"Broadcasting transaction:")
357
+ print(f" Transaction size: {len(self.tx_hex)//2} bytes")
358
+
359
+ # Broadcast endpoints
360
+ broadcast_apis = [
361
+ ("Blockstream testnet", "https://blockstream.info/testnet/api/tx"),
362
+ ("Mempool testnet", "https://mempool.space/testnet/api/tx"),
363
+ ]
364
+
365
+ # Try each API sequentially
366
+ for api_name, api_url in broadcast_apis:
367
+ try:
368
+ if self.debug:
369
+ print(f" Trying {api_name}...")
370
+
371
+ response = requests.post(
372
+ api_url,
373
+ data=self.tx_hex,
374
+ timeout=TIMEOUT,
375
+ headers={'Content-Type': 'text/plain'}
376
+ )
377
+
378
+ if response.status_code == 200:
379
+ response_text = response.text.strip()
380
+ # Validate response is a valid transaction ID
381
+ if len(response_text) == 64 and all(c in '0123456789abcdef' for c in response_text.lower()):
382
+ self.txid = response_text
383
+ if self.debug:
384
+ print(f" Success: {response_text}")
385
+ return response_text
386
+ else:
387
+ if self.debug:
388
+ print(f" Invalid response format: {response_text}")
389
+ else:
390
+ if self.debug:
391
+ print(f" HTTP {response.status_code}: {response.text[:100]}...")
392
+
393
+ except Exception as e:
394
+ if self.debug:
395
+ print(f" Error: {e}")
396
+
397
+ if self.debug:
398
+ print(" All broadcast attempts failed")
399
+ return None
400
+
401
+
402
+ # Convenience functions
403
+ def wif_to_addresses(wif: str) -> Dict[str, str]:
404
+ """
405
+ Generate all address types from WIF
406
+
407
+ Args:
408
+ wif: WIF private key string
409
+
410
+ Returns:
411
+ Dict[str, str]: Dictionary containing all address types
412
+ """
413
+ wif_key = WIFKey(wif)
414
+ return {
415
+ 'legacy': wif_key.get_legacy().address,
416
+ 'segwit': wif_key.get_segwit().address,
417
+ 'taproot': wif_key.get_taproot().address
418
+ }
419
+
420
+
421
+ def quick_transfer(wif: str, from_type: str, to_addr: str, amount: int,
422
+ fee: int = DEFAULT_FEE, debug: bool = False) -> Optional[str]:
423
+ """
424
+ Quick transfer using specified address type
425
+
426
+ Args:
427
+ wif: WIF private key
428
+ from_type: Sender address type ('legacy', 'segwit', 'taproot')
429
+ to_addr: Recipient address
430
+ amount: Transfer amount in satoshis
431
+ fee: Transaction fee in satoshis
432
+ debug: Whether to output debug information
433
+
434
+ Returns:
435
+ Optional[str]: Transaction ID if successful, None if failed
436
+ """
437
+ try:
438
+ if debug:
439
+ print(f"Quick transfer ({from_type}):")
440
+
441
+ # Create WIF key object
442
+ wif_key = WIFKey(wif)
443
+
444
+ # Get address object by type
445
+ if from_type.lower() == 'legacy':
446
+ from_addr = wif_key.get_legacy()
447
+ elif from_type.lower() == 'segwit':
448
+ from_addr = wif_key.get_segwit()
449
+ elif from_type.lower() == 'taproot':
450
+ from_addr = wif_key.get_taproot()
451
+ else:
452
+ raise ValueError(f"Unsupported address type: {from_type}")
453
+
454
+ # Create and sign transaction
455
+ tx = from_addr.send(to_addr, amount, fee, debug=debug)
456
+
457
+ # Broadcast transaction
458
+ txid = tx.broadcast()
459
+
460
+ if txid:
461
+ if debug:
462
+ print(f" Transfer successful! TxID: {txid}")
463
+ return txid
464
+ else:
465
+ if debug:
466
+ print(f" Transfer failed")
467
+ return None
468
+
469
+ except Exception as e:
470
+ if debug:
471
+ print(f"Quick transfer failed: {str(e)}")
472
+ return None
473
+
474
+
475
+ # Module information
476
+ __version__ = "0.1.1"
477
+ __author__ = "Aaron Zhang"
478
+ __all__ = [
479
+ 'WIFKey',
480
+ 'BTCAddress',
481
+ 'BTCTransaction',
482
+ 'wif_to_addresses',
483
+ 'quick_transfer'
484
+ ]
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.2
2
+ Name: btcaaron
3
+ Version: 0.1.1
4
+ Summary: A Bitcoin Testnet transaction toolkit supporting Legacy, SegWit, and Taproot
5
+ Home-page: https://x.com/aaron_recompile
6
+ Author: Aaron Zhang
7
+ Author-email: aaron.recompile@gmail.com
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Topic :: Software Development :: Libraries
14
+ Classifier: Topic :: Internet :: WWW/HTTP
15
+ Requires-Python: >=3.7
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: requests>=2.25.0
18
+ Requires-Dist: bitcoin-utils>=0.7.1
19
+ Dynamic: author
20
+ Dynamic: author-email
21
+ Dynamic: classifier
22
+ Dynamic: description
23
+ Dynamic: description-content-type
24
+ Dynamic: home-page
25
+ Dynamic: requires-dist
26
+ Dynamic: requires-python
27
+ Dynamic: summary
28
+
29
+
30
+
31
+ # btcaaron
32
+
33
+ A simple Bitcoin Testnet toolkit for developers.
34
+ Easily generate addresses, scan UTXOs, build and broadcast transactions β€” with full support for Legacy, SegWit, and Taproot.
35
+
36
+ ---
37
+
38
+ ## πŸ”§ Features
39
+
40
+ - βœ… Generate Legacy / SegWit / Taproot addresses from WIF
41
+ - πŸ” Scan UTXOs and check balance via public APIs
42
+ - 🧠 Build & sign transactions (manual or quick mode)
43
+ - πŸš€ Broadcast to Blockstream or Mempool endpoints
44
+ - πŸ§ͺ Simple test suite for local debugging
45
+
46
+ ---
47
+
48
+ ## πŸ“¦ Installation
49
+
50
+ ```bash
51
+ pip install btcaaron
52
+
53
+ Or install from source:
54
+
55
+ git clone https://github.com/aaron-recompile/btcaaron.git
56
+ cd btcaaron
57
+ pip install .
58
+
59
+
60
+ βΈ»
61
+
62
+ πŸš€ Quick Start
63
+
64
+ from btcaaron import WIFKey, quick_transfer
65
+
66
+ # Your testnet WIF private key
67
+ wif = ""
68
+
69
+ # Generate addresses
70
+ key = WIFKey(wif)
71
+ print("Taproot:", key.get_taproot().address)
72
+
73
+ # Check balance
74
+ balance = key.get_taproot().get_balance()
75
+ print("Balance:", balance, "sats")
76
+
77
+ # Quick transfer
78
+ if balance > 1000:
79
+ txid = quick_transfer(wif, "taproot", "tb1q...", amount=500, fee=300)
80
+ print("Broadcasted:", txid)
81
+
82
+
83
+ βΈ»
84
+
85
+ πŸ“ Project Structure
86
+
87
+ btcaaron/
88
+ β”œβ”€β”€ btcaaron.py # Main library
89
+ β”œβ”€β”€ test.py # Example-based test runner
90
+ β”œβ”€β”€ README.md # This file
91
+ β”œβ”€β”€ setup.py # Install and packaging
92
+ β”œβ”€β”€ LICENSE # MIT License
93
+
94
+
95
+ βΈ»
96
+
97
+ πŸ‘¨β€πŸ’» Author
98
+
99
+ Aaron Zhang
100
+ https://x.com/aaron_recompile
101
+
102
+ βΈ»
103
+
104
+ πŸ“„ License
105
+
106
+ MIT License - Free for commercial and personal use.
107
+
@@ -0,0 +1,10 @@
1
+ README.md
2
+ setup.py
3
+ btcaaron/__init__.py
4
+ btcaaron/btcaaron.py
5
+ btcaaron.egg-info/PKG-INFO
6
+ btcaaron.egg-info/SOURCES.txt
7
+ btcaaron.egg-info/dependency_links.txt
8
+ btcaaron.egg-info/requires.txt
9
+ btcaaron.egg-info/top_level.txt
10
+ tests/test_btcaaron.py
@@ -0,0 +1,2 @@
1
+ requests>=2.25.0
2
+ bitcoin-utils>=0.7.1
@@ -0,0 +1 @@
1
+ btcaaron
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,27 @@
1
+ from setuptools import setup
2
+
3
+ setup(
4
+ name="btcaaron",
5
+ version="0.1.1",
6
+ description="A Bitcoin Testnet transaction toolkit supporting Legacy, SegWit, and Taproot",
7
+ long_description=open("README.md", encoding="utf-8").read(),
8
+ long_description_content_type="text/markdown",
9
+ author="Aaron Zhang",
10
+ author_email="aaron.recompile@gmail.com",
11
+ url="https://x.com/aaron_recompile",
12
+ packages=["btcaaron"],
13
+ install_requires=[
14
+ "requests>=2.25.0",
15
+ "bitcoin-utils>=0.7.1"
16
+ ],
17
+ classifiers=[
18
+ "Programming Language :: Python :: 3",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Development Status :: 3 - Alpha",
22
+ "Intended Audience :: Developers",
23
+ "Topic :: Software Development :: Libraries",
24
+ "Topic :: Internet :: WWW/HTTP",
25
+ ],
26
+ python_requires=">=3.7",
27
+ )
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ btcaaron - Simple Test Examples (Refactored)
4
+ """
5
+ from btcaaron import WIFKey, wif_to_addresses, quick_transfer
6
+
7
+ WIF = "cPeon9fBsW2BxwJTALj3hGzh9vm8C52Uqsce7MzXGS1iFJkPF4AT"
8
+ RECIPIENT = "tb1q2w85fm5g8kfhk9f63njplzu3yzcnluz9dgztjz"
9
+ FEE = 300
10
+
11
+
12
+ def get_addresses(wif):
13
+ key = WIFKey(wif)
14
+ return {
15
+ "Legacy": key.get_legacy(),
16
+ "SegWit": key.get_segwit(),
17
+ "Taproot": key.get_taproot(),
18
+ }
19
+
20
+ def test_address_generation():
21
+ print("=" * 50)
22
+ print("TEST 1: Address Generation")
23
+ print("=" * 50)
24
+ try:
25
+ addresses = wif_to_addresses(WIF)
26
+ for typ, addr in addresses.items():
27
+ print(f" {typ}: {addr}")
28
+
29
+ print("\nUsing WIFKey object:")
30
+ for typ, addr_obj in get_addresses(WIF).items():
31
+ print(f" {typ:8}: {addr_obj.address}")
32
+
33
+ print("βœ… Address generation test passed!")
34
+ return True
35
+ except Exception as e:
36
+ print(f"❌ Address generation test failed: {e}")
37
+ return False
38
+
39
+ def test_utxo_scanning():
40
+ print("\n" + "=" * 50)
41
+ print("TEST 2: UTXO Scanning")
42
+ print("=" * 50)
43
+ try:
44
+ for typ, addr in get_addresses(WIF).items():
45
+ print(f"\n{typ} Address: {addr.address}")
46
+ utxos = addr.scan_utxos(debug=True)
47
+ balance = addr.get_balance()
48
+ print(f" UTXOs: {len(utxos)} | Balance: {balance:,} sats")
49
+ for i, utxo in enumerate(utxos[:3], 1):
50
+ print(f" {i}. {utxo['txid'][:16]}...:{utxo['vout']} = {utxo['amount']} sats")
51
+ if len(utxos) > 3:
52
+ print(f" ... and {len(utxos) - 3} more")
53
+
54
+ print("\nβœ… UTXO scanning test completed!")
55
+ return True
56
+ except Exception as e:
57
+ print(f"❌ UTXO scanning test failed: {e}")
58
+ return False
59
+
60
+ def test_balance_check():
61
+ print("\n" + "=" * 50)
62
+ print("TEST 3: Balance Check")
63
+ print("=" * 50)
64
+ try:
65
+ balances = [(typ, addr.get_balance(), addr.address) for typ, addr in get_addresses(WIF).items()]
66
+ total = sum(b for _, b, _ in balances)
67
+
68
+ for typ, bal, _ in balances:
69
+ print(f" {typ:8}: {bal:,} sats")
70
+ print(f" Total : {total:,} sats")
71
+
72
+ if total:
73
+ balances.sort(key=lambda x: x[1], reverse=True)
74
+ typ, bal, addr = balances[0]
75
+ print(f"\nHighest balance: {typ} | {bal:,} sats | {addr}")
76
+
77
+ print("\nβœ… Balance check test completed!")
78
+ return True
79
+ except Exception as e:
80
+ print(f"❌ Balance check test failed: {e}")
81
+ return False
82
+
83
+ def test_transfer():
84
+ print("\n" + "=" * 50)
85
+ print("TEST 4: Transfer Transaction")
86
+ print("=" * 50)
87
+ AMOUNT = 500
88
+ try:
89
+ candidates = [(t, a, a.get_balance()) for t, a in get_addresses(WIF).items() if a.get_balance() >= AMOUNT + FEE]
90
+ if not candidates:
91
+ print("❌ No address has sufficient balance.")
92
+ return False
93
+
94
+ from_type, from_addr, bal = sorted(candidates, key=lambda x: x[2], reverse=True)[0]
95
+ print(f"From: {from_addr.address} ({from_type}, {bal:,} sats)")
96
+ print(f"To: {RECIPIENT} | Amount: {AMOUNT} | Fee: {FEE}")
97
+ if input("Proceed? (y/N): ").lower() != 'y':
98
+ print("Cancelled")
99
+ return False
100
+
101
+ txid = from_addr.send(RECIPIENT, AMOUNT, fee=FEE, debug=True).broadcast()
102
+ print("βœ… Broadcast TXID:", txid)
103
+ return True
104
+ except Exception as e:
105
+ print(f"❌ Transfer test failed: {e}")
106
+ return False
107
+
108
+ def test_quick_transfer():
109
+ print("\n" + "=" * 50)
110
+ print("TEST 5: Quick Transfer")
111
+ print("=" * 50)
112
+ AMOUNT = 400
113
+ FROM = "Taproot"
114
+ try:
115
+ addr = get_addresses(WIF)[FROM]
116
+ bal = addr.get_balance()
117
+ print(f"Using {FROM}: {addr.address} | {bal:,} sats")
118
+ if bal < AMOUNT + FEE:
119
+ print("❌ Insufficient balance.")
120
+ return False
121
+
122
+ if input("Proceed? (y/N): ").lower() != 'y':
123
+ print("Cancelled")
124
+ return False
125
+
126
+ txid = quick_transfer(WIF, FROM.lower(), RECIPIENT, AMOUNT, fee=FEE, debug=True)
127
+ print("βœ… Quick TXID:", txid)
128
+ return True
129
+ except Exception as e:
130
+ print(f"❌ Quick transfer test failed: {e}")
131
+ return False
132
+
133
+ def main():
134
+ tests = [
135
+ ("Address Generation", test_address_generation),
136
+ ("UTXO Scanning", test_utxo_scanning),
137
+ ("Balance Check", test_balance_check),
138
+ ("Transfer Transaction", test_transfer),
139
+ ("Quick Transfer", test_quick_transfer)
140
+ ]
141
+
142
+ print("\nπŸ“¦ btcaaron Test Suite")
143
+ for i, (name, _) in enumerate(tests, 1):
144
+ print(f"{i}. {name}")
145
+ print("0. Run All")
146
+
147
+ try:
148
+ sel = input("Select (0-N): ").strip()
149
+ if sel == "0":
150
+ for name, fn in tests:
151
+ print(f"\nβ–Ά {name}")
152
+ fn()
153
+ elif sel.isdigit() and 0 < int(sel) <= len(tests):
154
+ name, fn = tests[int(sel) - 1]
155
+ print(f"\nβ–Ά {name}")
156
+ fn()
157
+ else:
158
+ print("❌ Invalid selection")
159
+ except Exception as e:
160
+ print(f"❌ Error: {e}")
161
+
162
+ if __name__ == "__main__":
163
+ main()