forked from JoinMarket-Org/joinmarket
-
Notifications
You must be signed in to change notification settings - Fork 0
/
yield-generator.py
161 lines (137 loc) · 6.27 KB
/
yield-generator.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#! /usr/bin/env python
import time, os, binascii, sys, datetime
import pprint
data_dir = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0, os.path.join(data_dir, 'lib'))
from maker import *
from irc import IRCMessageChannel, random_nick
import bitcoin as btc
import common, blockchaininterface
from socket import gethostname
txfee = 1000
cjfee = '0.002' # 0.2% fee
nickname = random_nick()
nickserv_password = ''
minsize = int(1.2 * txfee / float(cjfee)) #minimum size is such that you always net profit at least 20% of the miner fee
mix_levels = 5
#is a maker for the purposes of generating a yield from held
# bitcoins without ruining privacy for the taker, the taker could easily check
# the history of the utxos this bot sends, so theres not much incentive
# to ruin the privacy for barely any more yield
#sell-side algorithm:
#add up the value of each utxo for each mixing depth,
# announce a relative-fee order of the highest balance
#spent from utxos that try to make the highest balance even higher
# so try to keep coins concentrated in one mixing depth
class YieldGenerator(Maker):
statement_file = os.path.join('logs', 'yigen-statement.csv')
def __init__(self, msgchan, wallet):
Maker.__init__(self, msgchan, wallet)
self.msgchan.register_channel_callbacks(self.on_welcome, self.on_set_topic,
None, None, self.on_nick_leave, None)
self.tx_unconfirm_timestamp = {}
def log_statement(self, data):
if common.get_network() == 'testnet':
return
data = [str(d) for d in data]
self.income_statement = open(self.statement_file, 'a')
self.income_statement.write(','.join(data) + '\n')
self.income_statement.close()
def on_welcome(self):
Maker.on_welcome(self)
if not os.path.isfile(self.statement_file):
self.log_statement(['timestamp', 'cj amount/satoshi', 'my input count',
'my input value/satoshi', 'cjfee/satoshi', 'earned/satoshi',
'confirm time/min', 'notes'])
timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
self.log_statement([timestamp, '', '', '', '', '', '', 'Connected'])
def create_my_orders(self):
mix_balance = self.wallet.get_balance_by_mixdepth()
if len([b for m, b in mix_balance.iteritems() if b > 0]) == 0:
debug('do not have any coins left')
return []
#print mix_balance
max_mix = max(mix_balance, key=mix_balance.get)
order = {'oid': 0, 'ordertype': 'relorder', 'minsize': minsize,
'maxsize': mix_balance[max_mix] - common.DUST_THRESHOLD, 'txfee': txfee, 'cjfee': cjfee}
return [order]
def oid_to_order(self, cjorder, oid, amount):
mix_balance = self.wallet.get_balance_by_mixdepth()
max_mix = max(mix_balance, key=mix_balance.get)
#algo attempts to make the largest-balance mixing depth get an even larger balance
debug('finding suitable mixdepth')
mixdepth = (max_mix - 1) % self.wallet.max_mix_depth
while True:
if mixdepth in mix_balance and mix_balance[mixdepth] >= amount:
break
mixdepth = (mixdepth - 1) % self.wallet.max_mix_depth
#mixdepth is the chosen depth we'll be spending from
cj_addr = self.wallet.get_receive_addr((mixdepth + 1) % self.wallet.max_mix_depth)
change_addr = self.wallet.get_change_addr(mixdepth)
utxos = self.wallet.select_utxos(mixdepth, amount)
my_total_in = sum([va['value'] for va in utxos.values()])
real_cjfee = calc_cj_fee(cjorder.ordertype, cjorder.cjfee, amount)
change_value = my_total_in - amount - cjorder.txfee + real_cjfee
if change_value <= common.DUST_THRESHOLD:
debug('change value=%d below dust threshold, finding new utxos' % (change_value))
try:
utxos = self.wallet.select_utxos(mixdepth, amount + common.DUST_THRESHOLD)
except Exception:
debug('dont have the required UTXOs to make a output above the dust threshold, quitting')
return None, None, None
return utxos, cj_addr, change_addr
def on_tx_unconfirmed(self, cjorder, txid, removed_utxos):
self.tx_unconfirm_timestamp[cjorder.cj_addr] = int(time.time())
#if the balance of the highest-balance mixing depth change then reannounce it
oldorder = self.orderlist[0] if len(self.orderlist) > 0 else None
neworders = self.create_my_orders()
if len(neworders) == 0:
return ([0], []) #cancel old order
if oldorder: #oldorder may not exist when this is called from on_tx_confirmed
if oldorder['maxsize'] == neworders[0]['maxsize']:
return ([], []) #change nothing
#announce new order, replacing the old order
return ([], [neworders[0]])
def on_tx_confirmed(self, cjorder, confirmations, txid):
if cjorder.cj_addr in self.tx_unconfirm_timestamp:
confirm_time = int(time.time()) - self.tx_unconfirm_timestamp[cjorder.cj_addr]
else:
confirm_time = 0
timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
self.log_statement([timestamp, cjorder.cj_amount, len(cjorder.utxos),
sum([av['value'] for av in cjorder.utxos.values()]), cjorder.real_cjfee,
cjorder.real_cjfee - cjorder.txfee, round(confirm_time / 60.0, 2), ''])
return self.on_tx_unconfirmed(cjorder, txid, None)
def main():
common.load_program_config()
import sys
seed = sys.argv[1]
if isinstance(common.bc_interface, blockchaininterface.BlockrInterface):
print '\nYou are running a yield generator by polling the blockr.io website'
print 'This is quite bad for privacy. That site is owned by coinbase.com'
print 'Also your bot will run faster and more efficently, you can be immediately notified of new bitcoin network'
print ' information so your money will be working for you as hard as possible'
print 'Learn how to setup JoinMarket with Bitcoin Core: https://github.com/chris-belcher/joinmarket/wiki/Running-JoinMarket-with-Bitcoin-Core-full-node'
ret = raw_input('\nContinue? (y/n):')
if ret[0] != 'y':
return
wallet = Wallet(seed, max_mix_depth = mix_levels)
common.bc_interface.sync_wallet(wallet)
common.nickname = nickname
debug('starting yield generator')
irc = IRCMessageChannel(common.nickname, realname='btcint=' + common.config.get("BLOCKCHAIN", "blockchain_source"),
password=nickserv_password)
maker = YieldGenerator(irc, wallet)
try:
debug('connecting to irc')
irc.run()
except:
debug('CRASHING, DUMPING EVERYTHING')
debug_dump_object(wallet, ['addr_cache', 'keys', 'seed'])
debug_dump_object(maker)
debug_dump_object(irc)
import traceback
debug(traceback.format_exc())
if __name__ == "__main__":
main()
print('done')