class TabRewards(): 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.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.btn_sendRewards.clicked.connect(lambda: self.onSendRewards()) self.ui.btn_Cancel.clicked.connect(lambda: self.onCancel()) def display_utxos(self): try: if self.rewards is not None: def item(value): item = QTableWidgetItem(value) item.setTextAlignment(Qt.AlignCenter) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) return item self.ui.rewardsList.box.setRowCount(len(self.rewards)) for row, utxo in enumerate(self.rewards): txId = utxo.get('tx_hash', None) pivxAmount = round(int(utxo.get('value', 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('tx_ouput_n', None)))) self.ui.rewardsList.box.showRow(row) if len(self.rewards) > 0: self.ui.rewardsList.box.resizeColumnsToContents() self.ui.rewardsList.statusLabel.setVisible(False) self.ui.rewardsList.box.horizontalHeader().setSectionResizeMode(2, QHeaderView.Stretch) else: if not self.caller.rpcConnected: self.ui.rewardsList.statusLabel.setText('<b style="color:purple">PIVX wallet not connected</b>') else: if self.apiConnected: self.ui.rewardsList.statusLabel.setText('<b style="color:red">Found no UTXOs for %s</b>' % self.curr_addr) else: self.ui.rewardsList.statusLabel.setText('<b style="color:purple">Unable to connect to API provider</b>') self.ui.rewardsList.statusLabel.setVisible(True) except Exception as e: print(e) def getSelection(self): try: returnData = [] items = self.ui.rewardsList.box.selectedItems() # Save row indexes to a set to avoid repetition rows = set() for i in range(0, len(items)): row = items[i].row() rows.add(row) rowList = list(rows) return [self.rewards[row] for row in rowList] return returnData except Exception as e: print(e) @pyqtSlot() def loadSelection(self): # Check dongle printDbg("Checking HW device") if self.caller.hwStatus != 2: self.caller.myPopUp2(QMessageBox.Critical, '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() for i in range(spathFrom, spathTo+1): path = "44'/77'/%d'/%d/%d" % (hwAcc, intExt, i) address = self.caller.hwdevice.scanForAddress(path) try: balance = self.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): self.apiConnected = False try: if not self.caller.rpcConnected: self.rewards = [] printDbg('PIVX daemon not connected') else: try: if self.apiClient.getStatus() != 200: return self.apiConnected = True self.blockCount = self.caller.rpcClient.getBlockCount() self.rewards = self.apiClient.getAddressUtxos(self.curr_addr)['unspent_outputs'] for utxo in self.rewards: self.rawtransactions[utxo['tx_hash']] = self.caller.rpcClient.getRawTransaction(utxo['tx_hash']) except Exception as e: self.errorMsg = 'Error occurred while calling getaddressutxos method: ' + str(e) printDbg(self.errorMsg) except Exception as e: print(e) pass @pyqtSlot() def onCancel(self): self.ui.selectedRewardsLine.setText("0.0") self.ui.addySelect.setCurrentIndex(0) self.ui.destinationLine.setText('') self.ui.feeLine.setValue(MINIMUM_FEE) self.onChangeSelected() @pyqtSlot() def onChangeSelected(self): if self.ui.addySelect.currentIndex() >= 0: 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) @pyqtSlot() def onSelectAllRewards(self): self.ui.rewardsList.box.selectAll() self.updateSelection() @pyqtSlot() def onDeselectAllRewards(self): self.ui.rewardsList.box.clearSelection() self.updateSelection() @pyqtSlot() def onSendRewards(self): self.dest_addr = self.ui.destinationLine.text().strip() # Check dongle printDbg("Checking HW device") if self.caller.hwStatus != 2: self.caller.myPopUp2(QMessageBox.Critical, 'PET4L - hw device check', "Connect to HW device first") printDbg("Unable to connect - hw status: %d" % self.caller.hwStatus) return None # Check destination Address if not checkPivxAddr(self.dest_addr): self.caller.myPopUp2(QMessageBox.Critical, 'PET4L - PIVX address check', "Invalid Destination Address") return None # LET'S GO printDbg("Sending from PIVX address %s to PIVX address %s " % (self.curr_addr, self.dest_addr)) if self.selectedRewards: self.currFee = self.ui.feeLine.value() * 1e8 # connect signal self.caller.hwdevice.sigTxdone.connect(self.FinishSend) try: self.txFinished = False self.caller.hwdevice.prepare_transfer_tx(self.caller, self.curr_path, self.selectedRewards, self.dest_addr, self.currFee, self.rawtransactions) except Exception as e: err_msg = "Error while preparing transaction" printException(getCallerName(), getFunctionName(), err_msg, e.args) else: self.caller.myPopUp2(QMessageBox.Information, 'transaction NOT Sent', "No UTXO to send") # Activated by signal sigTxdone from hwdevice @pyqtSlot(bytearray, str) def FinishSend(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." self.caller.myPopUp2(QMessageBox.Warning, 'transaction Warning', mess) else: message = 'Broadcast signed transaction?<br><br>Destination address:<br><b>%s</b><br><br>' % (self.dest_addr) message += 'Amount to send: <b>%s PIV</b><br>' % amount_to_send message += 'Fee: <b>%s PIV</b><br>Size: <b>%d bytes</b>' % (str(round(self.currFee / 1e8, 8) ), len(tx_hex)/2) reply = self.caller.myPopUp(QMessageBox.Information, 'Send transaction', message) if reply == QMessageBox.Yes: txid = self.caller.rpcClient.sendRawTransaction(tx_hex) mess = QMessageBox(QMessageBox.Information, 'transaction Sent', 'transaction Sent') mess.setDetailedText(txid) mess.exec_() else: self.caller.myPopUp2(QMessageBox.Information, 'transaction NOT Sent', "transaction NOT sent") self.onCancel() except Exception as e: err_msg = "Exception in sendRewards" printException(getCallerName(), getFunctionName(), err_msg, e.args) 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('value')) # update suggested fee and selected rewards estimatedTxSize = (44+numOfInputs*148)*1.0 / 1000 # kB feePerKb = self.caller.rpcClient.getFeePerKb() suggestedFee = round(feePerKb * estimatedTxSize, 8) printDbg("estimatedTxSize is %s kB" % str(estimatedTxSize)) printDbg("suggested fee is %s PIV (%s PIV/kB)" % (str(suggestedFee), str(feePerKb))) self.ui.selectedRewardsLine.setText(str(round(total/1e8, 8))) self.ui.feeLine.setValue(suggestedFee) else: self.ui.selectedRewardsLine.setText("") self.ui.feeLine.setValue(MINIMUM_FEE)
class FindCollTx_dlg(QDialog): def __init__(self, main_wnd, rpcClient, pivx_addr): QDialog.__init__(self, parent=main_wnd) self.main_wnd = main_wnd self.rpcClient = rpcClient self.apiClient = ApiClient() self.pivx_addr = pivx_addr self.utxos = [] self.blockCount = 0 self.setupUI() def setupUI(self): Ui_FindCollateralTxDlg.setupUi(self, self) self.setWindowTitle('Find Collateral Tx') ##--- PIVX Address self.edtAddress.setText(self.pivx_addr) ##--- feedback self.lblMessage.setVisible(False) self.lblMessage.setVisible(True) self.lblMessage.setText('Checking explorer...') ##--- Load UTXOs self.runInThread = ThreadFuns.runInThread self.runInThread(self.load_utxos_thread, (), self.display_utxos) def display_utxos(self): def item(value): item = QTableWidgetItem(value) item.setTextAlignment(Qt.AlignCenter) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) return item self.tableW.setRowCount(len(self.utxos)) for row, utxo in enumerate(self.utxos): pivxAmount = round(int(utxo.get('value', 0)) / 1e8, 8) self.tableW.setItem(row, 0, item(str(pivxAmount))) self.tableW.setItem(row, 1, item(str(utxo['confirmations']))) self.tableW.setItem(row, 2, item(utxo.get('tx_hash', None))) self.tableW.setItem(row, 3, item(str(utxo.get('tx_ouput_n', None)))) if len(self.utxos): self.tableW.resizeColumnsToContents() self.lblMessage.setVisible(False) self.tableW.horizontalHeader().setSectionResizeMode( 2, QHeaderView.Stretch) else: if self.apiConnected: self.lblMessage.setText( '<b style="color:red">Found no unspent transactions with 10000 PIVs ' 'amount sent to address %s.</b> (or explorer offline - try manually)' % self.pivx_addr) else: self.lblMessage.setText( '<b style="color:purple">Unable to connect to API provider.\nEnter tx manually</b>' ) self.lblMessage.setVisible(True) def load_utxos_thread(self, ctrl): self.apiConnected = False try: if not self.rpcClient.getStatus(): printDbg('PIVX daemon not connected') else: try: if self.apiClient.getStatus() != 200: return self.apiConnected = True self.blockCount = self.rpcClient.getBlockCount() utxos = self.apiClient.getAddressUtxos( self.pivx_addr)['unspent_outputs'] printDbg("loading utxos\nblockCount=%s\n%s" % (str(self.blockCount), str(self.utxos))) self.utxos = [ utxo for utxo in utxos if round(int(utxo.get('value', 0)) / 1e8, 8) == 10000.00000000 ] except Exception as e: self.errorMsg = 'Error occurred while calling getaddressutxos method: ' + str( e) print(self.errorMsg) except Exception as e: print(e) pass def getSelection(self): items = self.tableW.selectedItems() if len(items): row = items[0].row() return self.utxos[row]['tx_hash'], self.utxos[row]['tx_ouput_n'] else: return None, 0
class TabRewards(): 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 display_utxos(self): if self.rewards is not None: def item(value): item = QTableWidgetItem(value) item.setTextAlignment(Qt.AlignCenter) item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable) return item self.ui.rewardsList.box.setRowCount(len(self.rewards)) for row, utxo in enumerate(self.rewards): txId = utxo.get('tx_hash', None) pivxAmount = round(int(utxo.get('value', 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('tx_ouput_n', None)))) self.ui.rewardsList.box.showRow(row) # 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 if self.ui.rewardsList.box.collateralRow is not None: self.ui.rewardsList.box.hideRow( self.ui.rewardsList.box.collateralRow) if len(self.rewards) > 0: self.ui.rewardsList.box.resizeColumnsToContents() self.ui.rewardsList.statusLabel.setVisible(False) self.ui.rewardsList.box.horizontalHeader( ).setSectionResizeMode(2, QHeaderView.Stretch) else: if not self.caller.rpcConnected: self.ui.rewardsList.statusLabel.setText( '<b style="color:purple">PIVX wallet not connected</b>' ) else: if self.apiConnected: self.ui.rewardsList.statusLabel.setText( '<b style="color:red">Found no Rewards for %s</b>' % self.curr_addr) else: self.ui.rewardsList.statusLabel.setText( '<b style="color:purple">Unable to connect to API provider</b>' ) self.ui.rewardsList.statusLabel.setVisible(True) def getSelection(self): try: returnData = [] items = self.ui.rewardsList.box.selectedItems() # Save row indexes to a set to avoid repetition rows = set() for i in range(0, len(items)): row = items[i].row() rows.add(row) rowList = list(rows) return [self.rewards[row] for row in rowList] return returnData except Exception as e: print(e) def loadMnSelect(self): try: self.ui.mnSelect.clear() for x in self.caller.masternode_list: name = x['name'] address = x['collateral'].get('address') txid = x['collateral'].get('txid') hwAcc = x['hwAcc'] spath = x['collateral'].get('spath') path = MPATH + "%d'/0/%d" % (hwAcc, spath) self.ui.mnSelect.addItem(name, [address, txid, path]) except Exception as e: print(e) def load_utxos_thread(self, ctrl): self.apiConnected = False try: if not self.caller.rpcConnected: self.rewards = [] printDbg('PIVX daemon not connected') else: try: if self.apiClient.getStatus() != 200: return self.apiConnected = True self.blockCount = self.caller.rpcClient.getBlockCount() self.rewards = self.apiClient.getAddressUtxos( self.curr_addr)['unspent_outputs'] for utxo in self.rewards: self.rawtransactions[utxo[ 'tx_hash']] = self.caller.rpcClient.getRawTransaction( utxo['tx_hash']) except Exception as e: self.errorMsg = 'Error occurred while calling getaddressutxos method: ' + str( e) printDbg(self.errorMsg) except Exception as e: print(e) pass @pyqtSlot() def onCancel(self): self.ui.selectedRewardsLine.setText("0.0") self.ui.mnSelect.setCurrentIndex(0) self.ui.destinationLine.setText('') self.ui.feeLine.setValue(MINIMUM_FEE) self.ui.btn_toggleCollateral.setText("Show Collateral") self.ui.collateralHidden = True self.onChangeSelectedMN() @pyqtSlot() def onChangeSelectedMN(self): if self.ui.mnSelect.currentIndex() >= 0: 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_path = self.ui.mnSelect.itemData( self.ui.mnSelect.currentIndex())[2] if self.curr_addr is not None: result = self.apiClient.getBalance(self.curr_addr) self.ui.addrAvailLine.setText("<i>%s PIVs</i>" % result) self.ui.selectedRewardsLine.setText("0.0") self.ui.rewardsList.box.clearSelection() self.ui.rewardsList.box.collateralRow = None self.ui.collateralHidden = True self.ui.btn_toggleCollateral.setText("Show Collateral") if result is not None: self.runInThread = ThreadFuns.runInThread( self.load_utxos_thread, (), self.display_utxos) @pyqtSlot() def onSelectAllRewards(self): self.ui.rewardsList.box.selectAll() self.updateSelection() @pyqtSlot() def onDeselectAllRewards(self): self.ui.rewardsList.box.clearSelection() self.updateSelection() @pyqtSlot() def onSendRewards(self): self.dest_addr = self.ui.destinationLine.text().strip() # Check dongle printDbg("Checking HW device") if self.caller.hwStatus != 2: self.caller.myPopUp2(QMessageBox.Critical, 'SPMT - hw device check', "Connect to HW device first") printDbg("Unable to connect - hw status: %d" % self.caller.hwStatus) return None # Check destination Address if not checkPivxAddr(self.dest_addr): self.caller.myPopUp2(QMessageBox.Critical, 'SPMT - PIVX address check', "Invalid Destination Address") return None # 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 = self.caller.myPopUp(QMessageBox.Warning, 'SPMT - warning', warning1) if ans == QMessageBox.No: return None else: ans2 = self.caller.myPopUp(QMessageBox.Warning, 'SPMT - warning', warning2) if ans2 == QMessageBox.No: return None else: ans3 = self.caller.myPopUp(QMessageBox.Critical, 'SPMT - warning', warning3) if ans3 == QMessageBox.No: 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...") self.currFee = self.ui.feeLine.value() * 1e8 # connect signal self.caller.hwdevice.sigTxdone.connect(self.FinishSend) try: self.txFinished = False self.caller.hwdevice.prepare_transfer_tx( self.caller, self.curr_path, self.selectedRewards, self.dest_addr, self.currFee, self.rawtransactions) 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) else: self.caller.myPopUp2(QMessageBox.Information, 'transaction NOT Sent', "No UTXO to send") @pyqtSlot() def onToggleCollateral(self): if (self.rewards is not None): if len(self.rewards ) and self.ui.rewardsList.box.collateralRow is not None: if not self.ui.collateralHidden: try: 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 while preparing transaction" 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: self.caller.myPopUp2(QMessageBox.Information, 'No Collateral', "No collateral selected") # Activated by signal sigTxdone from hwdevice #@pyqtSlot(bytearray, str) def FinishSend(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." self.caller.myPopUp2(QMessageBox.Warning, 'transaction Warning', mess) else: decodedTx = self.caller.rpcClient.decodeRawTransaction( tx_hex) destination = decodedTx.get("vout")[0].get( "scriptPubKey").get("addresses")[0] amount = decodedTx.get("vout")[0].get("value") message = 'Broadcast signed transaction?<br><br>Destination address:<br><b>%s</b><br><br>' % destination message += 'Amount to send: <b>%s PIV</b><br>' % str( amount) message += 'Fee: <b>%s PIV</b><br>Size: <b>%d bytes</b>' % ( str(round(self.currFee / 1e8, 8)), len(tx_hex) / 2) mess1 = QMessageBox(QMessageBox.Information, 'Send transaction', message) 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) mess2_text = "Transaction sent.<br><b>NOTE:</b> selected UTXOs will not disappear from the list until TX is confirmed" mess2 = QMessageBox(QMessageBox.Information, 'transaction Sent', mess2_text) mess2.setDetailedText(txid) mess2.exec_() self.onCancel() else: self.caller.myPopUp2(QMessageBox.Information, 'transaction NOT Sent', "transaction NOT sent") self.onCancel() except Exception as e: err_msg = "Exception in sendRewards" printException(getCallerName(), getFunctionName(), err_msg, e.args) 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('value')) # update suggested fee and selected rewards estimatedTxSize = (44 + numOfInputs * 148) * 1.0 / 1000 # kB feePerKb = self.caller.rpcClient.getFeePerKb() suggestedFee = round(feePerKb * estimatedTxSize, 8) printDbg("estimatedTxSize is %s kB" % str(estimatedTxSize)) printDbg("suggested fee is %s PIV (%s PIV/kB)" % (str(suggestedFee), str(feePerKb))) self.ui.selectedRewardsLine.setText(str(round(total / 1e8, 8))) self.ui.feeLine.setValue(suggestedFee) else: self.ui.selectedRewardsLine.setText("") self.ui.feeLine.setValue(MINIMUM_FEE)