This Python script generates a fully signed raw Bitcoin transaction that can be used with the MARA Slipstream service (or any other broadcast method). Accepts any type of destination address: Legacy (P2PKH), SegWit (P2WPKH), or Taproot (P2TR).
Supports private keys in WIF (Wallet Import Format).
Automatically:
Fetches UTXOs (unspent outputs) from mempool.space
import base58
import requests
import ecdsa
from bitcoin import *
import hashlib
def decode_wif(wif):
b = base58.b58decode(wif)
if len(b) == 38 and b[-5] == 0x01:
return b[1:-5], True # compressed
elif len(b) == 37:
return b[1:-1], False # uncompressed
raise ValueError("Invalid WIF format")
def get_pubkey(priv_bytes, compressed):
sk = ecdsa.SigningKey.from_string(priv_bytes, curve=ecdsa.SECP256k1)
vk = sk.get_verifying_key()
if compressed:
x = vk.pubkey.point.x()
y = vk.pubkey.point.y()
return ('02' if y % 2 == 0 else '03') + f"{x:064x}"
else:
return '04' + vk.to_string().hex()
def get_address(pubkey, compressed):
pubkey_bytes = bytes.fromhex(pubkey)
if compressed:
pubkey_hash = hash160(pubkey_bytes)
return pubkey_to_address(pubkey) # P2PKH
else:
return pubkey_to_address(pubkey) # P2PKH uncompressed
def address_type(address):
if address.startswith('1'):
return 'p2pkh'
elif address.startswith('3'):
return 'p2sh'
elif address.startswith('bc1q'):
return 'p2wpkh'
elif address.startswith('bc1p'):
return 'p2tr'
else:
raise ValueError("Unknown address type")
def fetch_utxos(address):
try:
url = f"https://mempool.space/api/address/{address}/utxo"
r = requests.get(url)
r.raise_for_status()
return r.json()
except Exception as e:
print("Failed to fetch UTXOs:", e)
return []
def estimate_fee(num_inputs, num_outputs, fee_rate, compressed, out_types):
# estimate size based on input/output types
input_size = 108 if compressed else 148
output_size = sum(43 if t == 'p2wpkh' else 34 for t in out_types)
total_size = 10 + num_inputs * input_size + output_size
return total_size * fee_rate
def create_transaction(wif, to_address, amount_btc, fee_rate):
priv_bytes, compressed = decode_wif(wif)
pubkey = get_pubkey(priv_bytes, compressed)
from_address = get_address(pubkey, compressed)
to_type = address_type(to_address)
utxos = fetch_utxos(from_address)
if not utxos:
raise RuntimeError("No UTXOs available")
inputs = []
total = 0
for utxo in utxos:
inputs.append({'output': f"{utxo['txid']}:{utxo['vout']}", 'value': utxo['value']})
total += utxo['value']
if total >= int(amount_btc * 1e8): break
if total == 0:
raise RuntimeError("Not enough inputs to cover amount")
send_amount = int(amount_btc * 1e8)
fee = estimate_fee(len(inputs), 2, fee_rate, compressed, [to_type, 'p2pkh'])
if total < send_amount + fee:
raise RuntimeError("Insufficient funds")
change = total - send_amount - fee
outputs = [{'address': to_address, 'value': send_amount}]
if change > 0:
outputs.append({'address': from_address, 'value': change})
tx = mktx(inputs, outputs)
for i in range(len(inputs)):
tx = sign(tx, i, wif)
return tx
# === Example Usage ===
if __name__ == "__main__":
WIF = "<wif_private_key>"
TO_ADDRESS = "1YourLegacyAddressHere" # works with 1..., 3..., bc1q..., bc1p...
AMOUNT_BTC = 6.70013241
FEE_RATE = 20 # sats/vB
try:
raw_tx = create_transaction(WIF, TO_ADDRESS, AMOUNT_BTC, FEE_RATE)
print("Raw Transaction:", raw_tx)
except Exception as e:
print("Error:", e)
Example script output
Raw Transaction: 0100000005b3138da741af259146ca2c4895bac7e0c08147679f88ce3302fb4db0584bf31205000 0006b483045022100fcc29e303b33016113a6ec23553187c0589859f0ba576e2dc09990fa29c61a cb02205a200c482ab1d4901853b47a62ec57cdfa87e8908e6e3204c1d5dbe7c14efb77012102122 09f5ec514a1580a2937bd833979d933199fc230e204c6cdc58872b7d46f75ffffffff15cda65f1e 46982fc082b15c8dbb60985d12a7e60b0c742263608cc9349f3808460000006b483045022100967 4dce2352f28d8ea52866a2cecbe153e6f9da0f228b0a5ceb7a2afa2a5cc78022045af0952718f04 e0f5c1dade18cc6f9309951d5025c3f9597af0e5c27e5afcde01210212209f5ec514a1580a2937b d833979d933199fc230e204c6cdc58872b7d46f75ffffffff5e24638c73bbff287f002d3035ac13 d43b373bed0470a8425b92ed7f601409b3000000006b483045022100d7acede82875fc6025d28a2 c7464a29b198ee477adcf0fbb7ac5bf1fc6b52b340220414620d32b062afef07773d90d958ad599 1bf06f3ff0720839c5f6e8364ec35901210212209f5ec514a1580a2937bd833979d933199fc230e 204c6cdc58872b7d46f75ffffffff10d650ec671f51e730c36da55623e6c9ae1f1803ea3cbb4cf8 2edc1f3e066358000000006b483045022100e55a20c9e40518ce4b9bbd3640350ae7d10ad7d86b0 bbfb3cac7dd778300ea0102203c54c9cd9d7fc8894b6d1f16b5558687047701e31e58257ea4b528 e14d86a58601210212209f5ec514a1580a2937bd833979d933199fc230e204c6cdc58872b7d46f7 5ffffffff6441384445a0f426ee689e2532e41fc6947dda41558026b80f5b1dfd7c58455d130000 006a47304402202dea305bc23b33c291aabde55273836b45c3f7a207cd138279133281a1e5094c0 22060a78ae12f61dee3ed8ae328a05c785957154e18abcd2d6893b4193eb2ed3ae601210212209f 5ec514a1580a2937bd833979d933199fc230e204c6cdc58872b7d46f75ffffffff023997ef27000 000001976a914f6f5431d25bbf7b12e8add9af5e3475c44a0a5b888aca1086202000000001976a9 14f6f5431d25bbf7b12e8add9af5e3475c44a0a5b888ac00000000
Cheers
