Sander van Grieken
3 years ago
5 changed files with 523 additions and 26 deletions
After Width: | Height: | Size: 1.3 KiB |
@ -0,0 +1,195 @@ |
|||
import QtQuick 2.6 |
|||
import QtQuick.Layouts 1.0 |
|||
import QtQuick.Controls 2.14 |
|||
import QtQuick.Controls.Material 2.0 |
|||
|
|||
import org.electrum 1.0 |
|||
|
|||
import "controls" |
|||
|
|||
Dialog { |
|||
id: dialog |
|||
|
|||
property alias address: finalizer.address |
|||
property alias satoshis: finalizer.amount |
|||
property string message |
|||
|
|||
width: parent.width |
|||
height: parent.height |
|||
|
|||
title: qsTr('Confirm Payment') |
|||
|
|||
modal: true |
|||
parent: Overlay.overlay |
|||
Overlay.modal: Rectangle { |
|||
color: "#aa000000" |
|||
} |
|||
|
|||
GridLayout { |
|||
id: layout |
|||
width: parent.width |
|||
height: parent.height |
|||
columns: 2 |
|||
|
|||
Rectangle { |
|||
height: 1 |
|||
Layout.fillWidth: true |
|||
Layout.columnSpan: 2 |
|||
color: Material.accentColor |
|||
} |
|||
|
|||
Label { |
|||
text: qsTr('Amount to send') |
|||
} |
|||
|
|||
RowLayout { |
|||
Layout.fillWidth: true |
|||
Label { |
|||
font.bold: true |
|||
text: Config.formatSats(satoshis, false) |
|||
} |
|||
|
|||
Label { |
|||
text: Config.baseUnit |
|||
color: Material.accentColor |
|||
} |
|||
|
|||
Label { |
|||
id: fiatValue |
|||
Layout.fillWidth: true |
|||
text: Daemon.fx.enabled |
|||
? '(' + Daemon.fx.fiatValue(satoshis, false) + ' ' + Daemon.fx.fiatCurrency + ')' |
|||
: '' |
|||
font.pixelSize: constants.fontSizeMedium |
|||
} |
|||
} |
|||
|
|||
Label { |
|||
text: qsTr('Mining fee') |
|||
} |
|||
|
|||
RowLayout { |
|||
Label { |
|||
id: fee |
|||
text: Config.formatSats(finalizer.fee) |
|||
} |
|||
|
|||
Label { |
|||
text: Config.baseUnit |
|||
color: Material.accentColor |
|||
} |
|||
} |
|||
|
|||
Label { |
|||
text: qsTr('Fee rate') |
|||
} |
|||
|
|||
RowLayout { |
|||
Label { |
|||
id: feeRate |
|||
text: finalizer.feeRate |
|||
} |
|||
|
|||
Label { |
|||
text: 'sat/vB' |
|||
color: Material.accentColor |
|||
} |
|||
} |
|||
|
|||
Label { |
|||
text: qsTr('Target') |
|||
} |
|||
|
|||
Label { |
|||
id: targetdesc |
|||
text: finalizer.target |
|||
} |
|||
|
|||
Slider { |
|||
id: feeslider |
|||
snapMode: Slider.SnapOnRelease |
|||
stepSize: 1 |
|||
from: 0 |
|||
to: finalizer.sliderSteps |
|||
onValueChanged: { |
|||
if (activeFocus) |
|||
finalizer.sliderPos = value |
|||
} |
|||
Component.onCompleted: { |
|||
value = finalizer.sliderPos |
|||
} |
|||
Connections { |
|||
target: finalizer |
|||
function onSliderPosChanged() { |
|||
feeslider.value = finalizer.sliderPos |
|||
} |
|||
} |
|||
} |
|||
|
|||
ComboBox { |
|||
id: target |
|||
textRole: 'text' |
|||
valueRole: 'value' |
|||
model: [ |
|||
{ text: qsTr('ETA'), value: 1 }, |
|||
{ text: qsTr('Mempool'), value: 2 }, |
|||
{ text: qsTr('Static'), value: 0 } |
|||
] |
|||
onCurrentValueChanged: { |
|||
if (activeFocus) |
|||
finalizer.method = currentValue |
|||
} |
|||
Component.onCompleted: { |
|||
currentIndex = indexOfValue(finalizer.method) |
|||
} |
|||
} |
|||
|
|||
InfoTextArea { |
|||
Layout.columnSpan: 2 |
|||
visible: finalizer.warning != '' |
|||
text: finalizer.warning |
|||
iconStyle: InfoTextArea.IconStyle.Warn |
|||
} |
|||
|
|||
CheckBox { |
|||
id: final_cb |
|||
text: qsTr('Final') |
|||
Layout.columnSpan: 2 |
|||
} |
|||
|
|||
Rectangle { |
|||
height: 1 |
|||
Layout.fillWidth: true |
|||
Layout.columnSpan: 2 |
|||
color: Material.accentColor |
|||
} |
|||
|
|||
RowLayout { |
|||
Layout.columnSpan: 2 |
|||
Layout.alignment: Qt.AlignHCenter |
|||
|
|||
Button { |
|||
text: qsTr('Cancel') |
|||
onClicked: dialog.close() |
|||
} |
|||
|
|||
Button { |
|||
text: qsTr('Pay') |
|||
enabled: finalizer.valid |
|||
onClicked: { |
|||
var f_amount = parseFloat(dialog.satoshis) |
|||
if (isNaN(f_amount)) |
|||
return |
|||
var result = Daemon.currentWallet.send_onchain(dialog.address, dialog.satoshis, undefined, false) |
|||
} |
|||
} |
|||
} |
|||
Item { Layout.fillHeight: true; Layout.preferredWidth: 1 } |
|||
} |
|||
|
|||
TxFinalizer { |
|||
id: finalizer |
|||
wallet: Daemon.currentWallet |
|||
onAmountChanged: console.log(amount) |
|||
} |
|||
} |
@ -0,0 +1,228 @@ |
|||
from decimal import Decimal |
|||
|
|||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject |
|||
|
|||
from electrum.logging import get_logger |
|||
from electrum.i18n import _ |
|||
from electrum.transaction import PartialTxOutput |
|||
from electrum.util import NotEnoughFunds, profiler |
|||
|
|||
from .qewallet import QEWallet |
|||
|
|||
class QETxFinalizer(QObject): |
|||
def __init__(self, parent=None): |
|||
super().__init__(parent) |
|||
|
|||
_logger = get_logger(__name__) |
|||
|
|||
_address = '' |
|||
_amount = '' |
|||
_fee = '' |
|||
_feeRate = '' |
|||
_wallet = None |
|||
_valid = False |
|||
_sliderSteps = 0 |
|||
_sliderPos = 0 |
|||
_method = -1 |
|||
_warning = '' |
|||
_target = '' |
|||
config = None |
|||
|
|||
validChanged = pyqtSignal() |
|||
@pyqtProperty(bool, notify=validChanged) |
|||
def valid(self): |
|||
return self._valid |
|||
|
|||
walletChanged = pyqtSignal() |
|||
@pyqtProperty(QEWallet, notify=walletChanged) |
|||
def wallet(self): |
|||
return self._wallet |
|||
|
|||
@wallet.setter |
|||
def wallet(self, wallet: QEWallet): |
|||
if self._wallet != wallet: |
|||
self._wallet = wallet |
|||
self.config = self._wallet.wallet.config |
|||
self.read_config() |
|||
self.walletChanged.emit() |
|||
|
|||
addressChanged = pyqtSignal() |
|||
@pyqtProperty(str, notify=addressChanged) |
|||
def address(self): |
|||
return self._address |
|||
|
|||
@address.setter |
|||
def address(self, address): |
|||
if self._address != address: |
|||
self._address = address |
|||
self.addressChanged.emit() |
|||
|
|||
amountChanged = pyqtSignal() |
|||
@pyqtProperty(str, notify=amountChanged) |
|||
def amount(self): |
|||
return self._amount |
|||
|
|||
@amount.setter |
|||
def amount(self, amount): |
|||
if self._amount != amount: |
|||
self._logger.info('amount = "%s"' % amount) |
|||
self._amount = amount |
|||
self.amountChanged.emit() |
|||
|
|||
feeChanged = pyqtSignal() |
|||
@pyqtProperty(str, notify=feeChanged) |
|||
def fee(self): |
|||
return self._fee |
|||
|
|||
@fee.setter |
|||
def fee(self, fee): |
|||
if self._fee != fee: |
|||
self._fee = fee |
|||
self.feeChanged.emit() |
|||
|
|||
feeRateChanged = pyqtSignal() |
|||
@pyqtProperty(str, notify=feeRateChanged) |
|||
def feeRate(self): |
|||
return self._feeRate |
|||
|
|||
@feeRate.setter |
|||
def feeRate(self, feeRate): |
|||
if self._feeRate != feeRate: |
|||
self._feeRate = feeRate |
|||
self.feeRateChanged.emit() |
|||
|
|||
targetChanged = pyqtSignal() |
|||
@pyqtProperty(str, notify=targetChanged) |
|||
def target(self): |
|||
return self._target |
|||
|
|||
@target.setter |
|||
def target(self, target): |
|||
if self._target != target: |
|||
self._target = target |
|||
self.targetChanged.emit() |
|||
|
|||
warningChanged = pyqtSignal() |
|||
@pyqtProperty(str, notify=warningChanged) |
|||
def warning(self): |
|||
return self._warning |
|||
|
|||
@warning.setter |
|||
def warning(self, warning): |
|||
if self._warning != warning: |
|||
self._warning = warning |
|||
self.warningChanged.emit() |
|||
|
|||
sliderStepsChanged = pyqtSignal() |
|||
@pyqtProperty(int, notify=sliderStepsChanged) |
|||
def sliderSteps(self): |
|||
return self._sliderSteps |
|||
|
|||
sliderPosChanged = pyqtSignal() |
|||
@pyqtProperty(int, notify=sliderPosChanged) |
|||
def sliderPos(self): |
|||
return self._sliderPos |
|||
|
|||
@sliderPos.setter |
|||
def sliderPos(self, sliderPos): |
|||
if self._sliderPos != sliderPos: |
|||
self._sliderPos = sliderPos |
|||
self.save_config() |
|||
self.sliderPosChanged.emit() |
|||
|
|||
methodChanged = pyqtSignal() |
|||
@pyqtProperty(int, notify=methodChanged) |
|||
def method(self): |
|||
return self._method |
|||
|
|||
@method.setter |
|||
def method(self, method): |
|||
if self._method != method: |
|||
self._method = method |
|||
self.update_slider() |
|||
self.methodChanged.emit() |
|||
self.save_config() |
|||
|
|||
def get_method(self): |
|||
dynfees = self._method > 0 |
|||
mempool = self._method == 2 |
|||
return dynfees, mempool |
|||
|
|||
def update_slider(self): |
|||
dynfees, mempool = self.get_method() |
|||
maxp, pos, fee_rate = self.config.get_fee_slider(dynfees, mempool) |
|||
self._sliderSteps = maxp |
|||
self._sliderPos = pos |
|||
self.sliderStepsChanged.emit() |
|||
self.sliderPosChanged.emit() |
|||
|
|||
def read_config(self): |
|||
mempool = self.config.use_mempool_fees() |
|||
dynfees = self.config.is_dynfee() |
|||
self._method = (2 if mempool else 1) if dynfees else 0 |
|||
self.update_slider() |
|||
self.methodChanged.emit() |
|||
self.update(False) |
|||
|
|||
def save_config(self): |
|||
value = int(self._sliderPos) |
|||
dynfees, mempool = self.get_method() |
|||
self.config.set_key('dynamic_fees', dynfees, False) |
|||
self.config.set_key('mempool_fees', mempool, False) |
|||
if dynfees: |
|||
if mempool: |
|||
self.config.set_key('depth_level', value, True) |
|||
else: |
|||
self.config.set_key('fee_level', value, True) |
|||
else: |
|||
self.config.set_key('fee_per_kb', self.config.static_fee(value), True) |
|||
self.update(False) |
|||
|
|||
@profiler |
|||
def make_tx(self, rbf: bool): |
|||
coins = self._wallet.wallet.get_spendable_coins(None) |
|||
outputs = [PartialTxOutput.from_address_and_value(self.address, int(self.amount))] |
|||
tx = self._wallet.wallet.make_unsigned_transaction(coins=coins,outputs=outputs, fee=None) |
|||
self._logger.debug('fee: %d, inputs: %d, outputs: %d' % (tx.get_fee(), len(tx.inputs()), len(tx.outputs()))) |
|||
return tx |
|||
|
|||
@pyqtSlot(bool) |
|||
def update(self, rbf): |
|||
#rbf = not bool(self.ids.final_cb.active) if self.show_final else False |
|||
try: |
|||
# make unsigned transaction |
|||
tx = self.make_tx(rbf) |
|||
except NotEnoughFunds: |
|||
self.warning = _("Not enough funds") |
|||
self._valid = False |
|||
self.validChanged.emit() |
|||
return |
|||
except Exception as e: |
|||
self._logger.error(str(e)) |
|||
self.warning = repr(e) |
|||
self._valid = False |
|||
self.validChanged.emit() |
|||
return |
|||
|
|||
amount = int(self.amount) if self.amount != '!' else tx.output_value() |
|||
tx_size = tx.estimated_size() |
|||
fee = tx.get_fee() |
|||
feerate = Decimal(fee) / tx_size # sat/byte |
|||
|
|||
self.fee = str(fee) |
|||
self.feeRate = f'{feerate:.1f}' |
|||
|
|||
#x_fee = run_hook('get_tx_extra_fee', self._wallet.wallet, tx) |
|||
fee_warning_tuple = self._wallet.wallet.get_tx_fee_warning( |
|||
invoice_amt=amount, tx_size=tx_size, fee=fee) |
|||
if fee_warning_tuple: |
|||
allow_send, long_warning, short_warning = fee_warning_tuple |
|||
self.warning = long_warning |
|||
else: |
|||
self.warning = '' |
|||
|
|||
target, tooltip, dyn = self.config.get_fee_target() |
|||
self.target = target |
|||
|
|||
self._valid = True |
|||
self.validChanged.emit() |
Loading…
Reference in new issue