Browse Source

Allow both BTC and sat amounts for single send / CJ

master
Kristaps Kaupe 6 years ago
parent
commit
b2e4308a90
No known key found for this signature in database
GPG Key ID: D47B1B4232B55437
  1. 1
      jmbitcoin/jmbitcoin/__init__.py
  2. 46
      jmbitcoin/jmbitcoin/amount.py
  3. 8
      jmclient/jmclient/taker_utils.py
  4. 18
      jmclient/jmclient/wallet_utils.py
  5. 23
      scripts/joinmarket-qt.py
  6. 7
      scripts/sendpayment.py

1
jmbitcoin/jmbitcoin/__init__.py

@ -7,4 +7,5 @@ from jmbitcoin.secp256k1_transaction import *
from jmbitcoin.secp256k1_deterministic import * from jmbitcoin.secp256k1_deterministic import *
from jmbitcoin.btscript import * from jmbitcoin.btscript import *
from jmbitcoin.bech32 import * from jmbitcoin.bech32 import *
from jmbitcoin.amount import *

46
jmbitcoin/jmbitcoin/amount.py

@ -0,0 +1,46 @@
from decimal import Decimal
def btc_to_sat(btc):
return int(Decimal(btc) * Decimal('1e8'))
def sat_to_btc(sat):
return Decimal(sat) / Decimal('1e8')
# 1 = 0.00000001 BTC = 1sat
# 1sat = 0.00000001 BTC = 1sat
# 1.123sat = 0.00000001 BTC = 1sat
# 0.00000001 = 0.00000001 BTC = 1sat
# 0.00000001btc = 0.00000001 BTC = 1sat
# 1.00000000 = 1.00000000 BTC = 100000000sat
# 1.12300000sat = 0.00000001 BTC = 1sat
# 1btc = 1.00000000 BTC = 10000000sat
def amount_to_sat(amount_str):
amount_str = str(amount_str)
if amount_str.lower().endswith("btc"):
return int(btc_to_sat(amount_str[:-3]))
elif amount_str.lower().endswith("sat"):
return int(Decimal(amount_str[:-3]))
elif "." in amount_str:
return int(btc_to_sat(amount_str))
else:
return int(Decimal(amount_str))
def amount_to_btc(amount_str):
return amount_to_sat(amount_str) / Decimal('1e8')
def amount_to_sat_str(amount_str):
return str(amount_to_sat(amount_str)) + " sat"
def amount_to_btc_str(amount_str):
return str(amount_to_btc(amount_str)) + " BTC"
def amount_to_str(amount_str):
return amount_to_btc_str(amount_str) + " (" + amount_to_sat_str(amount_str) + ")"
def sat_to_str(sat):
return '%.8f' % sat_to_btc(sat)
def sat_to_str_p(sat):
return '%+.8f' % sat_to_btc(sat)

8
jmclient/jmclient/taker_utils.py

