From 590c56ec8581e100b2870850fdecb10fbeb42d1b Mon Sep 17 00:00:00 2001
From: vtexier <vit@free.fr>
Date: Fri, 27 Mar 2020 18:34:19 +0100
Subject: [PATCH] [enh] #798 add context_menu "Send as source" and handle
 source in transfer GUI

---
 src/sakia/data/processors/sources.py          |   3 +
 .../gui/navigation/txhistory/controller.py    |  10 +-
 src/sakia/gui/sub/password_input/view.py      |   4 +
 src/sakia/gui/sub/transfer/controller.py      | 132 ++++++++------
 src/sakia/gui/sub/transfer/model.py           |  13 +-
 src/sakia/gui/sub/transfer/transfer.ui        | 166 ++++++++++++++++--
 src/sakia/gui/widgets/context_menu.py         |  29 ++-
 src/sakia/services/documents.py               |  56 +++---
 src/sakia/services/sources.py                 |   3 +
 9 files changed, 320 insertions(+), 96 deletions(-)

diff --git a/src/sakia/data/processors/sources.py b/src/sakia/data/processors/sources.py
index 4750ff56..0ca1812d 100644
--- a/src/sakia/data/processors/sources.py
+++ b/src/sakia/data/processors/sources.py
@@ -35,6 +35,9 @@ class SourcesProcessor:
         except sqlite3.IntegrityError:
             self._logger.debug("Source already known: {0}".format(source.identifier))
 
