def __init__(self, caller): self.caller = caller # --- Lock for loading UTXO thread self.runInThread = ThreadFuns.runInThread self.Lock = threading.Lock() # --- Initialize Selection self.selectedRewards = None self.feePerKb = MINIMUM_FEE self.suggestedFee = MINIMUM_FEE # --- Initialize GUI self.ui = TabRewards_gui(caller.imgDir) self.caller.tabRewards = self.ui # load last used destination from cache self.ui.destinationLine.setText(self.caller.parent.cache.get("lastAddress")) # init first selected MN self.loadMnSelect(True) # loads masternodes list in MnSelect and display utxos self.updateFee() # Connect GUI buttons self.ui.mnSelect.currentIndexChanged.connect(lambda: self.onChangeSelectedMN()) self.ui.btn_toggleCollateral.clicked.connect(lambda: self.onToggleCollateral()) self.ui.rewardsList.box.itemClicked.connect(lambda: self.updateSelection()) self.ui.btn_selectAllRewards.clicked.connect(lambda: self.onSelectAllRewards()) self.ui.btn_deselectAllRewards.clicked.connect(lambda: self.onDeselectAllRewards()) self.ui.btn_sendRewards.clicked.connect(lambda: self.onSendRewards()) self.ui.btn_Cancel.clicked.connect(lambda: self.onCancel()) self.ui.btn_ReloadUTXOs.clicked.connect(lambda: self.onReloadUTXOs()) # Connect Signals self.caller.sig_UTXOsLoading.connect(self.update_loading_utxos)
def __init__(self, caller): self.caller = caller ##--- Lock for loading UTXO thread self.runInThread = ThreadFuns.runInThread self.Lock = threading.Lock() ##--- Initialize Selection self.utxoLoaded = False self.selectedRewards = None self.feePerKb = MINIMUM_FEE self.suggestedFee = MINIMUM_FEE ##--- Initialize GUI self.ui = TabRewards_gui(caller) self.caller.tabRewards = self.ui # load last used destination from cache self.ui.destinationLine.setText( self.caller.parent.cache.get("lastAddress")) # load useSwiftX check from cache if self.caller.parent.cache.get("useSwiftX"): self.ui.swiftxCheck.setChecked(True) # init first selected MN self.loadMnSelect( ) # loads masternodes list in MnSelect and display utxos self.updateFee() # show UTXOs from DB self.display_mn_utxos() # Connect GUI buttons self.ui.mnSelect.currentIndexChanged.connect( lambda: self.onChangeSelectedMN()) self.ui.btn_toggleCollateral.clicked.connect( lambda: self.onToggleCollateral()) self.ui.rewardsList.box.itemClicked.connect( lambda: self.updateSelection()) self.ui.btn_selectAllRewards.clicked.connect( lambda: self.onSelectAllRewards()) self.ui.btn_deselectAllRewards.clicked.connect( lambda: self.onDeselectAllRewards()) self.ui.swiftxCheck.clicked.connect(lambda: self.updateFee()) self.ui.btn_sendRewards.clicked.connect(lambda: self.onSendRewards()) self.ui.btn_Cancel.clicked.connect(lambda: self.onCancel()) self.ui.btn_ReloadUTXOs.clicked.connect(lambda: self.onReloadUTXOs()) # Connect Signals self.caller.hwdevice.sigTxdone.connect(self.FinishSend) self.caller.hwdevice.sigTxabort.connect(self.onCancel) self.caller.hwdevice.tx_progress.connect(self.updateProgressPercent) self.caller.sig_UTXOsLoading.connect(self.update_loading_utxos) self.caller.sig_UTXOsLoaded.connect(self.display_mn_utxos)
def __init__(self, caller): self.caller = caller ##--- Lock for loading UTXO thread self.runInThread = ThreadFuns.runInThread self.Lock = threading.Lock() ##--- Initialize Selection self.utxoLoaded = False self.selectedRewards = None self.feePerKb = MINIMUM_FEE self.suggestedFee = MINIMUM_FEE ##--- Initialize GUI self.ui = TabRewards_gui(self.caller.imgDir) self.caller.tabRewards = self.ui self.ui.btn_Copy.setIcon(self.caller.copy_icon) # load cache self.ui.destinationLine.setText( self.caller.parent.cache.get("lastAddress")) if self.caller.parent.cache.get("useSwiftX"): self.ui.swiftxCheck.setChecked(True) self.ui.edt_hwAccount.setValue(self.caller.parent.cache["hwAcc"]) self.ui.edt_spathFrom.setValue(self.caller.parent.cache["spathFrom"]) self.ui.edt_spathTo.setValue(self.caller.parent.cache["spathTo"]) self.ui.edt_internalExternal.setValue( self.caller.parent.cache["intExt"]) self.updateFee() # Connect GUI buttons self.ui.addySelect.currentIndexChanged.connect( lambda: self.onChangeSelected()) self.ui.rewardsList.box.itemClicked.connect( lambda: self.updateSelection()) self.ui.btn_reload.clicked.connect(lambda: self.loadSelection()) self.ui.btn_selectAllRewards.clicked.connect( lambda: self.onSelectAllRewards()) self.ui.btn_deselectAllRewards.clicked.connect( lambda: self.onDeselectAllRewards()) self.ui.swiftxCheck.clicked.connect(lambda: self.updateFee()) self.ui.btn_sendRewards.clicked.connect(lambda: self.onSendRewards()) self.ui.btn_Cancel.clicked.connect(lambda: self.onCancel()) self.ui.btn_Copy.clicked.connect(lambda: self.onCopy()) # Connect Signals self.caller.sig_UTXOsLoading.connect(self.update_loading_utxos) self.caller.sig_UTXOsLoaded.connect(self.display_utxos)
def __init__(self, caller): self.caller = caller self.apiClient = ApiClient() ##--- Initialize Selection self.rewards = None self.selectedRewards = None self.rawtransactions = {} ##--- Initialize GUI self.ui = TabRewards_gui() self.caller.tabRewards = self.ui self.ui.feeLine.setValue(MINIMUM_FEE) # Connect GUI buttons self.ui.mnSelect.currentIndexChanged.connect( lambda: self.onChangeSelectedMN()) self.ui.btn_toggleCollateral.clicked.connect( lambda: self.onToggleCollateral()) self.ui.rewardsList.box.itemClicked.connect( lambda: self.updateSelection()) self.ui.btn_selectAllRewards.clicked.connect( lambda: self.onSelectAllRewards()) self.ui.btn_deselectAllRewards.clicked.connect( lambda: self.onDeselectAllRewards()) self.ui.btn_sendRewards.clicked.connect(lambda: self.onSendRewards()) self.ui.btn_Cancel.clicked.connect(lambda: self.onCancel()) # Init first selection self.loadMnSelect()
def __init__(self, caller): self.caller = caller ##--- Initialize Selection self.rewards = None self.selectedRewards = None self.rawtransactions = {} self.feePerKb = MINIMUM_FEE ##--- Initialize GUI self.ui = TabRewards_gui() self.caller.tabRewards = self.ui self.suggestedFee = MINIMUM_FEE # load last used destination from cache self.ui.destinationLine.setText( self.caller.parent.cache.get("lastAddress")) # load useSwiftX check from cache if self.caller.parent.cache.get("useSwiftX"): self.ui.swiftxCheck.setChecked(True) self.updateFee() # Connect GUI buttons self.ui.mnSelect.currentIndexChanged.connect( lambda: self.onChangeSelectedMN()) self.ui.btn_toggleCollateral.clicked.connect( lambda: self.onToggleCollateral()) self.ui.rewardsList.box.itemClicked.connect( lambda: self.updateSelection()) self.ui.btn_selectAllRewards.clicked.connect( lambda: self.onSelectAllRewards()) self.ui.btn_deselectAllRewards.clicked.connect( lambda: self.onDeselectAllRewards()) self.ui.swiftxCheck.clicked.connect(lambda: self.updateFee()) self.ui.btn_sendRewards.clicked.connect(lambda: self.onSendRewards()) self.ui.btn_Cancel.clicked.connect(lambda: self.onCancel())
class TabRewards(): def __init__(self, caller): self.caller = caller ##--- Lock for loading UTXO thread self.runInThread = ThreadFuns.runInThread self.Lock = threading.Lock() ##--- Initialize Selection self.selectedRewards = None self.feePerKb = MINIMUM_FEE self.suggestedFee = MINIMUM_FEE ##--- Initialize GUI self.ui = TabRewards_gui(caller.imgDir) self.caller.tabRewards = self.ui # load last used destination from cache self.ui.destinationLine.setText(self.caller.parent.cache.get("lastAddress")) # load useSwiftX check from cache if self.caller.parent.cache.get("useSwiftX"): self.ui.swiftxCheck.setChecked(True) # init first selected MN self.loadMnSelect(True) # loads masternodes list in MnSelect and display utxos self.updateFee() # Connect GUI buttons self.ui.mnSelect.currentIndexChanged.connect(lambda: self.onChangeSelectedMN()) self.ui.btn_toggleCollateral.clicked.connect(lambda: self.onToggleCollateral()) self.ui.rewardsList.box.itemClicked.connect(lambda: self.updateSelection()) self.ui.btn_selectAllRewards.clicked.connect(lambda: self.onSelectAllRewards()) self.ui.btn_deselectAllRewards.clicked.connect(lambda: self.onDeselectAllRewards()) self.ui.swiftxCheck.clicked.connect(lambda: self.updateFee()) self.ui.btn_sendRewards.clicked.connect(lambda: self.onSendRewards()) self.ui.btn_Cancel.clicked.connect(lambda: self.onCancel()) self.ui.btn_ReloadUTXOs.clicked.connect(lambda: self.onReloadUTXOs()) # Connect Signals self.caller.sig_UTXOsLoading.connect(self.update_loading_utxos) def display_mn_utxos(self): if self.curr_name is None: return # update fee if self.caller.rpcConnected: self.feePerKb = self.caller.rpcClient.getFeePerKb() if self.feePerKb is None: self.feePerKb = MINIMUM_FEE else: self.feePerKb = MINIMUM_FEE rewards = self.caller.parent.db.getRewardsList(self.curr_name) self.updateTotalBalance(rewards) if rewards is not None: def item(value): item = QTableWidgetItem(value) item.setTextAlignment(Qt.AlignCenter) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) return item # Clear up old list self.ui.rewardsList.box.setRowCount(0) # Make room for new list self.ui.rewardsList.box.setRowCount(len(rewards)) # Insert items for row, utxo in enumerate(rewards): txId = utxo.get('txid', None) pivxAmount = round(int(utxo.get('satoshis', 0))/1e8, 8) self.ui.rewardsList.box.setItem(row, 0, item(str(pivxAmount))) self.ui.rewardsList.box.setItem(row, 1, item(str(utxo.get('confirmations', None)))) self.ui.rewardsList.box.setItem(row, 2, item(txId)) self.ui.rewardsList.box.setItem(row, 3, item(str(utxo.get('vout', None)))) self.ui.rewardsList.box.showRow(row) # mark cold utxos if utxo['staker'] != "": self.ui.rewardsList.box.item(row, 2).setIcon(self.caller.tabMain.coldStaking_icon) self.ui.rewardsList.box.item(row, 2).setToolTip("Staked by <b>%s</b>" % utxo['staker']) # MARK COLLATERAL UTXO if txId == self.curr_txid: for i in range(0,4): self.ui.rewardsList.box.item(row, i).setFont(QFont("Arial", 9, QFont.Bold)) self.ui.rewardsList.box.collateralRow = row # make immature rewards unselectable if utxo['coinstake']: required = 16 if self.caller.isTestnetRPC else 101 if utxo['confirmations'] < required: for i in range(0,4): self.ui.rewardsList.box.item(row, i).setFlags(Qt.NoItemFlags) ttip = self.ui.rewardsList.box.item(row, i).toolTip() self.ui.rewardsList.box.item(row, i).setToolTip( ttip + "\n(Immature - %d confirmations required)" % required) self.ui.rewardsList.box.resizeColumnsToContents() if self.ui.rewardsList.box.collateralRow is not None: self.ui.rewardsList.box.hideRow(self.ui.rewardsList.box.collateralRow) if len(rewards) > 1: # (collateral is a reward) self.ui.rewardsList.statusLabel.setVisible(False) self.ui.rewardsList.box.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) else: if not self.caller.rpcConnected: self.ui.resetStatusLabel('<b style="color:red">PIVX wallet not connected</b>') else: self.ui.resetStatusLabel('<b style="color:red">Found no Rewards for %s</b>' % self.curr_addr) def getSelection(self): # Get selected rows indexes items = self.ui.rewardsList.box.selectedItems() rows = set() for i in range(0, len(items)): row = items[i].row() rows.add(row) indexes = list(rows) # Get UTXO info from DB for each selection = [] for idx in indexes: txid = self.ui.rewardsList.box.item(idx, 2).text() txidn = int(self.ui.rewardsList.box.item(idx, 3).text()) selection.append(self.caller.parent.db.getReward(txid, txidn)) return selection def loadMnSelect(self, isInitializing=False): # save previous index index = 0 if self.ui.mnSelect.count() > 0: index = self.ui.mnSelect.currentIndex() self.ui.mnSelect.clear() for x in self.caller.masternode_list: if x['isHardware']: name = x['name'] address = x['collateral'].get('address') txid = x['collateral'].get('txid') txidn = x['collateral'].get('txidn') hwAcc = x['hwAcc'] spath = x['collateral'].get('spath') hwpath = "%d'/0/%d" % (hwAcc, spath) self.ui.mnSelect.addItem(name, [address, txid, txidn, hwpath]) # restore previous index if index < self.ui.mnSelect.count(): self.ui.mnSelect.setCurrentIndex(index) self.onChangeSelectedMN(isInitializing) def load_utxos_thread(self, ctrl): with self.Lock: # clear rewards DB printDbg("Updating rewards...") self.caller.parent.db.clearTable('REWARDS') self.caller.parent.db.clearTable('MY_VOTES') # If rpc is not connected and hw device is Ledger, warn and return. if not self.caller.rpcConnected and self.caller.hwModel == 0: printError(getCallerName(), getFunctionName(), 'PIVX daemon not connected - Unable to update UTXO list') return total_num_of_utxos = 0 mn_rewards = {} for mn in self.caller.masternode_list: # Load UTXOs from API client rewards = self.caller.apiClient.getAddressUtxos(mn['collateral'].get('address')) if rewards is None: printError(getCallerName(), getFunctionName(), 'API client not responding.') return mn_rewards[mn['name']] = rewards total_num_of_utxos += len(rewards) printDbg("Number of UTXOs to load: %d" % total_num_of_utxos) curr_utxo = 0 for mn in mn_rewards: for utxo in mn_rewards[mn]: # Add mn_name to UTXO utxo['mn_name'] = mn # Get raw tx rawtx = TxCache(self.caller)[utxo['txid']] if rawtx is None: printDbg("Unable to get raw TX with hash=%s from RPC server." % utxo['txid']) # Don't save UTXO if raw TX is unavailable mn_rewards[mn].remove(utxo) continue utxo['raw_tx'] = rawtx utxo['staker'] = "" p2cs, utxo['coinstake'] = IsPayToColdStaking(rawtx, utxo['vout']) if p2cs: utxo['staker'] = GetDelegatedStaker(rawtx, utxo['vout'], self.caller.isTestnetRPC) # Add utxo to database self.caller.parent.db.addReward(utxo) # emit percent percent = int(100 * curr_utxo / total_num_of_utxos) self.caller.sig_UTXOsLoading.emit(percent) curr_utxo += 1 printDbg("--# REWARDS table updated") self.caller.sig_UTXOsLoading.emit(100) def onCancel(self): self.ui.rewardsList.box.clearSelection() self.selectedRewards = None self.ui.selectedRewardsLine.setText("0.0") self.suggestedFee = MINIMUM_FEE self.updateFee() self.ui.btn_toggleCollateral.setText("Show Collateral") self.ui.collateralHidden = True self.AbortSend() def onChangedMNlist(self): # reload MnSelect self.loadMnSelect() # reload utxos self.onReloadUTXOs() def onChangeSelectedMN(self, isInitializing=False): self.curr_name = None if self.ui.mnSelect.currentIndex() >= 0: self.curr_name = self.ui.mnSelect.itemText(self.ui.mnSelect.currentIndex()) self.curr_addr = self.ui.mnSelect.itemData(self.ui.mnSelect.currentIndex())[0] self.curr_txid = self.ui.mnSelect.itemData(self.ui.mnSelect.currentIndex())[1] self.curr_txidn = self.ui.mnSelect.itemData(self.ui.mnSelect.currentIndex())[2] self.curr_hwpath = self.ui.mnSelect.itemData(self.ui.mnSelect.currentIndex())[3] self.ui.rewardsList.box.collateralRow = None self.onCancel() # If we are initializing the class, don't display_mn_utxos. It's still empty if not isInitializing: self.ui.resetStatusLabel() self.display_mn_utxos() def onSelectAllRewards(self): self.ui.rewardsList.box.selectAll() self.updateSelection() def onDeselectAllRewards(self): self.ui.rewardsList.box.clearSelection() self.updateSelection() def onReloadUTXOs(self): if not self.Lock.locked(): self.ui.resetStatusLabel() self.runInThread(self.load_utxos_thread, ()) def onSendRewards(self): self.dest_addr = self.ui.destinationLine.text().strip() self.currFee = self.ui.feeLine.value() * 1e8 # Check spending collateral if (not self.ui.collateralHidden and self.ui.rewardsList.box.collateralRow is not None and self.ui.rewardsList.box.item(self.ui.rewardsList.box.collateralRow, 0).isSelected() ): warning1 = "Are you sure you want to transfer the collateral?" warning2 = "Really?" warning3 = "Take a deep breath. Do you REALLY want to transfer your collateral?" ans = myPopUp(self.caller, "warn", 'SPMT - warning', warning1) if ans == QMessageBox.No: return None else: ans2 = myPopUp(self.caller, "warn", 'SPMT - warning', warning2) if ans2 == QMessageBox.No: return None else: ans2 = myPopUp(self.caller, "crit", 'SPMT - warning', warning3) if ans2 == QMessageBox.No: return None # Check HW device while self.caller.hwStatus != 2: mess = "HW device not connected. Try to connect?" ans = myPopUp(self.caller, QMessageBox.Question, 'SPMT - hw check', mess) if ans == QMessageBox.No: return # re connect self.caller.onCheckHw() # SEND self.SendRewards(self.useSwiftX()) def SendRewards(self, useSwiftX, inputs=None, gui=None): # Default slots on tabRewards if gui is None: gui = self # re-connect signals try: self.caller.hwdevice.api.sigTxdone.disconnect() except: pass try: self.caller.hwdevice.api.sigTxabort.disconnect() except: pass try: self.caller.hwdevice.api.tx_progress.disconnect() except: pass self.caller.hwdevice.api.sigTxdone.connect(gui.FinishSend) self.caller.hwdevice.api.sigTxabort.connect(gui.AbortSend) self.caller.hwdevice.api.tx_progress.connect(gui.updateProgressPercent) # Check destination Address if not checkPivxAddr(self.dest_addr, self.caller.isTestnetRPC): myPopUp_sb(self.caller, "crit", 'SPMT - PIVX address check', "The destination address is missing, or invalid.") return None if inputs is None: # send from single path num_of_inputs = len(self.selectedRewards) else: # bulk send num_of_inputs = sum([len(x['utxos']) for x in inputs]) ans = checkTxInputs(self.caller, num_of_inputs) if ans is None or ans == QMessageBox.No: # emit sigTxAbort and return self.caller.hwdevice.api.sigTxabort.emit() return None # LET'S GO if inputs is None: printDbg("Sending from PIVX address %s to PIVX address %s " % (self.curr_addr, self.dest_addr)) else: printDbg("Sweeping rewards to PIVX address %s " % self.dest_addr) printDbg("Preparing transaction. Please wait...") self.ui.loadingLine.show() self.ui.loadingLinePercent.show() QApplication.processEvents() # save last destination address and swiftxCheck to cache and persist to settings self.caller.parent.cache["lastAddress"] = persistCacheSetting('cache_lastAddress', self.dest_addr) self.caller.parent.cache["useSwiftX"] = persistCacheSetting('cache_useSwiftX', useSwiftX) try: self.txFinished = False if inputs is None: # send from single path self.caller.hwdevice.prepare_transfer_tx(self.caller, self.curr_hwpath, self.selectedRewards, self.dest_addr, self.currFee, useSwiftX, self.caller.isTestnetRPC) else: # bulk send self.caller.hwdevice.prepare_transfer_tx_bulk(self.caller, inputs, self.dest_addr, self.currFee, useSwiftX, self.caller.isTestnetRPC) except DisconnectedException as e: self.caller.hwStatus = 0 self.caller.updateHWleds() except Exception as e: err_msg = "Error while preparing transaction. <br>" err_msg += "Probably Blockchain wasn't synced when trying to fetch raw TXs.<br>" err_msg += "<b>Wait for full synchronization</b> then hit 'Clear/Reload'" printException(getCallerName(), getFunctionName(), err_msg, e.args) def onToggleCollateral(self): if self.ui.rewardsList.box.collateralRow is not None: if not self.ui.collateralHidden: try: # If collateral row was selected, deselect it before hiding if self.ui.rewardsList.box.item(self.ui.rewardsList.box.collateralRow, 0).isSelected(): self.ui.rewardsList.box.selectRow(self.ui.rewardsList.box.collateralRow) except Exception as e: err_msg = "Error toggling collateral" printException(getCallerName(), getFunctionName(), err_msg, e.args) self.ui.rewardsList.box.hideRow(self.ui.rewardsList.box.collateralRow) self.ui.btn_toggleCollateral.setText("Show Collateral") self.ui.collateralHidden = True self.updateSelection() else: self.ui.rewardsList.box.showRow(self.ui.rewardsList.box.collateralRow) self.ui.btn_toggleCollateral.setText("Hide Collateral") self.ui.collateralHidden = False self.updateSelection() self.ui.rewardsList.box.resizeColumnsToContents() self.ui.rewardsList.box.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) else: myPopUp_sb(self.caller, "warn", 'No Collateral', "No collateral selected") def removeSpentRewards(self): if self.selectedRewards is not None: for utxo in self.selectedRewards: self.caller.parent.db.deleteReward(utxo['txid'], utxo['vout']) else: self.caller.parent.db.clearTable('REWARDS') # Activated by signal sigTxdone from hwdevice def FinishSend(self, serialized_tx, amount_to_send): self.AbortSend() self.FinishSend_int(serialized_tx, amount_to_send) def FinishSend_int(self, serialized_tx, amount_to_send): if not self.txFinished: try: self.txFinished = True tx_hex = serialized_tx.hex() printDbg("Raw signed transaction: " + tx_hex) printDbg("Amount to send :" + amount_to_send) if len(tx_hex) > 90000: mess = "Transaction's length exceeds 90000 bytes. Select less UTXOs and try again." myPopUp_sb(self.caller, "crit", 'transaction Warning', mess) else: decodedTx = None try: decodedTx = ParseTx(tx_hex, self.caller.isTestnetRPC) destination = decodedTx.get("vout")[0].get("scriptPubKey").get("addresses")[0] amount = decodedTx.get("vout")[0].get("value") message = '<p>Broadcast signed transaction?</p><p>Destination address:<br><b>%s</b></p>' % destination message += '<p>Amount: <b>%s</b> PIV<br>' % str(round(amount / 1e8, 8)) message += 'Fees: <b>%s</b> PIV <br>Size: <b>%d</b> Bytes</p>' % (str(round(self.currFee / 1e8, 8) ), len(tx_hex)/2) except Exception as e: printException(getCallerName(), getFunctionName(), "decoding exception", str(e)) message = '<p>Unable to decode TX- Broadcast anyway?</p>' mess1 = QMessageBox(QMessageBox.Information, 'Send transaction', message, parent=self.caller) if decodedTx is not None: mess1.setDetailedText(json.dumps(decodedTx, indent=4, sort_keys=False)) mess1.setStandardButtons(QMessageBox.Yes | QMessageBox.No) reply = mess1.exec_() if reply == QMessageBox.Yes: txid = self.caller.rpcClient.sendRawTransaction(tx_hex, self.useSwiftX()) if txid is None: raise Exception("Unable to send TX - connection to RPC server lost.") printDbg("Transaction sent. ID: %s" % txid) mess2_text = "<p>Transaction successfully sent.</p>" mess2 = QMessageBox(QMessageBox.Information, 'transaction Sent', mess2_text, parent=self.caller) mess2.setDetailedText(txid) mess2.exec_() # remove spent rewards from DB self.removeSpentRewards() # reload utxos self.display_mn_utxos() self.onCancel() else: myPopUp_sb(self.caller, "warn", 'Transaction NOT sent', "Transaction NOT sent") self.onCancel() except Exception as e: err_msg = "Exception in FinishSend" printException(getCallerName(), getFunctionName(), err_msg, e.args) # Activated by signal sigTxabort from hwdevice def AbortSend(self): self.ui.loadingLine.hide() self.ui.loadingLinePercent.setValue(0) self.ui.loadingLinePercent.hide() def updateFee(self): if self.useSwiftX(): self.ui.feeLine.setValue(0.01) self.ui.feeLine.setEnabled(False) else: self.ui.feeLine.setValue(self.suggestedFee) self.ui.feeLine.setEnabled(True) # Activated by signal tx_progress from hwdevice def updateProgressPercent(self, percent): self.ui.loadingLinePercent.setValue(percent) QApplication.processEvents() def updateSelection(self, clicked_item=None): total = 0 self.selectedRewards = self.getSelection() numOfInputs = len(self.selectedRewards) if numOfInputs: for i in range(0, numOfInputs): total += int(self.selectedRewards[i].get('satoshis')) # update suggested fee and selected rewards estimatedTxSize = (44+numOfInputs*148)*1.0 / 1000 # kB self.suggestedFee = round(self.feePerKb * estimatedTxSize, 8) printDbg("estimatedTxSize is %s kB" % str(estimatedTxSize)) printDbg("suggested fee is %s PIV (%s PIV/kB)" % (str(self.suggestedFee), str(self.feePerKb))) self.ui.selectedRewardsLine.setText(str(round(total/1e8, 8))) else: self.ui.selectedRewardsLine.setText("") self.updateFee() def update_loading_utxos(self, percent): if percent < 100: self.ui.resetStatusLabel('<em><b style="color:purple">Checking explorer... %d%%</b></em>' % percent) else: self.display_mn_utxos() def updateTotalBalance(self, rewards): nAmount = 0 if rewards is not None: for utxo in rewards: nAmount = nAmount + utxo['satoshis'] totalBalance = str(round(nAmount/1e8, 8)) self.ui.addrAvailLine.setText("<i>%s PIVs</i>" % totalBalance) def useSwiftX(self): return self.ui.swiftxCheck.isChecked()
class TabRewards(): def __init__(self, caller): self.caller = caller ##--- Lock for loading UTXO thread self.runInThread = ThreadFuns.runInThread self.Lock = threading.Lock() ##--- Initialize Selection self.utxoLoaded = False self.selectedRewards = None self.feePerKb = MINIMUM_FEE self.suggestedFee = MINIMUM_FEE ##--- Initialize GUI self.ui = TabRewards_gui(self.caller.imgDir) self.caller.tabRewards = self.ui self.ui.btn_Copy.setIcon(self.caller.copy_icon) # load cache self.ui.destinationLine.setText( self.caller.parent.cache.get("lastAddress")) if self.caller.parent.cache.get("useSwiftX"): self.ui.swiftxCheck.setChecked(True) self.ui.edt_hwAccount.setValue(self.caller.parent.cache["hwAcc"]) self.ui.edt_spathFrom.setValue(self.caller.parent.cache["spathFrom"]) self.ui.edt_spathTo.setValue(self.caller.parent.cache["spathTo"]) self.ui.edt_internalExternal.setValue( self.caller.parent.cache["intExt"]) self.updateFee() # Connect GUI buttons self.ui.addySelect.currentIndexChanged.connect( lambda: self.onChangeSelected()) self.ui.rewardsList.box.itemClicked.connect( lambda: self.updateSelection()) self.ui.btn_reload.clicked.connect(lambda: self.loadSelection()) self.ui.btn_selectAllRewards.clicked.connect( lambda: self.onSelectAllRewards()) self.ui.btn_deselectAllRewards.clicked.connect( lambda: self.onDeselectAllRewards()) self.ui.swiftxCheck.clicked.connect(lambda: self.updateFee()) self.ui.btn_sendRewards.clicked.connect(lambda: self.onSendRewards()) self.ui.btn_Cancel.clicked.connect(lambda: self.onCancel()) self.ui.btn_Copy.clicked.connect(lambda: self.onCopy()) # Connect Signals self.caller.sig_UTXOsLoading.connect(self.update_loading_utxos) self.caller.sig_UTXOsLoaded.connect(self.display_utxos) def display_utxos(self): # update fee if self.caller.rpcConnected: self.feePerKb = self.caller.rpcClient.getFeePerKb() if self.feePerKb is None: self.feePerKb = MINIMUM_FEE else: self.feePerKb = MINIMUM_FEE rewards = self.caller.parent.db.getRewardsList(self.curr_addr) if rewards is not None: def item(value): item = QTableWidgetItem(value) item.setTextAlignment(Qt.AlignCenter) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) return item # Clear up old list self.ui.rewardsList.box.setRowCount(0) # Make room for new list self.ui.rewardsList.box.setRowCount(len(rewards)) # Insert items for row, utxo in enumerate(rewards): txId = utxo.get('txid', None) pivxAmount = round(int(utxo.get('satoshis', 0)) / 1e8, 8) self.ui.rewardsList.box.setItem(row, 0, item(str(pivxAmount))) self.ui.rewardsList.box.setItem( row, 1, item(str(utxo.get('confirmations', None)))) self.ui.rewardsList.box.setItem(row, 2, item(txId)) self.ui.rewardsList.box.setItem( row, 3, item(str(utxo.get('vout', None)))) self.ui.rewardsList.box.showRow(row) if utxo['staker'] != "": self.ui.rewardsList.box.item(row, 2).setIcon( self.caller.coldStaking_icon) self.ui.rewardsList.box.item(row, 2).setToolTip( "Staked by <b>%s</b>" % utxo['staker']) # make immature rewards unselectable if utxo['coinstake']: required = 16 if self.caller.isTestnetRPC else 101 if utxo['confirmations'] < required: for i in range(0, 4): self.ui.rewardsList.box.item(row, i).setFlags( Qt.NoItemFlags) ttip = self.ui.rewardsList.box.item(row, i).toolTip() self.ui.rewardsList.box.item(row, i).setToolTip( ttip + "\n(Immature - %d confirmations required)" % required) self.ui.rewardsList.box.resizeColumnsToContents() if len(rewards) > 0: self.ui.rewardsList.statusLabel.setVisible(False) self.ui.rewardsList.box.horizontalHeader( ).setSectionResizeMode(2, QHeaderView.Stretch) else: if not self.caller.rpcConnected: self.ui.resetStatusLabel( '<b style="color:red">PIVX wallet not connected</b>') else: self.ui.resetStatusLabel( '<b style="color:red">Found no Rewards for %s</b>' % self.curr_addr) def getSelection(self): # Get selected rows indexes items = self.ui.rewardsList.box.selectedItems() rows = set() for i in range(0, len(items)): row = items[i].row() rows.add(row) indexes = list(rows) # Get UTXO info from DB for each selection = [] for idx in indexes: txid = self.ui.rewardsList.box.item(idx, 2).text() txidn = int(self.ui.rewardsList.box.item(idx, 3).text()) selection.append(self.caller.parent.db.getReward(txid, txidn)) return selection def loadSelection(self): # Check dongle printDbg("Checking HW device") if self.caller.hwStatus != 2: myPopUp_sb(self.caller, "crit", 'PET4L - hw device check', "Connect to HW device first") printDbg("Unable to connect - hw status: %d" % self.caller.hwStatus) return None self.ui.addySelect.clear() ThreadFuns.runInThread(self.loadSelection_thread, ()) def loadSelection_thread(self, ctrl): hwAcc = self.ui.edt_hwAccount.value() spathFrom = self.ui.edt_spathFrom.value() spathTo = self.ui.edt_spathTo.value() intExt = self.ui.edt_internalExternal.value() isTestnet = self.caller.isTestnetRPC # Save settings self.caller.parent.cache["hwAcc"] = persistCacheSetting( 'cache_hwAcc', hwAcc) self.caller.parent.cache["spathFrom"] = persistCacheSetting( 'cache_spathFrom', spathFrom) self.caller.parent.cache["spathTo"] = persistCacheSetting( 'cache_spathTo', spathTo) self.caller.parent.cache["intExt"] = persistCacheSetting( 'cache_intExt', intExt) for i in range(spathFrom, spathTo + 1): path = "%d'/%d/%d" % (hwAcc, intExt, i) address = self.caller.hwdevice.scanForAddress( hwAcc, i, intExt, isTestnet) try: balance = self.caller.apiClient.getBalance(address) except Exception as e: print(e) balance = 0 itemLine = "%s -- %s" % (path, address) if (balance): itemLine += " [%s PIV]" % str(balance) self.ui.addySelect.addItem(itemLine, [path, address, balance]) def load_utxos_thread(self, ctrl): with self.Lock: # clear utxos DB printDbg("Updating UTXOs...") self.caller.parent.db.clearTable('UTXOS') self.utxoLoaded = False if not self.caller.rpcConnected: printError( getCallerName(), getFunctionName(), 'PIVX daemon not connected - Unable to update UTXO list') return utxos = self.caller.apiClient.getAddressUtxos(self.curr_addr) total_num_of_utxos = len(utxos) # Get raw transactions curr_utxo = 0 percent = 0 for u in utxos: percent = int(100 * curr_utxo / total_num_of_utxos) # get raw TX from RPC client (only for ledger / trezor has own api) if self.caller.hwModel == 0: # double check that the rpc connection is still active, else reconnect if self.caller.rpcClient is None: self.caller.updateRPCstatus(None) rawtx = self.caller.rpcClient.getRawTransaction(u['txid']) if rawtx is None: printDbg( "Unable to get raw TX with hash=%s from RPC server." % u['txid']) # Don't save UTXO if raw TX is unavailable continue else: rawtx = "" # Save utxo to db u['receiver'] = self.curr_addr u['raw_tx'] = rawtx u['staker'] = "" p2cs, u['coinstake'] = IsPayToColdStaking(rawtx, u['vout']) if p2cs: u['staker'] = GetDelegatedStaker(rawtx, u['vout'], self.caller.isTestnetRPC) self.caller.parent.db.addReward(u) # emit percent self.caller.sig_UTXOsLoading.emit(percent) curr_utxo += 1 self.caller.sig_UTXOsLoading.emit(100) printDbg("--# REWARDS table updated") self.utxoLoaded = True self.caller.sig_UTXOsLoaded.emit() def onCancel(self): self.ui.rewardsList.box.clearSelection() self.selectedRewards = None self.ui.selectedRewardsLine.setText("0.0") self.suggestedFee = MINIMUM_FEE self.updateFee() self.AbortSend() def onCopy(self): if self.ui.addySelect.count() == 0: mess = "Nothing to copy. Load/Refresh addresses first." myPopUp_sb(self.caller, QMessageBox.Warning, 'PET4L - no address', mess) return cb = QApplication.clipboard() cb.clear(mode=cb.Clipboard) ct = self.ui.addySelect.currentText() addy = ct.split(" -- ")[1].split(" ")[0].strip() cb.setText(addy, mode=cb.Clipboard) myPopUp_sb(self.caller, QMessageBox.Information, 'PET4L - copied', "address copied to the clipboard") def onChangeSelected(self): if self.ui.addySelect.currentIndex() >= 0: self.ui.resetStatusLabel() self.curr_path = self.ui.addySelect.itemData( self.ui.addySelect.currentIndex())[0] self.curr_addr = self.ui.addySelect.itemData( self.ui.addySelect.currentIndex())[1] self.curr_balance = self.ui.addySelect.itemData( self.ui.addySelect.currentIndex())[2] if self.curr_balance is not None: self.runInThread = ThreadFuns.runInThread( self.load_utxos_thread, (), self.display_utxos) def onSelectAllRewards(self): self.ui.rewardsList.box.selectAll() self.updateSelection() def onDeselectAllRewards(self): self.ui.rewardsList.box.clearSelection() self.updateSelection() def onSendRewards(self): self.dest_addr = self.ui.destinationLine.text().strip() # Check HW device if self.caller.hwStatus != 2: myPopUp_sb(self.caller, "crit", 'PET4L - hw device check', "Connect to HW device first") printDbg( "Unable to connect to hardware device. The device status is: %d" % self.caller.hwStatus) return None # Check destination Address if not checkPivxAddr(self.dest_addr, self.caller.isTestnetRPC): myPopUp_sb(self.caller, "crit", 'PET4L - PIVX address check', "The destination address is missing, or invalid.") return None # LET'S GO if self.selectedRewards: printDbg("Sending from PIVX address %s to PIVX address %s " % (self.curr_addr, self.dest_addr)) printDbg("Preparing transaction. Please wait...") try: self.ui.loadingLine.show() self.ui.loadingLinePercent.show() QApplication.processEvents() self.currFee = self.ui.feeLine.value() * 1e8 # save last destination address and swiftxCheck to cache and persist to settings self.caller.parent.cache["lastAddress"] = persistCacheSetting( 'cache_lastAddress', self.dest_addr) self.caller.parent.cache["useSwiftX"] = persistCacheSetting( 'cache_useSwiftX', self.useSwiftX()) self.currFee = self.ui.feeLine.value() * 1e8 # re-connect signals try: self.caller.hwdevice.api.sigTxdone.disconnect() except: pass try: self.caller.hwdevice.api.sigTxabort.disconnect() except: pass try: self.caller.hwdevice.api.tx_progress.disconnect() except: pass self.caller.hwdevice.api.sigTxdone.connect(self.FinishSend) self.caller.hwdevice.api.sigTxabort.connect(self.AbortSend) self.caller.hwdevice.api.tx_progress.connect( self.updateProgressPercent) try: self.txFinished = False self.caller.hwdevice.prepare_transfer_tx( self.caller, self.curr_path, self.selectedRewards, self.dest_addr, self.currFee, self.useSwiftX(), self.caller.isTestnetRPC) except DisconnectedException as e: self.caller.hwStatus = 0 self.caller.updateHWleds() except Exception as e: err_msg = "Error while preparing transaction. <br>" err_msg += "Probably Blockchain wasn't synced when trying to fetch raw TXs.<br>" err_msg += "<b>Wait for full synchronization</b> then hit 'Clear/Reload'" printException(getCallerName(), getFunctionName(), err_msg, e.args) except Exception as e: print(e) else: myPopUp_sb(self.caller, "warn", 'Transaction NOT sent', "No UTXO to send") def removeSpentRewards(self): for utxo in self.selectedRewards: self.caller.parent.db.deleteReward(utxo['txid'], utxo['vout']) # Activated by signal sigTxdone from hwdevice def FinishSend(self, serialized_tx, amount_to_send): self.AbortSend() if not self.txFinished: try: self.txFinished = True tx_hex = serialized_tx.hex() printDbg("Raw signed transaction: " + tx_hex) printDbg("Amount to send :" + amount_to_send) if len(tx_hex) > 90000: mess = "Transaction's length exceeds 90000 bytes. Select less UTXOs and try again." self.caller.myPopUp2(QMessageBox.Warning, 'transaction Warning', mess) else: decodedTx = None try: decodedTx = ParseTx(tx_hex, self.caller.isTestnetRPC) destination = decodedTx.get("vout")[0].get( "scriptPubKey").get("addresses")[0] amount = decodedTx.get("vout")[0].get("value") message = '<p>Broadcast signed transaction?</p><p>Destination address:<br><b>%s</b></p>' % destination message += '<p>Amount: <b>%s</b> PIV<br>' % str( round(amount / 1e8, 8)) message += 'Fees: <b>%s</b> PIV <br>Size: <b>%d</b> Bytes</p>' % ( str(round(self.currFee / 1e8, 8)), len(tx_hex) / 2) except Exception as e: printException(getCallerName(), getFunctionName(), "decoding exception", str(e)) message = '<p>Unable to decode TX- Broadcast anyway?</p>' mess1 = QMessageBox(QMessageBox.Information, 'Send transaction', message) if decodedTx is not None: mess1.setDetailedText( json.dumps(decodedTx, indent=4, sort_keys=False)) mess1.setStandardButtons(QMessageBox.Yes | QMessageBox.No) reply = mess1.exec_() if reply == QMessageBox.Yes: txid = self.caller.rpcClient.sendRawTransaction( tx_hex, self.useSwiftX()) if txid is None: raise Exception( "Unable to send TX - connection to RPC server lost." ) printDbg("Transaction sent. ID: %s" % txid) mess2_text = "<p>Transaction successfully sent.</p>" mess2 = QMessageBox(QMessageBox.Information, 'transaction Sent', mess2_text) mess2.setDetailedText(txid) mess2.exec_() # remove spent rewards from DB self.removeSpentRewards() # reload utxos self.display_utxos() self.onCancel() else: myPopUp_sb(self.caller, "warn", 'Transaction NOT sent', "Transaction NOT sent") self.onCancel() except Exception as e: err_msg = "Exception in FinishSend" printException(getCallerName(), getFunctionName(), err_msg, e.args) # Activated by signal sigTxabort from hwdevice def AbortSend(self): self.ui.loadingLine.hide() self.ui.loadingLinePercent.setValue(0) self.ui.loadingLinePercent.hide() def updateFee(self): if self.useSwiftX(): self.ui.feeLine.setValue(0.01) self.ui.feeLine.setEnabled(False) else: self.ui.feeLine.setValue(self.suggestedFee) self.ui.feeLine.setEnabled(True) # Activated by signal tx_progress from hwdevice def updateProgressPercent(self, percent): self.ui.loadingLinePercent.setValue(percent) QApplication.processEvents() def updateSelection(self, clicked_item=None): total = 0 self.selectedRewards = self.getSelection() numOfInputs = len(self.selectedRewards) if numOfInputs: for i in range(0, numOfInputs): total += int(self.selectedRewards[i].get('satoshis')) # update suggested fee and selected rewards estimatedTxSize = (44 + numOfInputs * 148) * 1.0 / 1000 # kB feePerKb = self.caller.rpcClient.getFeePerKb() self.suggestedFee = round(feePerKb * estimatedTxSize, 8) printDbg("estimatedTxSize is %s kB" % str(estimatedTxSize)) printDbg("suggested fee is %s PIV (%s PIV/kB)" % (str(self.suggestedFee), str(feePerKb))) self.ui.selectedRewardsLine.setText(str(round(total / 1e8, 8))) else: self.ui.selectedRewardsLine.setText("") self.updateFee() def update_loading_utxos(self, percent): self.ui.resetStatusLabel( '<em><b style="color:purple">Checking explorer... %d%%</b></em>' % percent) def useSwiftX(self): return self.ui.swiftxCheck.isChecked()