Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create bitcoin_utils.py to make bitcoin payments available to agents #750

Closed
wants to merge 5 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions bitcoin_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import os
import random
import requests
from bitcoin import SelectParams
from bitcoin.wallet import CBitcoinSecret, P2PKHBitcoinAddress
from bitcoin.core import b2lx, lx
from bitcoin.core.script import CScript
from bitcoin.core.scripteval import VerifyScript, SCRIPT_VERIFY_P2SH
from bitcoin.rpc import Proxy


def choose_network(context):
if context == 'real_transaction':
return 'mainnet'
elif context == 'testing':
return 'testnet'
else:
raise ValueError("Invalid context. Choose 'real_transaction' or 'testing'.")


def create_wallet(network='mainnet'):
SelectParams(network)
secret = CBitcoinSecret.from_secret_bytes(os.urandom(32))
address = P2PKHBitcoinAddress.from_pubkey(secret.pub)
return str(address), str(secret)


def get_balance(address, network='mainnet'):
SelectParams(network)
if network == 'mainnet':
url = f'https://blockchain.info/q/addressbalance/{address}?confirmations=6'
elif network == 'testnet':
url = f'https://api.blockcypher.com/v1/btc/test3/addrs/{address}/balance'

response = requests.get(url)
if response.status_code == 200:
if network == 'mainnet':
return int(response.text) / 100000000 # Convert from satoshis to BTC
elif network == 'testnet':
return response.json()['balance'] / 100000000
else:
raise Exception(f'Error fetching balance: {response.text}')


def get_address(secret):
secret_key = CBitcoinSecret(secret)
address = P2PKHBitcoinAddress.from_pubkey(secret_key.pub)
return str(address)


def send_bitcoin(sender_secret, recipient_address, amount_btc, fee_btc, context):
network = choose_network(context)
SelectParams(network)
rpc = Proxy()

sender_secret = CBitcoinSecret(sender_secret)
sender_address = P2PKHBitcoinAddress.from_pubkey(sender_secret.pub)
recipient_address = P2PKHBitcoinAddress(recipient_address)

amount_satoshis = int(amount_btc * 100000000)
fee_satoshis = int(fee_btc * 100000000)

unspent = [utxo for utxo in rpc.listunspent(0) if utxo['address'] == sender_address]
if not unspent:
raise Exception('No available UTXOs for the sender address.')

unspent.sort(key=lambda x: -x['amount'])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you should randomize the list, if you sort by amount you'll either grind your utxos to dust or pay way too much fees

#randomise UTXOs
random.shuffle(unspent)
tx_ins = []
tx_outs = []
total_input_value = 0
for utxo in unspent:
txid = lx(utxo['txid'].hex())
vout = utxo['vout']
script_pub_key = utxo['scriptPubKey']
input_value = utxo['amount']

total_input_value += input_value

tx_ins.append(rpc.create_raw_input(txid, vout))
tx_outs.append((script_pub_key, input_value))

if total_input_value >= (amount_satoshis + fee_satoshis):
change_value = total_input_value - (amount_satoshis + fee_satoshis)
break
else:
raise Exception('Insufficient funds.')

if change_value > 0:
tx_outs.append((CScript([sender_secret.pub, 0xAC]), change_value))

tx_outs.append((CScript([recipient_address, 0xAC]), amount_satoshis))

raw_tx = rpc.create_raw_transaction(tx_ins, tx_outs)

for i, (script_pub_key, _) in enumerate(tx_outs):
sig = rpc.sign_raw_transaction_with_wallet(raw_tx, n_in=i, script_pub_key=script_pub_key, prev_pub_key=sender_secret.pub)
raw_tx = sig['hex']

txid = rpc.send_raw_transaction(raw_tx)
return b2lx(txid)