+    def get_one(self, **search):
+        return self._repo.get_one(**search)
+
     def amount(self, currency, pubkey):
         """
         Get the amount value of the sources for a given pubkey
diff --git a/src/sakia/gui/navigation/txhistory/controller.py b/src/sakia/gui/navigation/txhistory/controller.py
index f30362b0..2bbb639e 100644
--- a/src/sakia/gui/navigation/txhistory/controller.py
+++ b/src/sakia/gui/navigation/txhistory/controller.py
@@ -55,8 +55,8 @@ class TxHistoryController(QObject):
         sources_service,
     ):
 
-        transfer = TransferController.integrate_to_main_view(None, app, connection)
-        view = TxHistoryView(parent.view, transfer.view)
+        controller = TransferController.integrate_to_main_view(None, app, connection)
+        view = TxHistoryView(parent.view, controller.view)
         model = TxHistoryModel(
             None,
             app,
@@ -66,14 +66,14 @@ class TxHistoryController(QObject):
             transactions_service,
             sources_service,
         )
-        txhistory = cls(view, model, transfer)
+        txhistory = cls(view, model, controller)
         model.setParent(txhistory)
         app.referential_changed.connect(txhistory.refresh_balance)
         app.sources_refreshed.connect(txhistory.refresh_balance)
         txhistory.view_in_wot.connect(app.view_in_wot)
         txhistory.view.spin_page.valueChanged.connect(model.change_page)
-        transfer.accepted.connect(view.clear)
-        transfer.rejected.connect(view.clear)
+        controller.accepted.connect(view.clear)
+        controller.rejected.connect(view.clear)
         return txhistory
 
     def refresh_minimum_maximum(self):
diff --git a/src/sakia/gui/sub/password_input/view.py b/src/sakia/gui/sub/password_input/view.py
index 97ba9bdd..0e016bd6 100644
--- a/src/sakia/gui/sub/password_input/view.py
+++ b/src/sakia/gui/sub/password_input/view.py
@@ -20,6 +20,10 @@ class PasswordInputView(QWidget, Ui_PasswordInputWidget):
         self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)
         self.layout().addWidget(self.button_box)
         self.button_box.hide()
+        font = self.edit_secret_key.font()
+        font.setBold(False)
+        self.edit_secret_key.setFont(font)
+        self.edit_password.setFont(font)
 
     def error(self, text):
         self.label_info.setText(text)
diff --git a/src/sakia/gui/sub/transfer/controller.py b/src/sakia/gui/sub/transfer/controller.py
index e2d64c04..1528e3cf 100644
--- a/src/sakia/gui/sub/transfer/controller.py
+++ b/src/sakia/gui/sub/transfer/controller.py
@@ -70,51 +70,69 @@ class TransferController(QObject):
             password_input.view,
         )
         model = TransferModel(app)
-        transfer = cls(view, model, search_user, user_information, password_input)
+        controller = cls(view, model, search_user, user_information, password_input)
 
         search_user.identity_selected.connect(user_information.search_identity)
 
-        view.set_keys(transfer.model.available_connections())
-        view.set_contacts(transfer.model.contacts())
+        view.set_keys(controller.model.available_connections())
+        view.set_contacts(controller.model.contacts())
         app.new_connection.connect(view.add_key)
         app.connection_removed.connect(view.remove_key)
 
-        return transfer
+        return controller
 
     @classmethod
     def integrate_to_main_view(cls, parent, app, connection):
-        transfer = cls.create(parent, app)
-        transfer.view.combo_connections.setCurrentText(connection.title())
-        transfer.view.radio_pubkey.toggle()
-        transfer.view.groupbox_connection.hide()
-        transfer.view.label_total.hide()
-        return transfer
+        controller = cls.create(parent, app)
+        controller.view.combo_connections.setCurrentText(connection.title())
+        controller.view.radio_pubkey.toggle()
+        controller.view.label_connections.hide()
+        controller.view.combo_connections.hide()
+        controller.view.label_total.hide()
+        return controller
 
     @classmethod
-    def open_transfer_with_pubkey(cls, parent, app, connection, pubkey):
-        transfer = cls.create(parent, app)
-        transfer.view.groupbox_connection.show()
+    def open_transfer_with_pubkey(cls, parent, app, connection, pubkey, source):
+        controller = cls.create(parent, app)
+        controller.view.label_connections.show()
+        controller.view.combo_connections.show()
         if connection:
-            transfer.view.combo_connections.setCurrentText(connection.title())
-        transfer.view.edit_pubkey.setText(pubkey)
-        transfer.view.radio_pubkey.setChecked(True)
-        transfer.view.radio_pubkey.toggle()
-
-        transfer.refresh()
-        return transfer
+            controller.view.combo_connections.setCurrentText(connection.title())
+        if pubkey:
+            controller.view.edit_pubkey.setText(pubkey)
+            controller.view.radio_pubkey.setChecked(True)
+            controller.view.radio_pubkey.toggle()
+        else:
+            controller.view.radio_local_key.setChecked(True)
+            controller.view.radio_local_key.toggle()
+        if source:
+            controller.model.current_source = source
+            controller.view.label_source_identifier.setText(
+                "{}:{}".format(source.identifier, source.noffset)
+            )
+            amount = source.amount * (10 ** source.base) / 100
+            controller.view.spinbox_amount.setValue(amount)
+            controller.view.spinbox_amount.setDisabled(True)
+            controller.view.spinbox_relative.setDisabled(True)
+            controller.view.button_source_check.setEnabled(True)
+
+        controller.refresh()
+        return controller
 
     @classmethod
-    def send_money_to_pubkey(cls, parent, app, connection, pubkey):
+    def send_money_to_pubkey(cls, parent, app, connection, pubkey, source):
         dialog = QDialog(parent)
         dialog.setWindowTitle(
             QCoreApplication.translate("TransferController", "Transfer")
         )
         dialog.setLayout(QVBoxLayout(dialog))
-        transfer = cls.open_transfer_with_pubkey(parent, app, connection, pubkey)
+        controller = cls.open_transfer_with_pubkey(
+            parent, app, connection, pubkey, source
+        )
 
-        dialog.layout().addWidget(transfer.view)
-        transfer.accepted.connect(dialog.accept)
-        transfer.rejected.connect(dialog.reject)
+        dialog.layout().addWidget(controller.view)
+        controller.accepted.connect(dialog.accept)
+        controller.rejected.connect(dialog.reject)
         return dialog.exec()
 
     @classmethod
@@ -124,14 +142,14 @@ class TransferController(QObject):
             QCoreApplication.translate("TransferController", "Transfer")
         )
         dialog.setLayout(QVBoxLayout(dialog))
-        transfer = cls.open_transfer_with_pubkey(
-            parent, app, connection, identity.pubkey
+        controller = cls.open_transfer_with_pubkey(
+            parent, app, connection, identity.pubkey, None
         )
 
-        transfer.user_information.change_identity(identity)
-        dialog.layout().addWidget(transfer.view)
-        transfer.accepted.connect(dialog.accept)
-        transfer.rejected.connect(dialog.reject)
+        controller.user_information.change_identity(identity)
+        dialog.layout().addWidget(controller.view)
+        controller.accepted.connect(dialog.accept)
+        controller.rejected.connect(dialog.reject)
         return dialog.exec()
 
     @classmethod
@@ -141,34 +159,35 @@ class TransferController(QObject):
             QCoreApplication.translate("TransferController", "Transfer")
         )
         dialog.setLayout(QVBoxLayout(dialog))
-        transfer = cls.create(parent, app)
-        transfer.view.groupbox_connection.show()
-        transfer.view.label_total.show()
-        transfer.view.combo_connections.setCurrentText(connection.title())
-        transfer.view.edit_pubkey.setText(resent_transfer.receivers[0])
-        transfer.view.radio_pubkey.setChecked(True)
+        controller = cls.create(parent, app)
+        controller.view.label_connections.show()
+        controller.view.combo_connections.show()
+        controller.view.label_total.show()
+        controller.view.combo_connections.setCurrentText(connection.title())
+        controller.view.edit_pubkey.setText(resent_transfer.receivers[0])
+        controller.view.radio_pubkey.setChecked(True)
 
-        transfer.refresh()
+        controller.refresh()
 
-        current_base = transfer.model.current_base()
+        current_base = controller.model.current_base()
         current_base_amount = resent_transfer.amount / pow(
             10, resent_transfer.amount_base - current_base
         )
 
-        relative = transfer.model.quant_to_rel(current_base_amount / 100)
-        transfer.view.set_spinboxes_parameters(current_base_amount / 100, relative)
-        transfer.view.change_relative_amount(relative)
-        transfer.view.change_quantitative_amount(current_base_amount / 100)
+        relative = controller.model.quant_to_rel(current_base_amount / 100)
+        controller.view.set_spinboxes_parameters(current_base_amount / 100, relative)
+        controller.view.change_relative_amount(relative)
+        controller.view.change_quantitative_amount(current_base_amount / 100)
 
         connections_processor = ConnectionsProcessor.instanciate(app)
         wallet_index = connections_processor.connections().index(connection)
-        transfer.view.combo_connections.setCurrentIndex(wallet_index)
-        transfer.view.edit_pubkey.setText(resent_transfer.receivers[0])
-        transfer.view.radio_pubkey.toggle()
-        transfer.view.edit_message.setText(resent_transfer.comment)
-        dialog.layout().addWidget(transfer.view)
-        transfer.accepted.connect(dialog.accept)
-        transfer.rejected.connect(dialog.reject)
+        controller.view.combo_connections.setCurrentIndex(wallet_index)
+        controller.view.edit_pubkey.setText(resent_transfer.receivers[0])
+        controller.view.radio_pubkey.toggle()
+        controller.view.edit_message.setText(resent_transfer.comment)
+        dialog.layout().addWidget(controller.view)
+        controller.accepted.connect(dialog.accept)
+        controller.rejected.connect(dialog.reject)
         return dialog.exec()
 
     def valid_crc_pubkey(self):
@@ -215,7 +234,8 @@ class TransferController(QObject):
     async def accept(self):
         logging.debug("Accept transfer action...")
         self.view.button_box.setEnabled(False)
-        comment = self.view.edit_message.text()
+
+        source = self.model.current_source
 
         logging.debug("checking recipient mode...")
         recipient = self.selected_pubkey()
@@ -228,11 +248,19 @@ class TransferController(QObject):
         logging.debug("Setting cursor...")
         QApplication.setOverrideCursor(Qt.WaitCursor)
 
+        comment = self.view.edit_message.text()
         lock_mode = self.view.combo_locks.currentIndex()
 
         logging.debug("Send money...")
         result, transactions = await self.model.send_money(
-            recipient, secret_key, password, amount, amount_base, comment, lock_mode
+            recipient,
+            secret_key,
+            password,
+            amount,
+            amount_base,
+            comment,
+            lock_mode,
+            source,
         )
         if result[0]:
             await self.view.show_success(self.model.notifications(), recipient)
diff --git a/src/sakia/gui/sub/transfer/model.py b/src/sakia/gui/sub/transfer/model.py
index 071c02bc..7b2c632d 100644
--- a/src/sakia/gui/sub/transfer/model.py
+++ b/src/sakia/gui/sub/transfer/model.py
@@ -21,6 +21,7 @@ class TransferModel(QObject):
     app = attr.ib()
     connection = attr.ib(default=None)
     resent_transfer = attr.ib(default=None)
+    current_source = attr.ib(default=None)
     _blockchain_processor = attr.ib(default=None)
     _sources_processor = attr.ib(default=None)
     _connections_processor = attr.ib(default=None)
@@ -90,7 +91,15 @@ class TransferModel(QObject):
         self.connection = connections[index]
 
     async def send_money(
-        self, recipient, secret_key, password, amount, amount_base, comment, lock_mode
+        self,
+        recipient,
+        secret_key,
+        password,
+        amount,
+        amount_base,
+        comment,
+        lock_mode,
+        source,
     ):
         """
         Send money to given recipient using the account