@ -12,7 +12,7 @@ from .configure import jm_single, validate_address
from .schedule import human_readable_schedule_entry, tweak_tumble_schedule,\ from .schedule import human_readable_schedule_entry, tweak_tumble_schedule,\
schedule_to_text schedule_to_text
from .wallet import BaseWallet, estimate_tx_fee from .wallet import BaseWallet, estimate_tx_fee
from jmbitcoin import deserialize, mktx, serialize, txhash from jmbitcoin import deserialize, mktx, serialize, txhash, amount_to_str
log = get_log() log = get_log()
""" """
@ -74,9 +74,9 @@ def direct_send(wallet_service, amount, mixdepth, destaddr, answeryes=False,
outs.append({"value": changeval, "address": change_addr}) outs.append({"value": changeval, "address": change_addr})
#Now ready to construct transaction #Now ready to construct transaction
log.info("Using a fee of : " + str(fee_est) + " satoshis.") log.info("Using a fee of : " + amount_to_str(fee_est) + ".")
if amount != 0: if amount != 0:
log.info("Using a change value of: " + str(changeval) + " satoshis.") log.info("Using a change value of: " + amount_to_str(changeval) + ".")
txsigned = sign_tx(wallet_service, mktx(list(utxos.keys()), outs), utxos) txsigned = sign_tx(wallet_service, mktx(list(utxos.keys()), outs), utxos)
log.info("Got signed transaction:\n") log.info("Got signed transaction:\n")
log.info(pformat(txsigned)) log.info(pformat(txsigned))
@ -84,7 +84,7 @@ def direct_send(wallet_service, amount, mixdepth, destaddr, answeryes=False,
log.info("In serialized form (for copy-paste):") log.info("In serialized form (for copy-paste):")
log.info(tx) log.info(tx)
actual_amount = amount if amount != 0 else total_inputs_val - fee_est actual_amount = amount if amount != 0 else total_inputs_val - fee_est
log.info("Sends: " + str(actual_amount) + " satoshis to address: " + destaddr) log.info("Sends: " + amount_to_str(actual_amount) + " to address: " + destaddr)
if not answeryes: if not answeryes:
if not accept_callback: if not accept_callback:
if input('Would you like to push to the network? (y/n):')[0] != 'y': if input('Would you like to push to the network? (y/n):')[0] != 'y':

18
jmclient/jmclient/wallet_utils.py

@ -636,24 +636,20 @@ def wallet_fetch_history(wallet, options):
def s(): def s():
return ',' if options.csv else ' ' return ',' if options.csv else ' '
def sat_to_str(sat):
return '%.8f'%(sat/1e8)
def sat_to_str_p(sat):
return '%+.8f'%(sat/1e8)
def sat_to_str_na(sat): def sat_to_str_na(sat):
if sat == 0: if sat == 0:
return "N/A " return "N/A "
else: else:
return '%.8f'%(sat/1e8) return btc.sat_to_str(sat)
def skip_n1(v): def skip_n1(v):
return '% 2s'%(str(v)) if v != -1 else ' #' return '% 2s'%(str(v)) if v != -1 else ' #'
def skip_n1_btc(v): def skip_n1_btc(v):
return sat_to_str(v) if v != -1 else '#' + ' '*10 return btc.sat_to_str(v) if v != -1 else '#' + ' '*10
def print_row(index, time, tx_type, amount, delta, balance, cj_n, def print_row(index, time, tx_type, amount, delta, balance, cj_n,
total_fees, utxo_count, mixdepth_src, mixdepth_dst, txid): total_fees, utxo_count, mixdepth_src, mixdepth_dst, txid):
data = [index, datetime.fromtimestamp(time).strftime("%Y-%m-%d %H:%M"), data = [index, datetime.fromtimestamp(time).strftime("%Y-%m-%d %H:%M"),
tx_type, sat_to_str(amount), sat_to_str_p(delta), tx_type, btc.sat_to_str(amount), btc.sat_to_str_p(delta),
sat_to_str(balance), skip_n1(cj_n), sat_to_str_na(total_fees), btc.sat_to_str(balance), skip_n1(cj_n), sat_to_str_na(total_fees),
'% 3d' % utxo_count, skip_n1(mixdepth_src), skip_n1(mixdepth_dst)] '% 3d' % utxo_count, skip_n1(mixdepth_src), skip_n1(mixdepth_dst)]
if options.verbosity % 2 == 0: data += [txid] if options.verbosity % 2 == 0: data += [txid]
jmprint(s().join(map('"{}"'.format, data)), "info") jmprint(s().join(map('"{}"'.format, data)), "info")
@ -870,8 +866,8 @@ def wallet_fetch_history(wallet, options):
include_disabled=True).values()) include_disabled=True).values())
if balance + unconfirmed_balance != total_wallet_balance: if balance + unconfirmed_balance != total_wallet_balance:
jmprint(('BUG ERROR: wallet balance (%s) does not match balance from ' + jmprint(('BUG ERROR: wallet balance (%s) does not match balance from ' +
'history (%s)') % (sat_to_str(total_wallet_balance), 'history (%s)') % (btc.sat_to_str(total_wallet_balance),
sat_to_str(balance))) btc.sat_to_str(balance)))
wallet_utxo_count = sum(map(len, wallet.get_utxos_by_mixdepth( wallet_utxo_count = sum(map(len, wallet.get_utxos_by_mixdepth(
include_disabled=True, hexfmt=False).values())) include_disabled=True, hexfmt=False).values()))
if utxo_count + unconfirmed_utxo_count != wallet_utxo_count: if utxo_count + unconfirmed_utxo_count != wallet_utxo_count:
@ -879,7 +875,7 @@ def wallet_fetch_history(wallet, options):
'history (%s)') % (wallet_utxo_count, utxo_count)) 'history (%s)') % (wallet_utxo_count, utxo_count))
if unconfirmed_balance != 0: if unconfirmed_balance != 0:
jmprint('unconfirmed balance change = %s BTC' % sat_to_str(unconfirmed_balance)) jmprint('unconfirmed balance change = %s BTC' % btc.sat_to_str(unconfirmed_balance))
# wallet-tool.py prints return value, so return empty string instead of None here # wallet-tool.py prints return value, so return empty string instead of None here
return '' return ''

