Ejemplo n.º 1
0
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))
Ejemplo n.º 2
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]
Ejemplo n.º 3
0
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)
Ejemplo n.º 4
0
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)
Ejemplo n.º 5
0
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:
Ejemplo n.º 6
0
#!/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(
Ejemplo n.º 7
0
#!/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):
Ejemplo n.º 8
0
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))