@@ -102,6 +111,7 @@ class TransferModel(QObject):
         :param int amount_base:
         :param str comment:
         :param int lock_mode:
+        :param Source source:
         :return: the result of the send
         """
 
@@ -114,6 +124,7 @@ class TransferModel(QObject):
             amount_base,
             comment,
             lock_mode,
+            source,
         )
         for transaction in transactions:
             self.app.sources_service.parse_transaction_outputs(
diff --git a/src/sakia/gui/sub/transfer/transfer.ui b/src/sakia/gui/sub/transfer/transfer.ui
index 8a5da344..7c201ad5 100644
--- a/src/sakia/gui/sub/transfer/transfer.ui
+++ b/src/sakia/gui/sub/transfer/transfer.ui
@@ -15,22 +15,98 @@
   </property>
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
-    <widget class="QGroupBox" name="groupbox_connection">
-     <property name="title">
-      <string>Select account</string>
+    <layout class="QHBoxLayout" name="layout_connections">
+     <property name="sizeConstraint">
+      <enum>QLayout::SetDefaultConstraint</enum>
      </property>
-     <layout class="QHBoxLayout" name="horizontalLayout_4">
-      <item>
-       <widget class="QComboBox" name="combo_connections"/>
-      </item>
-     </layout>
-    </widget>
+     <item>
+      <widget class="QLabel" name="label_connections">
+       <property name="font">
+        <font>
+         <weight>75</weight>
+         <bold>true</bold>
+        </font>
+       </property>
+       <property name="text">
+        <string>Select account</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="combo_connections">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_4">
+     <property name="spacing">
+      <number>6</number>
+     </property>
+     <item>
+      <widget class="QLabel" name="label_source">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <weight>75</weight>
+         <bold>true</bold>
+        </font>
+       </property>
+       <property name="text">
+        <string>Source</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLabel" name="label_source_identifier">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Automatic</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="button_source_check">
+       <property name="enabled">
+        <bool>false</bool>
+       </property>
+       <property name="text">
+        <string>Check</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
    </item>
    <item>
     <widget class="QGroupBox" name="group_box_recipient">
+     <property name="font">
+      <font>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
      <property name="title">
       <string>Transfer money to</string>
      </property>
+     <property name="flat">
+      <bool>false</bool>
+     </property>
      <layout class="QHBoxLayout" name="horizontalLayout_5">
       <item>
        <layout class="QVBoxLayout" name="verticalLayout_4">
@@ -50,6 +126,12 @@
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
+            <property name="font">
+             <font>
+              <weight>75</weight>
+              <bold>true</bold>
+             </font>
+            </property>
             <property name="text">
              <string>&amp;Recipient public key</string>
             </property>
@@ -85,6 +167,12 @@
               <verstretch>0</verstretch>
              </sizepolicy>
             </property>
+            <property name="font">
+             <font>
+              <weight>50</weight>
+              <bold>false</bold>
+             </font>
+            </property>
             <property name="inputMask">
              <string/>
             </property>
@@ -147,7 +235,14 @@
            </widget>
           </item>
           <item>
-           <widget class="QComboBox" name="combo_local_keys"/>
+           <widget class="QComboBox" name="combo_local_keys">
+            <property name="font">
+             <font>
+              <weight>50</weight>
+              <bold>false</bold>
+             </font>
+            </property>
+           </widget>
           </item>
          </layout>
         </item>
@@ -164,7 +259,14 @@
            </widget>
           </item>
           <item>
-           <widget class="QComboBox" name="combo_contacts"/>
+           <widget class="QComboBox" name="combo_contacts">
+            <property name="font">
+             <font>
+              <weight>50</weight>
+              <bold>false</bold>
+             </font>
+            </property>
+           </widget>
           </item>
          </layout>
         </item>
@@ -184,6 +286,12 @@
      <layout class="QVBoxLayout" name="verticalLayout_8">
       <item>
        <widget class="QLabel" name="label_total">
+        <property name="font">
+         <font>
+          <weight>75</weight>
+          <bold>true</bold>
+         </font>
+        </property>
         <property name="text">
          <string>Available money: </string>
         </property>
@@ -193,6 +301,12 @@
        <layout class="QHBoxLayout" name="horizontalLayout_3">
         <item>
          <widget class="QLabel" name="label_3">
+          <property name="font">
+           <font>
+            <weight>75</weight>
+            <bold>true</bold>
+           </font>
+          </property>
           <property name="text">
            <string>Amount</string>
           </property>
@@ -251,6 +365,12 @@
     <layout class="QHBoxLayout" name="horizontalLayout_7">
      <item>
       <widget class="QGroupBox" name="groupBox_3">
+       <property name="font">
+        <font>
+         <weight>75</weight>
+         <bold>true</bold>
+        </font>
+       </property>
        <property name="title">
         <string>Message</string>
        </property>
@@ -273,12 +393,24 @@
          <verstretch>0</verstretch>
         </sizepolicy>
        </property>
+       <property name="font">
+        <font>
+         <weight>75</weight>
+         <bold>true</bold>
+        </font>
+       </property>
        <property name="title">
         <string>Spend condition</string>
        </property>
        <layout class="QHBoxLayout" name="horizontalLayout_9">
         <item>
          <widget class="QComboBox" name="combo_locks">
+          <property name="font">
+           <font>
+            <weight>50</weight>
+            <bold>false</bold>
+           </font>
+          </property>
           <item>
            <property name="text">
             <string>Receiver signature</string>
@@ -298,6 +430,18 @@
    </item>
    <item>
     <widget class="QGroupBox" name="groupBox">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="font">
+      <font>
+       <weight>75</weight>
+       <bold>true</bold>
+      </font>
+     </property>
      <property name="title">
       <string>Secret Key / Password</string>
      </property>
diff --git a/src/sakia/gui/widgets/context_menu.py b/src/sakia/gui/widgets/context_menu.py
index 6f01348d..d7f6709f 100644
--- a/src/sakia/gui/widgets/context_menu.py
+++ b/src/sakia/gui/widgets/context_menu.py
@@ -5,7 +5,9 @@ from PyQt5.QtWidgets import QMenu, QAction, QApplication, QMessageBox
 
 from duniterpy.constants import PUBKEY_REGEX
 from duniterpy.documents.crc_pubkey import CRCPubkey
-from sakia.data.entities import Identity, Transaction, Dividend
+
+from sakia.app import Application
+from sakia.data.entities import Identity, Transaction, Dividend, Connection
 from sakia.data.processors import BlockchainProcessor, TransactionsProcessor
 from sakia.decorators import asyncify
 from sakia.gui.sub.certification.controller import CertificationController
@@ -17,7 +19,7 @@ class ContextMenu(QObject):
     view_identity_in_wot = pyqtSignal(object)
     identity_information_loaded = pyqtSignal(Identity)
 
-    def __init__(self, qmenu, app, connection):
+    def __init__(self, qmenu: QMenu, app: Application, connection: Connection):
         """
         :param PyQt5.QtWidgets.QMenu: the qmenu widget
         :param sakia.app.Application app: Application instance
