Skip to content

Instantly share code, notes, and snippets.

@dagurval
Last active March 1, 2024 21:32
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dagurval/19cc375f2b16da26c7f6e06794f81a25 to your computer and use it in GitHub Desktop.
Save dagurval/19cc375f2b16da26c7f6e06794f81a25 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import qrcode
import asyncio
import time
from bitcoincash.core import b2x, lx, COutPoint, CMutableTxOut, CMutableTxIn, \
CMutableTransaction, b2lx
from bitcoincash.core.script import *
from bitcoincash.core.scripteval import VerifyScript
from bitcoincash.wallet import CBitcoinAddress
from bitcoincash.electrum import Electrum
# Warning: This redeem script provides no security. It is a anyone-can-spend contract.
# It has been used before, so you cannot rely on obscurity either.
# Someone can take your coins if you send them to this script without chainging it.
REDEEM_SCRIPT = CScript([
OP_NIP
])
UNLOCKING_SCRIPT = [1, 1]
async def main():
cli = Electrum()
await cli.connect()
locking_script = REDEEM_SCRIPT.to_p2sh_scriptPubKey();
contract_address = CBitcoinAddress.from_scriptPubKey(locking_script);
# Output a QR code for the address in the terminal
qr = qrcode.QRCode()
qr.add_data(str(contract_address))
qr.print_ascii()
print(contract_address)
try:
while True:
if (await main_loop(cli, contract_address, locking_script)):
return
time.sleep(1)
finally:
await cli.close()
def spend_input(coin_in, address):
tx_input = CMutableTxIn(COutPoint(lx(coin_in['tx_hash']), coin_in['tx_pos']))
tx_input.scriptSig = CScript(UNLOCKING_SCRIPT + [REDEEM_SCRIPT])
tx_output = CMutableTxOut(
nValue = -1, # we will set this after calcualting fee
scriptPubKey = address.to_scriptPubKey())
tx = CMutableTransaction()
tx.vin = [tx_input]
tx.vout = [tx_output]
tx.vout.append(CMutableTxOut(nValue = 0, scriptPubKey = CScript(
[OP_RETURN, b'!!'])))
fee = len(tx.serialize())
assert fee == 100, f"Tx size should be 100, was {fee}"
output_value = coin_in['value'] - fee
if output_value < 546:
# Below dust, unspendable
return None
tx.vout[0].nValue = output_value
return tx
async def broadcast(cli, tx):
tx_hex = b2x(tx.serialize())
while True:
try:
await cli.RPC('blockchain.transaction.broadcast', tx_hex)
break
except Exception as e:
print(f"Failed to broadcast {e}")
time.sleep(1)
continue
async def main_loop(cli, address, locking_script):
# Get list of spendable coins in address
print("")
print("Press enter when contract has been funded...")
input("")
coins = await cli.RPC('blockchain.address.listunspent', str(address))
tx = CMutableTransaction()
tx.nLockTime = 1
broadcasts = []
while len(coins):
tx = spend_input(coins.pop(), address)
if tx is None:
continue
broadcasts.append(broadcast(cli, tx))
coins.append({
'tx_hash': b2lx(tx.GetHash()),
'tx_pos': 0,
'value': tx.vout[0].nValue,
})
print(".", end = "", flush = True)
print(f"Broadcasting {len(broadcasts)} transactions")
await asyncio.gather(*broadcasts)
return True
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
loop.close()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment