From 98abc7e55c6330fc420e8adf39db3095fdd68b0e Mon Sep 17 00:00:00 2001
From: inso <insomniak.fr@gmaiL.com>
Date: Fri, 30 Dec 2016 10:46:45 +0100
Subject: [PATCH] Implement transfer dialog

---
 src/sakia/app.py                              |   5 +-
 src/sakia/data/entities/blockchain.py         |   2 +-
 src/sakia/data/entities/connection.py         |   2 +-
 src/sakia/data/entities/transaction.py        |  41 +++-
 src/sakia/data/processors/connections.py      |   4 +-
 src/sakia/data/processors/sources.py          |  25 ++-
 src/sakia/data/processors/transactions.py     |  27 +--
 src/sakia/data/processors/tx_lifecycle.py     |  24 +-
 .../dialogs/certification/certification.ui    |  15 +-
 .../gui/dialogs/certification/controller.py   |  30 +--
 src/sakia/gui/dialogs/certification/model.py  |  12 +-
 src/sakia/gui/dialogs/certification/view.py   |   5 -
 src/sakia/gui/dialogs/transfer/controller.py  | 150 ++++++-------
 src/sakia/gui/dialogs/transfer/model.py       | 104 ++++-----
 src/sakia/gui/dialogs/transfer/transfer.py    |   2 +-
 src/sakia/gui/dialogs/transfer/transfer.ui    |  81 +------
 src/sakia/gui/dialogs/transfer/view.py        |  40 +---
 src/sakia/gui/sub/user_information/model.py   |   5 +-
 src/sakia/services/documents.py               | 211 +++++++++++++++++-
 src/sakia/tests/conftest.py                   |  12 +-
 .../identities_tab/test_identities_table.py   |   2 +-
 .../functional/test_certification_dialog.py   |   2 +-
 .../tests/functional/test_transfer_dialog.py  |  32 +++
 .../functional/transfer/test_transfer.py      |  89 --------
 24 files changed, 477 insertions(+), 445 deletions(-)
 create mode 100644 src/sakia/tests/functional/test_transfer_dialog.py
 delete mode 100644 src/sakia/tests/functional/transfer/test_transfer.py

diff --git a/src/sakia/app.py b/src/sakia/app.py
index 49e1d2aa..43edddd1 100644
--- a/src/sakia/app.py
+++ b/src/sakia/app.py
@@ -13,6 +13,7 @@ from sakia.data.connectors import BmaConnector
 from sakia.services import NetworkService, BlockchainService, IdentitiesService, \
     SourcesServices, TransactionsService, DocumentsService
 from sakia.data.repositories import SakiaDatabase
+from sakia.data.entities import Transaction
 from sakia.data.processors import BlockchainProcessor, NodesProcessor, IdentitiesProcessor, \
     CertificationsProcessor, SourcesProcessor, TransactionsProcessor, ConnectionsProcessor
 from sakia.data.files import AppDataFile, UserParametersFile
@@ -43,6 +44,8 @@ class Application(QObject):
     :param sakia.services.DocumentsService documents_service: A service to broadcast documents
     """
 
+    new_transfer = pyqtSignal(Transaction)
+
     qapp = attr.ib()
     loop = attr.ib()
     options = attr.ib()
@@ -102,7 +105,7 @@ class Application(QObject):
         self.identities_services = {}
         self.sources_services = {}
         self.transactions_services = {}
-        self.documents_service = DocumentsService(bma_connector, blockchain_processor, identities_processor)
+        self.documents_service = DocumentsService.instanciate(self)
 
         for currency in self.db.connections_repo.get_currencies():
             if currency not in self.identities_services:
diff --git a/src/sakia/data/entities/blockchain.py b/src/sakia/data/entities/blockchain.py
index 7d5b3418..915551f2 100644
--- a/src/sakia/data/entities/blockchain.py
+++ b/src/sakia/data/entities/blockchain.py
@@ -57,7 +57,7 @@ class Blockchain:
     # Last members count
     last_members_count = attr.ib(convert=int, default=0, cmp=False, hash=False)
     # Last UD amount in units (multiply by 10^base)
-    last_ud = attr.ib(convert=int, default=0, cmp=False, hash=False)
+    last_ud = attr.ib(convert=int, default=1, cmp=False, hash=False)
     # Last UD base
     last_ud_base = attr.ib(convert=int, default=0, cmp=False, hash=False)
     # Last UD base
diff --git a/src/sakia/data/entities/connection.py b/src/sakia/data/entities/connection.py
index 15b9c2ce..79d23da9 100644
--- a/src/sakia/data/entities/connection.py
+++ b/src/sakia/data/entities/connection.py
@@ -21,7 +21,7 @@ class Connection:
     password = attr.ib(init=False, convert=str, default="", cmp=False, hash=False)
 
     def title(self):
-        return self.uid + " - " + self.pubkey[:5]
+        return self.uid + "[" + self.pubkey[:7] + "]@" + self.currency
 
     @property
     def scrypt_params(self):
diff --git a/src/sakia/data/entities/transaction.py b/src/sakia/data/entities/transaction.py
index 3f79dcb4..e89fadef 100644
--- a/src/sakia/data/entities/transaction.py
+++ b/src/sakia/data/entities/transaction.py
@@ -59,9 +59,26 @@ def parse_transaction_doc(tx_doc, pubkey, block_number, mediantime, txid):
         return transaction
     return None
 
+
 @attr.s()
 class Transaction:
+    """
+    Transaction entity
 
+    :param str currency: the currency of the transaction
+    :param str sha_hash: the hash of the transaction
+    :param int written_block: the number of the block
+    :param str blockstamp: the blockstamp of the transaction
+    :param int timestamp: the timestamp of the transaction
+    :param str signature: the signature
+    :param str issuer: the pubkey of the issuer
+    :param str receiver: the pubkey of the receiver
+    :param int amount: the amount
+    :param str amount_base: the amount base
+    :param str comment: a comment
+    :param str txid: the transaction id to sort transctions
+    :param str state: the state of the transaction
+    """
     TO_SEND = 0
     AWAITING = 1
     VALIDATING = 2
@@ -70,19 +87,19 @@ class Transaction:
     DROPPED = 16
     LOCAL = 128
 
-    currency = attr.ib(convert=str, cmp=False)
-    sha_hash = attr.ib(convert=str)
+    currency      = attr.ib(convert=str, cmp=False)
+    sha_hash      = attr.ib(convert=str)
     written_block = attr.ib(convert=int, cmp=False)
-    blockstamp = attr.ib(convert=block_uid, cmp=False)
-    timestamp = attr.ib(convert=int, cmp=False)
-    signature = attr.ib(convert=str, cmp=False)
-    issuer = attr.ib(convert=str, cmp=False)
-    receiver = attr.ib(convert=str, cmp=False)
-    amount = attr.ib(convert=int, cmp=False)
-    amount_base = attr.ib(convert=int, cmp=False)
-    comment = attr.ib(convert=str, cmp=False)
-    txid = attr.ib(convert=int, cmp=False)
-    state = attr.ib(convert=int, cmp=False)
+    blockstamp    = attr.ib(convert=block_uid, cmp=False)
+    timestamp     = attr.ib(convert=int, cmp=False)
+    signature     = attr.ib(convert=str, cmp=False)
+    issuer        = attr.ib(convert=str, cmp=False)
+    receiver      = attr.ib(convert=str, cmp=False)
+    amount        = attr.ib(convert=int, cmp=False)
+    amount_base   = attr.ib(convert=int, cmp=False)
+    comment       = attr.ib(convert=str, cmp=False)
+    txid          = attr.ib(convert=int, cmp=False)
+    state         = attr.ib(convert=int, cmp=False)
 
     @property
     def local(self):
diff --git a/src/sakia/data/processors/connections.py b/src/sakia/data/processors/connections.py
index 536b4d67..db120052 100644
--- a/src/sakia/data/processors/connections.py
+++ b/src/sakia/data/processors/connections.py
@@ -29,8 +29,8 @@ class ConnectionsProcessor:
     def pubkeys(self):
         return self._connections_repo.get_pubkeys()
 
-    def connections(self, currency):
-        return self._connections_repo.get_all(currency=currency)
+    def connections(self):
+        return self._connections_repo.get_all()
 
     def currencies(self):
         return self._connections_repo.get_currencies()
diff --git a/src/sakia/data/processors/sources.py b/src/sakia/data/processors/sources.py
index 0d43d4af..5477ff7a 100644
--- a/src/sakia/data/processors/sources.py
+++ b/src/sakia/data/processors/sources.py
@@ -9,8 +9,12 @@ import asyncio
 
 @attr.s
 class SourcesProcessor:
-    _repo = attr.ib()  # :type sakia.data.repositories.SourcesRepo
-    _bma_connector = attr.ib()  # :type sakia.data.connectors.bma.BmaConnector
+    """
+    :param sakia.data.repositories.SourcesRepo _repo: the repository of the sources
+    :param sakia.data.connectors.bma.BmaConnector _bma_connector: the bma connector
+    """
+    _repo = attr.ib()
+    _bma_connector = attr.ib()
 
     @classmethod
     def instanciate(cls, app):
@@ -55,3 +59,20 @@ class SourcesProcessor:
         """
         sources = self._repo.get_all(currency=currency, pubkey=pubkey)
         return sum([s.amount * (10**s.base) for s in sources])
+
+    def available(self, currency):
+        """"
+        :param str currency: the currency of the sources
+        :rtype: list[sakia.data.entities.Source]
+        """
+        return self._repo.get_all(currency=currency)
+
+    def consume(self, sources):
+        """
+
+        :param currency:
+        :param sources:
+        :return:
+        """
+        for s in sources:
+            self._repo.drop(s)
\ No newline at end of file
diff --git a/src/sakia/data/processors/transactions.py b/src/sakia/data/processors/transactions.py
index 19bcb0a2..25b83990 100644
--- a/src/sakia/data/processors/transactions.py
+++ b/src/sakia/data/processors/transactions.py
@@ -26,6 +26,15 @@ class TransactionsProcessor:
         return cls(app.db.transactions_repo,
                    BmaConnector(NodesProcessor(app.db.nodes_repo)))
 
+    def next_txid(self, currency, block_number):
+        """
+        :param str currency:
+        :param str block_number:
+        :rtype: int
+        """
+        transfers = self._repo.get_all(currency=currency, written_on=block_number)
+        return max([tx.txid for tx in transfers]) if transfers else 0
+
     def transfers(self, currency, pubkey):
         """
         Get all transfers from or to a given pubkey
@@ -94,7 +103,7 @@ class TransactionsProcessor:
         """
         self.run_state_transitions(tx, ())
 
-    async def send(self, tx, txdoc, community):
+    async def send(self, tx, txdoc, currency):
         """
         Send a transaction and update the transfer state to AWAITING if accepted.
         If the transaction was refused (return code != 200), state becomes REFUSED
@@ -102,16 +111,9 @@ class TransactionsProcessor:
 
         :param sakia.data.entities.Transaction tx: the transaction
         :param txdoc: A transaction duniterpy object
-        :param community: The community target of the transaction
-        """
-        tx.sha_hash = txdoc.sha_hash
-        responses = await community.bma_access.broadcast(bma.tx.process,
-                                                         post_args={'transaction': txdoc.signed_raw()})
-        blockUID = community.network.current_blockUID
-        block = await community.bma_access.future_request(bma.blockchain.block,
-                                                          req_args={'number': blockUID.number})
-        signed_raw = "{0}{1}\n".format(block['raw'], block['signature'])
-        block_doc = Block.from_signed_raw(signed_raw)
+        :param currency: The community target of the transaction
+        """
+        responses = await self._bma_connector.broadcast(currency, bma.tx.process, req_args={'transaction': txdoc.signed_raw()})
         result = (False, "")
         for r in responses:
             if r.status == 200:
@@ -120,9 +122,8 @@ class TransactionsProcessor:
                 result = (False, (await r.text()))
             else:
                 await r.text()
-        self.run_state_transitions(tx, ([r.status for r in responses], block_doc))
         self.run_state_transitions(tx, ([r.status for r in responses],))
-        return result
+        return result, tx
 
     async def initialize_transactions(self, identity, log_stream):
         """
diff --git a/src/sakia/data/processors/tx_lifecycle.py b/src/sakia/data/processors/tx_lifecycle.py
index df010d33..7769bab6 100644
--- a/src/sakia/data/processors/tx_lifecycle.py
+++ b/src/sakia/data/processors/tx_lifecycle.py
@@ -129,18 +129,6 @@ def _is_locally_created(tx):
     """
     return tx.local
 
-
-def _wait(tx, current_block):
-    """
-    Set the transfer as AWAITING confrmation.
-
-    :param sakia.data.entities.Transaction tx: the transaction
-    :param duniterpy.documents.Block current_block: Current block of the main blockchain
-    """
-    tx.blockstamp = current_block.blockUID
-    tx.timestamp = int(time.time())
-
-
 def _be_validating(tx, block):
     """
     Action when the transfer ins found in a block
