Advertisement
Guest User

bitpaint.py

a guest
Oct 18th, 2012
828
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/usr/bin/env python
  2.  
  3. """
  4. bitpaint.py
  5. ~~~~~~~~~~~
  6. Simple command-line utility to use the Bitcoin blockchain to track and manage so-called "colored coins", or "smart contracts/securities".
  7.  
  8. Caution:
  9. - Private keys are stored in plaintext in your configuration file
  10. - Colored coin tracking is implemented following the "output
  11.  ordering" method, but with very few test cases available,
  12.  it is currently difficult to say whether or not the approach is
  13.  compatible with approaches implemented elsewhere.
  14. - Remember to include a transaction fee when transferring assets,
  15.  so that the transaction will confirm.
  16.  
  17. Donations welcome: 1GsnaaAWYMb7yPwBQ19bSLLMTtsfSyjqg3
  18.  
  19. """
  20.  
  21. # Import libraries
  22. import jsonrpc
  23. from optparse import OptionParser
  24. import urllib2, ConfigParser, ctypes, ctypes.util, hashlib, simplejson as json, os, sys, binascii, collections
  25. jsonrpc.dumps = json.dumps
  26.  
  27. ### Start: Generic helpers
  28. def JSONtoAmount(value):
  29.     return long(round(value * 1e8))
  30. def AmountToJSON(amount):
  31.     return float(amount / 1e8)
  32. ### End: Generic helpers
  33.  
  34.  
  35. ### Start: Create/Read Config
  36. # Here we create a configuration file if one does not exist already.
  37. # User is asked for details about his bitcoind so the script can connect.
  38. config_file = "bitpaint.conf"
  39. basic_bitpaint_conf = """[bitcoind]
  40. rpchost = %s
  41. rpcport = %s
  42. rpcuser = %s
  43. rpcpwd = %s
  44.  
  45. [HoldingAddresses]
  46. addresses =
  47. private_keys =
  48. """
  49. # If the config file does not exists, ask user for details and write one
  50. if not os.path.exists(config_file):
  51.     print "Configuration file bitpaint.conf not found. Creating one..."
  52.     host = raw_input("bitcoind rpc host (default: 127.0.0.1): ")
  53.     if len(host) == 0: host = "127.0.0.1"
  54.     port = raw_input("bitcoind rpc port (default: 8332): ")
  55.     if len(port) == 0: port = "8332"
  56.     user = raw_input("bitcoind rpc username (default: <blank>): ")
  57.     pwd = raw_input("bitcoind rpc password (default: <blank>): ")
  58.     f = open(config_file, 'w')
  59.     f.write(basic_bitpaint_conf % (host,port,user,pwd))
  60.     f.close()
  61.  
  62. # Parse the config file
  63. reserved_sections = ['bitcoind', 'HoldingAddresses']
  64. config = ConfigParser.ConfigParser()
  65. config.read(config_file)
  66. rpchost = config.get('bitcoind', 'rpchost')
  67. rpcport = config.get('bitcoind', 'rpcport')
  68. rpcuser = config.get('bitcoind', 'rpcuser')
  69. rpcpwd  = config.get('bitcoind', 'rpcpwd')
  70.  
  71. # Connect to bitcoind
  72. if len(rpcuser) == 0 and len(rpcpwd) == 0:
  73.     bitcoind_connection_string = "http://s:%s" % (rpchost,rpcport)
  74. else:
  75.     bitcoind_connection_string = "http://%s:%s@%s:%s" % (rpcuser,rpcpwd,rpchost,rpcport)
  76. sp = jsonrpc.ServiceProxy(bitcoind_connection_string)
  77.  
  78. ### End: Create/Read Config
  79.  
  80.  
  81. ### Start: Address Generation code
  82. # The following code was yoinked from addrgen.py
  83. # Thanks to: Joric/bitcoin-dev, june 2012, public domain
  84. ssl = ctypes.cdll.LoadLibrary (ctypes.util.find_library ('ssl') or 'libeay32')
  85.  
  86. def check_result (val, func, args):
  87.     if val == 0: raise ValueError
  88.     else: return ctypes.c_void_p (val)
  89.  
  90. ssl.EC_KEY_new_by_curve_name.restype = ctypes.c_void_p
  91. ssl.EC_KEY_new_by_curve_name.errcheck = check_result
  92.  
  93. class KEY:
  94.     def __init__(self):
  95.         NID_secp256k1 = 714
  96.         self.k = ssl.EC_KEY_new_by_curve_name(NID_secp256k1)
  97.         self.compressed = False
  98.         self.POINT_CONVERSION_COMPRESSED = 2
  99.         self.POINT_CONVERSION_UNCOMPRESSED = 4
  100.  
  101.     def __del__(self):
  102.         if ssl:
  103.             ssl.EC_KEY_free(self.k)
  104.         self.k = None
  105.  
  106.     def generate(self, secret=None):
  107.         if secret:
  108.             self.prikey = secret
  109.             priv_key = ssl.BN_bin2bn(secret, 32, ssl.BN_new())
  110.             group = ssl.EC_KEY_get0_group(self.k)
  111.             pub_key = ssl.EC_POINT_new(group)
  112.             ctx = ssl.BN_CTX_new()
  113.             ssl.EC_POINT_mul(group, pub_key, priv_key, None, None, ctx)
  114.             ssl.EC_KEY_set_private_key(self.k, priv_key)
  115.             ssl.EC_KEY_set_public_key(self.k, pub_key)
  116.             ssl.EC_POINT_free(pub_key)
  117.             ssl.BN_CTX_free(ctx)
  118.             return self.k
  119.         else:
  120.             return ssl.EC_KEY_generate_key(self.k)
  121.  
  122.     def get_pubkey(self):
  123.         size = ssl.i2o_ECPublicKey(self.k, 0)
  124.         mb = ctypes.create_string_buffer(size)
  125.         ssl.i2o_ECPublicKey(self.k, ctypes.byref(ctypes.pointer(mb)))
  126.         return mb.raw
  127.  
  128.     def get_secret(self):
  129.         bn = ssl.EC_KEY_get0_private_key(self.k);
  130.         bytes = (ssl.BN_num_bits(bn) + 7) / 8
  131.         mb = ctypes.create_string_buffer(bytes)
  132.         n = ssl.BN_bn2bin(bn, mb);
  133.         return mb.raw.rjust(32, chr(0))
  134.  
  135.     def set_compressed(self, compressed):
  136.         self.compressed = compressed
  137.         if compressed:
  138.             form = self.POINT_CONVERSION_COMPRESSED
  139.         else:
  140.             form = self.POINT_CONVERSION_UNCOMPRESSED
  141.         ssl.EC_KEY_set_conv_form(self.k, form)
  142.  
  143. def dhash(s):
  144.     return hashlib.sha256(hashlib.sha256(s).digest()).digest()
  145.  
  146. def rhash(s):
  147.     h1 = hashlib.new('ripemd160')
  148.     h1.update(hashlib.sha256(s).digest())
  149.     return h1.digest()
  150.  
  151. b58_digits = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
  152.  
  153. def base58_encode(n):
  154.     l = []
  155.     while n > 0:
  156.         n, r = divmod(n, 58)
  157.         l.insert(0,(b58_digits[r]))
  158.     return ''.join(l)
  159.  
  160. def base58_decode(s):
  161.     n = 0
  162.     for ch in s:
  163.         n *= 58
  164.         digit = b58_digits.index(ch)
  165.         n += digit
  166.     return n
  167.  
  168. def base58_encode_padded(s):
  169.     res = base58_encode(int('0x' + s.encode('hex'), 16))
  170.     pad = 0
  171.     for c in s:
  172.         if c == chr(0):
  173.             pad += 1
  174.         else:
  175.             break
  176.     return b58_digits[0] * pad + res
  177.  
  178. def base58_decode_padded(s):
  179.     pad = 0
  180.     for c in s:
  181.         if c == b58_digits[0]:
  182.             pad += 1
  183.         else:
  184.             break
  185.     h = '%x' % base58_decode(s)
  186.     if len(h) % 2:
  187.         h = '0' + h
  188.     res = h.decode('hex')
  189.     return chr(0) * pad + res
  190.  
  191. def base58_check_encode(s, version=0):
  192.     vs = chr(version) + s
  193.     check = dhash(vs)[:4]
  194.     return base58_encode_padded(vs + check)
  195.  
  196. def base58_check_decode(s, version=0):
  197.     k = base58_decode_padded(s)
  198.     v0, data, check0 = k[0], k[1:-4], k[-4:]
  199.     check1 = dhash(v0 + data)[:4]
  200.     if check0 != check1:
  201.         raise BaseException('checksum error')
  202.     if version != ord(v0):
  203.         raise BaseException('version mismatch')
  204.     return data
  205.  
  206. def gen_eckey(passphrase=None, secret=None, pkey=None, compressed=False, rounds=1):
  207.     k = KEY()
  208.     if passphrase:
  209.         secret = passphrase.encode('utf8')
  210.         for i in xrange(rounds):
  211.             secret = hashlib.sha256(secret).digest()
  212.     if pkey:
  213.         secret = base58_check_decode(pkey, 128)
  214.         compressed = len(secret) == 33
  215.         secret = secret[0:32]
  216.     k.generate(secret)
  217.     k.set_compressed(compressed)
  218.     return k
  219.  
  220. def get_addr(k):
  221.     pubkey = k.get_pubkey()
  222.     secret = k.get_secret()
  223.     hash160 = rhash(pubkey)
  224.     addr = base58_check_encode(hash160)
  225.     payload = secret
  226.     if k.compressed:
  227.         payload = secret + chr(0)
  228.     pkey = base58_check_encode(payload, 128)
  229.     return addr, pkey
  230. ### End: Address Generation code
  231.  
  232.  
  233. ### Start: Transaction code
  234. def bc_address_to_hash_160(addr):
  235.     bytes = b58decode(addr, 25)
  236.     return bytes[1:21]
  237.  
  238. __b58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
  239. __b58base = len(__b58chars)
  240.  
  241. def b58decode(v, length):
  242.     """ decode v into a string of len bytes
  243.     """
  244.     long_value = 0L
  245.     for (i, c) in enumerate(v[::-1]):
  246.         long_value += __b58chars.find(c) * (__b58base**i)
  247.     result = ''
  248.     while long_value >= 256:
  249.         div, mod = divmod(long_value, 256)
  250.         result = chr(mod) + result
  251.         long_value = div
  252.     result = chr(long_value) + result
  253.     nPad = 0
  254.     for c in v:
  255.         if c == __b58chars[0]: nPad += 1
  256.         else: break
  257.     result = chr(0)*nPad + result
  258.     if length is not None and len(result) != length:
  259.         return None
  260.     return result
  261.  
  262. def makek():
  263.     # Create a dictionary with address as key and private-key
  264.     # as value
  265.     a = config.get('HoldingAddresses', 'addresses').split("+")
  266.     p = config.get('HoldingAddresses', 'private_keys').split("+")
  267.     k = {}
  268.     for a in zip(a,p):
  269.         k[a[0]] = a[1]
  270.     return k
  271.  
  272. def maketx(inputs, outputs, send=False):
  273.     # Create a transaction, sign it - possibly send it - but
  274.     # in either case return the raw hex
  275.     # inputs: [('a7813e20045b2f2caf612c589adc7e985029167106a300b6a7157084c26967f5', 1, '1PPgZP53BrcG7hyXdWT9fugewmxL1H8LS3'),...]
  276.     # outputs: [('1KRavVCsvaLi7ZzktHSCE3hPUvhPDhQKhz', 8000000),...]
  277.     ip = []
  278.     for txid,vout,_ in inputs:
  279.         ip.append({"txid": txid, "vout": vout})
  280.     op = collections.OrderedDict()
  281.     for addr,amnt in outputs:
  282.         op[addr] = AmountToJSON(amnt)
  283.     tx = sp.createrawtransaction(ip,op)
  284.     k = makek()
  285.     ip = []
  286.     pkeys = []
  287.     for txid,vout,addr in inputs:
  288.         ip.append({"txid": txid, "vout": vout, "scriptPubKey": '76a914'+bc_address_to_hash_160(addr).encode('hex')+'88ac'})
  289.         if addr in k:
  290.             pkeys.append(k[addr])
  291.         else:
  292.             pkeys.append(sp.dumpprivkey(addr))
  293.     final_t = sp.signrawtransaction(tx,ip,pkeys)
  294.     if send:
  295.         sp.sendrawtransaction(tx)
  296.     else:
  297.         print final_t['hex']
  298.     return final_t['hex']
  299.  
  300. ### End: Transaction code
  301.  
  302.  
  303. ### Start: Blockchain Inspection/Traversion code
  304. def translate_bctx_to_bitcoindtx(tx_bc):
  305.     tx = {}
  306.     tx['locktime'] = 0
  307.     tx['txid'] = tx_bc['hash']
  308.     tx['version'] = tx_bc['ver']
  309.     tx['vin'] = []
  310.     tx['vout'] = []
  311.     for i in tx_bc['inputs']:
  312.         v = {}
  313.         v['scriptSig'] = {'asm': '', 'hex': ''}
  314.         v['sequence'] = 4294967295
  315.         v['txid'] = json.loads(urllib2.urlopen("http://blockchain.info/rawtx/%s" % (i['prev_out']['tx_index'],)).read())['hash']
  316.         v['vout'] = i['prev_out']['n']
  317.         tx['vin'].append(v)
  318.     for i in range(len(tx_bc['out'])):
  319.         o = tx_bc['out'][i]
  320.         v = {}
  321.         v['n'] = i
  322.         v['scriptPubKey'] = {}
  323.         v['scriptPubKey']['addresses'] = [o['addr']]
  324.         v['scriptPubKey']['asm'] = []
  325.         v['scriptPubKey']['hex'] = []
  326.         v['scriptPubKey']['reqSigs'] = 1,
  327.         v['scriptPubKey']['type'] = 'pubkeyhash'
  328.         v['value'] = float(o['value'])/1e8
  329.         tx['vout'].append(v)
  330.     return tx
  331.  
  332. def gettx(txid):
  333.     # Get the information of a single transaction, using
  334.     # the bitcoind API
  335.     try:
  336.         tx_raw = sp.getrawtransaction(txid)
  337.         tx = sp.decoderawtransaction(tx_raw)
  338.     except:
  339.         print "Error getting transaction "+txid+" details from bitcoind, trying blockchain.info"
  340.         print "http://blockchain.info/rawtx/%s" % (txid,)
  341.         tx_bc = json.loads(urllib2.urlopen("http://blockchain.info/rawtx/%s" % (txid,)).read())
  342.         tx = translate_bctx_to_bitcoindtx(tx_bc)
  343.     return tx
  344.  
  345. def getaddresstxs(address):
  346.     # Get all transactions associated with an address.
  347.     # Uses blockchain.info to get this, bitcoind API
  348.     # apparently has no equivalent function.
  349.     address_info = json.loads(urllib2.urlopen("http://blockchain.info/address/%s?format=json"%(address,) ).read())
  350.     tx_list = []
  351.     for tx in address_info['txs']:
  352.         tx_list.append(tx['hash'])
  353.     return tx_list
  354.  
  355. def getholderschange(txid):
  356.     # Get a list of the new holders and old holders represented by a
  357.     # single transaction, given as the tx-id.
  358.     tid = txid.split(":")
  359.     tx = gettx(tid[0])
  360.     new_holders = []
  361.     old_holders = []
  362.     for i in tx['vin']:
  363.         old_holders.append(i['txid']+":"+str(i['vout']))
  364.     for o in tx['vout']:
  365.         new_holders.append((o['scriptPubKey']['addresses'][0], o['value']))
  366.     return new_holders, old_holders
  367.  
  368. def spentby(tx_out):
  369.     # Return the id of the transaction which spent the given txid/#
  370.     # if single_input is true, it only returns if the tx_out was used as a single
  371.     # input to the transaction.
  372.     # This is because it is not possible to follow a colored coin across a transaction
  373.     # with multiple inputs
  374.     tid = tx_out.split(":")
  375.     tx = gettx(tid[0])
  376.     address = tx['vout'][int(tid[1])]['scriptPubKey']['addresses'][0]
  377.     tx_list = getaddresstxs(address)
  378.     for t in tx_list:
  379.         outputs,inputs = getholderschange(t)
  380.         for i in inputs:
  381.             if i == tx_out:
  382.                 return t
  383.     return None
  384.  
  385. def match_outputs_to_inputs(input_values, output_values):
  386.     output_belongs_to_input = [-1]*len(output_values)
  387.     current_color_number = -1
  388.     current_color_total = 0.0
  389.     current_color_max = -1
  390.     for i in range(len(output_values)):
  391.         output_value = output_values[i]
  392.         while current_color_total+output_value > current_color_max:
  393.             current_color_number += 1
  394.             current_color_total = 0.0
  395.             if current_color_number >= len(input_values): return output_belongs_to_input
  396.             current_color_max = input_values[current_color_number]
  397.         output_belongs_to_input[i] = current_color_number
  398.         current_color_total += output_value
  399.     return output_belongs_to_input
  400.  
  401. def get_relevant_outputs(tx_data,prevout_txid):
  402.     global lost_track
  403.     relevant_outputs = []
  404.     input_values = []
  405.     output_values = []
  406.     input_colors = [-1]*len(tx_data['vin'])
  407.     for pon in range(len(tx_data['vin'])):
  408.         po = tx_data['vin'][pon]
  409.         p_tid = po['txid']+":"+str(po['vout'])
  410.         if p_tid == prevout_txid:
  411.             input_colors[pon] = 0
  412.         po_data = gettx(po['txid'])
  413.         input_values.append(po_data['vout'][po['vout']]['value'])
  414.     for pon in range(len(tx_data['vin'])):
  415.         p_tid = po['txid']+":"+str(po['vout'])
  416.         if p_tid in lost_track:
  417.             po_data = gettx(po['txid'])
  418.             input_values[input_colors.index(0)] += po_data['vout'][po['vout']]['value']
  419.             input_values[pon] = 0
  420.             lost_track.remove(p_tid)
  421.     for o in tx_data['vout']:
  422.         output_values.append(o['value'])
  423.     output_colors = match_outputs_to_inputs(input_values, output_values)
  424.     for o in range(len(output_colors)):
  425.         if output_colors[o] == 0:
  426.             relevant_outputs.append(tx_data['txid']+":"+str(o))
  427.     return relevant_outputs
  428.  
  429. def rec(prevout_txid, root_tx):
  430.     holder_list = []
  431.     spent_by = spentby(prevout_txid)
  432.     txid,n = prevout_txid.split(":")
  433.     if spent_by is None:
  434.         tx_data = gettx(txid)
  435.         o = tx_data['vout'][int(n)]
  436.         holder_list.append((o['scriptPubKey']['addresses'][0],o['value'],prevout_txid))
  437.         return holder_list
  438.     tx_data = gettx(spent_by)
  439.     relevant_outputs = get_relevant_outputs(tx_data,prevout_txid)
  440.     if len(relevant_outputs) == 0:
  441.         lost_track.append(spent_by)
  442.     for ro in relevant_outputs:
  443.         hl = rec(ro,root_tx)
  444.         for h in hl:
  445.             holder_list.append(h)
  446.     return holder_list
  447.  
  448. lost_track = []
  449. def get_current_holders(root_tx_out):
  450.     # Get the current holders of the "colored coin" with
  451.     # the given root (a string with txid+":"+n_output)
  452.     global lost_track
  453.     lost_track = []
  454.     return rec(root_tx_out,root_tx_out)
  455.  
  456. def get_unspent(addr):
  457.     # Get the unspent transactions for an address
  458.     # * Following section is disabled because blockchain.info has a bug that
  459.     #   returns the wrong transaction IDs.
  460.     #d = json.loads(urllib2.urlopen('http://blockchain.info/unspent?address=%s' % addr).read())
  461.     #return d
  462.     # * Start of blockchain.info bug workaround:
  463.     txs = getaddresstxs(addr)
  464.     received = []
  465.     sent = []
  466.     for txid in txs:
  467.         tx = gettx(txid)
  468.         for i in tx['vin']:
  469.             prev_out = gettx(i['txid'])
  470.             for po in prev_out['vout']:
  471.                 n = po['n']
  472.                 po = po['scriptPubKey']
  473.                 if po['type'] == 'pubkeyhash':
  474.                     if po['addresses'][0] == addr:
  475.                         sent.append(i['txid']+":"+str(n))
  476.         for o in tx['vout']:
  477.             n = o['n']
  478.             o = o['scriptPubKey']
  479.             if o['type'] == 'pubkeyhash':
  480.                 if o['addresses'][0] == addr:
  481.                     received.append(txid+":"+str(n))
  482.     unspent = []
  483.     for r in received:
  484.         if r not in sent:
  485.             d = {}
  486.             txid,n = r.split(":")
  487.             d['tx_hash'] = txid
  488.             d['tx_output_n'] = int(n)
  489.             d['value'] = int(1e8*gettx(txid)['vout'][int(n)]['value'])
  490.             unspent.append(d)
  491.     return unspent
  492.     # * End of blockchain.info bug workaround
  493.    
  494.  
  495. def get_non_asset_funds(addr):
  496.     unspent = get_unspent(addr)
  497.     asset_txids = []
  498.     for s in config.sections():
  499.         if s in reserved_sections: continue
  500.         for txid in config.get(s, 'txid').split("+"):
  501.             asset_txids.append(txid)
  502.     naf = []
  503.     for u in unspent:
  504.         txid = u['tx_hash']+":"+str(u['tx_output_n'])
  505.         if not txid in asset_txids:
  506.             naf.append(u)
  507.     return naf
  508.  
  509. ### End: Blockchain Inspection/Traversion code
  510.  
  511.  
  512. ### Start: "User-facing" methods
  513. def generate_holding_address():
  514.     # Generate an address, add it to the config file
  515.     addr, pkey = get_addr(gen_eckey())
  516.     addresses = config.get('HoldingAddresses', 'addresses')
  517.     private_keys = config.get('HoldingAddresses', 'private_keys')
  518.     if len(addresses) > 5:
  519.         config.set('HoldingAddresses', 'addresses', '+'.join([addresses,addr]))
  520.         config.set('HoldingAddresses', 'private_keys', '+'.join([private_keys,pkey]))
  521.     else:
  522.         config.set('HoldingAddresses', 'addresses', addr)
  523.         config.set('HoldingAddresses', 'private_keys', pkey)
  524.     config.write(open(config_file,'w'))
  525.     return "Address added: "+addr
  526.  
  527. def update_tracked_coins(name):
  528.     # Update the list of owners of a tracked coin
  529.     # and write to the config file
  530.     root_tx = config.get(name, "root_tx")
  531.     current_holders = get_current_holders(root_tx)
  532.     holding_addresses = ""
  533.     holding_amounts = ""
  534.     holding_txids = ""
  535.     total = 0.0
  536.     for h in current_holders:
  537.         holding_addresses += "+" + h[0]
  538.         holding_amounts += "+" + str(h[1])
  539.         holding_txids += "+" + h[2]
  540.     config.set(name, "holders", holding_addresses[1:])
  541.     config.set(name, "amounts", holding_amounts[1:])
  542.     config.set(name, "txid", holding_txids[1:])
  543.     config.write(open(config_file,'w'))
  544.  
  545. def start_tracking_coins(name,txid):
  546.     # Give a name of a tracked coin, together with a
  547.     # root output that will be used to track it.
  548.     # Write this to the config file, and update the
  549.     # list of owners.
  550.     if name in config.sections():
  551.         return name+" already exists."
  552.     config.add_section(name)
  553.     config.set(name, "root_tx", txid)
  554.     config.set(name, "holders", "")
  555.     config.set(name, "amounts", "")
  556.     config.set(name, "txid", "")
  557.     config.write(open(config_file,'w'))
  558.     update_tracked_coins(name)
  559.  
  560. def show_holders(name):
  561.     holders = config.get(name, "holders").split("+")
  562.     amounts = config.get(name, "amounts").split("+")
  563.     txids = config.get(name,"txid").split("+")
  564.     total = 0.0
  565.     print "*** %s ***" % (name,)
  566.     for h in zip(holders,amounts,txids):
  567.         print h[0],h[1],h[2]
  568.         total += float(h[1])
  569.     print "** Total %s: %f **" % (name,total)
  570.  
  571. def show_my_holdings():
  572.     sections = config.sections()
  573.     my_holding_addresses = config.get('HoldingAddresses', 'addresses').split("+")
  574.     for s in sections:
  575.         if s in reserved_sections: continue
  576.         holders = config.get(s, "holders").split("+")
  577.         amounts = config.get(s, "amounts").split("+")
  578.         txids = config.get(s, "txid").split("+")
  579.         for h in holders:
  580.             if h in my_holding_addresses:
  581.                 total_dividends = 0.0
  582.                 for naf in get_non_asset_funds(h):
  583.                     total_dividends += float(naf['value'])/1e8
  584.                 print s,amounts[holders.index(h)],"( div:",total_dividends,")",h,txids[holders.index(h)]
  585.  
  586. def show_my_holding_addresses():
  587.     my_holding_addresses = config.get('HoldingAddresses', 'addresses').split("+")
  588.     for a in my_holding_addresses:
  589.         print a
  590.  
  591. def show_colors():
  592.     sections = config.sections()
  593.     for s in sections:
  594.         if s in reserved_sections: continue
  595.         print s,config.get(s, 'root_tx')
  596.  
  597. def transfer_asset(sender, receivers,fee_size=None):
  598.     address,txid,n = sender.split(":")
  599.     tx_input = [(txid, int(n), address)]
  600.     tx_outputs = []
  601.     for l in receivers.split(","):
  602.         address,amount = l.split(":")
  603.         tx_outputs.append((address,int(float(amount)*1e8)))
  604.     if fee_size:
  605.         fee_p_out = sp.listunspent()[0]
  606.         in_addr = base58_check_encode(binascii.unhexlify(fee_p_out['scriptPubKey'][6:-4]))
  607.         change_address = config.get("bitcoind","change_address")
  608.         change_amount = fee_p_out['amount']-fee_size
  609.         tx_input.append((fee_p_out['txid'],fee_p_out['vout'],in_addr))
  610.         tx_outputs.append((change_address, int(1e8*change_amount)))
  611.     raw_transaction = maketx(tx_input, tx_outputs)
  612.  
  613. def pay_to_shareholders(name, wallet_acct, total_payment_amount):
  614.     holders = config.get(name, "holders").split("+")
  615.     amounts = config.get(name, "amounts").split("+")
  616.     total = 0.0
  617.     for a in amounts:
  618.         total += float(a)
  619.     payouts = {}
  620.     for h,a in zip(holders,amounts):
  621.         d = total_payment_amount*float(a)/total
  622.         payouts[h] = d
  623.     sp.sendmany(wallet_acct,payouts)
  624.     print "Payouts made:"
  625.     for k in payouts.keys():
  626.         print k,":",payouts[k]
  627.  
  628. def transfer_others(transfer_other_from,transfer_other_to):
  629.     naf = get_non_asset_funds(transfer_other_from)
  630.     fee = int(1e8*0.005)
  631.     total_value = 0
  632.     inputs = []
  633.     for u in naf:
  634.         total_value += u['value']
  635.         i = (u['tx_hash'], u['tx_output_n'], transfer_other_from)
  636.         inputs.append(i)
  637.     outputs = [(transfer_other_to, total_value-fee)]
  638.     maketx(inputs,outputs,send=False)
  639.     print "Paid",float(total_value-fee)/1e8,"to",transfer_other_to
  640.  
  641.  
  642. if __name__ == '__main__':
  643.     # Process command-line options
  644.     parser = OptionParser()
  645.     parser.add_option('-p', '--paint', help='Paint coins for tracking', dest='paint_txid', action='store')
  646.     parser.add_option('-n', '--new-address', help='Create new holding address for colored coins', dest='gen_address', default=False, action='store_true')
  647.     parser.add_option('-l', '--list-colors', help='List of names of painted coins being tracked', dest='list_colors', default=False, action='store_true')
  648.     parser.add_option('-u', '--update-ownership', help='Update ownership info for painted coins', dest='update_name', action='store')
  649.     parser.add_option('-o', '--owners', help='Show owners of painted coins', dest="holders_name", action="store")
  650.     parser.add_option('-m', '--my-holdings', help='Show holdings at my addresses', dest="show_holdings", action="store_true")
  651.     parser.add_option('-a', '--holding-addresses', help='Show my holding addresses', dest="show_addresses", action="store_true")
  652.     parser.add_option('-f', '--transfer-from', help='Asset to transfer to another address. address:txid:n', dest='transfer_from', action="store")
  653.     parser.add_option('-t', '--transfer-to', help='Address to transfer asset to. address:amount,...', dest='transfer_to', action="store")
  654.     parser.add_option('-d', '--pay-holders', help="Pay from your bitcoind wallet to asset holders: <asset_name>:<wallet_acctname>:<payout_amount>", dest="pay_to_holders", action="store")
  655.     parser.add_option('-w', '--fee', help="Pay a transaction fee from your wallet when transferring an asset: <amount>", dest="fee", action="store")
  656.     parser.add_option('-x', '--transfer-other-from', help='Transfer bitcoins UNRELATED to the tracked address/coins away from this address', dest="transfer_other_from", action="store")
  657.     parser.add_option('-y', '--transfer-other-to', help='Transfer bitcoins UNRELATED to the tracked address/coins to this address', dest="transfer_other_to", action="store")
  658.     opts, args = parser.parse_args()
  659.    
  660.     if opts.gen_address:
  661.         print generate_holding_address()
  662.     if opts.paint_txid:
  663.         name,txid,n = opts.paint_txid.split(":")
  664.         start_tracking_coins(name,txid+":"+n)
  665.     if opts.holders_name:
  666.         show_holders(opts.holders_name)
  667.     if opts.update_name:
  668.         update_tracked_coins(opts.update_name)
  669.     if opts.list_colors:
  670.         show_colors()
  671.     if opts.show_holdings:
  672.         show_my_holdings()
  673.     if opts.show_addresses:
  674.         show_my_holding_addresses()
  675.     if opts.pay_to_holders:
  676.         asset_name, wallet_acct_name, amount = opts.pay_to_holders.split(":")
  677.         pay_to_shareholders(asset_name, wallet_acct_name, float(amount))
  678.     if opts.transfer_from or opts.transfer_to:
  679.         if opts.transfer_to and opts.transfer_from:
  680.             if opts.fee:
  681.                 transfer_asset(opts.transfer_from, opts.transfer_to,fee_size=float(opts.fee))
  682.             else:
  683.                 transfer_asset(opts.transfer_from, opts.transfer_to)
  684.         else:
  685.             print "Make sure you give both a source and destination"
  686.     if opts.transfer_other_from or opts.transfer_other_to:
  687.         if opts.transfer_other_to and opts.transfer_other_from:
  688.             transfer_others(opts.transfer_other_from,opts.transfer_other_to)
  689.         else:
  690.             print "Make sure you give both a source and destination"
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement