def oid_to_order(self, cjorder, oid, amount): total_amount = amount + cjorder.txfee mix_balance = self.wallet.get_balance_by_mixdepth() max_mix = max(mix_balance, key=mix_balance.get) filtered_mix_balance = [ m for m in mix_balance.iteritems() if m[1] >= total_amount ] log.debug('mix depths that have enough = ' + str(filtered_mix_balance)) filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0]) mixdepth = filtered_mix_balance[0][0] log.debug('filling offer, mixdepth=' + str(mixdepth)) # mixdepth is the chosen depth we'll be spending from cj_addr = self.wallet.get_internal_addr( (mixdepth + 1) % self.wallet.max_mix_depth) change_addr = self.wallet.get_internal_addr(mixdepth) utxos = self.wallet.select_utxos(mixdepth, total_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 <= jm_single().DUST_THRESHOLD: log.debug(('change value={} below dust threshold, ' 'finding new utxos').format(change_value)) try: utxos = self.wallet.select_utxos( mixdepth, total_amount + jm_single().DUST_THRESHOLD) except Exception: log.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 oid_to_order(self, cjorder, oid, amount): total_amount = amount + cjorder.txfee mix_balance = self.wallet.get_balance_by_mixdepth() max_mix = max(mix_balance, key=mix_balance.get) filtered_mix_balance = [m for m in mix_balance.iteritems() if m[1] >= total_amount] log.debug('mix depths that have enough = ' + str(filtered_mix_balance)) filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0]) mixdepth = filtered_mix_balance[0][0] log.debug('filling offer, mixdepth=' + str(mixdepth)) # mixdepth is the chosen depth we'll be spending from cj_addr = self.wallet.get_internal_addr((mixdepth + 1) % self.wallet.max_mix_depth) change_addr = self.wallet.get_internal_addr(mixdepth) utxos = self.wallet.select_utxos(mixdepth, total_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 <= jm_single().DUST_THRESHOLD: log.debug(('change value={} below dust threshold, ' 'finding new utxos').format(change_value)) try: utxos = self.wallet.select_utxos( mixdepth, total_amount + jm_single().DUST_THRESHOLD) except Exception: log.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 oid_to_order(self, cjorder, oid, amount): total_amount = amount + cjorder.txfee mix_balance = self.wallet.get_balance_by_mixdepth() filtered_mix_balance = [ m for m in mix_balance.iteritems() if m[1] >= total_amount ] if not filtered_mix_balance: return None, None, None log.debug('mix depths that have enough, filtered_mix_balance = ' + str(filtered_mix_balance)) # use mix depth that has the closest amount of coins to what this transaction needs # keeps coins moving through mix depths more quickly # and its more likely to use txos of a similiar size to this transaction filtered_mix_balance = sorted( filtered_mix_balance, key=lambda x: x[1]) #sort smallest to largest usable amount log.debug('sorted order of filtered_mix_balance = ' + str(filtered_mix_balance)) mixdepth = filtered_mix_balance[0][0] log.debug('filling offer, mixdepth=' + str(mixdepth)) # mixdepth is the chosen depth we'll be spending from cj_addr = self.wallet.get_internal_addr( (mixdepth + 1) % self.wallet.max_mix_depth) change_addr = self.wallet.get_internal_addr(mixdepth) utxos = self.wallet.select_utxos(mixdepth, total_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 <= jm_single().DUST_THRESHOLD: log.debug( 'change value=%d below dust threshold, finding new utxos' % (change_value)) try: utxos = self.wallet.select_utxos( mixdepth, total_amount + jm_single().DUST_THRESHOLD) except Exception: log.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 oid_to_order(self, cjorder, oid, amount): total_amount = amount + cjorder.txfee mix_balance = self.wallet.get_balance_by_mixdepth() filtered_mix_balance = [m for m in mix_balance.iteritems() if m[1] >= total_amount] if not filtered_mix_balance: return None, None, None log.debug('mix depths that have enough, filtered_mix_balance = ' + str( filtered_mix_balance)) # use mix depth that has the closest amount of coins to what this transaction needs # keeps coins moving through mix depths more quickly # and its more likely to use txos of a similiar size to this transaction filtered_mix_balance = sorted( filtered_mix_balance, key=lambda x: x[1]) #sort smallest to largest usable amount log.debug('sorted order of filtered_mix_balance = ' + str( filtered_mix_balance)) mixdepth = filtered_mix_balance[0][0] log.debug('filling offer, mixdepth=' + str(mixdepth)) # mixdepth is the chosen depth we'll be spending from cj_addr = self.wallet.get_internal_addr((mixdepth + 1) % self.wallet.max_mix_depth) change_addr = self.wallet.get_internal_addr(mixdepth) utxos = self.wallet.select_utxos(mixdepth, total_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 <= jm_single().DUST_THRESHOLD: log.debug('change value=%d below dust threshold, finding new utxos' % (change_value)) try: utxos = self.wallet.select_utxos( mixdepth, total_amount + jm_single().DUST_THRESHOLD) except Exception: log.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 create_depth_chart(db, cj_amount, args=None): if args is None: args = {} sqlorders = db.execute('SELECT * FROM orderbook;').fetchall() orderfees = sorted([ calc_cj_fee(o['ordertype'], o['cjfee'], cj_amount) / 1e8 for o in sqlorders if o['minsize'] <= cj_amount <= o['maxsize'] ]) if len(orderfees) == 0: return 'No orders at amount ' + str(cj_amount / 1e8) fig = plt.figure() scale = args.get("scale") if (scale is not None) and (scale[0] == "log"): orderfees = [float(fee) for fee in orderfees] if orderfees[0] > 0: ratio = orderfees[-1] / orderfees[0] step = ratio**0.0333 # 1/30 bins = [orderfees[0] * (step**i) for i in range(30)] else: ratio = orderfees[-1] / 1e-8 # single satoshi placeholder step = ratio**0.0333 # 1/30 bins = [1e-8 * (step**i) for i in range(30)] bins[0] = orderfees[0] # replace placeholder plt.xscale('log') else: bins = 30 if len(orderfees ) == 1: # these days we have liquidity, but just in case... plt.hist(orderfees, bins, rwidth=0.8, range=(0, orderfees[0] * 2)) else: plt.hist(orderfees, bins, rwidth=0.8) plt.grid() plt.title('CoinJoin Orderbook Depth Chart for amount=' + str(cj_amount / 1e8) + 'btc') plt.xlabel('CoinJoin Fee / btc') plt.ylabel('Frequency') return get_graph_html(fig)
def create_depth_chart(db, cj_amount, args=None): if args is None: args = {} sqlorders = db.execute('SELECT * FROM orderbook;').fetchall() orderfees = sorted([calc_cj_fee(o['ordertype'], o['cjfee'], cj_amount) / 1e8 for o in sqlorders if o['minsize'] <= cj_amount <= o[ 'maxsize']]) if len(orderfees) == 0: return 'No orders at amount ' + str(cj_amount / 1e8) fig = plt.figure() scale = args.get("scale") if (scale is not None) and (scale[0] == "log"): orderfees = [float(fee) for fee in orderfees] if orderfees[0] > 0: ratio = orderfees[-1] / orderfees[0] step = ratio ** 0.0333 # 1/30 bins = [orderfees[0] * (step ** i) for i in range(30)] else: ratio = orderfees[-1] / 1e-8 # single satoshi placeholder step = ratio ** 0.0333 # 1/30 bins = [1e-8 * (step ** i) for i in range(30)] bins[0] = orderfees[0] # replace placeholder plt.xscale('log') else: bins = 30 if len(orderfees) == 1: # these days we have liquidity, but just in case... plt.hist(orderfees, bins, rwidth=0.8, range=(0, orderfees[0] * 2)) else: plt.hist(orderfees, bins, rwidth=0.8) plt.grid() plt.title('CoinJoin Orderbook Depth Chart for amount=' + str(cj_amount / 1e8) + 'btc') plt.xlabel('CoinJoin Fee / btc') plt.ylabel('Frequency') return get_graph_html(fig)
def oid_to_order(self, cjorder, oid, amount): '''Coins rotate circularly from max mixdepth back to mixdepth 0''' mix_balance = self.wallet.get_balance_by_mixdepth() total_amount = amount + cjorder.txfee log.debug('amount, txfee, total_amount = ' + str(amount) + str( cjorder.txfee) + str(total_amount)) # look for exact amount available with no change filtered_mix_balance = [m for m in mix_balance.iteritems() if m[1] == total_amount] if filtered_mix_balance: log.debug('mix depths that have the exact amount needed = ' + str( filtered_mix_balance)) else: log.debug('no mix depths contain the exact amount needed.') filtered_mix_balance = [m for m in mix_balance.iteritems() if m[1] >= total_amount] log.debug('mix depths that have enough = ' + str( filtered_mix_balance)) filtered_mix_balance = [m for m in mix_balance.iteritems() if m[1] >= total_amount + min_output_size] log.debug('mix depths that have enough with min_output_size, ' + str(filtered_mix_balance)) try: len(filtered_mix_balance) > 0 except Exception: log.debug('No mix depths have enough funds to cover the ' + 'amount, cjfee, and min_output_size.') return None, None, None # prioritize by mixdepths sequencially # keep coins moving towards last mixdepth, clumps once they get there # makes sure coins sent to mixdepth 0 will get mixed to max mixdepth filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0]) # clumping. push all coins towards the largest mixdepth # the largest amount of coins are available to join with (since joins always come from a single depth) # the maker commands a higher fee for the larger amounts # order ascending but circularly with largest last # note, no need to consider max_offer_size here #largest_mixdepth = sorted( # filtered_mix_balance, # key=lambda x: x[1],)[-1] # find largest amount #smb = sorted(filtered_mix_balance, # key=lambda x: x[0]) # seq of mixdepth num #next_index = smb.index(largest_mixdepth) + 1 #mmd = self.wallet.max_mix_depth #filtered_mix_balance = smb[next_index % mmd:] + smb[:next_index % mmd] # use mix depth that has the closest amount of coins to what this transaction needs # keeps coins moving through mix depths more quickly # and its more likely to use txos of a similiar size to this transaction # sort smallest to largest usable amount #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[1]) # use mix depth with the most coins, # creates a more even distribution across mix depths # and a more diverse txo selection in each depth # sort largest to smallest amount #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[1], reverse=True) # use a random usable mixdepth. # warning, could expose more txos to malicous taker requests #filtered_mix_balance = random.choice(filtered_mix_balance) log.debug('sorted order of filtered_mix_balance = ' + str( filtered_mix_balance)) mixdepth = filtered_mix_balance[0][0] log.debug('filling offer, mixdepth=' + str(mixdepth)) # mixdepth is the chosen depth we'll be spending from cj_addr = self.wallet.get_internal_addr((mixdepth + 1) % self.wallet.max_mix_depth) change_addr = self.wallet.get_internal_addr(mixdepth) utxos = self.wallet.select_utxos(mixdepth, total_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 <= min_output_size: log.debug('change value=%d below dust threshold, finding new utxos' % (change_value)) try: utxos = self.wallet.select_utxos(mixdepth, total_amount + min_output_size) except Exception: log.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 create_my_orders(self): mix_balance = self.wallet.get_balance_by_mixdepth() log.debug('mix_balance = ' + str(mix_balance)) log.debug('mix_btcance = ' + str([(x, y / 1e8) for x, y in mix_balance.iteritems()])) sorted_mix_balance = sorted( list(mix_balance.iteritems()), key=lambda a: a[1]) #sort by size largest_mixdepth_size = sorted_mix_balance[-1][1] if largest_mixdepth_size <= min_output_size: print("ALERT: not enough funds available in wallet") return [] if override_offers: log.debug('override_offers = \n' + '\n'.join([str( o) for o in override_offers])) # make sure custom offers dont create a negative net for offer in override_offers: if offer['ordertype'] == 'absorder': profit = offer['cjfee'] needed = 'make txfee be less then the cjfee' elif offer['ordertype'] == 'relorder': profit = calc_cj_fee(offer['ordertype'], offer['cjfee'], offer['minsize']) if float(offer['cjfee']) > 0: needed = 'set minsize to ' + str(int(int(offer[ 'txfee'] / float(offer['cjfee'])))) if int(offer['txfee']) > profit: print("ALERT: negative yield") print('-> ' + str(offer)) print(needed) # if you really wanted to, you could comment out the next line. sys.exit(0) return override_offers offer_lowx = max(offer_low, min_output_size) if offer_high: offer_highx = min(offer_high, largest_mixdepth_size - min_output_size) else: offer_highx = largest_mixdepth_size - min_output_size # note, subtracting mix_output_size here to make minimum size change # todo, make an offer for exactly the max size with no change # Offers if offer_spread == 'fibonacci': offer_levels = fib_seq(offer_lowx, offer_highx, num_offers, upper_bound=True) offer_levels = [int(round(x)) for x in offer_levels] elif offer_spread == 'evenly': first_upper_bound = (offer_highx - offer_lowx) / num_offers offer_levels = list(range(first_upper_bound, offer_highx, ( offer_highx - first_upper_bound) / (num_offers - 1))) offer_levels = offer_levels[0:(num_offers - 1)] + [offer_highx] elif offer_spread == 'random': offer_levels = sorted([random.randrange(offer_lowx, offer_highx) for n in range(num_offers - 1)] + [random.randrange(offer_highx - ( offer_highx / num_offers), offer_highx)]) elif offer_spread == 'bymixdepth': offer_levels = [] for m in sorted_mix_balance: if m[1] == 0: continue elif m[1] <= offer_lowx: # todo, low mix balances get an absolute offer continue elif m[1] > offer_highx: offer_levels += [offer_highx] break else: offer_levels += [m[1]] # note, offer_levels len can be less then num_offers here elif offer_spread == 'custom': assert len(custom_offers) == num_offers offer_levels = [ int((decimal.Decimal(str(x))).quantize(0)) for x in sorted(custom_offers) ] if offer_levels[-1] > offer_highx: log.debug( 'ALERT: Your custom offers exceeds you max offer size.') log.debug('offer = ' + str(offer_levels[-1]) + ' offer_highx = ' + str(offer_highx)) sys.exit(0) else: log.debug('invalid offer_spread = ' + str(offer_spread)) sys.exit(0) # CJFees cjfee_lowx = decimal.Decimal(str(cjfee_low)) / 100 cjfee_highx = decimal.Decimal(str(cjfee_high)) / 100 if cjfee_spread == 'fibonacci': cjfee_levels = fib_seq(cjfee_lowx, cjfee_highx, num_offers) cjfee_levels = ["%0.7f" % x for x in cjfee_levels] elif cjfee_spread == 'evenly': cjfee_levels = drange(cjfee_lowx, cjfee_highx, (cjfee_highx - cjfee_lowx) / (num_offers - 1)) # evenly spaced cjfee_levels = ["%0.7f" % x for x in cjfee_levels] elif cjfee_spread == 'random': cjfee_levels = sorted( ["%0.7f" % random.uniform( float(cjfee_lowx), float(cjfee_highx)) for n in range(num_offers)]) # randomly spaced elif cjfee_spread == 'custom': cjfee_levels = [str(decimal.Decimal(str(x)) / 100) for x in custom_cjfees] leftout = num_offers - len(cjfee_levels) while leftout > 0: log.debug('ALERT: cjfee_custom has too few items') cjfee_levels.append(cjfee_levels[-1]) leftout -= 1 else: log.debug('invalid cjfee_spread = ' + str(cjfee_spread)) sys.exit(0) # TXFees if txfee_spread == 'fibonacci': txfee_levels = fib_seq(txfee_low, txfee_high, num_offers) txfee_levels = [int(round(x)) for x in txfee_levels] elif txfee_spread == 'evenly': txfee_levels = list(range(txfee_low, txfee_high, ( txfee_high - txfee_low) / (num_offers - 1))) txfee_levels = txfee_levels[0:(num_offers - 1)] + [txfee_high] elif txfee_spread == 'random': txfee_levels = sorted([random.randrange(txfee_low, txfee_high) for n in range(num_offers - 1)] + [random.randrange(txfee_high - ( txfee_high / num_offers), txfee_high)]) elif txfee_spread == 'custom': txfee_levels = [x for x in custom_txfees] else: log.debug('invalid txfee_spread = ' + str(txfee_spread)) sys.exit(0) log.debug('offer_levels = ' + str(offer_levels)) lower_bound_balances = [offer_lowx] + [x for x in offer_levels[:-1]] if offer_spread == 'bymixdepth': cjfee_levels = cjfee_levels[-len(offer_levels):] txfee_levels = txfee_levels[-len(offer_levels):] offer_ranges = zip(offer_levels, lower_bound_balances, cjfee_levels, txfee_levels) log.debug('offer_ranges = ' + str(offer_ranges)) offers = [] oid = 0 # create absorders for mixdepth dust offer_levels = [] for m in sorted_mix_balance: if m[1] == 0: continue #elif False: # disabled #elif m[1] <= 2e8: # absorder all mixdepths less then elif m[1] <= offer_lowx: offer = {'oid': oid, 'ordertype': 'absorder', 'minsize': m[1], 'maxsize': m[1], 'txfee': 0, 'cjfee': 0} #'txfee': txfee_low, #'cjfee': min_revenue} oid += 1 offers.append(offer) elif m[1] > offer_lowx: break for upper, lower, cjfee, txfee in offer_ranges: cjfee = float(cjfee) if cjfee == 0: min_needed = profit_req_per_transaction + txfee elif cjfee > 0: min_needed = int((profit_req_per_transaction + txfee + 1) / cjfee) elif cjfee < 0: sys.exit('negative fee not supported here') if min_needed <= lower: # create a regular relorder offer = {'oid': oid, 'ordertype': 'relorder', 'minsize': lower, 'maxsize': upper, 'txfee': txfee, 'cjfee': cjfee} elif min_needed > lower and min_needed < upper: # create two offers. An absolute for lower bound need, and relorder for the rest offer = {'oid': oid, 'ordertype': 'absorder', 'minsize': lower, 'maxsize': min_needed - 1, 'txfee': txfee, 'cjfee': profit_req_per_transaction + txfee} oid += 1 offers.append(offer) offer = {'oid': oid, 'ordertype': 'relorder', 'minsize': min_needed, 'maxsize': upper, 'txfee': txfee, 'cjfee': cjfee} elif min_needed >= upper: # just create an absolute offer offer = {'oid': oid, 'ordertype': 'absorder', 'minsize': lower, 'maxsize': upper, 'txfee': txfee, 'cjfee': profit_req_per_transaction + txfee} # todo: combine neighboring absorders into a single one oid += 1 offers.append(offer) deluxe_offer_display = [] header = 'oid'.rjust(5) header += 'type'.rjust(7) header += 'minsize btc'.rjust(15) header += 'maxsize btc'.rjust(15) header += 'min revenue satosh'.rjust(22) header += 'max revenue satosh'.rjust(22) deluxe_offer_display.append(header) for o in offers: line = str(o['oid']).rjust(5) if o['ordertype'] == 'absorder': line += 'abs'.rjust(7) elif o['ordertype'] == 'relorder': line += 'rel'.rjust(7) line += str(o['minsize'] / 1e8).rjust(15) line += str(o['maxsize'] / 1e8).rjust(15) if o['ordertype'] == 'absorder': line += str(o['cjfee']).rjust(22) elif o['ordertype'] == 'relorder': line += str(int(float(o['cjfee']) * int(o['minsize']))).rjust( 22) line += str(int(float(o['cjfee']) * int(o['maxsize']))).rjust( 22) deluxe_offer_display.append(line) log.debug('deluxe offer display = \n' + '\n'.join([str( x) for x in deluxe_offer_display])) log.debug('generated offers = \n' + '\n'.join([str(o) for o in offers])) # sanity check for offer in offers: assert offer['minsize'] >= 0 assert offer['maxsize'] > 0 assert offer['minsize'] <= offer['maxsize'] return offers
def oid_to_order(self, cjorder, oid, amount): """The only change from *basic here (for now) is that we choose outputs to avoid increasing the max_mixdepth as much as possible, thus avoiding reannouncement as much as possible. """ total_amount = amount + cjorder.txfee mix_balance = self.wallet.get_balance_by_mixdepth() max_mix = max(mix_balance, key=mix_balance.get) min_mix = min(mix_balance, key=mix_balance.get) filtered_mix_balance = [ m for m in mix_balance.iteritems() if m[1] >= total_amount ] if not filtered_mix_balance: return None, None, None log.debug('mix depths that have enough = ' + str(filtered_mix_balance)) # Avoid the max mixdepth wherever possible, to avoid changing the # offer. Algo: #"mixdepth" is the mixdepth we are spending FROM, so it is also # the destination of change. #"cjoutdepth" is the mixdepth we are sending coinjoin out to. # # Find a mixdepth, in the set that have enough, which is # not the maximum, and choose any from that set as "mixdepth". # If not possible, it means only the max_mix depth has enough, # so must choose "mixdepth" to be that. # To find the cjoutdepth: ensure that max != min, if so it means # we had only one depth; in that case, just set "cjoutdepth" # to the next mixdepth. Otherwise, we set "cjoutdepth" to the minimum. nonmax_mix_balance = [ m for m in filtered_mix_balance if m[0] != max_mix ] if not nonmax_mix_balance: log.debug("Could not spend from a mixdepth which is not max") mixdepth = max_mix else: mixdepth = nonmax_mix_balance[0][0] log.info('filling offer, mixdepth=' + str(mixdepth)) # mixdepth is the chosen depth we'll be spending from # min_mixdepth is the one we want to send our cjout TO, # to minimize chance of it becoming the largest, and reannouncing # offer. if mixdepth == min_mix: cjoutmix = (mixdepth + 1) % self.wallet.max_mix_depth # don't send cjout to max if cjoutmix == max_mix: cjoutmix = (cjoutmix + 1) % self.wallet.max_mix_depth else: cjoutmix = min_mix cj_addr = self.wallet.get_internal_addr(cjoutmix) change_addr = self.wallet.get_internal_addr(mixdepth) utxos = self.wallet.select_utxos(mixdepth, total_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 <= jm_single().DUST_THRESHOLD: log.debug(('change value={} below dust threshold, ' 'finding new utxos').format(change_value)) try: utxos = self.wallet.select_utxos( mixdepth, total_amount + jm_single().DUST_THRESHOLD) except Exception: log.info( 'dont have the required UTXOs to make a ' 'output above the dust threshold, quitting. ' 'This can sometimes happen and does not require user action.' ) return None, None, None return utxos, cj_addr, change_addr
def oid_to_order(self, cjorder, oid, amount): '''Coins rotate circularly from max mixdepth back to mixdepth 0''' mix_balance = self.wallet.get_balance_by_mixdepth() total_amount = amount + cjorder.txfee log.debug('amount, txfee, total_amount = ' + str(amount) + str(cjorder.txfee) + str(total_amount)) # look for exact amount available with no change # not supported because change output required # needs this fixed https://github.com/JoinMarket-Org/joinmarket/issues/418 #filtered_mix_balance = [m # for m in mix_balance.iteritems() # if m[1] == total_amount] #if filtered_mix_balance: # log.debug('mix depths that have the exact amount needed = ' + str( # filtered_mix_balance)) #else: # log.debug('no mix depths contain the exact amount needed.') filtered_mix_balance = [ m for m in mix_balance.iteritems() if m[1] >= (total_amount) ] log.debug('mix depths that have enough = ' + str(filtered_mix_balance)) filtered_mix_balance = [ m for m in mix_balance.iteritems() if m[1] >= total_amount + output_size_min ] log.debug('mix depths that have enough with output_size_min, ' + str(filtered_mix_balance)) try: len(filtered_mix_balance) > 0 except Exception: log.debug('No mix depths have enough funds to cover the ' + 'amount, cjfee, and output_size_min.') return None, None, None # slinky clumping: push all coins towards the largest mixdepth, # then spend from the largest mixdepth into the next mixdepth. # the coins stay in the next mixdepth until they are all there, # and then get spent into the next mixdepth, ad infinitum. lmd = sorted( filtered_mix_balance, key=lambda x: x[1], )[-1] smb = sorted(filtered_mix_balance, key=lambda x: x[0]) # seq of md num mmd = self.wallet.max_mix_depth nmd = (lmd[0] + 1) % mmd if nmd not in [x[0] for x in smb]: # use all usable next_si = (smb.index(lmd) + 1) % len(smb) filtered_mix_balance = smb[next_si:] + smb[:next_si] else: nmd = [x for x in smb if x[0] == nmd][0] others = [x for x in smb if x != nmd and x != lmd] if not others: # just these two remain, prioritize largest filtered_mix_balance = [lmd, nmd] else: # use all usable if [x for x in others if x[1] >= nmd[1]]: next_si = (smb.index(lmd) + 1) % len(smb) filtered_mix_balance = smb[next_si:] + smb[:next_si] else: # others are not large, dont use nmd next_si = (smb.index(lmd) + 2) % len(smb) filtered_mix_balance = smb[next_si:] + smb[:next_si] # prioritize by mixdepths ascending # keep coins moving towards last mixdepth, clumps there. # makes sure coins sent to mixdepth 0 will get mixed to mixdepth 5 #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0]) # use mix depth with the most coins, # creates a more even distribution across mix depths # and a more diverse txo selection in each depth # sort largest to smallest amount #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[1], reverse=True) # use a random usable mixdepth. # warning, could expose more txos to malicous taker requests #filtered_mix_balance = [random.choice(filtered_mix_balance)] log.debug('sorted order of filtered_mix_balance = ' + str(filtered_mix_balance)) mixdepth = filtered_mix_balance[0][0] log.debug('filling offer, mixdepth=' + str(mixdepth)) # mixdepth is the chosen depth we'll be spending from cj_addr = self.wallet.get_internal_addr( (mixdepth + 1) % self.wallet.max_mix_depth) change_addr = self.wallet.get_internal_addr(mixdepth) utxos = self.wallet.select_utxos(mixdepth, total_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 <= output_size_min: log.debug( 'change value=%d below dust threshold, finding new utxos' % (change_value)) try: utxos = self.wallet.select_utxos( mixdepth, total_amount + output_size_min) except Exception: log.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 oid_to_order(self, cjorder, oid, amount): '''Coins rotate circularly from max mixdepth back to mixdepth 0''' mix_balance = self.wallet.get_balance_by_mixdepth() total_amount = amount + cjorder.txfee log.debug('amount, txfee, total_amount = ' + str(amount) + str(cjorder.txfee) + str(total_amount)) # look for exact amount available with no change filtered_mix_balance = [ m for m in mix_balance.iteritems() if m[1] == total_amount ] if filtered_mix_balance: log.debug('mix depths that have the exact amount needed = ' + str(filtered_mix_balance)) else: log.debug('no mix depths contain the exact amount needed.') filtered_mix_balance = [ m for m in mix_balance.iteritems() if m[1] >= total_amount ] log.debug('mix depths that have enough = ' + str(filtered_mix_balance)) filtered_mix_balance = [ m for m in mix_balance.iteritems() if m[1] >= total_amount + min_output_size ] log.debug('mix depths that have enough with min_output_size, ' + str(filtered_mix_balance)) try: len(filtered_mix_balance) > 0 except Exception: log.debug('No mix depths have enough funds to cover the ' + 'amount, cjfee, and min_output_size.') return None, None, None # prioritize by mixdepths sequencially # keep coins moving towards last mixdepth, clumps once they get there # makes sure coins sent to mixdepth 0 will get mixed to max mixdepth filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0]) # clumping. push all coins towards the largest mixdepth # the largest amount of coins are available to join with (since joins always come from a single depth) # the maker commands a higher fee for the larger amounts # order ascending but circularly with largest last # note, no need to consider max_offer_size here #largest_mixdepth = sorted( # filtered_mix_balance, # key=lambda x: x[1],)[-1] # find largest amount #smb = sorted(filtered_mix_balance, # key=lambda x: x[0]) # seq of mixdepth num #next_index = smb.index(largest_mixdepth) + 1 #mmd = self.wallet.max_mix_depth #filtered_mix_balance = smb[next_index % mmd:] + smb[:next_index % mmd] # use mix depth that has the closest amount of coins to what this transaction needs # keeps coins moving through mix depths more quickly # and its more likely to use txos of a similiar size to this transaction # sort smallest to largest usable amount #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[1]) # use mix depth with the most coins, # creates a more even distribution across mix depths # and a more diverse txo selection in each depth # sort largest to smallest amount #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[1], reverse=True) # use a random usable mixdepth. # warning, could expose more txos to malicous taker requests #filtered_mix_balance = random.choice(filtered_mix_balance) log.debug('sorted order of filtered_mix_balance = ' + str(filtered_mix_balance)) mixdepth = filtered_mix_balance[0][0] log.debug('filling offer, mixdepth=' + str(mixdepth)) # mixdepth is the chosen depth we'll be spending from cj_addr = self.wallet.get_internal_addr( (mixdepth + 1) % self.wallet.max_mix_depth) change_addr = self.wallet.get_internal_addr(mixdepth) utxos = self.wallet.select_utxos(mixdepth, total_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 <= min_output_size: log.debug( 'change value=%d below dust threshold, finding new utxos' % (change_value)) try: utxos = self.wallet.select_utxos( mixdepth, total_amount + min_output_size) except Exception: log.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 create_my_orders(self): mix_balance = self.wallet.get_balance_by_mixdepth() log.debug('mix_balance = ' + str(mix_balance)) log.debug('mix_btcance = ' + str([(x, y / 1e8) for x, y in mix_balance.iteritems()])) sorted_mix_balance = sorted(list(mix_balance.iteritems()), key=lambda a: a[1]) #sort by size largest_mixdepth_size = sorted_mix_balance[-1][1] if largest_mixdepth_size <= min_output_size: print("ALERT: not enough funds available in wallet") return [] if override_offers: log.debug('override_offers = \n' + '\n'.join([str(o) for o in override_offers])) # make sure custom offers dont create a negative net for offer in override_offers: if offer['ordertype'] == 'absorder': profit = offer['cjfee'] needed = 'make txfee be less then the cjfee' elif offer['ordertype'] == 'relorder': profit = calc_cj_fee(offer['ordertype'], offer['cjfee'], offer['minsize']) if float(offer['cjfee']) > 0: needed = 'set minsize to ' + str( int(int(offer['txfee'] / float(offer['cjfee'])))) if int(offer['txfee']) > profit: print("ALERT: negative yield") print('-> ' + str(offer)) print(needed) # if you really wanted to, you could comment out the next line. sys.exit(0) return override_offers offer_lowx = max(offer_low, min_output_size) if offer_high: offer_highx = min(offer_high, largest_mixdepth_size - min_output_size) else: offer_highx = largest_mixdepth_size - min_output_size # note, subtracting mix_output_size here to make minimum size change # todo, make an offer for exactly the max size with no change # Offers if offer_spread == 'fibonacci': offer_levels = fib_seq(offer_lowx, offer_highx, num_offers, upper_bound=True) offer_levels = [int(round(x)) for x in offer_levels] elif offer_spread == 'evenly': first_upper_bound = (offer_highx - offer_lowx) / num_offers offer_levels = list( range(first_upper_bound, offer_highx, (offer_highx - first_upper_bound) / (num_offers - 1))) offer_levels = offer_levels[0:(num_offers - 1)] + [offer_highx] elif offer_spread == 'random': offer_levels = sorted([ random.randrange(offer_lowx, offer_highx) for n in range(num_offers - 1) ] + [ random.randrange(offer_highx - (offer_highx / num_offers), offer_highx) ]) elif offer_spread == 'bymixdepth': offer_levels = [] for m in sorted_mix_balance: if m[1] == 0: continue elif m[1] <= offer_lowx: # todo, low mix balances get an absolute offer continue elif m[1] > offer_highx: offer_levels += [offer_highx] break else: offer_levels += [m[1]] # note, offer_levels len can be less then num_offers here elif offer_spread == 'custom': assert len(custom_offers) == num_offers offer_levels = [ int((decimal.Decimal(str(x))).quantize(0)) for x in sorted(custom_offers) ] if offer_levels[-1] > offer_highx: log.debug( 'ALERT: Your custom offers exceeds you max offer size.') log.debug('offer = ' + str(offer_levels[-1]) + ' offer_highx = ' + str(offer_highx)) sys.exit(0) else: log.debug('invalid offer_spread = ' + str(offer_spread)) sys.exit(0) # CJFees cjfee_lowx = decimal.Decimal(str(cjfee_low)) / 100 cjfee_highx = decimal.Decimal(str(cjfee_high)) / 100 if cjfee_spread == 'fibonacci': cjfee_levels = fib_seq(cjfee_lowx, cjfee_highx, num_offers) cjfee_levels = ["%0.7f" % x for x in cjfee_levels] elif cjfee_spread == 'evenly': cjfee_levels = drange(cjfee_lowx, cjfee_highx, (cjfee_highx - cjfee_lowx) / (num_offers - 1)) # evenly spaced cjfee_levels = ["%0.7f" % x for x in cjfee_levels] elif cjfee_spread == 'random': cjfee_levels = sorted([ "%0.7f" % random.uniform(float(cjfee_lowx), float(cjfee_highx)) for n in range(num_offers) ]) # randomly spaced elif cjfee_spread == 'custom': cjfee_levels = [ str(decimal.Decimal(str(x)) / 100) for x in custom_cjfees ] leftout = num_offers - len(cjfee_levels) while leftout > 0: log.debug('ALERT: cjfee_custom has too few items') cjfee_levels.append(cjfee_levels[-1]) leftout -= 1 else: log.debug('invalid cjfee_spread = ' + str(cjfee_spread)) sys.exit(0) # TXFees if txfee_spread == 'fibonacci': txfee_levels = fib_seq(txfee_low, txfee_high, num_offers) txfee_levels = [int(round(x)) for x in txfee_levels] elif txfee_spread == 'evenly': txfee_levels = list( range(txfee_low, txfee_high, (txfee_high - txfee_low) / (num_offers - 1))) txfee_levels = txfee_levels[0:(num_offers - 1)] + [txfee_high] elif txfee_spread == 'random': txfee_levels = sorted([ random.randrange(txfee_low, txfee_high) for n in range(num_offers - 1) ] + [ random.randrange(txfee_high - (txfee_high / num_offers), txfee_high) ]) elif txfee_spread == 'custom': txfee_levels = [x for x in custom_txfees] else: log.debug('invalid txfee_spread = ' + str(txfee_spread)) sys.exit(0) log.debug('offer_levels = ' + str(offer_levels)) lower_bound_balances = [offer_lowx] + [x for x in offer_levels[:-1]] if offer_spread == 'bymixdepth': cjfee_levels = cjfee_levels[-len(offer_levels):] txfee_levels = txfee_levels[-len(offer_levels):] offer_ranges = zip(offer_levels, lower_bound_balances, cjfee_levels, txfee_levels) log.debug('offer_ranges = ' + str(offer_ranges)) offers = [] oid = 0 # create absorders for mixdepth dust offer_levels = [] for m in sorted_mix_balance: if m[1] == 0: continue #elif False: # disabled #elif m[1] <= 2e8: # absorder all mixdepths less then elif m[1] <= offer_lowx: offer = { 'oid': oid, 'ordertype': 'absorder', 'minsize': m[1], 'maxsize': m[1], 'txfee': 0, 'cjfee': 0 } #'txfee': txfee_low, #'cjfee': min_revenue} oid += 1 offers.append(offer) elif m[1] > offer_lowx: break for upper, lower, cjfee, txfee in offer_ranges: cjfee = float(cjfee) if cjfee == 0: min_needed = profit_req_per_transaction + txfee elif cjfee > 0: min_needed = int( (profit_req_per_transaction + txfee + 1) / cjfee) elif cjfee < 0: sys.exit('negative fee not supported here') if min_needed <= lower: # create a regular relorder offer = { 'oid': oid, 'ordertype': 'relorder', 'minsize': lower, 'maxsize': upper, 'txfee': txfee, 'cjfee': cjfee } elif min_needed > lower and min_needed < upper: # create two offers. An absolute for lower bound need, and relorder for the rest offer = { 'oid': oid, 'ordertype': 'absorder', 'minsize': lower, 'maxsize': min_needed - 1, 'txfee': txfee, 'cjfee': profit_req_per_transaction + txfee } oid += 1 offers.append(offer) offer = { 'oid': oid, 'ordertype': 'relorder', 'minsize': min_needed, 'maxsize': upper, 'txfee': txfee, 'cjfee': cjfee } elif min_needed >= upper: # just create an absolute offer offer = { 'oid': oid, 'ordertype': 'absorder', 'minsize': lower, 'maxsize': upper, 'txfee': txfee, 'cjfee': profit_req_per_transaction + txfee } # todo: combine neighboring absorders into a single one oid += 1 offers.append(offer) deluxe_offer_display = [] header = 'oid'.rjust(5) header += 'type'.rjust(7) header += 'minsize btc'.rjust(15) header += 'maxsize btc'.rjust(15) header += 'min revenue satosh'.rjust(22) header += 'max revenue satosh'.rjust(22) deluxe_offer_display.append(header) for o in offers: line = str(o['oid']).rjust(5) if o['ordertype'] == 'absorder': line += 'abs'.rjust(7) elif o['ordertype'] == 'relorder': line += 'rel'.rjust(7) line += str(o['minsize'] / 1e8).rjust(15) line += str(o['maxsize'] / 1e8).rjust(15) if o['ordertype'] == 'absorder': line += str(o['cjfee']).rjust(22) elif o['ordertype'] == 'relorder': line += str(int(float(o['cjfee']) * int(o['minsize']))).rjust(22) line += str(int(float(o['cjfee']) * int(o['maxsize']))).rjust(22) deluxe_offer_display.append(line) log.debug('deluxe offer display = \n' + '\n'.join([str(x) for x in deluxe_offer_display])) log.debug('generated offers = \n' + '\n'.join([str(o) for o in offers])) # sanity check for offer in offers: assert offer['minsize'] >= 0 assert offer['maxsize'] > 0 assert offer['minsize'] <= offer['maxsize'] return offers
def create_my_orders(self): mix_balance = self.wallet.get_balance_by_mixdepth() log.debug('mix_balance = ' + str(mix_balance)) nondust_mix_balance = dict([(m, b) for m, b in mix_balance.iteritems() if b > jm_single().DUST_THRESHOLD]) if len(nondust_mix_balance) == 0: log.debug('do not have any coins left') return [] #sorts the mixdepth_balance map by balance size sorted_mix_balance = sorted( list(mix_balance.iteritems()), key=lambda a: a[1], reverse=True) minsize = int( 1.5 * txfee / float(min(cjfee)) ) #minimum size is such that you always net profit at least 50% of the miner fee filtered_mix_balance = [f for f in sorted_mix_balance if f[1] > minsize] delta = mix_levels - len(filtered_mix_balance) log.debug('minsize=' + str(minsize) + ' calc\'d with cjfee=' + str(min( cjfee))) lower_bound_balances = filtered_mix_balance[1:] + [(-1, minsize)] mix_balance_min = [ (mxb[0], mxb[1], minb[1]) for mxb, minb in zip(filtered_mix_balance, lower_bound_balances) ] mix_balance_min = mix_balance_min[::-1] #reverse list order thecjfee = cjfee[::-1] log.debug('mixdepth_balance_min = ' + str(mix_balance_min)) orders = [] oid = 0 for mix_bal_min in mix_balance_min: mixdepth, balance, mins = mix_bal_min #the maker class reads specific keys from the dict, but others # are allowed in there and will be ignored order = {'oid': oid + 1, 'ordertype': 'relorder', 'minsize': max(mins - jm_single().DUST_THRESHOLD, jm_single().DUST_THRESHOLD) + 1, 'maxsize': max(balance - jm_single().DUST_THRESHOLD, jm_single().DUST_THRESHOLD), 'txfee': txfee, 'cjfee': thecjfee[oid + delta], 'mixdepth': mixdepth} oid += 1 orders.append(order) absorder_size = min(minsize, sorted_mix_balance[0][1]) if absorder_size != 0: lowest_cjfee = thecjfee[min(oid, len(thecjfee) - 1)] absorder_fee = calc_cj_fee('relorder', lowest_cjfee, minsize) log.debug('absorder fee = ' + str(absorder_fee) + ' uses cjfee=' + str(lowest_cjfee)) #the absorder is always oid=0 order = {'oid': 0, 'ordertype': 'absorder', 'minsize': jm_single().DUST_THRESHOLD + 1, 'maxsize': absorder_size - jm_single().DUST_THRESHOLD, 'txfee': txfee, 'cjfee': absorder_fee} orders = [order] + orders log.debug('generated orders = \n' + '\n'.join([str(o) for o in orders])) # sanity check for order in orders: assert order['minsize'] >= 0 assert order['maxsize'] > 0 assert order['minsize'] <= order['maxsize'] return orders
def oid_to_order(self, cjorder, oid, amount): """The only change from *basic here (for now) is that we choose outputs to avoid increasing the max_mixdepth as much as possible, thus avoiding reannouncement as much as possible. """ total_amount = amount + cjorder.txfee mix_balance = self.wallet.get_balance_by_mixdepth() max_mix = max(mix_balance, key=mix_balance.get) min_mix = min(mix_balance, key=mix_balance.get) filtered_mix_balance = [m for m in mix_balance.iteritems() if m[1] >= total_amount] if not filtered_mix_balance: return None, None, None log.debug('mix depths that have enough = ' + str(filtered_mix_balance)) #Avoid the max mixdepth wherever possible, to avoid changing the #offer. Algo: #"mixdepth" is the mixdepth we are spending FROM, so it is also #the destination of change. #"cjoutdepth" is the mixdepth we are sending coinjoin out to. # #Find a mixdepth, in the set that have enough, which is #not the maximum, and choose any from that set as "mixdepth". #If not possible, it means only the max_mix depth has enough, #so must choose "mixdepth" to be that. #To find the cjoutdepth: ensure that max != min, if so it means #we had only one depth; in that case, just set "cjoutdepth" #to the next mixdepth. Otherwise, we set "cjoutdepth" to the minimum. nonmax_mix_balance = [m for m in filtered_mix_balance if m[0] != max_mix] if not nonmax_mix_balance: log.debug("Could not spend from a mixdepth which is not max") mixdepth = max_mix else: mixdepth = nonmax_mix_balance[0][0] log.debug('filling offer, mixdepth=' + str(mixdepth)) # mixdepth is the chosen depth we'll be spending from # min_mixdepth is the one we want to send our cjout TO, # to minimize chance of it becoming the largest, and reannouncing offer. if mixdepth == min_mix: cjoutmix = (mixdepth + 1) % self.wallet.max_mix_depth #don't send cjout to max if cjoutmix == max_mix: cjoutmix = (cjoutmix + 1) % self.wallet.max_mix_depth else: cjoutmix = min_mix cj_addr = self.wallet.get_internal_addr(cjoutmix) change_addr = self.wallet.get_internal_addr(mixdepth) utxos = self.wallet.select_utxos(mixdepth, total_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 <= jm_single().DUST_THRESHOLD: log.debug(('change value={} below dust threshold, ' 'finding new utxos').format(change_value)) try: utxos = self.wallet.select_utxos( mixdepth, total_amount + jm_single().DUST_THRESHOLD) except Exception: log.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 oid_to_order(self, cjorder, oid, amount): '''Coins rotate circularly from max mixdepth back to mixdepth 0''' mix_balance = self.wallet.get_balance_by_mixdepth() total_amount = amount + cjorder.txfee log.debug('amount, txfee, total_amount = ' + str(amount) + str( cjorder.txfee) + str(total_amount)) # look for exact amount available with no change # not supported because change output required # needs this fixed https://github.com/JoinMarket-Org/joinmarket/issues/418 #filtered_mix_balance = [m # for m in mix_balance.iteritems() # if m[1] == total_amount] #if filtered_mix_balance: # log.debug('mix depths that have the exact amount needed = ' + str( # filtered_mix_balance)) #else: # log.debug('no mix depths contain the exact amount needed.') filtered_mix_balance = [m for m in mix_balance.iteritems() if m[1] >= (total_amount)] log.debug('mix depths that have enough = ' + str(filtered_mix_balance)) filtered_mix_balance = [m for m in mix_balance.iteritems() if m[1] >= total_amount + output_size_min] log.debug('mix depths that have enough with output_size_min, ' + str( filtered_mix_balance)) try: len(filtered_mix_balance) > 0 except Exception: log.debug('No mix depths have enough funds to cover the ' + 'amount, cjfee, and output_size_min.') return None, None, None # slinky clumping: push all coins towards the largest mixdepth, # then spend from the largest mixdepth into the next mixdepth. # the coins stay in the next mixdepth until they are all there, # and then get spent into the next mixdepth, ad infinitum. lmd = sorted(filtered_mix_balance, key=lambda x: x[1],)[-1] smb = sorted(filtered_mix_balance, key=lambda x: x[0]) # seq of md num mmd = self.wallet.max_mix_depth nmd = (lmd[0] + 1) % mmd if nmd not in [x[0] for x in smb]: # use all usable next_si = (smb.index(lmd) + 1) % len(smb) filtered_mix_balance = smb[next_si:] + smb[:next_si] else: nmd = [x for x in smb if x[0] == nmd][0] others = [x for x in smb if x != nmd and x != lmd] if not others: # just these two remain, prioritize largest filtered_mix_balance = [lmd, nmd] else: # use all usable if [x for x in others if x[1] >= nmd[1]]: next_si = (smb.index(lmd) + 1) % len(smb) filtered_mix_balance = smb[next_si:] + smb[:next_si] else: # others are not large, dont use nmd next_si = (smb.index(lmd) + 2) % len(smb) filtered_mix_balance = smb[next_si:] + smb[:next_si] # prioritize by mixdepths ascending # keep coins moving towards last mixdepth, clumps there. # makes sure coins sent to mixdepth 0 will get mixed to mixdepth 5 #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[0]) # use mix depth with the most coins, # creates a more even distribution across mix depths # and a more diverse txo selection in each depth # sort largest to smallest amount #filtered_mix_balance = sorted(filtered_mix_balance, key=lambda x: x[1], reverse=True) # use a random usable mixdepth. # warning, could expose more txos to malicous taker requests #filtered_mix_balance = [random.choice(filtered_mix_balance)] log.debug('sorted order of filtered_mix_balance = ' + str( filtered_mix_balance)) mixdepth = filtered_mix_balance[0][0] log.debug('filling offer, mixdepth=' + str(mixdepth)) # mixdepth is the chosen depth we'll be spending from cj_addr = self.wallet.get_internal_addr((mixdepth + 1) % self.wallet.max_mix_depth) change_addr = self.wallet.get_internal_addr(mixdepth) utxos = self.wallet.select_utxos(mixdepth, total_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 <= output_size_min: log.debug('change value=%d below dust threshold, finding new utxos' % (change_value)) try: utxos = self.wallet.select_utxos(mixdepth, total_amount + output_size_min) except Exception: log.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