@@ -25,7 +27,7 @@ class ContextMenu(QObject):
         """
         super().__init__()
         self.qmenu = qmenu
-        self._app = app
+        self._app = app  # type: Application
         self._connection = connection
 
     @staticmethod
@@ -130,6 +132,16 @@ class ContextMenu(QObject):
             )
             menu.qmenu.addAction(cancel)
 
+        # if special lock condition on transaction...
+        if transfer.conditions is not None:
+            send_as_source = QAction(
+                QCoreApplication.translate("ContextMenu", "Send as source"),
+                menu.qmenu.parent(),
+            )
+            send_as_source.triggered.connect(
+                lambda checked, tr=transfer: menu.send_as_source(tr)
+            )
+            menu.qmenu.addAction(send_as_source)
         if menu._app.parameters.expert_mode:
             copy_doc = QAction(
                 QCoreApplication.translate(
@@ -250,7 +262,7 @@ class ContextMenu(QObject):
             )
         else:
             TransferController.send_money_to_pubkey(
-                None, self._app, self._connection, identity_or_pubkey
+                None, self._app, self._connection, identity_or_pubkey, None
             )
 
     def view_wot(self, identity):
@@ -286,6 +298,15 @@ This money transfer will be removed and not sent.""",
             self._app.db.commit()
             self._app.transaction_state_changed.emit(transfer)
 
