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=True):
sk = ecdsa.SigningKey.from_string(priv_bytes, curve=ecdsa.SECP256k1)
vk = sk.get_verifying_key()
if compressed: x = vk.pubkey.point.x()
xy = vk.pubkey.point.xy()
return ('02' if y % 2 = vk.pubkey.point.y(= 0 else '03') + f"{x:064x}" # Always compressed
return ('02' if y % 2 == 0 else '03') + f"{x:064x}"
elsedef get_address(pubkey):
return '04' + vkpubkey_bytes = bytes.to_stringfromhex(pubkey).hex()
pubkey_hash = hash160(pubkey_bytes)
def get_address return pubkey_to_address(pubkey, compressed): # Always compressed P2PKH
pubkey_bytes = bytes.fromhex(pubkey)
if compresseddef is_legacy_address(address):
pubkey_hash = hash160return address.startswith(pubkey_bytes'1')
return pubkey_to_address(pubkey) # P2PKH
elsedef fetch_utxos(address):
return pubkey_to_addressif not is_legacy_address(pubkeyaddress) # P2PKH uncompressed:
raise ValueError("Only legacy addresses (starting with '1') are supported")
def address_type(address):
if address.startswith('1') try:
return 'p2pkh' url = f"https://mempool.space/api/address/{address}/utxo"
elif address r = requests.startswithget('3'url):
return 'p2sh' r.raise_for_status()
elif address return r.startswithjson('bc1q'):
return 'p2wpkh'except Exception as e:
elif address.startswith print('bc1p')"Failed to fetch UTXOs:", e)
return 'p2tr'[]
else:
raise ValueErrordef estimate_fee("Unknown address type"num_inputs, num_outputs, fee_rate):
# Legacy transaction size estimation with compressed pubkeys
def fetch_utxos(address): input_size = 148 # Standard for P2PKH with compressed pubkey
try: output_size = 34 # P2PKH output size
urltotal_size = f"https://mempool.space/api/address/{address}/utxo"10 + num_inputs * input_size + num_outputs * output_size
r = requests.get(url)return total_size * fee_rate
r.raise_for_status()
return r.jsondef create_transaction(wif, to_address, amount_btc, fee_rate):
except Exception as e: # Validate addresses are legacy
printif not is_legacy_address("Failed to fetch UTXOs:", eto_address):
return [] raise ValueError("Only legacy addresses (starting with '1') are supported for recipient")
def estimate_fee(num_inputs priv_bytes, num_outputs, fee_rate, compressed, out_types_ = decode_wif(wif):
# estimate size based on input/output types pubkey = get_pubkey(priv_bytes)
input_size from_address = 108 if compressed else 148get_address(pubkey)
output_size = sum(43 if t == 'p2wpkh' else 34 for t in out_types)
total_size = 10 + num_inputs * input_size + output_size if not is_legacy_address(from_address):
return total_size * fee_rate raise ValueError("Only legacy addresses (starting with '1') are supported for sender")
def create_transaction utxos = fetch_utxos(wif, to_address, amount_btc, fee_ratefrom_address):
priv_bytes, compressed = decode_wif(wif) if not utxos:
pubkey = get_pubkey raise RuntimeError(priv_bytes, compressed"No UTXOs available")
from_address = get_address(pubkey, compressed)
to_type = address_type # Sort UTXOs by value (to_addressdescending) to minimize number of inputs
utxos.sort(key=lambda x: x['value'], reverse=True)
utxos = fetch_utxos(from_address)
if not utxos: inputs = []
raise RuntimeError("No UTXOs available")total = 0
for utxo in utxos:
inputs = .append({'output': f"{utxo['txid']}:{utxo['vout']}", 'value': utxo['value']})
total += 0utxo['value']
for utxo in utxos if total >= int(amount_btc * 1e8) + estimate_fee(len(inputs), 2, fee_rate):
inputs.append({'output': f"{utxo['txid']}:{utxo['vout']}", 'value': utxo['value']}) break # We have enough including fees
total += utxo['value']
if total >= int(amount_btc * 1e8)= 0: break
raise RuntimeError("No UTXOs available")
if total == 0:
raise RuntimeErrorsend_amount = int("Not enough inputs to cover amount"amount_btc * 1e8)
fee = estimate_fee(len(inputs), 2, fee_rate)
send_amount = int(amount_btc * 1e8)
if total < send_amount + fee = estimate_fee(len(inputs), 2, fee_rate, compressed, [to_type, 'p2pkh']):
raise RuntimeError(f"Insufficient funds. Need {send_amount + fee} satoshis, have {total}")
if total < send_amount + fee:
raise RuntimeError("Insufficient funds")change = total - send_amount - fee
outputs = [{'address': to_address, 'value': send_amount}]
if change = total - send_amount - fee> 546: # Minimum dust amount
outputs = [.append({'address': to_addressfrom_address, 'value': send_amountchange}])
if elif change > 0:
outputs.append({'address': from_address, 'value': fee += change}) # Add remaining change to fee if it's too small
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" # worksmust start 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:
# To broadcast the transaction (uncomment to use)
# broadcast_url = "https://mempool.space/api/tx"
# response = requests.post(broadcast_url, data=raw_tx)
# print("Broadcast response:", response.text)
except Exception as e:
print("Error:", e)
Example script output
Raw Transaction: 0100000005b3138da741af259146ca2c4895bac7e0c08147679f88ce3302fb4db0584bf312050000100000002b3138da741af259146ca2c4895bac7e0c08147679f88ce3302fb4db0584bf31205000 0006b483045022100fcc29e303b33016113a6ec23553187c0589859f0ba576e2dc09990fa29c61a0006b4830450221009131a350b98aab71d77f5a9a94dd5bac9a5602e2b761a2dc605ba03cb996df cb02205a200c482ab1d4901853b47a62ec57cdfa87e8908e6e3204c1d5dbe7c14efb77012102122ff0220480b53128af0cf7c9b74fa8d625e2a9d143d15fdaa2fd24e421d4d0d29c1532201210279b 09f5ec514a1580a2937bd833979d933199fc230e204c6cdc58872b7d46f75ffffffff15cda65f1ee667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff6441384445 46982fc082b15c8dbb60985d12a7e60b0c742263608cc9349f3808460000006b483045022100967a0f426ee689e2532e41fc6947dda41558026b80f5b1dfd7c58455d130000006a473044022032ccf 4dce2352f28d8ea52866a2cecbe153e6f9da0f228b0a5ceb7a2afa2a5cc78022045af0952718f04651486b1055ea188d95645dc61329e9fa89a18b33417d33dc0aa61484d602206bbb8e8cf1b2a6e8 e0f5c1dade18cc6f9309951d5025c3f9597af0e5c27e5afcde01210212209f5ec514a1580a2937bea64f78f7d7625d05014f593c76438c7127d5e2b147d971601210279be667ef9dcbbac55a06295c d833979d933199fc230e204c6cdc58872b7d46f75ffffffff5e24638c73bbff287f002d3035ac13e870b07029bfcdb2dce28d959f2815b16f81798ffffffff023997ef27000000001976a914751e76 d43b373bed0470a8425b92ed7f601409b3000000006b483045022100d7acede82875fc6025d28a2e8199196d454941c45d1b3a323f1433bd688acafb2f501000000001976a914f6f5431d25bbf7b12 c7464a29b198ee477adcf0fbb7ac5bf1fc6b52b340220414620d32b062afef07773d90d958ad599 1bf06f3ff0720839c5f6e8364ec35901210212209f5ec514a1580a2937bd833979d933199fc230e 204c6cdc58872b7d46f75ffffffff10d650ec671f51e730c36da55623e6c9ae1f1803ea3cbb4cf8 2edc1f3e066358000000006b483045022100e55a20c9e40518ce4b9bbd3640350ae7d10ad7d86b0 bbfb3cac7dd778300ea0102203c54c9cd9d7fc8894b6d1f16b5558687047701e31e58257ea4b528 e14d86a58601210212209f5ec514a1580a2937bd833979d933199fc230e204c6cdc58872b7d46f7 5ffffffff6441384445a0f426ee689e2532e41fc6947dda41558026b80f5b1dfd7c58455d130000 006a47304402202dea305bc23b33c291aabde55273836b45c3f7a207cd138279133281a1e5094c0 22060a78ae12f61dee3ed8ae328a05c785957154e18abcd2d6893b4193eb2ed3ae601210212209f 5ec514a1580a2937bd833979d933199fc230e204c6cdc58872b7d46f75ffffffff023997ef27000 000001976a914f6f5431d25bbf7b12e8add9af5e3475c44a0a5b888aca1086202000000001976a9 14f6f5431d25bbf7b12e8add9af5e3475c44a0a5b888ac00000000e8add9af5e3475c44a0a5b888ac00000000
Cheers