@@ -166,10 +154,10 @@ def _drop(tx):
 # keys are a tuple containg (current_state, transition_parameters)
 # values are tuples containing (transition_test, transition_success, new_state)
 states = {
-            (Transaction.TO_SEND, (list, Block)):
+            (Transaction.TO_SEND, (list,)):
                 (
-                    (_broadcast_success, lambda tx, l, b: _wait(tx, b), Transaction.AWAITING),
-                    (lambda tx, l, b: _broadcast_failure(tx, l), None, Transaction.REFUSED),
+                    (_broadcast_success, None, Transaction.AWAITING),
+                    (lambda tx, l: _broadcast_failure(tx, l), None, Transaction.REFUSED),
                 ),
             (Transaction.TO_SEND, ()):
                 ((_is_locally_created, _drop, Transaction.DROPPED),),
@@ -187,10 +175,10 @@ states = {
             (Transaction.VALIDATED, (bool, Block, int)):
                 ((_rollback_in_fork_window, lambda tx, r, b, i: _be_validating(tx, b), Transaction.VALIDATING),),
 
-            (Transaction.VALIDATED, (bool, Block)):
+            (Transaction.VALIDATED, (bool,)):
                 (
-                    (_rollback_and_removed, lambda tx, r, b: _drop(tx), Transaction.DROPPED),
-                    (_rollback_and_local, lambda tx, r, b: _wait(tx, b), Transaction.AWAITING),
+                    (_rollback_and_removed, lambda tx, r: _drop(tx), Transaction.DROPPED),
+                    (_rollback_and_local, None, Transaction.AWAITING),
                 ),
 
             (Transaction.REFUSED, ()):
diff --git a/src/sakia/gui/dialogs/certification/certification.ui b/src/sakia/gui/dialogs/certification/certification.ui
index db5a007b..f67156ae 100644
--- a/src/sakia/gui/dialogs/certification/certification.ui
+++ b/src/sakia/gui/dialogs/certification/certification.ui
@@ -23,22 +23,9 @@
       </sizepolicy>
      </property>
      <property name="title">
-      <string>Send to currency network :</string>
+      <string>Select your identity</string>
      </property>
      <layout class="QVBoxLayout" name="verticalLayout_2">
-      <item>
-       <widget class="QComboBox" name="combo_currency"/>
-      </item>
-      <item>
-       <widget class="QLabel" name="label">
-        <property name="text">
-         <string>With the following identity : </string>
-        </property>
-        <property name="alignment">
-         <set>Qt::AlignCenter</set>
-        </property>
-       </widget>
-      </item>
       <item>
        <widget class="QComboBox" name="combo_pubkey"/>
       </item>
diff --git a/src/sakia/gui/dialogs/certification/controller.py b/src/sakia/gui/dialogs/certification/controller.py
index 2e408256..f7a8615a 100644
--- a/src/sakia/gui/dialogs/certification/controller.py
+++ b/src/sakia/gui/dialogs/certification/controller.py
@@ -28,7 +28,6 @@ class CertificationController(QObject):
         super().__init__()
         self.view.button_box.accepted.connect(self.accept)
         self.view.button_box.rejected.connect(self.reject)
-        self.view.combo_currency.currentIndexChanged.connect(self.change_currency)
         self.view.combo_pubkey.currentIndexChanged.connect(self.change_connection)
 
     @classmethod
@@ -44,15 +43,13 @@ class CertificationController(QObject):
         model = CertificationModel(app)
         certification = cls(view, model, None, None)
 
-        search_user = SearchUserController.create(certification, app, model.available_currencies()[0])
+        search_user = SearchUserController.create(certification, app, "")
         certification.set_search_user(search_user)
 
-        user_information = UserInformationController.create(certification, app,
-                                                            model.available_currencies()[0], None)
+        user_information = UserInformationController.create(certification, app, "", None)
         certification.set_user_information(user_information)
 
-        view.set_currencies(certification.model.available_currencies())
-        view.set_keys(certification.model.available_connections(certification.model.available_currencies()[0]))
+        view.set_keys(certification.model.available_connections())
         return certification
 
     @classmethod
@@ -67,7 +64,7 @@ class CertificationController(QObject):
         """
         dialog = cls.create(parent, app)
         if connection:
-            dialog.view.combo_currency.setCurrentText(connection.currency)
+            dialog.view.combo_pubkey.setCurrentText(connection.title())
         dialog.refresh()
         return dialog.exec()
 
@@ -77,13 +74,12 @@ class CertificationController(QObject):
         Certify and identity
         :param sakia.gui.component.controller.ComponentController parent: the parent
         :param sakia.core.Application app: the application
-        :param sakia.core.Account account: the account certifying the identity
-        :param sakia.core.Community community: the community
-        :param sakia.core.registry.Identity identity: the identity certified
+        :param sakia.data.entities.Connection connection: the connection
+        :param sakia.data.entities.Identity identity: the identity certified
         :return:
         """
         dialog = cls.create(parent, app)
-        dialog.view.combo_community.setCurrentText(connection.currency)
+        dialog.view.combo_pubkey.setCurrentText(connection.title())
         dialog.refresh()
         return await dialog.async_exec()
 
@@ -162,16 +158,10 @@ class CertificationController(QObject):
         """
         self.user_information.search_identity(self.search_user.model.identity())
 
-    def change_currency(self, index):
-        currency = self.model.available_currencies()[index]
-        connections = self.model.available_connections(currency)
-        self.view.set_selected_key(connections[0])
-        self.search_user.set_currency(currency)
-        self.user_information.set_currency(currency)
-
     def change_connection(self, index):
-        currency = self.model.available_currencies()[index]
-        self.model.set_connection(currency, index)
+        self.model.set_connection(index)
+        self.search_user.set_currency(self.model.connection.currency)
+        self.user_information.set_currency(self.model.connection.currency)
         self.refresh()
 
     def async_exec(self):
diff --git a/src/sakia/gui/dialogs/certification/model.py b/src/sakia/gui/dialogs/certification/model.py
index 3c3cbfaf..2c15e4ba 100644
--- a/src/sakia/gui/dialogs/certification/model.py
+++ b/src/sakia/gui/dialogs/certification/model.py
@@ -16,7 +16,6 @@ class CertificationModel(QObject):
     _certifications_processor = attr.ib(default=None)
     _identities_processor = attr.ib(default=None)
     _blockchain_processor = attr.ib(default=None)
-    _documents_service = attr.ib(default=None)
 
     def __attrs_post_init__(self):
         super().__init__()
@@ -81,14 +80,11 @@ class CertificationModel(QObject):
 
         return is_member and self._blockchain_processor.current_buid(self.connection.currency)
 
-    def available_connections(self, currency):
-        return self._connections_processor.connections(currency=currency)
+    def available_connections(self):
+        return self._connections_processor.connections()
 
-    def available_currencies(self):
-        return self._connections_processor.currencies()
-
-    def set_connection(self, currency, index):
-        connections = self._connections_processor.connections(currency=currency)
+    def set_connection(self,  index):
+        connections = self._connections_processor.connections()
         self.connection = connections[index]
 
     def notification(self):
diff --git a/src/sakia/gui/dialogs/certification/view.py b/src/sakia/gui/dialogs/certification/view.py
index e6107f74..1ac5dc8a 100644
--- a/src/sakia/gui/dialogs/certification/view.py
+++ b/src/sakia/gui/dialogs/certification/view.py
@@ -54,11 +54,6 @@ class CertificationView(QDialog, Ui_CertificationDialog):
         """
         self.combo_pubkey.setCurrentText(connection.title())
 
-    def set_currencies(self, currencies):
-        self.combo_currency.clear()
-        for c in currencies:
-            self.combo_currency.addItem(c)
-
     def set_search_user(self, search_user_view):
         """
 
diff --git a/src/sakia/gui/dialogs/transfer/controller.py b/src/sakia/gui/dialogs/transfer/controller.py
index 45a0d389..c9a368a4 100644
--- a/src/sakia/gui/dialogs/transfer/controller.py
+++ b/src/sakia/gui/dialogs/transfer/controller.py
@@ -5,6 +5,7 @@ from PyQt5.QtCore import Qt, QObject
 from PyQt5.QtWidgets import QApplication
 
 from sakia.decorators import asyncify
+from sakia.gui.password_asker import PasswordAskerDialog
 from sakia.gui.sub.search_user.controller import SearchUserController
 from sakia.gui.sub.user_information.controller import UserInformationController
 from .model import TransferModel
@@ -16,26 +17,26 @@ class TransferController(QObject):
     The transfer component controller
     """
 
-    def __init__(self, parent, view, model, search_user, user_information, password_asker):
+    def __init__(self, view, model, search_user, user_information):
         """
         Constructor of the transfer component
 
         :param sakia.gui.transfer.view.TransferView: the view
         :param sakia.gui.transfer.model.TransferModel model: the model
         """
-        super().__init__(parent)
-        self.password_asker = password_asker
+        super().__init__()
+        self.view = view
+        self.model = model
         self.search_user = search_user
         self.user_information = user_information
         self.view.button_box.accepted.connect(self.accept)
         self.view.button_box.rejected.connect(self.reject)
-        self.view.combo_community.currentIndexChanged.connect(self.change_current_community)
-        self.view.combo_wallets.currentIndexChanged.connect(self.change_current_wallet)
+        self.view.combo_connections.currentIndexChanged.connect(self.change_current_connection)
         self.view.spinbox_amount.valueChanged.connect(self.handle_amount_change)
         self.view.spinbox_relative.valueChanged.connect(self.handle_relative_change)
 
     @classmethod
-    def create(cls, parent, app, account, community, transfer, password_asker):
+    def create(cls, parent, app):
         """
         Instanciate a transfer component
         :param sakia.gui.component.controller.ComponentController parent:
@@ -43,54 +44,48 @@ class TransferController(QObject):
         :return: a new Transfer controller
         :rtype: TransferController
         """
-        communities_names = [c.name for c in account.communities]
-        wallets_names = [w.name for w in account.wallets]
-        contacts_names = [c['name'] for c in account.contacts]
+        view = TransferView(parent.view if parent else None, None, None)
+        model = TransferModel(app)
+        transfer = cls(view, model, None, None)
 
-        view = TransferView(parent.view, None, None, communities_names, contacts_names, wallets_names)
-        model = TransferModel(None, app, account=account, community=community, resent_transfer=transfer)
-        transfer = cls(parent, view, model, None, None, password_asker)
-
-        search_user = SearchUserController.create(transfer, app,
-                                                  account=model.account,
-                                                  community=model.community)
+        search_user = SearchUserController.create(transfer, app, "")
         transfer.set_search_user(search_user)
 
-        user_information = UserInformationController.create(transfer, app,
-                                                            account=model.account,
-                                                            community=model.community,
-                                                            identity=None)
+        user_information = UserInformationController.create(transfer, app, "", None)
         transfer.set_user_information(user_information)
-        model.setParent(transfer)
+
+        view.set_keys(transfer.model.available_connections())
         return transfer
 
     @classmethod
-    def open_dialog(cls, parent, app, account, password_asker, community):
-        dialog = cls.create(parent, app,
-                     account=account,
-                     password_asker=password_asker,
-                     community=community,
-                     transfer=None)
+    def open_dialog(cls, parent, app, connection):
+        dialog = cls.create(parent, app)
+        if connection:
+            dialog.view.combo_currency.setCurrentText(connection.currency)
+            dialog.view.combo_wallets.setCurrentText(connection.title())
+        dialog.refresh()
         return dialog.exec()
 
     @classmethod
-    async def send_money_to_identity(cls, parent, app, account, password_asker, community, identity):
-        dialog = cls.create(parent, app,
-                     account=account,
-                     password_asker=password_asker,
-                     community=community,
-                     transfer=None)
+    async def send_money_to_identity(cls, parent, app, connection, identity):
+        dialog = cls.create(parent, app)
+        dialog.view.combo_currency.setCurrentText(identity.currency)
+        dialog.view.combo_wallets.setCurrentText(connection.title())
         dialog.view.edit_pubkey.setText(identity.pubkey)
         dialog.view.radio_pubkey.setChecked(True)
+
+        dialog.refresh()
         return await dialog.async_exec()
 
     @classmethod
-    async def send_transfer_again(cls, parent, app, account, password_asker, community, resent_transfer):
-        dialog = cls.create(parent, app,
-                            account=account,
-                            password_asker=password_asker,
-                            community=community,
-                            resent_transfer=resent_transfer)
+    async def send_transfer_again(cls, parent, app, connection, resent_transfer):
+        dialog = cls.create(parent, app)
+        dialog.view.combo_currency.setCurrentText(resent_transfer.currency)
+        dialog.view.combo_wallets.setCurrentText(connection.title())
+        dialog.view.edit_pubkey.setText(resent_transfer.receiver)
+        dialog.view.radio_pubkey.setChecked(True)
+
+        dialog.refresh()
         relative = await dialog.model.quant_to_rel(resent_transfer.metadata['amount'])
         dialog.view.set_spinboxes_parameters(1, resent_transfer.metadata['amount'], relative)
         dialog.view.change_relative_amount(relative)
@@ -105,14 +100,6 @@ class TransferController(QObject):
 
         return await dialog.async_exec()
 
-    @property
-    def view(self) -> TransferView:
-        return self._view
-
-    @property
-    def model(self) -> TransferModel:
-        return self._model
-
     def set_search_user(self, search_user):
         """
 
@@ -147,10 +134,7 @@ class TransferController(QObject):
         """
         pubkey = None
 
-        if self.view.recipient_mode() == TransferView.RecipientMode.CONTACT:
-            contact_name = self.view.selected_contact()
-            pubkey = self.model.contact_name_pubkey(contact_name)
-        elif self.view.recipient_mode() == TransferView.RecipientMode.SEARCH:
+        if self.view.recipient_mode() == TransferView.RecipientMode.SEARCH:
             if self.search_user.current_identity():
                 pubkey = self.search_user.current_identity().pubkey
         else:
@@ -166,9 +150,11 @@ class TransferController(QObject):
         logging.debug("checking recipient mode...")
         recipient = self.selected_pubkey()
         amount = self.view.spinbox_amount.value()
+        #TODO: Handle other amount base than 0
+        amount_base = 0
 
         logging.debug("Showing password dialog...")
-        password = await self.password_asker.async_exec()
+        password = await PasswordAskerDialog(self.model.connection).async_exec()
         if password == "":
             self.view.button_box.setEnabled(True)
             return
@@ -177,18 +163,18 @@ class TransferController(QObject):
         QApplication.setOverrideCursor(Qt.WaitCursor)
 
         logging.debug("Send money...")
-        result = await self.model.send_money(recipient, amount, comment, password)
+        result, transaction = await self.model.send_money(recipient, password, amount, amount_base, comment)
         if result[0]:
-            await self.view.show_success(self.model.app.preferences['notifications'], recipient)
+            await self.view.show_success(self.model.notifications(), recipient)
             logging.debug("Restore cursor...")
             QApplication.restoreOverrideCursor()
 
             # If we sent back a transaction we cancel the first one
             self.model.cancel_previous()
-            self.model.app.refresh_transfers.emit()
+            self.model.app.new_transfer.emit(transaction)
             self.view.accept()
         else:
-            await self.view.show_error(self.model.app.preferences['notifications'], result[1])
+            await self.view.show_error(self.model.notifications(), result[1])
 
             QApplication.restoreOverrideCursor()
             self.view.button_box.setEnabled(True)
@@ -196,40 +182,48 @@ class TransferController(QObject):
     def reject(self):
         self.view.reject()
 
-    @asyncify
-    async def refresh(self):
-        amount = await self.model.wallet_value()
-        total_text = await self.model.localized_amount(amount)
-        self.view.refresh_labels(total_text, self.model.community.currency)
+    def refresh(self):
+        amount = self.model.wallet_value()
+        total_text = self.model.localized_amount(amount)
+        self.view.refresh_labels(total_text)
 
         if amount == 0:
             self.view.set_button_box(TransferView.ButtonBoxState.NO_AMOUNT)
         else:
             self.view.set_button_box(TransferView.ButtonBoxState.OK)
 
-        max_relative = await self.model.quant_to_rel(amount)
-        current_base = await self.model.current_base()
+        max_relative = self.model.quant_to_rel(amount)
+        current_base = self.model.current_base()
 
         self.view.set_spinboxes_parameters(pow(10, current_base), amount, max_relative)
 
-    @asyncify
-    async def handle_amount_change(self, value):
-        relative = await self.model.quant_to_rel(value)
+    def handle_amount_change(self, value):
+        relative = self.model.quant_to_rel(value)
         self.view.change_relative_amount(relative)
-
-    @asyncify
-    async def handle_relative_change(self, value):
-        amount = await self.model.rel_to_quant(value)
+        self.refresh_amount_suffix()
+
+    def refresh_amount_suffix(self):
+        #TODO: Handle other exponents than 0 (using a custom spinbox ?)
+        unicodes = {
+            '0': ord('\u2070'),
+            '1': ord('\u00B9'),
+            '2': ord('\u00B2'),
+            '3': ord('\u00B3'),
+        }
+        for n in range(4, 10):
+            unicodes[str(n)] = ord('\u2070') + n
+
+        exponent = ""
+        for n in str('0'):
+            exponent += chr(unicodes[n])
+        self.view.spinbox_amount.setSuffix(" x10" + exponent + " " + self.model.connection.currency)
+
+    def handle_relative_change(self, value):
+        amount = self.model.rel_to_quant(value)
         self.view.change_quantitative_amount(amount)
 
-    def change_current_community(self, index):
-        self.model.change_community(index)
-        self.search_user.set_community(self.community)
-        self.user_information.change_community(self.community)
-        self.refresh()
-
-    def change_current_wallet(self, index):
-        self.model.change_wallet(index)
+    def change_current_connection(self, index):
+        self.model.set_connection(index)
         self.refresh()
 
     def async_exec(self):
diff --git a/src/sakia/gui/dialogs/transfer/model.py b/src/sakia/gui/dialogs/transfer/model.py
index ae51a9a0..91c8072b 100644
--- a/src/sakia/gui/dialogs/transfer/model.py
+++ b/src/sakia/gui/dialogs/transfer/model.py
@@ -1,29 +1,30 @@
+import attr
 from PyQt5.QtCore import QObject
+from sakia.data.processors import BlockchainProcessor, SourcesProcessor, ConnectionsProcessor
 
 
+@attr.s()
 class TransferModel(QObject):
     """
     The model of transfer component
+
+    :param sakia.app.Application app:
+    :param sakia.data.entities.Connection connection:
+    :param sakia.data.processors.BlockchainProcessor _blockchain_processor:
     """
 
-    def __init__(self, parent, app, account, community, resent_transfer):
-        super().__init__(parent)
-        self.app = app
-        self.account = account
-        self.resent_transfer = resent_transfer
-        self.community = community if community else self.account.communities[0]
-        self.wallet = self.account.wallets[0]
+    app = attr.ib()
+    connection = attr.ib(default=None)
+    resent_transfer = attr.ib(default=None)
+    _blockchain_processor = attr.ib(default=None)
+    _sources_processor = attr.ib(default=None)
+    _connections_processor = attr.ib(default=None)
 
-    def contact_name_pubkey(self, name):
-        """
-        Get the pubkey of a contact from its name
-        :param str name:
-        :return:
-        :rtype: str
-        """
-        for contact in self.account.contacts:
-            if contact['name'] == name:
-                return contact['pubkey']
+    def __attrs_post_init__(self):
+        super().__init__()
+        self._blockchain_processor = BlockchainProcessor.instanciate(self.app)
+        self._sources_processor = SourcesProcessor.instanciate(self.app)
+        self._connections_processor = ConnectionsProcessor.instanciate(self.app)
 
     async def rel_to_quant(self, rel_value):
         """
@@ -31,88 +32,69 @@ class TransferModel(QObject):
         :param float rel_value:
         :rtype: int
         """
-        ud_block = await self.community.get_ud_block()
-        if ud_block:
-            dividend = ud_block['dividend']
-            base = ud_block['unitbase']
-        else:
-            dividend = 1
-            base = 0
+        dividend, base = await self._blockchain_processor.last_ud(self.connection.currency)
         amount = rel_value * dividend * pow(10, base)
         # amount is rounded to the nearest power of 10 depending of last ud base
         rounded = int(pow(10, base) * round(float(amount) / pow(10, base)))
         return rounded
 
-    async def quant_to_rel(self, amount):
+    def quant_to_rel(self, amount):
         """
         Get the relative value of a given amount
         :param int amount:
         :rtype: float
         """
-
-        ud_block = await self.community.get_ud_block()
-        if ud_block:
-            dividend = ud_block['dividend']
-            base = ud_block['unitbase']
-        else:
-            dividend = 1
-            base = 0
+        dividend, base = self._blockchain_processor.last_ud(self.connection.currency)
         relative = amount / (dividend * pow(10, base))
         return relative
 
-    async def wallet_value(self):
+    def wallet_value(self):
         """
         Get the value of the current wallet in the current community
         """
-        return await self.wallet.value(self.community)
+        return self._sources_processor.amount(self.connection.currency, self.connection.pubkey)
 
-    async def current_base(self):
+    def current_base(self):
         """
         Get the current base of the network
         """
-        ud_block = await self.community.get_ud_block()
-        if ud_block:
-            base = ud_block['unitbase']
-        else:
-            base = 0
+        dividend, base = self._blockchain_processor.last_ud(self.connection.currency)
         return base
 
-    async def localized_amount(self, amount):
+    def localized_amount(self, amount):
         """
         Get the value of the current referential
         """
-        localized = await self.account.current_ref.instance(amount, self.community, self.app) \
+
+        localized = self.app.current_ref.instance(amount, self.connection.currency, self.app) \
             .diff_localized(units=True,
-                            international_system=self.app.preferences['international_system_of_units'])
+                            international_system=self.app.parameters.international_system_of_units)
         return localized
 
-    def change_community(self, index):
-        """
-        Change the current community
-        :param int index: index in the list of communities
-        """
-        self.community = self.account.communities[index]
-
-    def change_wallet(self, index):
-        """
-        Change the current wallet
-        :param int index: index in the list of wallets
-        """
-        self.wallet = self.account.wallets[index]
-
     def cancel_previous(self):
         if self.resent_transfer:
             self.resent_transfer.cancel()
 
-    async def send_money(self, recipient, amount, comment, password):
+    def available_connections(self):
+        return self._connections_processor.connections()
+
+    def set_connection(self, index):
+        connections = self._connections_processor.connections()
+        self.connection = connections[index]
+
+    async def send_money(self, recipient, password, amount, amount_base, comment):
         """
         Send money to given recipient using the account
         :param str recipient:
         :param int amount:
+        :param int amount_base:
         :param str comment:
         :param str password:
         :return: the result of the send
         """
 
-        return await self.wallet.send_money(self.account.salt, password, self.community,
-                                   recipient, amount, comment)
\ No newline at end of file
+        return await self.app.documents_service.send_money(self.connection, password,
+                                                           recipient, amount, amount_base, comment)
+
+    def notifications(self):
+        return self.app.parameters.notifications
diff --git a/src/sakia/gui/dialogs/transfer/transfer.py b/src/sakia/gui/dialogs/transfer/transfer.py
index 39bf940c..343b93f3 100644
--- a/src/sakia/gui/dialogs/transfer/transfer.py
+++ b/src/sakia/gui/dialogs/transfer/transfer.py
@@ -61,7 +61,7 @@ class TransferMoneyDialog(QObject):
         self.ui.search_user.button_reset.hide()
         self.ui.search_user.init(self.app)
         self.ui.search_user.change_account(self.account)
-        self.ui.search_user.change_community(self.community)
+        self.ui.search_user.change_currency(self.community)
         self.ui.search_user.search_started.connect(lambda: self.ui.button_box.setEnabled(False))
         self.ui.search_user.search_completed.connect(lambda: self.ui.button_box.setEnabled(True))
 
diff --git a/src/sakia/gui/dialogs/transfer/transfer.ui b/src/sakia/gui/dialogs/transfer/transfer.ui
index 5c5f691f..e8f16f25 100644
--- a/src/sakia/gui/dialogs/transfer/transfer.ui
+++ b/src/sakia/gui/dialogs/transfer/transfer.ui
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>800</width>
-    <height>500</height>
+    <width>566</width>
+    <height>415</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -17,11 +17,11 @@
    <item>
     <widget class="QGroupBox" name="groupBox_2">
      <property name="title">
-      <string>Community</string>
+      <string>Select connection</string>
      </property>
      <layout class="QHBoxLayout" name="horizontalLayout_4">
       <item>
-       <widget class="QComboBox" name="combo_community"/>
+       <widget class="QComboBox" name="combo_connections"/>
       </item>
      </layout>
     </widget>
@@ -40,55 +40,6 @@
         <property name="bottomMargin">
          <number>6</number>
         </property>
-        <item>
-         <layout class="QHBoxLayout" name="horizontalLayout_2">
-          <item>
-           <widget class="QRadioButton" name="radio_contact">
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-            <property name="text">
-             <string>Con&amp;tact</string>
-            </property>
-            <property name="checked">
-             <bool>true</bool>
-            </property>
-           </widget>
-          </item>
-          <item>
-           <spacer name="horizontalSpacer_3">
-            <property name="orientation">
-             <enum>Qt::Horizontal</enum>
-            </property>
-            <property name="sizeType">
-             <enum>QSizePolicy::Maximum</enum>
-            </property>
-            <property name="sizeHint" stdset="0">
-             <size>
-              <width>40</width>
-              <height>20</height>
-             </size>
-            </property>
-           </spacer>
-          </item>
-          <item>
-           <widget class="QComboBox" name="combo_contact">
-            <property name="enabled">
-             <bool>true</bool>
-            </property>
-            <property name="sizePolicy">
-             <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
-              <horstretch>0</horstretch>
-              <verstretch>0</verstretch>
-             </sizepolicy>
-            </property>
-           </widget>
-          </item>
-         </layout>
-        </item>
         <item>
          <layout class="QHBoxLayout" name="horizontalLayout">
           <item>
@@ -197,30 +148,6 @@
       <enum>QFrame::Raised</enum>
      </property>
      <layout class="QVBoxLayout" name="verticalLayout_8">
-      <item>
-       <layout class="QHBoxLayout" name="horizontalLayout_7">
-        <property name="topMargin">
-         <number>5</number>
-        </property>
-        <item>
-         <widget class="QLabel" name="label_2">
-          <property name="text">
-           <string>Wallet</string>
-          </property>
-         </widget>
-        </item>
-        <item>
-         <widget class="QComboBox" name="combo_wallets">
-          <property name="minimumSize">
-           <size>
-            <width>0</width>
-            <height>30</height>
-           </size>
-          </property>
-         </widget>
-        </item>
-       </layout>
-      </item>
       <item>
        <widget class="QLabel" name="label_total">
         <property name="text">
diff --git a/src/sakia/gui/dialogs/transfer/view.py b/src/sakia/gui/dialogs/transfer/view.py
index c44be0ea..67b448d0 100644
--- a/src/sakia/gui/dialogs/transfer/view.py
+++ b/src/sakia/gui/dialogs/transfer/view.py
@@ -17,7 +17,6 @@ class TransferView(QDialog, Ui_TransferMoneyDialog):
         OK = 1
 
     class RecipientMode(Enum):
-        CONTACT = 0
         PUBKEY = 1
         SEARCH = 2
 
@@ -27,21 +26,16 @@ class TransferView(QDialog, Ui_TransferMoneyDialog):
         ButtonBoxState.OK: (True, QT_TRANSLATE_NOOP("CertificationView", "&Ok"))
     }
 
-    def __init__(self, parent, search_user_view, user_information_view,
-                 communities_names, contacts_names, wallets_names):
+    def __init__(self, parent, search_user_view, user_information_view):
         """
 
         :param parent:
         :param sakia.gui.search_user.view.SearchUserView search_user_view:
         :param sakia.gui.user_information.view.UserInformationView user_information_view:
-        :param list[str] communities_names:
-        :param list[str] contacts_names:
-        :param list[str] wallets_names:
         """
         super().__init__(parent)
         self.setupUi(self)
 
-        self.radio_contact.toggled.connect(lambda c, radio=TransferView.RecipientMode.CONTACT: self.recipient_mode_changed(radio))
         self.radio_pubkey.toggled.connect(lambda c, radio=TransferView.RecipientMode.PUBKEY: self.recipient_mode_changed(radio))
         self.radio_search.toggled.connect(lambda c, radio=TransferView.RecipientMode.SEARCH: self.recipient_mode_changed(radio))
 
@@ -49,33 +43,20 @@ class TransferView(QDialog, Ui_TransferMoneyDialog):
         validator = QRegExpValidator(regexp)
         self.edit_message.setValidator(validator)
 
-        for name in communities_names:
-            self.combo_community.addItem(name)
-
-        for name in sorted(contacts_names):
-            self.combo_contact.addItem(name)
-
-        for name in wallets_names:
-            self.combo_wallets.addItem(name)
-
-        if len(contacts_names) == 0:
-            self.combo_contact.setEnabled(False)
-            self.radio_pubkey.setChecked(True)
-            self.radio_contact.setEnabled(False)
-
         self.search_user = search_user_view
         self.user_information_view = user_information_view
