diff --git a/src/cryptoadvance/specter/controller.py b/src/cryptoadvance/specter/controller.py index 9e382efb07..545254eb0e 100644 --- a/src/cryptoadvance/specter/controller.py +++ b/src/cryptoadvance/specter/controller.py @@ -1459,6 +1459,10 @@ def btc2sat(value): @app.template_filter('feerate') def feerate(value): value = float(value)*1e8 + # workaround for minimal fee rate + # because 1.01 doesn't look nice + if value <= 1.02: + value = 1 return "{:,.2f}".format(value).rstrip("0").rstrip(".") @app.template_filter('btcunitamount') diff --git a/src/cryptoadvance/specter/wallet.py b/src/cryptoadvance/specter/wallet.py index a500df32d4..a1ec3730f5 100644 --- a/src/cryptoadvance/specter/wallet.py +++ b/src/cryptoadvance/specter/wallet.py @@ -22,6 +22,9 @@ class Wallet(): IMPORT_KEYPOOL = 300 # a gap of 20 addresses is what many wallets do GAP_LIMIT = 20 + # minimal fee rate is slightly above 1 sat/vbyte + # to avoid rounding errors + MIN_FEE_RATE = 1.01 def __init__( self, name, @@ -696,8 +699,10 @@ def labels(self): def createpsbt(self, addresses:[str], amounts:[float], subtract:bool=False, subtract_from:int=0, fee_rate:float=1.0, selected_coins=[], readonly=False): """ - fee_rate: in sat/B or BTC/kB. Default (None) bitcoin core sets feeRate automatically. + fee_rate: in sat/B or BTC/kB. If set to 0 Bitcoin Core sets feeRate automatically. """ + if fee_rate > 0 and fee_rate < self.MIN_FEE_RATE: + fee_rate = self.MIN_FEE_RATE if self.full_available_balance < sum(amounts): raise SpecterError('The wallet does not have sufficient funds to make the transaction.') @@ -737,8 +742,9 @@ def createpsbt(self, addresses:[str], amounts:[float], subtract:bool=False, subt self.setlabel(self.change_address, "Change #{}".format(self.change_index)) - # bitcoin core needs us to convert sat/B to BTC/kB - options["feeRate"] = round((fee_rate * 1000) / 1e8, 8) + if fee_rate > 0: + # bitcoin core needs us to convert sat/B to BTC/kB + options["feeRate"] = round((fee_rate * 1000) / 1e8, 8) # don't reuse change addresses - use getrawchangeaddress instead r = self.rpc.walletcreatefundedpsbt( @@ -753,16 +759,13 @@ def createpsbt(self, addresses:[str], amounts:[float], subtract:bool=False, subt psbt = self.rpc.decodepsbt(b64psbt) if fee_rate > 0.0: psbt_fees_sats = int(psbt['fee'] * 1e8) - tx_full_size = psbt['tx']['vsize'] - for _ in psbt['inputs']: - # size is weight / 4 - tx_full_size += self.weight_per_input/4 - tx_full_size = ceil(tx_full_size) + # estimate final size: add weight of inputs + tx_full_size = ceil(psbt['tx']['vsize'] + + len(psbt["inputs"])*self.weight_per_input/4) adjusted_fee_rate = fee_rate * ( fee_rate / (psbt_fees_sats / psbt['tx']['vsize']) ) * (tx_full_size / psbt['tx']['vsize']) - # add 0.5 to make sure we round up - options["feeRate"] = '%.8f' % round((adjusted_fee_rate * 1000 + 0.5) / 1e8, 8) + options["feeRate"] = '%.8f' % round((adjusted_fee_rate * 1000) / 1e8, 8) r = self.rpc.walletcreatefundedpsbt( extra_inputs, # inputs [{addresses[i]: amounts[i]} for i in range(len(addresses))], # output @@ -773,8 +776,11 @@ def createpsbt(self, addresses:[str], amounts:[float], subtract:bool=False, subt b64psbt = r["psbt"] psbt = self.rpc.decodepsbt(b64psbt) - psbt["tx_full_size"] = tx_full_size psbt["fee_rate"] = options["feeRate"] + # estimate full size + tx_full_size = ceil(psbt['tx']['vsize'] + + len(psbt["inputs"])*self.weight_per_input/4) + psbt["tx_full_size"] = tx_full_size psbt['base64'] = b64psbt psbt["amount"] = amounts @@ -888,12 +894,12 @@ def weight_per_input(self): input_size += 75 # max sig size if not self.recv_descriptor.startswith('wsh'): - # P2SH scriptsig: 00 20 <32-byte-hash> - input_size += 34 * 4 + # P2SH scriptsig: 22 00 20 <32-byte-hash> + input_size += 35 * 4 return input_size # else: single-sig if self.recv_descriptor.startswith('wpkh'): # pubkey, signature return 75 + 34 - # pubkey, signature, 4* P2SH: 00 14 20-byte-hash - return 75 + 34 + 22 * 4 + # pubkey, signature, 4* P2SH: 16 00 14 20-byte-hash + return 75 + 34 + 23 * 4