Browse Source
for confirm and unconfirm callback; yg-basic now functioning. Some rearrangement of daemon protocol module.master
6 changed files with 685 additions and 161 deletions
@ -0,0 +1,168 @@ |
|||||||
|
#! /usr/bin/env python |
||||||
|
from __future__ import print_function |
||||||
|
|
||||||
|
import base64 |
||||||
|
import pprint |
||||||
|
import random |
||||||
|
import sys |
||||||
|
import time |
||||||
|
import copy |
||||||
|
|
||||||
|
import btc |
||||||
|
from jmclient.configure import jm_single, get_p2pk_vbyte, get_p2sh_vbyte |
||||||
|
from jmbase.support import get_log |
||||||
|
from jmclient.support import (calc_cj_fee) |
||||||
|
from jmclient.wallet import estimate_tx_fee |
||||||
|
from jmclient.podle import (generate_podle, get_podle_commitments, verify_podle, |
||||||
|
PoDLE, PoDLEError, generate_podle_error_string) |
||||||
|
jlog = get_log() |
||||||
|
|
||||||
|
class Maker(object): |
||||||
|
def __init__(self, wallet): |
||||||
|
self.active_orders = {} |
||||||
|
self.wallet = wallet |
||||||
|
self.nextoid = -1 |
||||||
|
self.offerlist = self.create_my_orders() |
||||||
|
self.aborted = False |
||||||
|
|
||||||
|
def on_auth_received(self, nick, offer, commitment, cr, amount, kphex): |
||||||
|
"""Receives data on proposed transaction offer from daemon, verifies |
||||||
|
commitment, returns necessary data to send ioauth message (utxos etc) |
||||||
|
""" |
||||||
|
#deserialize the commitment revelation |
||||||
|
cr_dict = PoDLE.deserialize_revelation(cr) |
||||||
|
#check the validity of the proof of discrete log equivalence |
||||||
|
tries = jm_single().config.getint("POLICY", "taker_utxo_retries") |
||||||
|
def reject(msg): |
||||||
|
jlog.info("Counterparty commitment not accepted, reason: " + msg) |
||||||
|
return (False,) |
||||||
|
if not verify_podle(str(cr_dict['P']), str(cr_dict['P2']), str(cr_dict['sig']), |
||||||
|
str(cr_dict['e']), str(commitment), |
||||||
|
index_range=range(tries)): |
||||||
|
reason = "verify_podle failed" |
||||||
|
return reject(reason) |
||||||
|
#finally, check that the proffered utxo is real, old enough, large enough, |
||||||
|
#and corresponds to the pubkey |
||||||
|
res = jm_single().bc_interface.query_utxo_set([cr_dict['utxo']], |
||||||
|
includeconf=True) |
||||||
|
if len(res) != 1 or not res[0]: |
||||||
|
reason = "authorizing utxo is not valid" |
||||||
|
return reject(reason) |
||||||
|
age = jm_single().config.getint("POLICY", "taker_utxo_age") |
||||||
|
if res[0]['confirms'] < age: |
||||||
|
reason = "commitment utxo not old enough: " + str(res[0]['confirms']) |
||||||
|
return reject(reason) |
||||||
|
reqd_amt = int(amount * jm_single().config.getint( |
||||||
|
"POLICY", "taker_utxo_amtpercent") / 100.0) |
||||||
|
if res[0]['value'] < reqd_amt: |
||||||
|
reason = "commitment utxo too small: " + str(res[0]['value']) |
||||||
|
return reject(reason) |
||||||
|
if res[0]['address'] != btc.pubkey_to_p2sh_p2wpkh_address(cr_dict['P'], |
||||||
|
self.wallet.get_vbyte()): |
||||||
|
reason = "Invalid podle pubkey: " + str(cr_dict['P']) |
||||||
|
return reject(reason) |
||||||
|
|
||||||
|
# authorisation of taker passed |
||||||
|
#Find utxos for the transaction now: |
||||||
|
utxos, cj_addr, change_addr = self.oid_to_order(offer, amount) |
||||||
|
if not utxos: |
||||||
|
#could not find funds |
||||||
|
return (False,) |
||||||
|
# Construct data for auth request back to taker. |
||||||
|
# Need to choose an input utxo pubkey to sign with |
||||||
|
# (no longer using the coinjoin pubkey from 0.2.0) |
||||||
|
# Just choose the first utxo in self.utxos and retrieve key from wallet. |
||||||
|
auth_address = utxos[utxos.keys()[0]]['address'] |
||||||
|
auth_key = self.wallet.get_key_from_addr(auth_address) |
||||||
|
auth_pub = btc.privtopub(auth_key) |
||||||
|
btc_sig = btc.ecdsa_sign(kphex, auth_key) |
||||||
|
return (True, utxos, auth_pub, cj_addr, change_addr, btc_sig) |
||||||
|
|
||||||
|
def on_tx_received(self, nick, txhex, offerinfo): |
||||||
|
try: |
||||||
|
tx = btc.deserialize(txhex) |
||||||
|
except IndexError as e: |
||||||
|
return (False, 'malformed txhex. ' + repr(e)) |
||||||
|
jlog.info('obtained tx\n' + pprint.pformat(tx)) |
||||||
|
goodtx, errmsg = self.verify_unsigned_tx(tx, offerinfo) |
||||||
|
if not goodtx: |
||||||
|
jlog.info('not a good tx, reason=' + errmsg) |
||||||
|
return (False, errmsg) |
||||||
|
jlog.info('goodtx') |
||||||
|
sigs = [] |
||||||
|
utxos = offerinfo["utxos"] |
||||||
|
for index, ins in enumerate(tx['ins']): |
||||||
|
utxo = ins['outpoint']['hash'] + ':' + str(ins['outpoint']['index']) |
||||||
|
if utxo not in utxos.keys(): |
||||||
|
continue |
||||||
|
addr = utxos[utxo]['address'] |
||||||
|
amount = utxos[utxo]["value"] |
||||||
|
txs = self.wallet.sign(txhex, index, |
||||||
|
self.wallet.get_key_from_addr(addr), |
||||||
|
amount=amount) |
||||||
|
sigmsg = btc.deserialize(txs)["ins"][index]["script"].decode("hex") |
||||||
|
if "txinwitness" in btc.deserialize(txs)["ins"][index].keys(): |
||||||
|
#We prepend the witness data since we want (sig, pub, scriptCode); |
||||||
|
#also, the items in witness are not serialize_script-ed. |
||||||
|
sigmsg = "".join([btc.serialize_script_unit( |
||||||
|
x.decode("hex")) for x in btc.deserialize( |
||||||
|
txs)["ins"][index]["txinwitness"]]) + sigmsg |
||||||
|
sigs.append(base64.b64encode(sigmsg)) |
||||||
|
return (True, sigs) |
||||||
|
|
||||||
|
def verify_unsigned_tx(self, txd, offerinfo): |
||||||
|
tx_utxo_set = set(ins['outpoint']['hash'] + ':' + str( |
||||||
|
ins['outpoint']['index']) for ins in txd['ins']) |
||||||
|
|
||||||
|
utxos = offerinfo["utxos"] |
||||||
|
cjaddr = offerinfo["cjaddr"] |
||||||
|
changeaddr = offerinfo["changeaddr"] |
||||||
|
amount = offerinfo["amount"] |
||||||
|
cjfee = offerinfo["offer"]["cjfee"] |
||||||
|
txfee = offerinfo["offer"]["txfee"] |
||||||
|
ordertype = offerinfo["offer"]["ordertype"] |
||||||
|
my_utxo_set = set(utxos.keys()) |
||||||
|
if not tx_utxo_set.issuperset(my_utxo_set): |
||||||
|
return (False, 'my utxos are not contained') |
||||||
|
|
||||||
|
my_total_in = sum([va['value'] for va in utxos.values()]) |
||||||
|
real_cjfee = calc_cj_fee(ordertype, cjfee, amount) |
||||||
|
expected_change_value = (my_total_in - amount - txfee + real_cjfee) |
||||||
|
jlog.info('potentially earned = {}'.format(real_cjfee - txfee)) |
||||||
|
jlog.debug('mycjaddr, mychange = {}, {}'.format(cjaddr, changeaddr)) |
||||||
|
|
||||||
|
times_seen_cj_addr = 0 |
||||||
|
times_seen_change_addr = 0 |
||||||
|
for outs in txd['outs']: |
||||||
|
addr = btc.script_to_address(outs['script'], get_p2sh_vbyte()) |
||||||
|
if addr == cjaddr: |
||||||
|
times_seen_cj_addr += 1 |
||||||
|
if outs['value'] != amount: |
||||||
|
return (False, 'Wrong cj_amount. I expect ' + str(amount)) |
||||||
|
if addr == changeaddr: |
||||||
|
times_seen_change_addr += 1 |
||||||
|
if outs['value'] != expected_change_value: |
||||||
|
return (False, 'wrong change, i expect ' + str( |
||||||
|
expected_change_value)) |
||||||
|
if times_seen_cj_addr != 1 or times_seen_change_addr != 1: |
||||||
|
fmt = ('cj or change addr not in tx ' |
||||||
|
'outputs once, #cjaddr={}, #chaddr={}').format |
||||||
|
return (False, (fmt(times_seen_cj_addr, times_seen_change_addr))) |
||||||
|
return (True, None) |
||||||
|
|
||||||
|
def modify_orders(self, to_cancel, to_announce): |
||||||
|
jlog.info('modifying orders. to_cancel={}\nto_announce={}'.format( |
||||||
|
to_cancel, to_announce)) |
||||||
|
for oid in to_cancel: |
||||||
|
order = [o for o in self.offerlist if o['oid'] == oid] |
||||||
|
if len(order) == 0: |
||||||
|
fmt = 'didnt cancel order which doesnt exist, oid={}'.format |
||||||
|
jlog.debug(fmt(oid)) |
||||||
|
self.offerlist.remove(order[0]) |
||||||
|
if len(to_announce) > 0: |
||||||
|
for ann in to_announce: |
||||||
|
oldorder_s = [order for order in self.offerlist |
||||||
|
if order['oid'] == ann['oid']] |
||||||
|
if len(oldorder_s) > 0: |
||||||
|
self.offerlist.remove(oldorder_s[0]) |
||||||
|
self.offerlist += to_announce |
||||||
@ -0,0 +1,153 @@ |
|||||||
|
#! /usr/bin/env python |
||||||
|
from __future__ import absolute_import, print_function |
||||||
|
|
||||||
|
import datetime |
||||||
|
import os |
||||||
|
import time |
||||||
|
import abc |
||||||
|
from optparse import OptionParser |
||||||
|
|
||||||
|
from jmclient import (Maker, jm_single, get_network, load_program_config, get_log, |
||||||
|
SegwitWallet, sync_wallet, JMClientProtocolFactory, |
||||||
|
start_reactor) |
||||||
|
|
||||||
|
jlog = get_log() |
||||||
|
|
||||||
|
MAX_MIX_DEPTH = 5 |
||||||
|
|
||||||
|
# is a maker for the purposes of generating a yield from held |
||||||
|
# bitcoins |
||||||
|
class YieldGenerator(Maker): |
||||||
|
__metaclass__ = abc.ABCMeta |
||||||
|
statement_file = os.path.join('logs', 'yigen-statement.csv') |
||||||
|
|
||||||
|
def __init__(self, wallet): |
||||||
|
Maker.__init__(self, wallet) |
||||||
|
self.tx_unconfirm_timestamp = {} |
||||||
|
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 log_statement(self, data): |
||||||
|
if 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() |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def create_my_orders(self): |
||||||
|
"""Must generate a set of orders to be displayed |
||||||
|
according to the contents of the wallet + some algo. |
||||||
|
(Note: should be called "create_my_offers") |
||||||
|
""" |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def oid_to_order(self, cjorder, oid, amount): |
||||||
|
"""Must convert an order with an offer/order id |
||||||
|
into a set of utxos to fill the order. |
||||||
|
Also provides the output addresses for the Taker. |
||||||
|
""" |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def on_tx_unconfirmed(self, cjorder, txid, removed_utxos): |
||||||
|
"""Performs action on receipt of transaction into the |
||||||
|
mempool in the blockchain instance (e.g. announcing orders) |
||||||
|
""" |
||||||
|
|
||||||
|
@abc.abstractmethod |
||||||
|
def on_tx_confirmed(self, cjorder, confirmations, txid): |
||||||
|
"""Performs actions on receipt of 1st confirmation of |
||||||
|
a transaction into a block (e.g. announce orders) |
||||||
|
""" |
||||||
|
|
||||||
|
|
||||||
|
def ygmain(ygclass, txfee=1000, cjfee_a=200, cjfee_r=0.002, ordertype='swreloffer', |
||||||
|
nickserv_password='', minsize=100000, gaplimit=6): |
||||||
|
import sys |
||||||
|
|
||||||
|
parser = OptionParser(usage='usage: %prog [options] [wallet file]') |
||||||
|
parser.add_option('-o', '--ordertype', action='store', type='string', |
||||||
|
dest='ordertype', default=ordertype, |
||||||
|
help='type of order; can be either reloffer or absoffer') |
||||||
|
parser.add_option('-t', '--txfee', action='store', type='int', |
||||||
|
dest='txfee', default=txfee, |
||||||
|
help='minimum miner fee in satoshis') |
||||||
|
parser.add_option('-c', '--cjfee', action='store', type='string', |
||||||
|
dest='cjfee', default='', |
||||||
|
help='requested coinjoin fee in satoshis or proportion') |
||||||
|
parser.add_option('-p', '--password', action='store', type='string', |
||||||
|
dest='password', default=nickserv_password, |
||||||
|
help='irc nickserv password') |
||||||
|
parser.add_option('-s', '--minsize', action='store', type='int', |
||||||
|
dest='minsize', default=minsize, |
||||||
|
help='minimum coinjoin size in satoshis') |
||||||
|
parser.add_option('-g', '--gap-limit', action='store', type="int", |
||||||
|
dest='gaplimit', default=gaplimit, |
||||||
|
help='gap limit for wallet, default='+str(gaplimit)) |
||||||
|
parser.add_option('--fast', |
||||||
|
action='store_true', |
||||||
|
dest='fastsync', |
||||||
|
default=False, |
||||||
|
help=('choose to do fast wallet sync, only for Core and ' |
||||||
|
'only for previously synced wallet')) |
||||||
|
(options, args) = parser.parse_args() |
||||||
|
if len(args) < 1: |
||||||
|
parser.error('Needs a wallet') |
||||||
|
sys.exit(0) |
||||||
|
wallet_name = args[0] |
||||||
|
ordertype = options.ordertype |
||||||
|
txfee = options.txfee |
||||||
|
if ordertype == 'swreloffer': |
||||||
|
if options.cjfee != '': |
||||||
|
cjfee_r = options.cjfee |
||||||
|
# minimum size is such that you always net profit at least 20% |
||||||
|
#of the miner fee |
||||||
|
minsize = max(int(1.2 * txfee / float(cjfee_r)), options.minsize) |
||||||
|
elif ordertype == 'swabsoffer': |
||||||
|
if options.cjfee != '': |
||||||
|
cjfee_a = int(options.cjfee) |
||||||
|
minsize = options.minsize |
||||||
|
else: |
||||||
|
parser.error('You specified an incorrect order type which ' +\ |
||||||
|
'can be either reloffer or absoffer') |
||||||
|
sys.exit(0) |
||||||
|
nickserv_password = options.password |
||||||
|
|
||||||
|
load_program_config() |
||||||
|
if not os.path.exists(os.path.join('wallets', wallet_name)): |
||||||
|
wallet = SegwitWallet(wallet_name, None, max_mix_depth=MAX_MIX_DEPTH, |
||||||
|
gaplimit=options.gaplimit) |
||||||
|
else: |
||||||
|
while True: |
||||||
|
try: |
||||||
|
pwd = get_password("Enter wallet decryption passphrase: ") |
||||||
|
wallet = SegwitWallet(wallet_name, pwd, |
||||||
|
max_mix_depth=MAX_MIX_DEPTH, |
||||||
|
gaplimit=options.gaplimit) |
||||||
|
except WalletError: |
||||||
|
print("Wrong password, try again.") |
||||||
|
continue |
||||||
|
except Exception as e: |
||||||
|
print("Failed to load wallet, error message: " + repr(e)) |
||||||
|
sys.exit(0) |
||||||
|
break |
||||||
|
sync_wallet(wallet, fast=options.fastsync) |
||||||
|
|
||||||
|
maker = ygclass(wallet, [options.txfee, cjfee_a, cjfee_r, |
||||||
|
options.ordertype, options.minsize]) |
||||||
|
jlog.info('starting yield generator') |
||||||
|
clientfactory = JMClientProtocolFactory(maker, proto_type="MAKER") |
||||||
|
nodaemon = jm_single().config.getint("DAEMON", "no_daemon") |
||||||
|
daemon = True if nodaemon == 1 else False |
||||||
|
start_reactor(jm_single().config.get("DAEMON", "daemon_host"), |
||||||
|
jm_single().config.getint("DAEMON", "daemon_port"), |
||||||
|
clientfactory, daemon=daemon) |
||||||
|
|
||||||
@ -0,0 +1,137 @@ |
|||||||
|
#! /usr/bin/env python |
||||||
|
from __future__ import absolute_import, print_function |
||||||
|
|
||||||
|
import datetime |
||||||
|
import os |
||||||
|
import time |
||||||
|
|
||||||
|
from jmclient import YieldGenerator, ygmain, get_log, jm_single, calc_cj_fee |
||||||
|
txfee = 1000 |
||||||
|
cjfee_a = 200 |
||||||
|
cjfee_r = '0.0002' |
||||||
|
ordertype = 'swreloffer' #'swreloffer' or 'swabsoffer' |
||||||
|
nickserv_password = '' |
||||||
|
max_minsize = 100000 |
||||||
|
gaplimit = 6 |
||||||
|
|
||||||
|
jlog = get_log() |
||||||
|
|
||||||
|
# is a maker for the purposes of generating a yield from held |
||||||
|
# bitcoins, offering from the maximum mixdepth and trying to offer |
||||||
|
# the largest amount within the constraints of mixing depth isolation. |
||||||
|
# It will often (but not always) reannounce orders after transactions, |
||||||
|
# thus is somewhat suboptimal in giving more information to spies. |
||||||
|
class YieldGeneratorBasic(YieldGenerator): |
||||||
|
|
||||||
|
def __init__(self, wallet, offerconfig): |
||||||
|
self.txfee, self.cjfee_a, self.cjfee_r, self.ordertype, self.minsize \ |
||||||
|
= offerconfig |
||||||
|
super(YieldGeneratorBasic,self).__init__(wallet) |
||||||
|
|
||||||
|
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: |
||||||
|
jlog.error('do not have any coins left') |
||||||
|
return [] |
||||||
|
|
||||||
|
# print mix_balance |
||||||
|
max_mix = max(mix_balance, key=mix_balance.get) |
||||||
|
f = '0' |
||||||
|
if self.ordertype == 'swreloffer': |
||||||
|
f = self.cjfee_r |
||||||
|
#minimum size bumped if necessary such that you always profit |
||||||
|
#least 50% of the miner fee |
||||||
|
self.minsize = max(int(1.5 * self.txfee / float(self.cjfee_r)), |
||||||
|
max_minsize) |
||||||
|
elif self.ordertype == 'swabsoffer': |
||||||
|
f = str(self.txfee + self.cjfee_a) |
||||||
|
order = {'oid': 0, |
||||||
|
'ordertype': self.ordertype, |
||||||
|
'minsize': self.minsize, |
||||||
|
'maxsize': mix_balance[max_mix] - max( |
||||||
|
jm_single().DUST_THRESHOLD,txfee), |
||||||
|
'txfee': self.txfee, |
||||||
|
'cjfee': f} |
||||||
|
|
||||||
|
# sanity check |
||||||
|
assert order['minsize'] >= 0 |
||||||
|
assert order['maxsize'] > 0 |
||||||
|
if order['minsize'] > order['maxsize']: |
||||||
|
jlog.info('minsize (' + str(order['minsize']) + ') > maxsize (' + str( |
||||||
|
order['maxsize']) + ')') |
||||||
|
return [] |
||||||
|
|
||||||
|
return [order] |
||||||
|
|
||||||
|
def oid_to_order(self, offer, amount): |
||||||
|
total_amount = amount + offer["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] |
||||||
|
if not filtered_mix_balance: |
||||||
|
return None, None, None |
||||||
|
jlog.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] |
||||||
|
jlog.info('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(offer["ordertype"], offer["cjfee"], amount) |
||||||
|
change_value = my_total_in - amount - offer["txfee"] + real_cjfee |
||||||
|
if change_value <= jm_single().DUST_THRESHOLD: |
||||||
|
jlog.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: |
||||||
|
jlog.info('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, offer, txid, removed_utxos): |
||||||
|
self.tx_unconfirm_timestamp[offer["cjaddr"]] = int(time.time()) |
||||||
|
# if the balance of the highest-balance mixing depth change then |
||||||
|
# reannounce it |
||||||
|
oldoffer = self.offerlist[0] if len(self.offerlist) > 0 else None |
||||||
|
newoffers = self.create_my_orders() |
||||||
|
if len(newoffers) == 0: |
||||||
|
return [0], [] # cancel old order |
||||||
|
if oldoffer: |
||||||
|
if oldoffer['maxsize'] == newoffers[0]['maxsize']: |
||||||
|
return [], [] # change nothing |
||||||
|
# announce new order, replacing the old order |
||||||
|
return [], [newoffers[0]] |
||||||
|
|
||||||
|
def on_tx_confirmed(self, offer, confirmations, txid): |
||||||
|
if offer["cjaddr"] in self.tx_unconfirm_timestamp: |
||||||
|
confirm_time = int(time.time()) - self.tx_unconfirm_timestamp[ |
||||||
|
offer["cjaddr"]] |
||||||
|
else: |
||||||
|
confirm_time = 0 |
||||||
|
timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S") |
||||||
|
real_cjfee = calc_cj_fee(offer["offer"]["ordertype"], |
||||||
|
offer["offer"]["cjfee"], offer["amount"]) |
||||||
|
self.log_statement([timestamp, offer["amount"], len( |
||||||
|
offer["utxos"]), sum([av['value'] for av in offer["utxos"].values( |
||||||
|
)]), real_cjfee, real_cjfee - offer["offer"]["txfee"], round( |
||||||
|
confirm_time / 60.0, 2), '']) |
||||||
|
return self.on_tx_unconfirmed(offer, txid, None) |
||||||
|
|
||||||
|
if __name__ == "__main__": |
||||||
|
ygmain(YieldGeneratorBasic, txfee=txfee, cjfee_a=cjfee_a, |
||||||
|
cjfee_r=cjfee_r, ordertype=ordertype, |
||||||
|
nickserv_password=nickserv_password, |
||||||
|
minsize=max_minsize, gaplimit=gaplimit) |
||||||
|
print('done') |
||||||
Loading…
Reference in new issue