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