View difference between Paste ID: hbUz0vS9 and NnNug0nL
SHOW: | | - or go back to the newest paste.
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-
- Do not allow the "colored coin" to be used in a transaction with
10+
- Colored coin tracking is implemented following the "output
11-
  multiple inputs. That would mess up the tracking. I suggest avoid
11+
  ordering" method, but with very few test cases available, 
12-
  using holding-addresses in your normal wallet, and just use this
12+
  it is currently difficult to say whether or not the approach is
13-
  client to manage your colored coins.
13+
  compatible with approaches implemented elsewhere.
14-
- Since there is no transaction fee included during asset transfer, 
14+
- Remember to include a transaction fee when transferring assets,
15-
  you cannot be sure that the transaction will be confirmed when transferring 
15+
  so that the transaction will confirm.
16-
  "assets"
16+
17
Donations welcome: 1GsnaaAWYMb7yPwBQ19bSLLMTtsfSyjqg3
18
19
"""
20
21
# Import libraries
22
import jsonrpc
23-
import urllib2, ConfigParser, ctypes, ctypes.util, hashlib, simplejson as json, os
23+
24-
from jsonrpc import ServiceProxy
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 = ServiceProxy(bitcoind_connection_string)
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 = {}
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-
		pkeys.append(k[addr])
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-
def spentby(tx_out, single_input = False):
365+
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-
		if single_input and not len(inputs) == 1:
377+
378-
			print t,"used with multiple inputs: tracking lost at address",address
378+
379-
			continue
379+
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-
	# Recursive function to traverse the "tree of ownership", depth-first,
386+
	output_belongs_to_input = [-1]*len(output_values)
387-
	# head-recursion.
387+
	current_color_number = -1
388
	current_color_total = 0.0
389
	current_color_max = -1
390-
	if prevout_txid == root_tx:
390+
	for i in range(len(output_values)):
391-
		spent_by = spentby(prevout_txid, single_input = False)
391+
		output_value = output_values[i]
392
		while current_color_total+output_value > current_color_max:
393-
		spent_by = spentby(prevout_txid, single_input = True)
393+
			current_color_number += 1
394
			current_color_total = 0.0
395-
		tx_data = gettx(prevout_txid.split(":")[0])
395+
			if current_color_number >= len(input_values): return output_belongs_to_input
396-
		o = tx_data['vout'][int(prevout_txid.split(":")[1])]
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-
		ntxid = spent_by+":"+str(o['n'])
402+
	global lost_track
403-
		hl = rec(ntxid,root_tx)
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-
def transfer_asset(sender, receivers):
549+
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-
	print maketx(tx_input, tx_outputs)
556+
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-
			transfer_asset(opts.transfer_from, opts.transfer_to)
622+
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"