+        self._amount_base = 0
+        self._currency = ""
 
     def recipient_mode(self):
-        if self.radio_contact.isChecked():
-            return TransferView.RecipientMode.CONTACT
-        elif self.radio_search.isChecked():
+        if self.radio_search.isChecked():
             return TransferView.RecipientMode.SEARCH
         else:
             return TransferView.RecipientMode.PUBKEY
 
-    def selected_contact(self):
-        return self.combo_contact.currentText()
+    def set_keys(self, connections):
+        for conn in connections:
+            self.combo_connections.addItem(conn.title())
 
     def set_search_user(self, search_user_view):
         """
@@ -96,7 +77,6 @@ class TransferView(QDialog, Ui_TransferMoneyDialog):
         :param str radio:
         """
         self.edit_pubkey.setEnabled(radio == TransferView.RecipientMode.PUBKEY)
-        self.combo_contact.setEnabled(radio == TransferView.RecipientMode.CONTACT)
         self.search_user.setEnabled(radio == TransferView.RecipientMode.SEARCH)
 
     def change_quantitative_amount(self, amount):
@@ -129,14 +109,13 @@ class TransferView(QDialog, Ui_TransferMoneyDialog):
         self.spinbox_relative.setMaximum(max_rel)
         self.spinbox_amount.setSingleStep(tick_quant)
 
