def main(): # parse arguments parser = argparse.ArgumentParser(description='Fetch latest Bitcoin price') parser.add_argument('-u', '--USD', help='convert a USD dollar amount to BTC', nargs=1, type=float, required=False) parser.add_argument('-b', '--BTC', help='convert a BTC amount to USD', nargs=1, type=float, required=False) parser.add_argument('-v', '--verbose', help='show verbose output', action='store_true', required=False) args = vars(parser.parse_args()) if (args['verbose']): logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.WARNING) price = get_bitcoin_price() if price is None: logger.critical('Could not obtain latest BTC price') exit(1) # USD amount specified if args['USD'] is not None: usd = args['USD'][0] btc = usd / price sys.stdout.write('${:,.2f} = {:,.8f} BTC\n'.format(usd, btc)) # BTC amount specified elif args['BTC'] is not None: btc = args['BTC'][0] usd = btc * price sys.stdout.write('${:,.2f} = {:,.8f} BTC\n'.format(usd, btc)) else: sys.stdout.write('${:,.2f} = {:,.8f} BTC\n'.format(price, 1.0))
def main(): # parse arguments parser = argparse.ArgumentParser(description='List transactions for one or more addresses') parser.add_argument('-f', '--from', help='fetch transactions from these addresses', nargs='+', required=False) parser.add_argument('-v', '--verbose', help='show verbose output', action='store_true', required=False) args = vars(parser.parse_args()) if (args['verbose']): logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) addrs = None if args['from']: addrs = args['from'] else: w = get_wallet() addrs = [x['address'] for x in w] logger.warning('fetching all transactions on all addresses') txs = [] for name in addrs: txs.extend(get_transactions(lookup(name)['address'])) if len(txs) == 0: logger.info('No transactions found') exit(0) width = max([len(x['memo']) for x in txs]) for rec in txs: rec['memo'] += ' ' * (width - len(rec['memo'])) report = [] balance = 0.0 for rec in sorted(txs, key=lambda k : k['date']): balance += rec['amount'] id = rec['id'] if args['verbose'] else rec['id'][:16] line = '%s %s %4s %+13.8f %+13.8f' % (rec['date'], id, rec['memo'], rec['amount'], balance) report.append(line) n = len(report) for i in range(n): print '%3d' % i, report[i]
def main(): # parse arguments parser = argparse.ArgumentParser(description='Add a memo to a transaction') parser.add_argument('-t', '--txid', help='transaction id', nargs=1, required=True) parser.add_argument('-m', '--memo', help='up to 64 chars of memo text', nargs=1, required=True) parser.add_argument('-v', '--verbose', help='show verbose output', action='store_true', required=False) args = vars(parser.parse_args()) if (args['verbose']): logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) txid = args['txid'][0] memo = args['memo'][0] m = load_memos() m[txid] = memo save_memos(m)
def main(): # parse arguments parser = argparse.ArgumentParser(description='Displays Bitcoin balances') parser.add_argument('-a', '--showall', help='show inactive and zero balance addresses', action='store_true', required=False) parser.add_argument('-f', '--from', help='get balances from just these addresses', nargs='+', required=False) parser.add_argument('-v', '--verbose', help='show verbose output', action='store_true', required=False) parser.add_argument('-c', '--cache', help='use cached data only', action='store_true', required=False) args = vars(parser.parse_args()) if (args['verbose']): logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.WARNING) # report column format fmt = '%-40s %-40s %12s %12s' rpt = '\n' + fmt % ('Name', 'Address', 'BTC', 'USD') + '\n\n' # fetch balances total = 0.0 btc = get_bitcoin_price(args['cache']) single_address = None entries = [] # all balances if args['from'] is None: single_address = False entries = get_wallet() # single balance else: single_address = True found = lookup(args['from'][0]) if found is None: logger.warning('No address found matching "{}"'.format( args['from'][0])) found = {} found['active'] = True found['privkey'] = None found['name'] = 'Address' found['address'] = args['from'][0] entries = [found] # create report for item in entries: name = item['name'] addr = item['address'] active = item['active'] # only get balances for active addresses you own if (item['privkey'] is not None and active) or args['showall'] or single_address: satoshi = get_balance(addr, args['cache']) if satoshi is None: logger.critical('unable to fetch balances') exit(1) bal = float(satoshi) / 1e8 total += bal bal_disp = '{:,.8f}'.format(bal) usd_disp = '{:,.2f}'.format(bal * btc) if (bal > 0) or args['showall'] or single_address: rpt += fmt % (name, addr, bal_disp, usd_disp) + '\n' # totals if not single_address: total_disp = '{:,.8f}'.format(total) usd_total_disp = '{:,.2f}'.format(total * btc) rpt += '\n' + fmt % ('', 'Total', total_disp, usd_total_disp) + '\n' rpt += '\n' sys.stdout.write(rpt)
import argparse, logging from bitcoin import privtopub, pubkey_to_address from btclib import logger, config from validate import validate_address parser = argparse.ArgumentParser( description='Check for invalid key pairs in the wallet') parser.add_argument('-v', '--verbose', help='show status of all key pairs', action='store_true', required=False) args = vars(parser.parse_args()) if (args['verbose']): logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) clean = True for entry in config['wallet']: valid = True name = None address = None privkey = None # verify name and address are present if 'name' not in entry.keys(): logger.error('a wallet entry is MISSING a name') valid = False else:
#!/usr/bin/env python # spill excess bitcoin import os, yaml, logging from btclib import logger, get_balance, validate_address from pprint import pprint from validate import validate_address logger.setLevel(logging.INFO) # load spill config config = None config_file = os.path.expanduser('~/.spill.conf') try: with open(config_file, 'r') as stream: config = yaml.load(stream) except: logger.critical('unable to open config file: {}'.format(config_file)) exit(1) # fetch balances and index by address label idx = {} for rec in config['spill']: if 'label' not in rec.keys(): logger.critical('config file entry is missing a label') exit(1) if 'address' not in rec.keys(): logger.critical('{} has no public address configured'.format(
#!/usr/bin/env python import sys, os, json, unittest, logging from mock import patch from validate import validate_address from btclib import config, logger, get_wallet, get_bitcoin_price, lookup from btclib import get_cache_path, url_get, get_balance, get_unspent logger.setLevel(100) # suppress logging class TestAddressValidation(unittest.TestCase): valid_addresses = [ '1PFzobFoKmEnUu2AKJ5JTKrXaR5vh5Ejp6', '1447xMP8SWGEK88DTtQmH35Jn2U3fY6JAG', '1LCcFfMVa4WnSDxngZhiVzpmCPcCvXMat5' ] invalid_addresses = [ '', 'NotAnAddress' '19ChmEKxFCMhQTDTniZ2BR7YRgvPz19tu8', '9268FM3VGLR7uvbFEesydGYtCPmc1uDcpx', '1J9MSBD4z62M2u654XzwavTgtaKdUbigGB' ] def test_valid_bitcoin_adresses(self): for address in self.valid_addresses: self.assertTrue(validate_address(address), 'False negative') def test_invalid_bitcoin_addresses(self):
def main(): # get current fast confirmation bitcoin fee best_fee = bitcoin_fee() # command line arguments parser = argparse.ArgumentParser( description='Create a Bitcoin transaction') parser.add_argument('-f', '--from', help='one of more from addresses', nargs='+', required=True) parser.add_argument('-t', '--to', help='address to send to', nargs=1, required=True) parser.add_argument('-m', '--fee', help='miner fee in Satoshis per byte', nargs=1, type=valid_fee, required=False, default=[best_fee]) parser.add_argument('-b', '--bitcoin', help='amount to transfer in BTC', nargs=1, type=float, required=False) parser.add_argument('-u', '--usd', help='amount to transfer in USD', nargs=1, type=float, required=False) parser.add_argument('-o', '--override', help='override high fee sanity check', action='store_true', required=False) parser.add_argument( '-e', '--envfriendly', help= 'spend small UXTO amounts first; results in higher fees, but reduces global UTXO DB size', action='store_true', required=False) parser.add_argument('-v', '--verbose', help='show verbose output', action='store_true', required=False) args = vars(parser.parse_args()) if (args['verbose']): logger.setLevel(logging.DEBUG) else: logger.setLevel(logging.INFO) # check which currency sweep = False btc_specified = (args['bitcoin'] is not None) usd_specified = (args['usd'] is not None) amount_satoshi = None if not (btc_specified or usd_specified): sweep = True logger.debug('SWEEP all funds') elif (btc_specified and usd_specified): logger.error('Specify the amount in Bitcoin or USD, but not both') exit(1) elif btc_specified: amount_btc = args['bitcoin'][0] amount_satoshi = int(round(amount_btc * 1e8)) logger.info('AMOUNT {:,.8f} BTC = {:,.0f} Satoshi'.format( amount_btc, amount_satoshi)) elif usd_specified: amount_usd = args['usd'][0] btc_price = get_bitcoin_price() amount_btc = amount_usd / btc_price amount_satoshi = int(round(amount_btc * 1e8)) logger.info( 'AMOUNT {:,.2f} USD = {:,.8f} BTC = {:,.0f} Satoshi'.format( amount_usd, amount_btc, amount_satoshi)) # substring search for destination address in wallet dest = None item = lookup(args['to'][0]) if item is not None: dest = item['address'] logger.debug('Found destination address {} {} in wallet'.format( item['name'], dest)) else: dest = args['to'][0] logger.debug('Using destination address {}'.format(dest)) if not validate_address(dest): logger.warning( 'Destination address "{}" is not a valid Bitcoin address'.format( dest)) # gather UTXOs from inputs utxos = [] privkeys = {} from_addrs = [] for source in args['from']: entry = lookup(source) if (entry == None): logger.error( 'No source address found in wallet matching "{}"'.format( source)) exit(1) name = entry['name'] address = entry['address'] privkey = entry['privkey'] logger.debug('Found source address {} {} in wallet'.format( name, address)) from_addrs.append(address) privkeys[address] = privkey # gather UTXOs unspent = get_unspent(address) logger.debug('Address {} has {} unspent{}'.format( address, len(unspent), pluralize(len(unspent)))) has_utxos = False for tx in unspent: utxo = {} utxo['output'] = '{}:{}'.format(tx['id'], tx['vout']) utxo['address'] = address utxo['value'] = tx['amount'] logger.debug('utxo["value"] = {}'.format(utxo['value'])) utxos.append(utxo) has_utxos = True if not has_utxos: logger.warning('Address {} has no confirmed UTXOs'.format(address)) # must have at least one UTXO nutxos = len(utxos) if nutxos == 0: logger.error('No confirmed UTXOs found') exit(1) # report UTXO summary naddr = len(args['from']) btc_price = get_bitcoin_price() avail_satoshi = sum([tx['value'] for tx in utxos]) avail_usd = (avail_satoshi / 1e8) * btc_price addr_suffix = '' if naddr == 1 else 'es' logger.debug( 'UTXO Summary: {} address{} {} UTXO{} {:,.0f} Satoshi ${:,.2f} USD'. format(naddr, addr_suffix, nutxos, pluralize(nutxos), avail_satoshi, avail_usd)) # build tx txins = [] txouts = [] fee_per_byte = int(args['fee'][0]) logger.debug('Using fee of {} satoshis per byte'.format(fee_per_byte)) # sweep all BTC if sweep: logger.warning('Sweeping entire {:,.0f} satoshi from all UTXOs'.format( avail_satoshi)) est_length = config['len-base'] + (config['len-per-input'] * nutxos) + config['len-per-output'] fee = est_length * fee_per_byte # inputs n = 0 total = 0 for utxo in utxos: total += utxo['value'] logger.debug('Input {} UTXO {} Value {:,.0f} Total {:,.0f}'.format( n, utxo['output'], utxo['value'], total)) txins.append(utxo) n += 1 # output send_satoshi = avail_satoshi - fee txouts = [{'value': send_satoshi, 'address': dest}] logger.debug('OUTPUT 0 Address {} Value {:,.0f}'.format( dest, send_satoshi)) # transfer specific amount else: change_address = None send_satoshi = amount_satoshi logger.debug('transferring {:,.0f} Satoshi'.format(send_satoshi)) initial_fee = (config['len-base'] + (config['len-per-output'] * 2)) * fee_per_byte remaining = send_satoshi + initial_fee total_fees = initial_fee # Environmentally-friendly transfer spends smallest UTXOs # first. This reduces the size of UTXO database each full-node # must store, but results in a higher fee. reverse = True if args['envfriendly']: logger.warning( 'environmentally friendly mode active, higher fees apply') reverse = False ordered_utxos = sorted(utxos, key=lambda k: k['value'], reverse=reverse) # inputs n = 0 total = 0 change = 0 for utxo in ordered_utxos: fee_inc = (config['len-per-input'] * fee_per_byte) remaining += fee_inc total_fees += fee_inc remaining -= utxo['value'] total += utxo['value'] logger.debug('Input {} UTXO {} Value {:,.0f} Total {:,.0f}'.format( n, utxo['output'], utxo['value'], total)) txins.append(utxo) n += 1 if remaining < 0: change = -remaining change_address = utxo['address'] break # insufficient funds if (remaining > 0): note = '' if (remaining <= total_fees): note = 'after adding miner fees ' logger.critical( 'Insufficient funds {}{:,.0f} > {:,.0f}'.format( note, send_satoshi + total_fees, avail_satoshi)) exit(1) # outputs txouts = [{'address': dest, 'value': send_satoshi}] logger.debug('OUTPUT 0 Address {} Value {:,.0f}'.format( dest, send_satoshi)) if (change > 0): # trivial remainder condition: it costs more in fees to # use the UTXO than what actually remains there, so just # leave it for the miner and a take speed bonus. sweep_utxo_len = config['len-base'] + config[ 'len-per-input'] + config['len-per-output'] sweep_utxo_fee = sweep_utxo_len * best_fee if change < sweep_utxo_fee: change_usd = (change / 1e8) * btc_price logger.warning( 'Trivial UTXO remainder released to miner {:,.0f} Satoshi ${:,.2f} USD' .format(change, change_usd)) # return change else: # merge if change going to dest address if (change_address == dest): merged_value = send_satoshi + change logger.warning( 'Change address same as destination, merging output values {:,.0f}' .format(merged_value)) txouts = [{'address': dest, 'value': merged_value}] # add extra output else: txouts.append({'address': change_address, 'value': change}) logger.debug('OUTPUT 1 Address {} Value {:,.0f}'.format( change_address, change)) # sanity checks sum_ins = sum([x['value'] for x in txins]) logger.debug('SUM(inputs) = {:,.0f}'.format(sum_ins)) sum_outs = sum([x['value'] for x in txouts]) logger.debug('SUM(outputs) = {:,.0f}'.format(sum_outs)) fee_satoshi = (sum_ins - sum_outs) fee_btc = (fee_satoshi / 1e8) fee_usd = btc_price * fee_btc logger.info('Paying miner fee of {:,.0f} Satoshi ${:,.2f} USD'.format( fee_satoshi, fee_usd)) if (fee_usd < 0): logger.critical( 'Sanity check failed: sum of outputs {:,.0f} exceeds sum of inputs {:,.0f}' .format(sum_outs, sum_ins)) exit(1) elif (fee_usd < 0.01): logger.critical( 'Bad transaction: miner fee too small: ${:,.6f}'.format(fee_usd)) exit(1) if (fee_usd > config['insane-fee-usd']): msg = 'Sanity check failed: miner fee too large ${:,.2f} >= ${:,.2f}'.format( fee_usd, config['insane-fee-usd']) if args['override']: logger.warning(msg) logger.warning('Overriding sanity check') else: logger.error(msg) exit(1) # sign tx inputs tx = mktx(txins, txouts) for i in range(len(txins)): logger.debug('Signing input {}'.format(i)) try: tx = sign(tx, i, privkeys[txins[i]['address']]) except: logger.critical('Failed to sign UTXO {}'.format( txins[i]['output'])) exit(1) # confirm send_btc = send_satoshi / 1e8 confirm = 'Sending {:,.8f} BTC ${:,.2f} USD '.format( send_btc, send_btc * btc_price) confirm += 'from {} to {} '.format(from_addrs, [dest]) confirm += 'using fee of {:,.8f} BTC ${:,.2f} USD'.format(fee_btc, fee_usd) logger.warning(confirm) raw_input('[ Press Enter to confirm ]') # submit tid = broadcast(tx) if tid is None: logger.critical('Failed to submit transaction to the network') else: logger.warning('Broadcasted TXID {}'.format(tid))