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