-    def refresh_labels(self, total_text, currency):
+    def refresh_labels(self, total_text):
         """
         Refresh displayed texts
         :param str total_text:
         :param str currency:
         """
         self.label_total.setText("{0}".format(total_text))
-        self.spinbox_amount.setSuffix(" " + currency)
 
     def set_button_box(self, state, **kwargs):
         """
@@ -162,3 +141,6 @@ class TransferView(QDialog, Ui_TransferMoneyDialog):
             toast.display(self.tr("Transfer"), "Error : {0}".format(error_txt))
         else:
             await QAsyncMessageBox.critical(self.widget, self.tr("Transfer"), error_txt)
+
+    def pubkey_value(self):
+        return self.edit_pubkey.text()
\ No newline at end of file
diff --git a/src/sakia/gui/sub/user_information/model.py b/src/sakia/gui/sub/user_information/model.py
index 3b8e62ef..11c74185 100644
--- a/src/sakia/gui/sub/user_information/model.py
+++ b/src/sakia/gui/sub/user_information/model.py
@@ -24,7 +24,10 @@ class UserInformationModel(QObject):
         if identity:
             self.certs_sent = self._certifications_processor.certifications_sent(currency, identity.pubkey)
             self.certs_received = self._certifications_processor.certifications_received(currency, identity.pubkey)
-        self.identities_service = self.app.identities_services[self.currency]
+        if currency:
+            self.identities_service = self.app.identities_services[self.currency]
+        else:
+            self.identities_service = None
 
     async def load_identity(self, identity):
         """
diff --git a/src/sakia/services/documents.py b/src/sakia/services/documents.py
index 85453e40..5edb8fa4 100644
--- a/src/sakia/services/documents.py
+++ b/src/sakia/services/documents.py
@@ -6,13 +6,18 @@ from collections import Counter
 
 from duniterpy.key import SigningKey
 from duniterpy import PROTOCOL_VERSION
-from duniterpy.documents import BlockUID, Block, Certification, Membership, Revocation
+from duniterpy.documents import BlockUID, Block, Certification, Membership, Revocation, InputSource, \
+    OutputSource, SIGParameter, Unlock
 from duniterpy.documents import Identity as IdentityDoc
+from duniterpy.documents import Transaction as TransactionDoc
+from duniterpy.documents.transaction import reduce_base
+from duniterpy.grammars import output
 from duniterpy.api import bma, errors
-from sakia.data.entities import Identity
-from sakia.data.processors import BlockchainProcessor, IdentitiesProcessor, NodesProcessor
+from sakia.data.entities import Identity, Transaction
+from sakia.data.processors import BlockchainProcessor, IdentitiesProcessor, NodesProcessor, \
+    TransactionsProcessor, SourcesProcessor
 from sakia.data.connectors import BmaConnector
-from aiohttp.errors import ClientError, DisconnectedError
+from sakia.errors import NotEnoughChangeError
 
 
 @attr.s()
@@ -20,10 +25,18 @@ class DocumentsService:
     """
     A service to forge and broadcast documents
     to the network
+
+    :param sakia.data.connectors.BmaConnector _bma_connector: the connector
+    :param sakia.data.processors.BlockchainProcessor _blockchain_processor: the blockchain processor
+    :param sakia.data.processors.IdentitiesProcessor _identities_processor: the identities processor
+    :param sakia.data.processors.TransactionsProcessor _transactions_processor: the transactions processor
+    :param sakia.data.processors.SourcesProcessor _sources_processor: the sources processor
     """
-    _bma_connector = attr.ib()  # :type: sakia.data.connectors.BmaConnector
-    _blockchain_processor = attr.ib()  # :type: sakia.data.processors.BlockchainProcessor
-    _identities_processor = attr.ib()  # :type: sakia.data.processors.IdentitiesProcessor
+    _bma_connector = attr.ib()
+    _blockchain_processor = attr.ib()
+    _identities_processor = attr.ib()
+    _transactions_processor = attr.ib()
+    _sources_processor = attr.ib()
     _logger = attr.ib(default=attr.Factory(lambda: logging.getLogger('sakia')))
 
     @classmethod
@@ -34,7 +47,9 @@ class DocumentsService:
         """
         return cls(BmaConnector(NodesProcessor(app.db.nodes_repo)),
                    BlockchainProcessor.instanciate(app),
-                   IdentitiesProcessor.instanciate(app))
+                   IdentitiesProcessor.instanciate(app),
+                   TransactionsProcessor.instanciate(app),
+                   SourcesProcessor.instanciate(app))
 
     async def broadcast_identity(self, connection, password):
         """
@@ -73,6 +88,8 @@ class DocumentsService:
             identity.blockstamp = block_uid
             identity.signature = selfcert.signatures[0]
             identity.timestamp = timestamp
+        else:
+            identity = None
 
         return result, identity
 
@@ -189,3 +206,181 @@ class DocumentsService:
 
         document.sign(self_cert, [key])
         return document.signed_raw(self_cert)
