text: #!/usr/bin/python
  import json, re
  import random
  import sys
  import binascii
  import requests
  from bitcoin import safe_hexlify
  
  try:
      from urllib.request import build_opener
  except:
      from urllib2 import build_opener
  
  
  # Makes a request to a given URL (first arg) and optional params (second arg)
  def make_request(*args):
      opener = build_opener()
      opener.addheaders = [('User-agent',
                            'Mozilla/5.0'+str(random.randrange(1000000)))]
      try:
          return opener.open(*args).read().strip()
      except Exception as e:
          try:
              p = e.read().strip()
          except:
              p = e
          raise Exception(p)
  
  
  def is_testnet(inp):
      '''Checks if inp is a testnet address or if UTXO is a known testnet TxID''' 
      if isinstance(inp, (list, tuple)) and len(inp) >= 1:
          return any([is_testnet(x) for x in inp])
      elif not isinstance(inp, str):    # sanity check
          raise TypeError("Input must be str/unicode, not type %s" % str(type(inp)))
  
      if not inp or (inp.lower() in ("btc", "testnet")): 
          pass
  
      ## ADDRESSES
      if inp[0] in "123mn":
          if re.match("^[2mn][a-km-zA-HJ-NP-Z0-9]{26,33}$", inp):
              return True
          elif re.match("^[13][a-km-zA-HJ-NP-Z0-9]{26,33}$", inp):
              return False
          else:
              #sys.stderr.write("Bad address format %s")
              return None
  
      ## TXID
      elif re.match('^[0-9a-fA-F]{64}$', inp):
          base_url = "http://api.blockcypher.com/v1/btc/{network}/txs/{txid}?includesHex=false"
          try:
              # try testnet fetchtx
              make_request(base_url.format(network="test3", txid=inp.lower()))
              return True
          except:
              # try mainnet fetchtx
              make_request(base_url.format(network="main", txid=inp.lower()))
              return False
          sys.stderr.write("TxID %s has no match for testnet or mainnet (Bad TxID)")
          return None
      else:
          raise TypeError("{0} is unknown input".format(inp))
  
  
  def set_network(*args):
      '''Decides if args for unspent/fetchtx/pushtx are mainnet or testnet'''
      r = []
      for arg in args:
          if not arg: 
              pass
          if isinstance(arg, str):
              r.append(is_testnet(arg))
          elif isinstance(arg, (list, tuple)):
              return set_network(*arg)
      if any(r) and not all(r):
          raise Exception("Mixed Testnet/Mainnet queries")
      return "testnet" if any(r) else "btc"
  
  
  def parse_addr_args(*args):
      # Valid input formats: unspent([addr1, addr2, addr3])
      #                      unspent([addr1, addr2, addr3], network)
      #                      unspent(addr1, addr2, addr3)
      #                      unspent(addr1, addr2, addr3, network)
      addr_args = args
      network = "btc"
      if len(args) == 0:
          return [], 'btc'
      if len(args) >= 1 and args[-1] in ('testnet', 'btc'):
          network = args[-1]
          addr_args = args[:-1]
      if len(addr_args) == 1 and isinstance(addr_args, list):
          network = set_network(*addr_args[0])
          addr_args = addr_args[0]
      if addr_args and isinstance(addr_args, tuple) and isinstance(addr_args[0], list):
          addr_args = addr_args[0]
      network = set_network(addr_args)
      return addr_args, network   # note params are "reversed" now
  
  
  # Gets the unspent outputs of one or more addresses
  def bci_unspent(*args):
      addrs, network = parse_addr_args(*args)
      u = []
      for a in addrs:
          try:
              data = make_request('https://blockchain.info/unspent?active='+a)
          except Exception as e:
              if str(e) == 'No free outputs to spend':
                  continue
              else:
                  raise Exception(e)
          jsonobj = json.loads(data.decode("utf-8"))
          for o in jsonobj["unspent_outputs"]:
              h = safe_hexlify(binascii.unhexlify(o['tx_hash'])[::-1])
              u.append({
                  "output": h+':'+str(o['tx_output_n']),
                  "value": o['value']
              })
      return u
  
  def blockinfo_unspent(*args):
      addrs, network = parse_addr_args(*args)
  
      if network == 'testnet':
          blockinfo_url = 'https://testnet.blockchain.info/unspent?active='
      else:
          blockinfo_url = 'https://blockchain.info/unspent?active='
  
      u = []
      for a in addrs:
          try:
              data = make_request(blockinfo_url+a)
          except Exception as e:
              if str(e) == 'No free outputs to spend':
                  continue
              else:
                  raise Exception(e)
          jsonobj = json.loads(data.decode("utf-8"))
          for o in jsonobj["unspent_outputs"]:
              h = safe_hexlify(binascii.unhexlify(o['tx_hash'])[::-1])
              u.append({
                  "output": h+':'+str(o['tx_output_n']),
                  "value": o['value']
              })
      return u
  
  
  def blockr_unspent(*args):
      # Valid input formats: blockr_unspent([addr1, addr2,addr3])
      #                      blockr_unspent(addr1, addr2, addr3)
      #                      blockr_unspent([addr1, addr2, addr3], network)
      #                      blockr_unspent(addr1, addr2, addr3, network)
      # Where network is 'btc' or 'testnet'
      network, addr_args = parse_addr_args(*args)
  
      if network == 'testnet':
          blockr_url = 'http://tbtc.blockr.io/api/v1/address/unspent/'
      elif network == 'btc':
          blockr_url = 'http://btc.blockr.io/api/v1/address/unspent/'
      else:
          raise Exception(
              'Unsupported network {0} for blockr_unspent'.format(network))
  
      if len(addr_args) == 0:
          return []
      elif isinstance(addr_args[0], list):
          addrs = addr_args[0]
      else:
          addrs = addr_args
      res = make_request(blockr_url+','.join(addrs))
      try:
          data = json.loads(res.decode('utf-8'))['data']
      except:
          data = json.loads(res)['data']
  
      o = []
      if 'unspent' in data:
          data = [data]
      for dat in data:
          for u in dat['unspent']:
              o.append({
                  "output": u['tx']+':'+str(u['n']),
                  "value": int(u['amount'].replace('.', ''))
              })
      return o
  
  
  def helloblock_unspent(*args):
      addrs, network = parse_addr_args(*args)
      if network == 'testnet':
          url = 'https://testnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s'
      elif network == 'btc':
          url = 'https://mainnet.helloblock.io/v1/addresses/%s/unspents?limit=500&offset=%s'
      o = []
      for addr in addrs:
          for offset in xrange(0, 10**9, 500):
              res = make_request(url % (addr, offset))
              data = json.loads(res.decode("utf-8"))["data"]
              if not len(data["unspents"]):
                  break
              elif offset:
                  sys.stderr.write("Getting more unspents: %d\n" % offset)
              for dat in data["unspents"]:
                  o.append({
                      "output": dat["txHash"]+':'+str(dat["index"]),
                      "value": dat["value"],
                  })
      return o
  
  
  unspent_getters = {
      'bci': bci_unspent,
      'blockr': blockr_unspent,
      'helloblock': helloblock_unspent
  }
  
  
  def unspent(*args, **kwargs):
      f = unspent_getters.get(kwargs.get('source', ''), bci_unspent)
      return f(*args)
  
  
  # Gets the transaction output history of a given set of addresses,
  # including whether or not they have been spent
  def history(*args):
      # Valid input formats: history([addr1, addr2,addr3])
      #                      history(addr1, addr2, addr3)
      addr_args, network = parse_addr_args(*args)
  
      if network == 'testnet':
          blockinfo_url = 'https://testnet.blockchain.info/'
      else:
          blockinfo_url = 'https://blockchain.info/'
  
      if len(addr_args) == 0:
          return []
      elif isinstance(addr_args[0], list):
          addrs = addr_args[0]
      else:
          addrs = addr_args
  
      txs = []
      for addr in addrs:
          offset = 0
          while 1:
              gathered = False
              while not gathered:
                  try:
                      data = make_request(
                          blockinfo_url + 'address/%s?format=json&offset=%s' %
                          (addr, offset))
                      gathered = True
                  except Exception as e:
                      try:
                          sys.stderr.write(e.read().strip())
                      except:
                          sys.stderr.write(str(e))
                      gathered = False
              try:
                  jsonobj = json.loads(data.decode("utf-8"))
              except:
                  raise Exception("Failed to decode data: "+data)
              txs.extend(jsonobj["txs"])
              if len(jsonobj["txs"]) < 50:
                  break
              offset += 50
              sys.stderr.write("Fetching more transactions... "+str(offset)+'\n')
      outs = {}
      for tx in txs:
          for o in tx["out"]:
              if o.get('addr', None) in addrs:
                  key = str(tx["tx_index"])+':'+str(o["n"])
                  outs[key] = {
                      "address": o["addr"],
                      "value": o["value"],
                      "output": tx["hash"]+':'+str(o["n"]),
                      "block_height": tx.get("block_height", None)
                  }
      for tx in txs:
          for i, inp in enumerate(tx["inputs"]):
              if "prev_out" in inp:
                  if inp["prev_out"].get("addr", None) in addrs:
                      key = str(inp["prev_out"]["tx_index"]) + \
                          ':'+str(inp["prev_out"]["n"])
                      if outs.get(key):
                          outs[key]["spend"] = tx["hash"]+':'+str(i)
      return [outs[k] for k in outs]
  
  
  # Pushes a transaction to the network using https://blockchain.info/pushtx
  def bci_pushtx(tx):
      if not re.match('^[0-9a-fA-F]*$', tx):
          tx = tx.encode('hex')
      return make_request('https://blockchain.info/pushtx', 'tx='+tx)
  
  def testnet_pushtx(tx):
      if not re.match('^[0-9a-fA-F]*$', tx):
          tx = tx.encode('hex')
  
      # see https://www.blockcypher.com/dev/bitcoin/#introduction
      url = 'https://api.blockcypher.com/v1/btc/test3/txs/push'
      
      # blockchain.info doesn't support testnet
      # return make_request('https://testnet.blockchain.info/pushtx', 'tx='+tx)    
      return requests.post(url, json = {'tx':tx})
  
  def eligius_pushtx(tx):
      if not re.match('^[0-9a-fA-F]*$', tx):
          tx = tx.encode('hex')
      s = make_request(
          'http://eligius.st/~wizkid057/newstats/pushtxn.php',
          'transaction='+tx+'&send=Push')
      strings = re.findall('string[^"]*"[^"]*"', s)
      for string in strings:
          quote = re.findall('"[^"]*"', string)[0]
          if len(quote) >= 5:
              return quote[1:-1]
  
  
  def blockr_pushtx(tx, network='btc'):
      if network == 'testnet':
          blockr_url = 'http://tbtc.blockr.io/api/v1/tx/push'
      elif network == 'btc':
          blockr_url = 'http://btc.blockr.io/api/v1/tx/push'
      else:
          raise Exception(
              'Unsupported network {0} for blockr_pushtx'.format(network))
  
      if not re.match('^[0-9a-fA-F]*$', tx):
          tx = tx.encode('hex')
      return make_request(blockr_url, '{"hex":"%s"}' % tx)
  
  
  def helloblock_pushtx(tx):
      if not re.match('^[0-9a-fA-F]*$', tx):
          tx = tx.encode('hex')
      return make_request('https://mainnet.helloblock.io/v1/transactions',
                          'rawTxHex='+tx)
  
  pushtx_getters = {
      'bci': bci_pushtx,
      'blockr': blockr_pushtx,
      'helloblock': helloblock_pushtx
  }
  
  
  def pushtx(*args, **kwargs):
      f = pushtx_getters.get(kwargs.get('source', ''), bci_pushtx)
      return f(*args)
  
  
  def last_block_height(network='btc'):
      if network == 'testnet':
          data = make_request('http://tbtc.blockr.io/api/v1/block/info/last')
          jsonobj = json.loads(data.decode("utf-8"))
          return jsonobj["data"]["nb"]
  
      data = make_request('https://blockchain.info/latestblock')
      jsonobj = json.loads(data.decode("utf-8"))
      return jsonobj["height"]
  
  
  # Gets a specific transaction
  def bci_fetchtx(txhash):
      if isinstance(txhash, list):
          return [bci_fetchtx(h) for h in txhash]
      if not re.match('^[0-9a-fA-F]*$', txhash):
          txhash = txhash.encode('hex')
      data = make_request('https://blockchain.info/rawtx/'+txhash+'?format=hex')
      return data
  
  def testnet_fetchtx(txhash):
      if isinstance(txhash, list):
          return [testnet_fetchtx(h) for h in txhash]
      if not re.match('^[0-9a-fA-F]*$', txhash):
          txhash = txhash.encode('hex')
      data = make_request('https://testnet.blockchain.info/rawtx/'+txhash+'?format=hex')
      return data
  
  def blockr_fetchtx(txhash, network='btc'):
      if network == 'testnet':
          blockr_url = 'http://tbtc.blockr.io/api/v1/tx/raw/'
      elif network == 'btc':
          blockr_url = 'http://btc.blockr.io/api/v1/tx/raw/'
      else:
          raise Exception(
              'Unsupported network {0} for blockr_fetchtx'.format(network))
      if isinstance(txhash, list):
          txhash = ','.join([x.encode('hex') if not re.match('^[0-9a-fA-F]*$', x)
                             else x for x in txhash])
          jsondata = json.loads(make_request(blockr_url+txhash).decode("utf-8"))
          return [d['tx']['hex'] for d in jsondata['data']]
      else:
          if not re.match('^[0-9a-fA-F]*$', txhash):
              txhash = txhash.encode('hex')
          jsondata = json.loads(make_request(blockr_url+txhash).decode("utf-8"))
          return jsondata['data']['tx']['hex']
  
  
  def helloblock_fetchtx(txhash, network='btc'):
      if isinstance(txhash, list):
          return [helloblock_fetchtx(h) for h in txhash]
      if not re.match('^[0-9a-fA-F]*$', txhash):
          txhash = txhash.encode('hex')
      if network == 'testnet':
          url = 'https://testnet.helloblock.io/v1/transactions/'
      elif network == 'btc':
          url = 'https://mainnet.helloblock.io/v1/transactions/'
      else:
          raise Exception(
              'Unsupported network {0} for helloblock_fetchtx'.format(network))
      data = json.loads(make_request(url + txhash).decode("utf-8"))["data"]["transaction"]
      o = {
          "locktime": data["locktime"],
          "version": data["version"],
          "ins": [],
          "outs": []
      }
      for inp in data["inputs"]:
          o["ins"].append({
              "script": inp["scriptSig"],
              "outpoint": {
                  "index": inp["prevTxoutIndex"],
                  "hash": inp["prevTxHash"],
              },
              "sequence": 4294967295
          })
      for outp in data["outputs"]:
          o["outs"].append({
              "value": outp["value"],
              "script": outp["scriptPubKey"]
          })
      from bitcoin.transaction import serialize
      from bitcoin.transaction import txhash as TXHASH
      tx = serialize(o)
      assert TXHASH(tx) == txhash
      return tx
  
  
  fetchtx_getters = {
      'bci': bci_fetchtx,
      'blockr': blockr_fetchtx,
      'helloblock': helloblock_fetchtx
  }
  
  
  def fetchtx(*args, **kwargs):
      f = fetchtx_getters.get(kwargs.get('source', ''), bci_fetchtx)
      return f(*args)
  
  
  def firstbits(address):
      if len(address) >= 25:
          return make_request('https://blockchain.info/q/getfirstbits/'+address)
      else:
          return make_request(
              'https://blockchain.info/q/resolvefirstbits/'+address)
  
  
  def get_block_at_height(height):
      j = json.loads(make_request("https://blockchain.info/block-height/" +
                     str(height)+"?format=json").decode("utf-8"))
      for b in j['blocks']:
          if b['main_chain'] is True:
              return b
      raise Exception("Block at this height not found")
  
  
  def _get_block(inp):
      if len(str(inp)) < 64:
          return get_block_at_height(inp)
      else:
          return json.loads(make_request(
                            'https://blockchain.info/rawblock/'+inp).decode("utf-8"))
  
  
  def bci_get_block_header_data(inp):
      j = _get_block(inp)
      return {
          'version': j['ver'],
          'hash': j['hash'],
          'prevhash': j['prev_block'],
          'timestamp': j['time'],
          'merkle_root': j['mrkl_root'],
          'bits': j['bits'],
          'nonce': j['nonce'],
      }
  
  def blockr_get_block_header_data(height, network='btc'):
      if network == 'testnet':
          blockr_url = "http://tbtc.blockr.io/api/v1/block/raw/"
      elif network == 'btc':
          blockr_url = "http://btc.blockr.io/api/v1/block/raw/"
      else:
          raise Exception(
              'Unsupported network {0} for blockr_get_block_header_data'.format(network))
  
      k = json.loads(make_request(blockr_url + str(height)).decode("utf-8"))
      j = k['data']
      return {
          'version': j['version'],
          'hash': j['hash'],
          'prevhash': j['previousblockhash'],
          'timestamp': j['time'],
          'merkle_root': j['merkleroot'],
          'bits': int(j['bits'], 16),
          'nonce': j['nonce'],
      }
  
  
  def get_block_timestamp(height, network='btc'):
      if network == 'testnet':
          blockr_url = "http://tbtc.blockr.io/api/v1/block/info/"
      elif network == 'btc':
          blockr_url = "http://btc.blockr.io/api/v1/block/info/"
      else:
          raise Exception(
              'Unsupported network {0} for get_block_timestamp'.format(network))
  
      import time, calendar
      if isinstance(height, list):
          k = json.loads(make_request(blockr_url + ','.join([str(x) for x in height])).decode("utf-8"))
          o = {x['nb']: calendar.timegm(time.strptime(x['time_utc'],
               "%Y-%m-%dT%H:%M:%SZ")) for x in k['data']}
          return [o[x] for x in height]
      else:
          k = json.loads(make_request(blockr_url + str(height)).decode("utf-8"))
          j = k['data']['time_utc']
          return calendar.timegm(time.strptime(j, "%Y-%m-%dT%H:%M:%SZ"))
  
  
  block_header_data_getters = {
      'bci': bci_get_block_header_data,
      'blockr': blockr_get_block_header_data
  }
  
  
  def get_block_header_data(inp, **kwargs):
      f = block_header_data_getters.get(kwargs.get('source', ''),
                                        bci_get_block_header_data)
      return f(inp, **kwargs)
  
  
  def get_txs_in_block(inp):
      j = _get_block(inp)
      hashes = [t['hash'] for t in j['tx']]
      return hashes
  
  
  def get_block_height(txhash):
      j = json.loads(make_request('https://blockchain.info/rawtx/'+txhash).decode("utf-8"))
      return j['block_height']
  
  # fromAddr, toAddr, 12345, changeAddress
  def get_tx_composite(inputs, outputs, output_value, change_address=None, network=None):
      """mktx using blockcypher API"""
      inputs = [inputs] if not isinstance(inputs, list) else inputs
      outputs = [outputs] if not isinstance(outputs, list) else outputs
      network = set_network(change_address or inputs) if not network else network.lower()
      url = "http://api.blockcypher.com/v1/btc/{network}/txs/new?includeToSignTx=true".format(
                    network=('test3' if network=='testnet' else 'main'))
      is_address = lambda a: bool(re.match("^[123mn][a-km-zA-HJ-NP-Z0-9]{26,33}$", a))
      if any([is_address(x) for x in inputs]):
          inputs_type = 'addresses'        # also accepts UTXOs, only addresses supported presently
      if any([is_address(x) for x in outputs]):
          outputs_type = 'addresses'       # TODO: add UTXO support
      data = {
              'inputs':  [{inputs_type:  inputs}], 
              'confirmations': 0, 
              'preference': 'high', 
              'outputs': [{outputs_type: outputs, "value": output_value}]
              }
      if change_address:
          data["change_address"] = change_address    # 
      jdata = json.loads(make_request(url, data))
      hash, txh = jdata.get("tosign")[0], jdata.get("tosign_tx")[0]
      assert bin_dbl_sha256(txh.decode('hex')).encode('hex') == hash, "checksum mismatch %s" % hash
      return txh.encode("utf-8")
  
  blockcypher_mktx = get_tx_composite
  