diff --git a/jmbitcoin/jmbitcoin/__init__.py b/jmbitcoin/jmbitcoin/__init__.py index 2a530a3..c4a8d32 100644 --- a/jmbitcoin/jmbitcoin/__init__.py +++ b/jmbitcoin/jmbitcoin/__init__.py @@ -7,4 +7,5 @@ from jmbitcoin.secp256k1_transaction import * from jmbitcoin.secp256k1_deterministic import * from jmbitcoin.btscript import * from jmbitcoin.bech32 import * +from jmbitcoin.amount import * diff --git a/jmbitcoin/jmbitcoin/amount.py b/jmbitcoin/jmbitcoin/amount.py new file mode 100644 index 0000000..4ea8289 --- /dev/null +++ b/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) + diff --git a/jmclient/jmclient/taker_utils.py b/jmclient/jmclient/taker_utils.py index 2592668..dae40c4 100644 --- a/jmclient/jmclient/taker_utils.py +++ b/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,\ schedule_to_text 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() """ @@ -74,9 +74,9 @@ def direct_send(wallet_service, amount, mixdepth, destaddr, answeryes=False, outs.append({"value": changeval, "address": change_addr}) #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: - 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) log.info("Got signed transaction:\n") 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(tx) 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 accept_callback: if input('Would you like to push to the network? (y/n):')[0] != 'y': diff --git a/jmclient/jmclient/wallet_utils.py b/jmclient/jmclient/wallet_utils.py index ecc9376..dc78ed2 100644 --- a/jmclient/jmclient/wallet_utils.py +++ b/jmclient/jmclient/wallet_utils.py @@ -636,24 +636,20 @@ def wallet_fetch_history(wallet, options): def s(): 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): if sat == 0: return "N/A " else: - return '%.8f'%(sat/1e8) + return btc.sat_to_str(sat) def skip_n1(v): return '% 2s'%(str(v)) if v != -1 else ' #' 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, total_fees, utxo_count, mixdepth_src, mixdepth_dst, txid): data = [index, datetime.fromtimestamp(time).strftime("%Y-%m-%d %H:%M"), - tx_type, sat_to_str(amount), sat_to_str_p(delta), - sat_to_str(balance), skip_n1(cj_n), sat_to_str_na(total_fees), + tx_type, btc.sat_to_str(amount), btc.sat_to_str_p(delta), + 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)] if options.verbosity % 2 == 0: data += [txid] jmprint(s().join(map('"{}"'.format, data)), "info") @@ -870,8 +866,8 @@ def wallet_fetch_history(wallet, options): include_disabled=True).values()) if balance + unconfirmed_balance != total_wallet_balance: jmprint(('BUG ERROR: wallet balance (%s) does not match balance from ' + - 'history (%s)') % (sat_to_str(total_wallet_balance), - sat_to_str(balance))) + 'history (%s)') % (btc.sat_to_str(total_wallet_balance), + btc.sat_to_str(balance))) wallet_utxo_count = sum(map(len, wallet.get_utxos_by_mixdepth( include_disabled=True, hexfmt=False).values())) 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)) 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 return '' diff --git a/scripts/joinmarket-qt.py b/scripts/joinmarket-qt.py index 433325b..110604a 100644 --- a/scripts/joinmarket-qt.py +++ b/scripts/joinmarket-qt.py @@ -27,7 +27,6 @@ import sys, datetime, os, logging import platform, json, threading, time import qrcode -from decimal import Decimal from PySide2 import QtCore from PySide2.QtGui import * @@ -83,8 +82,6 @@ from qtsupport import ScheduleWizard, TumbleRestartWizard, config_tips,\ from twisted.internet import task -def satoshis_to_amt_str(x): - return str(Decimal(x)/Decimal('1e8')) + " BTC" log = get_log() @@ -124,13 +121,13 @@ def checkAddress(parent, addr): def getSettingsWidgets(): results = [] 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', 'How many other parties to send to; if you enter 4\n' + ', there will be 5 participants, including you.\n' + 'Enter 0 to send direct without coinjoin.', '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,' + ' spending all the coins \nin the given mixdepth.'] sT = [str, int, int, float] @@ -591,9 +588,9 @@ class SpendTab(QWidget): note the callback includes the full prettified transaction, but currently not printing it for space reasons. """ - mbinfo = ["Sending " + satoshis_to_amt_str(amount) + ",", + mbinfo = ["Sending " + btc.amount_to_str(amount) + ",", "to: " + destaddr + ",", - "Fee: " + satoshis_to_amt_str(fee) + ".", + "Fee: " + btc.amount_to_str(fee) + ".", "Accept?"] reply = JMQtMessageBox(self, '\n'.join([m + '
' for m in mbinfo]), mbtype='question', title="Direct send") @@ -614,7 +611,7 @@ class SpendTab(QWidget): destaddr = str(self.widgets[0][1].text()) #convert from bitcoins (enforced by QDoubleValidator) to satoshis 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()) mixdepth = int(self.widgets[2][1].text()) if makercount == 0: @@ -724,11 +721,9 @@ class SpendTab(QWidget): return offers, total_cj_fee = offers_fee 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.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(" ") mbinfo.append("Counterparties chosen:") @@ -746,8 +741,8 @@ class SpendTab(QWidget): return False mbinfo.append(k + ', ' + str(o['oid']) + ', ' + str( display_fee)) - mbinfo.append('Total coinjoin fee = ' + str(total_cj_fee) + - ' satoshis, or ' + str(float('%.3g' % ( + mbinfo.append('Total coinjoin fee = ' + btc.amount_to_str(total_cj_fee) + + ', or ' + str(float('%.3g' % ( 100.0 * total_fee_pc))) + '%') title = 'Check Transaction' if total_fee_pc * 100 > jm_single().config.getint("GUI", @@ -842,7 +837,7 @@ class SpendTab(QWidget): def persistTxToHistory(self, addr, amt, txid): #persist the transaction to history 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( ).strftime("%Y/%m/%d %H:%M:%S")])).encode('utf-8')) f.write(b'\n') #TODO: Windows diff --git a/scripts/sendpayment.py b/scripts/sendpayment.py index 3f5eb87..12e6808 100644 --- a/scripts/sendpayment.py +++ b/scripts/sendpayment.py @@ -22,6 +22,7 @@ from twisted.python.log import startLogging from jmbase.support import get_log, set_logging_level, jmprint from cli_options import get_sendpayment_parser, get_max_cj_fee_values, \ check_regtest +import jmbitcoin as btc log = get_log() @@ -64,9 +65,7 @@ def main(): #of a single transaction sweeping = False if options.schedule == '': - #note that sendpayment doesn't support fractional amounts, fractions throw - #here. - amount = int(args[1]) + amount = btc.amount_to_sat(args[1]) if amount == 0: sweeping = True destaddr = args[2] @@ -123,7 +122,7 @@ def main(): if not options.p2ep and not options.pickorders and options.makercount != 0: maxcjfee = get_max_cj_fee_values(jm_single().config, options) 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')