+
+    def tx_sources(self, amount, amount_base, currency):
+        """
+        Get inputs to generate a transaction with a given amount of money
+        :param int amount: The amount target value
+        :param int amount_base: The amount base target value
+        :param str currency: The community target of the transaction
+        :return: The list of inputs to use in the transaction document
+        """
+
+        # such a dirty algorithmm
+        # everything should be done again from scratch
+        # in future versions
+
+        def current_value(inputs, overhs):
+            i = 0
+            for s in inputs:
+                i += s.amount * (10**s.base)
+            for o in overhs:
+                i -= o[0] * (10**o[1])
+            return i
+
+        amount, amount_base = reduce_base(amount, amount_base)
+        available_sources = self._sources_processor.available(currency)
+        if available_sources:
+            current_base = max([src.base for src in available_sources])
+            value = 0
+            sources = []
+            outputs = []
+            overheads = []
+            buf_sources = list(available_sources)
+            while current_base >= 0:
+                for s in [src for src in available_sources if src.base == current_base]:
+                    test_sources = sources + [s]
+                    val = current_value(test_sources, overheads)
+                    # if we have to compute an overhead
+                    if current_value(test_sources, overheads) > amount * (10**amount_base):
+                        overhead = current_value(test_sources, overheads) - int(amount) * (10**amount_base)
+                        # we round the overhead in the current base
+                        # exemple : 12 in base 1 -> 1*10^1
+                        overhead = int(round(float(overhead) / (10**current_base)))
+                        source_value = s.amount * (10**s.base)
+                        out = int((source_value - (overhead * (10**current_base)))/(10**current_base))
+                        if out * (10**current_base) <= amount * (10**amount_base):
+                            sources.append(s)
+                            buf_sources.remove(s)
+                            overheads.append((overhead, current_base))
+                            outputs.append((out, current_base))
+                    # else just add the output
+                    else:
+                        sources.append(s)
+                        buf_sources.remove(s)
+                        outputs.append((s.amount, s.base))
+                    if current_value(sources, overheads) == amount * (10 ** amount_base):
+                        return sources, outputs, overheads
+
+                current_base -= 1
+
+        raise NotEnoughChangeError(value, currency, len(sources), amount * pow(10, amount_base))
+
+    def tx_inputs(self, sources):
+        """
+        Get inputs to generate a transaction with a given amount of money
+        :param list[sakia.data.entities.Source] sources: The sources used to send the given amount of money
+        :return: The list of inputs to use in the transaction document
+        """
+        inputs = []
+        for s in sources:
+            inputs.append(InputSource(s.amount, s.base, s.type, s.identifier, s.noffset))
+        return inputs
+
+    def tx_unlocks(self, sources):
+        """
+        Get unlocks to generate a transaction with a given amount of money
+        :param list sources: The sources used to send the given amount of money
+        :return: The list of unlocks to use in the transaction document
+        """
+        unlocks = []
+        for i, s in enumerate(sources):
+            unlocks.append(Unlock(i, [SIGParameter(0)]))
+        return unlocks
+
+    def tx_outputs(self, issuer, receiver, outputs, overheads):
+        """
+        Get outputs to generate a transaction with a given amount of money
+        :param str issuer: The issuer of the transaction
+        :param str receiver: The target of the transaction
+        :param list outputs: The amount to send
+        :param list inputs: The inputs used to send the given amount of money
+        :param list overheads: The overheads used to send the given amount of money
+        :return: The list of outputs to use in the transaction document
+        """
+        total = []
+        outputs_bases = set(o[1] for o in outputs)
+        for base in outputs_bases:
+            output_sum = 0
+            for o in outputs:
+                if o[1] == base:
+                    output_sum += o[0]
+            total.append(OutputSource(output_sum, base, output.Condition.token(output.SIG.token(receiver))))
+
+        overheads_bases = set(o[1] for o in overheads)
+        for base in overheads_bases:
+            overheads_sum = 0
+            for o in overheads:
+                if o[1] == base:
+                    overheads_sum += o[0]
+            total.append(OutputSource(overheads_sum, base, output.Condition.token(output.SIG.token(issuer))))
+
+        return total
+
+    def prepare_tx(self, issuer, receiver, blockstamp, amount, amount_base, message, currency):
+        """
+        Prepare a simple Transaction document
+        :param str issuer: the issuer of the transaction
+        :param str receiver: the target of the transaction
+        :param duniterpy.documents.BlockUID blockstamp: the blockstamp
+        :param int amount: the amount sent to the receiver
+        :param int amount_base: the amount base of the currency
+        :param str message: the comment of the tx
+        :param str currency: the target community
+        :return: the transaction document
+        :rtype: duniterpy.documents.Transaction
+        """
+        result = self.tx_sources(int(amount), amount_base, currency)
+        sources = result[0]
+        computed_outputs = result[1]
+        overheads = result[2]
+        self._sources_processor.consume(sources)
+        logging.debug("Inputs : {0}".format(sources))
+
+        inputs = self.tx_inputs(sources)
+        unlocks = self.tx_unlocks(sources)
+        outputs = self.tx_outputs(issuer, receiver, computed_outputs, overheads)
+        logging.debug("Outputs : {0}".format(outputs))
+        tx = TransactionDoc(3, currency, blockstamp, 0,
+                            [issuer], inputs, unlocks,
+                            outputs, message, None)
+        return tx
+
+    async def send_money(self, connection, password, recipient, amount, amount_base, message):
+        """
+        Send money to a given recipient in a specified community
+        :param sakia.data.entities.Connection connection: The account salt
+        :param str password: The account password
+        :param str recipient: The pubkey of the recipient
+        :param int amount: The amount of money to transfer
+        :param int amount_base: The amount base of the transfer
+        :param str message: The message to send with the transfer
+        """
+        blockstamp = self._blockchain_processor.current_buid(connection.currency)
+        time = self._blockchain_processor.time(connection.currency)
+        key = SigningKey(connection.salt, password, connection.scrypt_params)
+        logging.debug("Sender pubkey:{0}".format(key.pubkey))
+        try:
+            txdoc = self.prepare_tx(connection.pubkey, recipient, blockstamp, amount, amount_base,
+                                    message, connection.currency)
+            logging.debug("TX : {0}".format(txdoc.raw()))
+
+            txdoc.sign([key])
+            logging.debug("Transaction : [{0}]".format(txdoc.signed_raw()))
+            txid = self._transactions_processor.next_txid(connection.currency, blockstamp.number)
+            tx = Transaction(currency=connection.currency,
+                             sha_hash=txdoc.sha_hash,
+                             written_block=0,
+                             blockstamp=blockstamp,
+                             timestamp=time,
+                             signature=txdoc.signatures[0],
+                             issuer=connection.pubkey,
+                             receiver=recipient,
+                             amount=amount,
+                             amount_base=amount_base,
+                             comment=message,
+                             txid=txid,
+                             state=Transaction.TO_SEND)
+            return await self._transactions_processor.send(tx, txdoc, connection.currency)
+        except NotEnoughChangeError as e:
+            return (False, str(e)), None
diff --git a/src/sakia/tests/conftest.py b/src/sakia/tests/conftest.py
index bab6a63b..07cd5f33 100644
--- a/src/sakia/tests/conftest.py
+++ b/src/sakia/tests/conftest.py
@@ -146,6 +146,14 @@ def application_with_one_connection(application, simple_fake_server, bob):
                             previous_ud_time=previous_ud_block.mediantime,
                             currency=simple_fake_server.forge.currency)
     application.db.blockchains_repo.insert(blockchain)
+    for s in simple_fake_server.forge.user_identities[bob.key.pubkey].sources:
+        application.db.sources_repo.insert(Source(currency=simple_fake_server.forge.currency,
+                                                  pubkey=bob.key.pubkey,
+                                                  identifier=s.origin_id,
+                                                  noffset=s.index,
+                                                  type=s.source,
+                                                  amount=s.amount,
+                                                  base=s.base))
     bob_blockstamp = simple_fake_server.forge.user_identities[bob.key.pubkey].blockstamp
     bob_user_identity = simple_fake_server.forge.user_identities[bob.key.pubkey]
     bob_ms = bob_user_identity.memberships[-1]
@@ -155,8 +163,8 @@ def application_with_one_connection(application, simple_fake_server, bob):
                             blockstamp=bob_blockstamp,
                             signature=bob_user_identity.signature,
                             timestamp=simple_fake_server.forge.blocks[bob_blockstamp.number].mediantime,