+    def send_as_source(self, transfer: Transaction):
+        # get source from conditions and transaction hash
+        source = self._app.sources_service.get_one(
+            identifier=transfer.sha_hash, conditions=transfer.conditions
+        )
+        TransferController.send_money_to_pubkey(
+            None, self._app, self._connection, None, source
+        )
+
     def copy_transaction_to_clipboard(self, tx):
         clipboard = QApplication.clipboard()
         clipboard.setText(tx.raw)
diff --git a/src/sakia/services/documents.py b/src/sakia/services/documents.py
index e2805005..afab7abe 100644
--- a/src/sakia/services/documents.py
+++ b/src/sakia/services/documents.py
@@ -494,10 +494,10 @@ class DocumentsService:
         message,
         currency,
         lock_mode=0,
+        source=None,
     ):
         """
         Prepare a simple Transaction document
-        :param int lock_mode: Lock condition mode selected in combo box
         :param SigningKey key: the issuer of the transaction
         :param str receiver: the target of the transaction
         :param duniterpy.documents.BlockUID blockstamp: the blockstamp
@@ -505,32 +505,39 @@ class DocumentsService:
         :param int amount_base: the amount base of the currency
         :param str message: the comment of the tx
         :param str currency: the target community
+        :param int lock_mode: Lock condition mode selected in combo box
+        :param Source source: Source instance or None
         :return: the transaction document
         :rtype: List[sakia.data.entities.Transaction]
         """
         forged_tx = []
