This Python script generates a fully signed raw Bitcoin transaction that can be used with the MARA Slipstream service (or any other broadcast method).
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()
x = vk.pubkey.point.x()
y = vk.pubkey.point.y()
return ('02' if y % 2 == 0 else '03') + f"{x:064x}" # Always compressed
def get_address(pubkey):
pubkey_bytes = bytes.fromhex(pubkey)
pubkey_hash = hash160(pubkey_bytes)
return pubkey_to_address(pubkey) # Always compressed P2PKH
def is_legacy_address(address):
return address.startswith('1')
def fetch_utxos(address):
if not is_legacy_address(address):
raise ValueError("Only legacy addresses (starting with '1') are supported")
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):
# Legacy transaction size estimation with compressed pubkeys
input_size = 148 # Standard for P2PKH with compressed pubkey
output_size = 34 # P2PKH output size
total_size = 10 + num_inputs * input_size + num_outputs * output_size
return total_size * fee_rate
def create_transaction(wif, to_address, amount_btc, fee_rate):
# Validate addresses are legacy
if not is_legacy_address(to_address):
raise ValueError("Only legacy addresses (starting with '1') are supported for recipient")
priv_bytes, _ = decode_wif(wif)
pubkey = get_pubkey(priv_bytes)
from_address = get_address(pubkey)
if not is_legacy_address(from_address):
raise ValueError("Only legacy addresses (starting with '1') are supported for sender")
utxos = fetch_utxos(from_address)
if not utxos:
raise RuntimeError("No UTXOs available")
# Sort UTXOs by value (descending) to minimize number of inputs
utxos.sort(key=lambda x: x['value'], reverse=True)
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) + estimate_fee(len(inputs), 2, fee_rate):
break # We have enough including fees
if total == 0:
raise RuntimeError("No UTXOs available")
send_amount = int(amount_btc * 1e8)
fee = estimate_fee(len(inputs), 2, fee_rate)
if total < send_amount + fee:
raise RuntimeError(f"Insufficient funds. Need {send_amount + fee} satoshis, have {total}")
change = total - send_amount - fee
outputs = [{'address': to_address, 'value': send_amount}]
if change > 546: # Minimum dust amount
outputs.append({'address': from_address, 'value': change})
elif change > 0:
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" # must start with '1'
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)
# 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: 0100000002b3138da741af259146ca2c4895bac7e0c08147679f88ce3302fb4db0584bf31205000 0006b4830450221009131a350b98aab71d77f5a9a94dd5bac9a5602e2b761a2dc605ba03cb996df ff0220480b53128af0cf7c9b74fa8d625e2a9d143d15fdaa2fd24e421d4d0d29c1532201210279b e667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ffffffff6441384445 a0f426ee689e2532e41fc6947dda41558026b80f5b1dfd7c58455d130000006a473044022032ccf 651486b1055ea188d95645dc61329e9fa89a18b33417d33dc0aa61484d602206bbb8e8cf1b2a6e8 ea64f78f7d7625d05014f593c76438c7127d5e2b147d971601210279be667ef9dcbbac55a06295c e870b07029bfcdb2dce28d959f2815b16f81798ffffffff023997ef27000000001976a914751e76 e8199196d454941c45d1b3a323f1433bd688acafb2f501000000001976a914f6f5431d25bbf7b12 e8add9af5e3475c44a0a5b888ac00000000
Cheers

Did u test it? Do you have a example that it worked out?
Cheers

There's just one thing left. How do I get the WIF from puzzle 71?

I accidentally found it when i was typing 18 random numbers, i will send you so you can be happy 