-                            written_on=None,
-                            revoked_on=bob_user_identity.revoked_on,
+                            written_on=0,
+                            revoked_on=0,
                             member=bob_user_identity.member,
                             membership_buid=bob_ms.blockstamp,
                             membership_timestamp=simple_fake_server.forge.blocks[bob_ms.blockstamp.number].mediantime,
diff --git a/src/sakia/tests/functional/identities_tab/test_identities_table.py b/src/sakia/tests/functional/identities_tab/test_identities_table.py
index d64481fa..8342015c 100644
--- a/src/sakia/tests/functional/identities_tab/test_identities_table.py
+++ b/src/sakia/tests/functional/identities_tab/test_identities_table.py
@@ -69,7 +69,7 @@ class TestIdentitiesTable(unittest.TestCase, QuamashTest):
             self.addCleanup(srv.close)
 
             identities_tab.change_account(self.account, self.password_asker)
-            identities_tab.change_community(self.community)
+            identities_tab.change_currency(self.community)
             await asyncio.sleep(1)
 
             QTest.keyClicks(identities_tab.ui.edit_textsearch, "doe")
diff --git a/src/sakia/tests/functional/test_certification_dialog.py b/src/sakia/tests/functional/test_certification_dialog.py
index 6e5f7503..c96668e9 100644
--- a/src/sakia/tests/functional/test_certification_dialog.py
+++ b/src/sakia/tests/functional/test_certification_dialog.py
@@ -35,7 +35,7 @@ async def test_certification_init_community(application_with_one_connection, fak
         assert certification_dialog.view.button_box.button(QDialogButtonBox.Ok).isEnabled()
         QTest.mouseClick(certification_dialog.view.button_box.button(QDialogButtonBox.Ok), Qt.LeftButton)
         await asyncio.sleep(0.1)
-        assert Certification is type(fake_server.forge.pool[0])
+        assert isinstance(fake_server.forge.pool[0], Certification)
 
     application_with_one_connection.loop.call_later(10, close_dialog)
     asyncio.ensure_future(exec_test())
diff --git a/src/sakia/tests/functional/test_transfer_dialog.py b/src/sakia/tests/functional/test_transfer_dialog.py
new file mode 100644
index 00000000..74a63db9
--- /dev/null
+++ b/src/sakia/tests/functional/test_transfer_dialog.py
@@ -0,0 +1,32 @@
+import asyncio
+import pytest
+from PyQt5.QtCore import QLocale, Qt
+from PyQt5.QtTest import QTest
+from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox, QApplication
+from sakia.gui.dialogs.transfer.controller import TransferController
+from duniterpy.documents import Transaction
+
+
+@pytest.mark.asyncio
+async def test_transfer(application_with_one_connection, simple_fake_server, bob, alice):
+    transfer_dialog = TransferController.create(None, application_with_one_connection)
+
+    def close_dialog():
+        if transfer_dialog.view.isVisible():
+            transfer_dialog.view.close()
+
+    async def exec_test():
+        transfer_dialog.model.connection.password = bob.password
+        QTest.mouseClick(transfer_dialog.view.radio_pubkey, Qt.LeftButton)
+        QTest.keyClicks(transfer_dialog.view.edit_pubkey, alice.key.pubkey)
+        transfer_dialog.view.spinbox_amount.setValue(10)
+        await asyncio.sleep(0.1)
+        assert transfer_dialog.view.button_box.button(QDialogButtonBox.Ok).isEnabled()
+        QTest.mouseClick(transfer_dialog.view.button_box.button(QDialogButtonBox.Ok), Qt.LeftButton)
+        await asyncio.sleep(0.1)
+        assert isinstance(simple_fake_server.forge.pool[0], Transaction)
+
+    application_with_one_connection.loop.call_later(10, close_dialog)
+    asyncio.ensure_future(exec_test())
+    await transfer_dialog.async_exec()
+    await simple_fake_server.close()
diff --git a/src/sakia/tests/functional/transfer/test_transfer.py b/src/sakia/tests/functional/transfer/test_transfer.py
deleted file mode 100644
index 087bb872..00000000
--- a/src/sakia/tests/functional/transfer/test_transfer.py
+++ /dev/null
@@ -1,89 +0,0 @@
-import asyncio
-import time
-import unittest
-
-import aiohttp
-from PyQt5.QtCore import QLocale, Qt
-from PyQt5.QtTest import QTest
-from PyQt5.QtWidgets import QDialog, QDialogButtonBox, QMessageBox, QApplication
-from sakia.core.net import Network, Node
-from sakia.core.net.api.bma.access import BmaAccess
-from sakia.gui.transfer import TransferMoneyDialog
-
-from sakia.app import Application
-from sakia.core import Account, Community, Wallet
-from sakia.core.registry.identities import IdentitiesRegistry
-from sakia.gui.password_asker import PasswordAskerDialog
-from sakia.tests import QuamashTest
-from sakia.tests.mocks.bma import nice_blockchain
-
-
-class TestTransferDialog(unittest.TestCase, QuamashTest):
-    def setUp(self):
-        self.setUpQuamash()
-        QLocale.setDefault(QLocale("en_GB"))
-        self.identities_registry = IdentitiesRegistry({})
-
-        self.application = Application(self.qapplication, self.lp, self.identities_registry)
-        self.application.preferences['notifications'] = False
-
-        self.mock_nice_blockchain = nice_blockchain.get_mock(self.lp)
-        self.node = Node(self.mock_nice_blockchain.peer(),
-                         "", "HnFcSms8jzwngtVomTTnzudZx7SHUQY8sVE1y8yBmULk",
-                         None, Node.ONLINE,
-                         time.time(), {}, "duniter", "0.14.0", 0, session=aiohttp.ClientSession())
-        self.network = Network.create(self.node)
-        self.bma_access = BmaAccess.create(self.network)
-        self.community = Community("test_currency", self.network, self.bma_access)
-
-        self.wallet = Wallet(0, "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                             "Wallet 1", self.identities_registry)
-
-        # Salt/password : "testsakia/testsakia"
-        # Pubkey : 7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ
-        self.account = Account("testsakia", "7Aqw6Efa9EzE7gtsc8SveLLrM7gm6NEGoywSv4FJx6pZ",
-                               "john", [self.community], [self.wallet], [], self.identities_registry)
-
-        self.password_asker = PasswordAskerDialog(self.account)
-        self.password_asker.password = "testsakia"
-        self.password_asker.remember = True
-
-    def tearDown(self):
-        self.tearDownQuamash()
-
-    def test_transfer_nice_community(self):
-        transfer_dialog = TransferMoneyDialog(self.application,
-                                              self.account,
-                                              self.password_asker,
-                                              self.community,
-                                              None)
-        self.account.wallets[0].init_cache(self.application, self.community)
-
-        async def open_dialog(transfer_dialog):
-            srv, port, url = await self.mock_nice_blockchain.create_server()
-            self.addCleanup(srv.close)
-            await asyncio.sleep(1)
-            result = await transfer_dialog.async_exec()
-            await self.mock_nice_blockchain.close()
-            self.assertEqual(result, QDialog.Accepted)
-
-        def close_dialog():
-            if transfer_dialog.widget.isVisible():
-                transfer_dialog.widget.close()
-
-        async def exec_test():
-            self.account.wallets[0].caches[self.community.currency].available_sources = await self.wallet.sources(self.community)
-            QTest.mouseClick(transfer_dialog.ui.radio_pubkey, Qt.LeftButton)
-            QTest.keyClicks(transfer_dialog.ui.edit_pubkey, "FADxcH5LmXGmGFgdixSes6nWnC4Vb4pRUBYT81zQRhjn")
-            transfer_dialog.ui.spinbox_amount.setValue(10)
-            QTest.mouseClick(transfer_dialog.ui.button_box.button(QDialogButtonBox.Ok), Qt.LeftButton)
-            await asyncio.sleep(1)
-            topWidgets = QApplication.topLevelWidgets()
-            for w in topWidgets:
-                if type(w) is QMessageBox:
-                    QTest.keyClick(w, Qt.Key_Enter)
-            await asyncio.sleep(1)
-
-        self.lp.call_later(30, close_dialog)
-        asyncio.ensure_future(exec_test())
-        self.lp.run_until_complete(open_dialog(transfer_dialog))
-- 
GitLab