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" |