-        sources = [None] * 41
-        while len(sources) > 40:
-            result = self.tx_sources(int(amount), amount_base, currency, key)
-            sources = result[0]
-            computed_outputs = result[1]
-            overheads = result[2]
-            # Fix issue #594
-            if len(sources) > 40:
-                sources_value = 0
-                for s in sources[:39]:
-                    sources_value += s.amount * (10 ** s.base)
-                sources_value, sources_base = reduce_base(sources_value, 0)
-                chained_tx = self.prepare_tx(
-                    key,
-                    key.pubkey,
-                    blockstamp,
-                    sources_value,
-                    sources_base,
-                    "[CHAINED]",
-                    currency,
-                )
-                forged_tx += chained_tx
+        if source is None:
+            # automatic selection of sources
+            sources = [None] * 41
+            while len(sources) > 40:
+                result = self.tx_sources(int(amount), amount_base, currency, key)
+                sources = result[0]
+                computed_outputs = result[1]
+                overheads = result[2]
+                # Fix issue #594
+                if len(sources) > 40:
+                    sources_value = 0
+                    for s in sources[:39]:
+                        sources_value += s.amount * (10 ** s.base)
+                    sources_value, sources_base = reduce_base(sources_value, 0)
+                    chained_tx = self.prepare_tx(
+                        key,
+                        key.pubkey,
+                        blockstamp,
+                        sources_value,
+                        sources_base,
+                        "[CHAINED]",
+                        currency,
+                    )
+                    forged_tx += chained_tx
+        else:
+            sources = [source]
+
         logging.debug("Inputs: {0}".format(sources))
 
         inputs = self.tx_inputs(sources)
@@ -588,9 +595,11 @@ class DocumentsService:
         amount_base,
         message,
         lock_mode,
+        source,
     ):
         """
         Send money to a given recipient in a specified community
+        :param Source source: Source instance or None
         :param int lock_mode: Index in the combo_locks combobox
         :param sakia.data.entities.Connection connection: The account salt
         :param str secret_key: The account secret_key
@@ -617,6 +626,7 @@ class DocumentsService:
                 message,
                 connection.currency,
                 lock_mode,
+                source,
             )
 
             for i, tx in enumerate(tx_entities):
diff --git a/src/sakia/services/sources.py b/src/sakia/services/sources.py
index a13e7798..be5ce7a3 100644
--- a/src/sakia/services/sources.py
+++ b/src/sakia/services/sources.py
@@ -43,6 +43,9 @@ class SourcesServices(QObject):
         self.currency = currency
         self._logger = logging.getLogger("sakia")
 
+    def get_one(self, **search):
+        return self._sources_processor.get_one(**search)
+
     def amount(self, pubkey):
         return self._sources_processor.amount(self.currency, pubkey)
 
-- 
GitLab