23
scripts/joinmarket-qt.py

@ -27,7 +27,6 @@ import sys, datetime, os, logging
import platform, json, threading, time import platform, json, threading, time
import qrcode import qrcode
from decimal import Decimal
from PySide2 import QtCore from PySide2 import QtCore
from PySide2.QtGui import * from PySide2.QtGui import *
@ -83,8 +82,6 @@ from qtsupport import ScheduleWizard, TumbleRestartWizard, config_tips,\
from twisted.internet import task from twisted.internet import task
def satoshis_to_amt_str(x):
return str(Decimal(x)/Decimal('1e8')) + " BTC"
log = get_log() log = get_log()
@ -124,13 +121,13 @@ def checkAddress(parent, addr):
def getSettingsWidgets(): def getSettingsWidgets():
results = [] results = []
sN = ['Recipient address', 'Number of counterparties', 'Mixdepth', sN = ['Recipient address', 'Number of counterparties', 'Mixdepth',
'Amount in bitcoins (BTC)'] 'Amount (BTC or sat)']
sH = ['The address you want to send the payment to', sH = ['The address you want to send the payment to',
'How many other parties to send to; if you enter 4\n' + 'How many other parties to send to; if you enter 4\n' +
', there will be 5 participants, including you.\n' + ', there will be 5 participants, including you.\n' +
'Enter 0 to send direct without coinjoin.', 'Enter 0 to send direct without coinjoin.',
'The mixdepth of the wallet to send the payment from', 'The mixdepth of the wallet to send the payment from',
'The amount IN BITCOINS to send.\n' + 'The amount to send, either BTC (if contains dot) or satoshis.\n' +
'If you enter 0, a SWEEP transaction\nwill be performed,' + 'If you enter 0, a SWEEP transaction\nwill be performed,' +
' spending all the coins \nin the given mixdepth.'] ' spending all the coins \nin the given mixdepth.']
sT = [str, int, int, float] sT = [str, int, int, float]
@ -591,9 +588,9 @@ class SpendTab(QWidget):
note the callback includes the full prettified transaction, note the callback includes the full prettified transaction,
but currently not printing it for space reasons. but currently not printing it for space reasons.
""" """
mbinfo = ["Sending " + satoshis_to_amt_str(amount) + ",", mbinfo = ["Sending " + btc.amount_to_str(amount) + ",",
"to: " + destaddr + ",", "to: " + destaddr + ",",
"Fee: " + satoshis_to_amt_str(fee) + ".", "Fee: " + btc.amount_to_str(fee) + ".",
"Accept?"] "Accept?"]
reply = JMQtMessageBox(self, '\n'.join([m + '<p>' for m in mbinfo]), reply = JMQtMessageBox(self, '\n'.join([m + '<p>' for m in mbinfo]),
mbtype='question', title="Direct send") mbtype='question', title="Direct send")
@ -614,7 +611,7 @@ class SpendTab(QWidget):
destaddr = str(self.widgets[0][1].text()) destaddr = str(self.widgets[0][1].text())
#convert from bitcoins (enforced by QDoubleValidator) to satoshis #convert from bitcoins (enforced by QDoubleValidator) to satoshis
btc_amount_str = self.widgets[3][1].text() btc_amount_str = self.widgets[3][1].text()
amount = int(Decimal(btc_amount_str) * Decimal('1e8')) amount = btc.amount_to_sat(btc_amount_str)
makercount = int(self.widgets[1][1].text()) makercount = int(self.widgets[1][1].text())
mixdepth = int(self.widgets[2][1].text()) mixdepth = int(self.widgets[2][1].text())
if makercount == 0: if makercount == 0:
@ -724,11 +721,9 @@ class SpendTab(QWidget):
return return
offers, total_cj_fee = offers_fee offers, total_cj_fee = offers_fee
total_fee_pc = 1.0 * total_cj_fee / self.taker.cjamount total_fee_pc = 1.0 * total_cj_fee / self.taker.cjamount
#Note this will be a new value if sweep, else same as previously entered
btc_amount_str = satoshis_to_amt_str(self.taker.cjamount)
mbinfo = [] mbinfo = []
mbinfo.append("Sending amount: " + btc_amount_str) mbinfo.append("Sending amount: " + btc.amount_to_str(self.taker.cjamount))
mbinfo.append("to address: " + self.taker.my_cj_addr) mbinfo.append("to address: " + self.taker.my_cj_addr)
mbinfo.append(" ") mbinfo.append(" ")
mbinfo.append("Counterparties chosen:") mbinfo.append("Counterparties chosen:")
@ -746,8 +741,8 @@ class SpendTab(QWidget):
return False return False
mbinfo.append(k + ', ' + str(o['oid']) + ', ' + str( mbinfo.append(k + ', ' + str(o['oid']) + ', ' + str(
display_fee)) display_fee))
mbinfo.append('Total coinjoin fee = ' + str(total_cj_fee) + mbinfo.append('Total coinjoin fee = ' + btc.amount_to_str(total_cj_fee) +
' satoshis, or ' + str(float('%.3g' % ( ', or ' + str(float('%.3g' % (
100.0 * total_fee_pc))) + '%') 100.0 * total_fee_pc))) + '%')
title = 'Check Transaction' title = 'Check Transaction'
if total_fee_pc * 100 > jm_single().config.getint("GUI", if total_fee_pc * 100 > jm_single().config.getint("GUI",
@ -842,7 +837,7 @@ class SpendTab(QWidget):
def persistTxToHistory(self, addr, amt, txid): def persistTxToHistory(self, addr, amt, txid):
#persist the transaction to history #persist the transaction to history
with open(jm_single().config.get("GUI", "history_file"), 'ab') as f: with open(jm_single().config.get("GUI", "history_file"), 'ab') as f:
f.write((','.join([addr, satoshis_to_amt_str(amt), txid, f.write((','.join([addr, btc.amount_to_btc_str(amt), txid,
datetime.datetime.now( datetime.datetime.now(
).strftime("%Y/%m/%d %H:%M:%S")])).encode('utf-8')) ).strftime("%Y/%m/%d %H:%M:%S")])).encode('utf-8'))
f.write(b'\n') #TODO: Windows f.write(b'\n') #TODO: Windows

7
scripts/sendpayment.py

@ -22,6 +22,7 @@ from twisted.python.log import startLogging
from jmbase.support import get_log, set_logging_level, jmprint from jmbase.support import get_log, set_logging_level, jmprint
from cli_options import get_sendpayment_parser, get_max_cj_fee_values, \ from cli_options import get_sendpayment_parser, get_max_cj_fee_values, \
check_regtest check_regtest
import jmbitcoin as btc
log = get_log() log = get_log()
@ -64,9 +65,7 @@ def main():
#of a single transaction #of a single transaction
sweeping = False sweeping = False
if options.schedule == '': if options.schedule == '':
#note that sendpayment doesn't support fractional amounts, fractions throw amount = btc.amount_to_sat(args[1])
#here.
amount = int(args[1])
if amount == 0: if amount == 0:
sweeping = True sweeping = True
destaddr = args[2] destaddr = args[2]
@ -123,7 +122,7 @@ def main():
if not options.p2ep and not options.pickorders and options.makercount != 0: if not options.p2ep and not options.pickorders and options.makercount != 0:
maxcjfee = get_max_cj_fee_values(jm_single().config, options) maxcjfee = get_max_cj_fee_values(jm_single().config, options)
log.info("Using maximum coinjoin fee limits per maker of {:.4%}, {} " log.info("Using maximum coinjoin fee limits per maker of {:.4%}, {} "
"sat".format(*maxcjfee)) "".format(maxcjfee[0], btc.amount_to_str(maxcjfee[1])))
log.debug('starting sendpayment') log.debug('starting sendpayment')

Loading…
Cancel
Save