View difference between Paste ID: NnNug0nL and h103Yj3N
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 so-called "colored coins", or "smart contracts/securities".
6+
Simple command-line utility to use the Bitcoin blockchain to track and manage so-called "colored coins", or "smart contracts/securities".
7
8-
Create "colored coin", receive colored coin, pay to owners of colored coins.
8+
9
- Private keys are stored in plaintext in your configuration file
10-
Features:
10+
11-
- Tracking using bitcoind and blockchain.info
11+
12-
- Generate address for colored coin (so you can store
12+
13-
  coins outside your normal wallet, so that the satoshi
13+
14-
  client doesn't accidentally spend them)
14+
- Since there is no transaction fee included during asset transfer, 
15-
- Start tracking a "colored coin": "paint" a coin
15+
  you cannot be sure that the transaction will be confirmed when transferring 
16-
- Generate list of holders of colored coin (btc-address and
16+
  "assets"
17-
  amount of coin held)
17+
18-
- Transfer colored coin from one address to another
18+
19
20-
Upcoming features:
20+
21-
- Pay from wallet to colored coin owners
21+
22-
- Forward non-colored payments from asset-addresses to
22+
23-
  wallet-addresses
23+
24
from jsonrpc import ServiceProxy
25
from optparse import OptionParser
26-
Quick guide - issue "security":
26+
27-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
27+
28-
1. Create a holding address:
28+
29-
   $ python bitpaint.py --new-address
29+
30-
2. Transfer the coins you want to color to that address from your
30+
31-
   regular wallet.
31+
32-
3. Make a note of the transaction id <txid> and the output number
32+
33-
   that transferred the coins to the holding address <n>.
33+
34-
4. Start tracking from the output of that transaction:
34+
35-
   $ python bitpaint.py --paint <name>:<txid>:<n>
35+
36
# Here we create a configuration file if one does not exist already.
37-
After these steps, the address you generated holds the full colored
37+
38-
coin. <name> is any name you would like to give this colored coin.
38+
39-
<name> only exists locally.
39+
40
rpchost = %s
41
rpcport = %s
42-
Quick guide - transfer ownership
42+
43-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
43+
44-
1. Ask receiver to download this script and generate a holding
44+
45-
   address, <receiver_address>
45+
46-
2. Transfer the colored coin:
46+
47-
   $ python --transfer-from <addr>:<txid>:<n> --transfer-to <receiver_address1>:<amount1>,<receiver_address2>:<amount2>,...
47+
48
"""
49
# If the config file does not exists, ask user for details and write one
50-
Quick guide - list current owners
50+
51-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
51+
52-
1. Update the list of owners of an asset <name>:
52+
53-
   $ python --update-ownership <name>
53+
54-
2. Show the list of owners:
54+
55-
   $ python --owners <name>
55+
56
	user = raw_input("bitcoind rpc username (default: <blank>): ")
57
	pwd = raw_input("bitcoind rpc password (default: <blank>): ")
58-
Quick guide - list "colored coins" I own
58+
59-
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
59+
60-
1. Show the list of colored coins owned by my addresses:
60+
61-
   $ python --my-holdings
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-
- Since there is no transaction fee included, you cannot be sure that
70+
71-
  the transaction will be confirmed
71+
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)
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-
	user = raw_input("bitcoind rpc username (default: <blank>: ")
111+
112-
	pwd = raw_input("bitcoind rpc password (default: <blank>: ")
112+
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-
bitcoind_connection_string = "http://%s:%s@%s:%s" % (rpcuser,rpcpwd,rpchost,rpcport)
126+
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 = {}
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])
290
	final_t = sp.signrawtransaction(tx,ip,pkeys)
291
	if send:
292
		sp.sendrawtransaction(tx)
293
	else:
294
		print final_t['hex']
295
	return final_t['hex']
296
297
### End: Transaction code
298
299
300
### Start: Blockchain Inspection/Traversion code
301
def translate_bctx_to_bitcoindtx(tx_bc):
302
	tx = {}
303
	tx['locktime'] = 0
304
	tx['txid'] = tx_bc['hash']
305
	tx['version'] = tx_bc['ver']
306
	tx['vin'] = []
307
	tx['vout'] = []
308
	for i in tx_bc['inputs']:
309
		v = {}
310
		v['scriptSig'] = {'asm': '', 'hex': ''}
311
		v['sequence'] = 4294967295
312
		v['txid'] = json.loads(urllib2.urlopen("http://blockchain.info/rawtx/%s" % (i['prev_out']['tx_index'],)).read())['hash']
313
		v['vout'] = i['prev_out']['n']
314
		tx['vin'].append(v)
315
	for i in range(len(tx_bc['out'])):
316
		o = tx_bc['out'][i]
317
		v = {}
318
		v['n'] = i
319
		v['scriptPubKey'] = {}
320
		v['scriptPubKey']['addresses'] = [o['addr']]
321
		v['scriptPubKey']['asm'] = []
322
		v['scriptPubKey']['hex'] = []
323
		v['scriptPubKey']['reqSigs'] = 1,
324
		v['scriptPubKey']['type'] = 'pubkeyhash'
325
		v['value'] = float(o['value'])/1e8
326
		tx['vout'].append(v)
327
	return tx
328
329
def gettx(txid):
330
	# Get the information of a single transaction, using
331
	# the bitcoind API
332
	try:
333
		tx_raw = sp.getrawtransaction(txid)
334
		tx = sp.decoderawtransaction(tx_raw)
335
	except:
336
		print "Error getting transaction "+txid+" details from bitcoind, trying blockchain.info"
337
		print "http://blockchain.info/rawtx/%s" % (txid,)
338
		tx_bc = json.loads(urllib2.urlopen("http://blockchain.info/rawtx/%s" % (txid,)).read())
339
		tx = translate_bctx_to_bitcoindtx(tx_bc)
340
	return tx
341
342
def getaddresstxs(address):
343
	# Get all transactions associated with an address.
344
	# Uses blockchain.info to get this, bitcoind API
345
	# apparently has no equivalent function.
346
	address_info = json.loads(urllib2.urlopen("http://blockchain.info/address/%s?format=json"%(address,) ).read())
347
	tx_list = []
348
	for tx in address_info['txs']:
349
		tx_list.append(tx['hash'])
350
	return tx_list
351
352
def getholderschange(txid):
353-
	tx_raw = sp.getrawtransaction(txid)
353+
354-
	tx = sp.decoderawtransaction(tx_raw)
354+
355
	tid = txid.split(":")
356
	tx = gettx(tid[0])
357
	new_holders = []
358
	old_holders = []
359
	for i in tx['vin']:
360
		old_holders.append(i['txid']+":"+str(i['vout']))
361
	for o in tx['vout']:
362
		new_holders.append((o['scriptPubKey']['addresses'][0], o['value']))
363
	return new_holders, old_holders
364
365
def spentby(tx_out, single_input = False):
366
	# Return the id of the transaction which spent the given txid/#
367
	# if single_input is true, it only returns if the tx_out was used as a single
368
	# input to the transaction.
369
	# This is because it is not possible to follow a colored coin across a transaction
370
	# with multiple inputs
371
	tid = tx_out.split(":")
372
	tx = gettx(tid[0])
373
	address = tx['vout'][int(tid[1])]['scriptPubKey']['addresses'][0]
374
	tx_list = getaddresstxs(address)
375
	for t in tx_list:
376
		outputs,inputs = getholderschange(t)
377
		if single_input and not len(inputs) == 1:
378
			print t,"used with multiple inputs: tracking lost at address",address
379
			continue
380
		for i in inputs:
381
			if i == tx_out:
382
				return t
383
	return None
384
385
def rec(prevout_txid, root_tx):
386
	# Recursive function to traverse the "tree of ownership", depth-first,
387
	# head-recursion.
388
	holder_list = []
389
	
390
	if prevout_txid == root_tx:
391
		spent_by = spentby(prevout_txid, single_input = False)
392
	else:
393
		spent_by = spentby(prevout_txid, single_input = True)
394
	if spent_by is None:
395
		tx_data = gettx(prevout_txid.split(":")[0])
396
		o = tx_data['vout'][int(prevout_txid.split(":")[1])]
397
		holder_list.append((o['scriptPubKey']['addresses'][0],o['value'],prevout_txid))
398
		return holder_list
399
	tx_data = gettx(spent_by)
400-
def rec(prevout_txid):
400+
401
	for o in tx_data['vout']:
402
		ntxid = spent_by+":"+str(o['n'])
403
		hl = rec(ntxid,root_tx)
404
		for h in hl:
405-
	spent_by = spentby(prevout_txid, single_input = True)
405+
			holder_list.append(h)
406
407
	return holder_list
408
409
def get_current_holders(root_tx_out):
410
	# Get the current holders of the "colored coin" with
411
	# the given root (a string with txid+":"+n_output)
412-
		hl = rec(ntxid)
412+
	return rec(root_tx_out,root_tx_out)
413-
		if hl is None:
413+
414-
			holder_list.append((o['scriptPubKey']['addresses'][0],o['value'],ntxid))
414+
def get_unspent(addr):
415
	# Get the unspent transactions for an address
416-
			for h in hl:
416+
	# * Following section is disabled because blockchain.info has a bug that
417-
				holder_list.append(h)
417+
	#   returns the wrong transaction IDs.
418
	#d = json.loads(urllib2.urlopen('http://blockchain.info/unspent?address=%s' % addr).read())
419
	#return d
420
	# * Start of blockchain.info bug workaround:
421
	txs = getaddresstxs(addr)
422
	received = []
423
	sent = []
424-
	return rec(root_tx_out)
424+
	for txid in txs:
425
		tx = gettx(txid)
426
		for i in tx['vin']:
427
			prev_out = gettx(i['txid'])
428
			for po in prev_out['vout']:
429
				n = po['n']
430
				po = po['scriptPubKey']
431
				if po['type'] == 'pubkeyhash':
432
					if po['addresses'][0] == addr:
433
						sent.append(i['txid']+":"+str(n))
434
		for o in tx['vout']:
435
			n = o['n']
436
			o = o['scriptPubKey']
437
			if o['type'] == 'pubkeyhash':
438
				if o['addresses'][0] == addr:
439
					received.append(txid+":"+str(n))
440
	unspent = []
441
	for r in received:
442
		if r not in sent:
443
			d = {}
444
			txid,n = r.split(":")
445
			d['tx_hash'] = txid
446
			d['tx_output_n'] = int(n)
447
			d['value'] = int(1e8*gettx(txid)['vout'][int(n)]['value'])
448
			unspent.append(d)
449
	return unspent
450
	# * End of blockchain.info bug workaround
451
	
452
453
def get_non_asset_funds(addr):
454
	unspent = get_unspent(addr)
455
	asset_txids = []
456
	for s in config.sections():
457
		if s in reserved_sections: continue
458
		for txid in config.get(s, 'txid').split("+"):
459
			asset_txids.append(txid)
460
	naf = []
461
	for u in unspent:
462
		txid = u['tx_hash']+":"+str(u['tx_output_n'])
463
		if not txid in asset_txids:
464
			naf.append(u)
465
	return naf
466
467
### End: Blockchain Inspection/Traversion code
468
469
470
### Start: "User-facing" methods
471
def generate_holding_address():
472
	# Generate an address, add it to the config file
473
	addr, pkey = get_addr(gen_eckey())
474
	addresses = config.get('HoldingAddresses', 'addresses')
475
	private_keys = config.get('HoldingAddresses', 'private_keys')
476
	if len(addresses) > 5:
477
		config.set('HoldingAddresses', 'addresses', '+'.join([addresses,addr]))
478
		config.set('HoldingAddresses', 'private_keys', '+'.join([private_keys,pkey]))
479
	else:
480
		config.set('HoldingAddresses', 'addresses', addr)
481
		config.set('HoldingAddresses', 'private_keys', pkey)
482
	config.write(open(config_file,'w'))
483
	return "Address added: "+addr
484
485
def update_tracked_coins(name):
486
	# Update the list of owners of a tracked coin
487
	# and write to the config file
488
	root_tx = config.get(name, "root_tx")
489-
	reserved_sections = ['bitcoind', 'HoldingAddresses']
489+
490
	holding_addresses = ""
491
	holding_amounts = ""
492
	holding_txids = ""
493
	total = 0.0
494
	for h in current_holders:
495
		holding_addresses += "+" + h[0]
496
		holding_amounts += "+" + str(h[1])
497
		holding_txids += "+" + h[2]
498-
				print h, amounts[holders.index(h)],txids[holders.index(h)]
498+
499
	config.set(name, "amounts", holding_amounts[1:])
500
	config.set(name, "txid", holding_txids[1:])
501
	config.write(open(config_file,'w'))
502
503
def start_tracking_coins(name,txid):
504
	# Give a name of a tracked coin, together with a
505
	# root output that will be used to track it.
506
	# Write this to the config file, and update the
507-
	tx_input = (txid, int(n), address)
507+
508
	if name in config.sections():
509
		return name+" already exists."
510
	config.add_section(name)
511
	config.set(name, "root_tx", txid)
512
	config.set(name, "holders", "")
513
	config.set(name, "amounts", "")
514
	config.set(name, "txid", "")
515-
	######
515+
516-
	# Upcoming: 
516+
517-
	# - GUI
517+
518-
	# - Paint coins
518+
519-
	# - Create holding address
519+
520-
	# - Transfer asset
520+
521-
	# - Forward dividends to wallet
521+
522-
	# - Proportional payments to asset holders
522+
523-
	# - Add options for losing track: cutoff, remove, proportional follow.
523+
524-
	######
524+
525
		print h[0],h[1],h[2]
526
		total += float(h[1])
527
	print "** Total %s: %f **" % (name,total)
528
529
def show_my_holdings():
530
	sections = config.sections()
531
	my_holding_addresses = config.get('HoldingAddresses', 'addresses').split("+")
532
	for s in sections:
533
		if s in reserved_sections: continue
534
		holders = config.get(s, "holders").split("+")
535
		amounts = config.get(s, "amounts").split("+")
536
		txids = config.get(s, "txid").split("+")
537-
	# Not implemented yet - function to safely transfer other unrelated funds (e.g. dividends, coupons, etc.) AWAY from addresses
537+
538-
	# holding some asset:
538+
539-
	#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")
539+
				total_dividends = 0.0
540-
	#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")
540+
				for naf in get_non_asset_funds(h):
541
					total_dividends += float(naf['value'])/1e8
542
				print s,amounts[holders.index(h)],"( div:",total_dividends,")",h,txids[holders.index(h)]
543
544
def show_my_holding_addresses():
545
	my_holding_addresses = config.get('HoldingAddresses', 'addresses').split("+")
546
	for a in my_holding_addresses:
547-
		print start_tracking_coins(name,txid+":"+n)
547+
548
549
def transfer_asset(sender, receivers):
550
	address,txid,n = sender.split(":")
551
	tx_input = [(txid, int(n), address)]
552
	tx_outputs = []
553
	for l in receivers.split(","):
554
		address,amount = l.split(":")
555
		tx_outputs.append((address,int(float(amount)*1e8)))
556-
	if opts.transfer_from:
556+
557-
		if opts.transfer_to:
557+
558
def pay_to_shareholders(name, wallet_acct, total_payment_amount):
559
	holders = config.get(name, "holders").split("+")
560-
			print "Missing address to transfer to"
560+
561-
	if opts.transfer_to:
561+
562-
		if opts.transfer_from:
562+
	for a in amounts:
563
		total += float(a)
564
	payouts = {}
565-
			print "Missing address/input-txid to transfer from"
565+
	for h,a in zip(holders,amounts):
566
		d = total_payment_amount*float(a)/total
567
		payouts[h] = d
568
	sp.sendmany(wallet_acct,payouts)
569
	print "Payouts made:"
570
	for k in payouts.keys():
571
		print k,":",payouts[k]
572
573
def transfer_others(transfer_other_from,transfer_other_to):
574
	naf = get_non_asset_funds(transfer_other_from)
575
	fee = int(1e8*0.005)
576
	total_value = 0
577
	inputs = []
578
	for u in naf:
579
		total_value += u['value']
580
		i = (u['tx_hash'], u['tx_output_n'], transfer_other_from)
581
		inputs.append(i)
582
	outputs = [(transfer_other_to, total_value-fee)]
583
	maketx(inputs,outputs,send=False)
584
	print "Paid",float(total_value-fee)/1e8,"to",transfer_other_to
585
586
587
if __name__ == '__main__':
588
	# Process command-line options
589
	parser = OptionParser()
590
	parser.add_option('-p', '--paint', help='Paint coins for tracking', dest='paint_txid', action='store')
591
	parser.add_option('-n', '--new-address', help='Create new holding address for colored coins', dest='gen_address', default=False, action='store_true')
592
	parser.add_option('-l', '--list-colors', help='List of names of painted coins being tracked', dest='list_colors', default=False, action='store_true')
593
	parser.add_option('-u', '--update-ownership', help='Update ownership info for painted coins', dest='update_name', action='store')
594
	parser.add_option('-o', '--owners', help='Show owners of painted coins', dest="holders_name", action="store")
595
	parser.add_option('-m', '--my-holdings', help='Show holdings at my addresses', dest="show_holdings", action="store_true")
596
	parser.add_option('-a', '--holding-addresses', help='Show my holding addresses', dest="show_addresses", action="store_true")
597
	parser.add_option('-f', '--transfer-from', help='Asset to transfer to another address. address:txid:n', dest='transfer_from', action="store")
598
	parser.add_option('-t', '--transfer-to', help='Address to transfer asset to. address:amount,...', dest='transfer_to', action="store")
599
	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")
600
	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")
601
	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")
602
	opts, args = parser.parse_args()
603
	
604
	if opts.gen_address:
605
		print generate_holding_address()
606
	if opts.paint_txid:
607
		name,txid,n = opts.paint_txid.split(":")
608
		start_tracking_coins(name,txid+":"+n)
609
	if opts.holders_name:
610
		show_holders(opts.holders_name)
611
	if opts.update_name:
612
		update_tracked_coins(opts.update_name)
613
	if opts.show_holdings:
614
		show_my_holdings()
615
	if opts.show_addresses:
616
		show_my_holding_addresses()
617
	if opts.pay_to_holders:
618
		asset_name, wallet_acct_name, amount = opts.pay_to_holders.split(":")
619
		pay_to_shareholders(asset_name, wallet_acct_name, float(amount))
620
	if opts.transfer_from or opts.transfer_to:
621
		if opts.transfer_to and opts.transfer_from:
622
			transfer_asset(opts.transfer_from, opts.transfer_to)
623
		else:
624
			print "Make sure you give both a source and destination"
625
	if opts.transfer_other_from or opts.transfer_other_to:
626
		if opts.transfer_other_to and opts.transfer_other_from:
627
			transfer_others(opts.transfer_other_from,opts.transfer_other_to)
628
		else:
629
			print "Make sure you give both a source and destination"